小飞侠接口调通测试

This commit is contained in:
2026-07-06 17:07:35 +08:00
parent 3625f037d0
commit 4e18763968
8 changed files with 271 additions and 31 deletions
+143 -20
View File
@@ -1,26 +1,39 @@
import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Alert,
Button,
Descriptions,
Drawer,
Form,
Input,
Modal,
Select,
Space,
Table,
Tag,
Typography,
message,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { request, type AdminUserRow, type Paginated } from '../lib/api';
import { ORDER_STATUS_LABELS, fmtTime } from '../lib/constants';
type UserOrderRow = {
id: string;
orderNo: string;
status: string;
payAmount: number;
payStatus?: string;
createdAt: string;
};
type UserDetail = AdminUserRow & {
wxUnionId?: string | null;
cityPref?: Record<string, unknown> | null;
mergedInto?: { id: string; userNo: string; phone: string | null; nickname: string | null } | null;
orders?: Array<{ id: string; orderNo: string; status: string; payAmount: number; createdAt: string }>;
orders?: UserOrderRow[];
mergedFromCount?: number;
orderCount?: number;
addressCount?: number;
};
@@ -33,6 +46,9 @@ export default function UsersPage() {
const [pageSize, setPageSize] = useState(20);
const [detail, setDetail] = useState<UserDetail | null>(null);
const [drawerOpen, setDrawerOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const [deleteConfirm, setDeleteConfirm] = useState('');
const [deleting, setDeleting] = useState(false);
const load = useCallback(async () => {
setLoading(true);
@@ -65,6 +81,44 @@ export default function UsersPage() {
setDrawerOpen(true);
}
function openDeleteModal() {
setDeleteConfirm('');
setDeleteOpen(true);
}
async function confirmDelete() {
if (!detail) return;
if (deleteConfirm !== detail.userNo) {
message.error('请输入正确的用户编号以确认删除');
return;
}
setDeleting(true);
try {
await request(`/admin/users/${detail.id}`, { method: 'DELETE' });
message.success('用户已删除(行为日志已保留)');
setDeleteOpen(false);
setDrawerOpen(false);
setDetail(null);
void load();
} catch (e) {
message.error(e instanceof Error ? e.message : '删除失败');
} finally {
setDeleting(false);
}
}
const orderColumns: ColumnsType<UserOrderRow> = [
{ title: '订单号', dataIndex: 'orderNo', width: 170 },
{
title: '状态',
dataIndex: 'status',
width: 100,
render: (s) => <Tag>{ORDER_STATUS_LABELS[s] || s}</Tag>,
},
{ title: '实付', dataIndex: 'payAmount', width: 90, render: (v) => `¥${v}` },
{ title: '下单时间', dataIndex: 'createdAt', width: 160, render: fmtTime },
];
const columns: ColumnsType<AdminUserRow> = [
{ title: '用户编号', dataIndex: 'userNo', width: 120 },
{ title: '昵称', dataIndex: 'nickname', width: 100 },
@@ -80,6 +134,12 @@ export default function UsersPage() {
width: 90,
render: (v) => (v ? <Tag color="green"></Tag> : <Tag color="orange">访</Tag>),
},
{
title: '微信',
dataIndex: 'wechatVerified',
width: 100,
render: (v) => (v ? <Tag color="blue"></Tag> : <Tag></Tag>),
},
{
title: 'deviceKey',
dataIndex: 'deviceKey',
@@ -153,7 +213,7 @@ export default function UsersPage() {
loading={loading}
columns={columns}
dataSource={data?.items ?? []}
scroll={{ x: 1100 }}
scroll={{ x: 1200 }}
pagination={{
current: page,
pageSize,
@@ -166,7 +226,15 @@ export default function UsersPage() {
}}
/>
<Drawer title="用户详情" width={560} open={drawerOpen} onClose={() => setDrawerOpen(false)}>
<Drawer
title="用户详情"
width={640}
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
extra={detail && (
<Button danger onClick={openDeleteModal}></Button>
)}
>
{detail && (
<>
<Descriptions column={1} bordered size="small">
@@ -177,6 +245,11 @@ export default function UsersPage() {
<Descriptions.Item label="验手机时间">
{detail.phoneVerifiedAt ? new Date(detail.phoneVerifiedAt).toLocaleString('zh-CN') : '未验证'}
</Descriptions.Item>
<Descriptions.Item label="微信验证">
{detail.wechatVerified ? <Tag color="blue"></Tag> : <Tag></Tag>}
</Descriptions.Item>
<Descriptions.Item label="wxOpenId">{detail.wxOpenId || '—'}</Descriptions.Item>
<Descriptions.Item label="wxUnionId">{detail.wxUnionId || '—'}</Descriptions.Item>
<Descriptions.Item label="deviceKey">{detail.deviceKey || '—'}</Descriptions.Item>
<Descriptions.Item label="合并至">
{detail.mergedInto
@@ -189,22 +262,20 @@ export default function UsersPage() {
{new Date(detail.createdAt).toLocaleString('zh-CN')}
</Descriptions.Item>
</Descriptions>
{detail.orders && detail.orders.length > 0 && (
<>
<Typography.Title level={5} style={{ marginTop: 16 }}></Typography.Title>
<Table
size="small"
rowKey="id"
pagination={false}
dataSource={detail.orders}
columns={[
{ title: '订单号', dataIndex: 'orderNo' },
{ title: '状态', dataIndex: 'status' },
{ title: '金额', dataIndex: 'payAmount', render: (v) => `¥${v}` },
]}
/>
</>
)}
<Typography.Title level={5} style={{ marginTop: 16 }}>
{detail.orders?.length ?? 0}
</Typography.Title>
<Table
size="small"
rowKey="id"
pagination={false}
scroll={{ x: 520, y: 240 }}
dataSource={detail.orders ?? []}
columns={orderColumns}
locale={{ emptyText: '暂无订单' }}
/>
<Button
type="primary"
style={{ marginTop: 16 }}
@@ -215,6 +286,58 @@ export default function UsersPage() {
</>
)}
</Drawer>
<Modal
title="确认删除用户"
open={deleteOpen}
okText="确认删除"
okButtonProps={{
danger: true,
disabled: !detail || deleteConfirm !== detail.userNo,
loading: deleting,
}}
onOk={() => void confirmDelete()}
onCancel={() => setDeleteOpen(false)}
width={720}
destroyOnClose
>
{detail && (
<>
<Alert
type="error"
showIcon
style={{ marginBottom: 16 }}
message="此操作不可恢复"
description={(
<>
<strong>{detail.userNo}</strong>
<br />
<strong></strong>
</>
)}
/>
<Typography.Text strong>{detail.orders?.length ?? 0} </Typography.Text>
<Table
size="small"
style={{ marginTop: 8, marginBottom: 16 }}
rowKey="id"
pagination={false}
scroll={{ x: 520, y: 200 }}
dataSource={detail.orders ?? []}
columns={orderColumns}
locale={{ emptyText: '无订单' }}
/>
<Typography.Paragraph type="secondary">
<Typography.Text code>{detail.userNo}</Typography.Text>
</Typography.Paragraph>
<Input
value={deleteConfirm}
placeholder={detail.userNo}
onChange={(e) => setDeleteConfirm(e.target.value)}
/>
</>
)}
</Modal>
</div>
);
}