import { useCallback, useEffect, useState } from 'react'; import { Button, Descriptions, Drawer, Form, Input, Select, Space, Table, Tag, Typography, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { request, type AdminUserRow, type Paginated } from '../lib/api'; type UserDetail = AdminUserRow & { cityPref?: Record | 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 }>; mergedFromCount?: number; orderCount?: number; addressCount?: number; }; export default function UsersPage() { const [form] = Form.useForm(); const [data, setData] = useState | null>(null); const [loading, setLoading] = useState(false); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(20); const [detail, setDetail] = useState(null); const [drawerOpen, setDrawerOpen] = useState(false); const load = useCallback(async () => { setLoading(true); try { const values = form.getFieldsValue(); const qs = new URLSearchParams({ page: String(page), pageSize: String(pageSize) }); if (values.phone) qs.set('phone', values.phone); if (values.userNo) qs.set('userNo', values.userNo); if (values.deviceKey) qs.set('deviceKey', values.deviceKey); if (values.phoneVerified !== undefined && values.phoneVerified !== '') { qs.set('phoneVerified', values.phoneVerified); } if (values.status !== undefined && values.status !== '') { qs.set('status', String(values.status)); } const res = await request>(`/admin/users?${qs}`); setData(res); } finally { setLoading(false); } }, [form, page, pageSize]); useEffect(() => { void load(); }, [load]); async function openDetail(id: string) { const res = await request(`/admin/users/${id}`); setDetail(res); setDrawerOpen(true); } const columns: ColumnsType = [ { title: '用户编号', dataIndex: 'userNo', width: 120 }, { title: '昵称', dataIndex: 'nickname', width: 100 }, { title: '手机', dataIndex: 'phone', width: 120, render: (v) => v || '—', }, { title: '验手机', dataIndex: 'phoneVerifiedAt', width: 90, render: (v) => (v ? 已验证 : 访客), }, { title: 'deviceKey', dataIndex: 'deviceKey', ellipsis: true, render: (v) => v || '—', }, { title: '合并', dataIndex: 'mergedIntoUserId', width: 80, render: (v) => (v ? 已合并 : '—'), }, { title: '订单数', dataIndex: 'orderCount', width: 80 }, { title: '注册时间', dataIndex: 'createdAt', width: 170, render: (v) => new Date(v).toLocaleString('zh-CN'), }, { title: '操作', width: 80, render: (_, row) => ( ), }, ]; return (
用户监控
{ setPage(1); void load(); }}>
{ setPage(p); setPageSize(ps); }, }} /> setDrawerOpen(false)}> {detail && ( <> {detail.id} {detail.userNo} {detail.nickname || '—'} {detail.phone || '—'} {detail.phoneVerifiedAt ? new Date(detail.phoneVerifiedAt).toLocaleString('zh-CN') : '未验证'} {detail.deviceKey || '—'} {detail.mergedInto ? `${detail.mergedInto.userNo} (${detail.mergedInto.phone || '无手机'})` : '—'} {detail.mergedFromCount ?? 0} {detail.orderCount} / {detail.addressCount} {new Date(detail.createdAt).toLocaleString('zh-CN')} {detail.orders && detail.orders.length > 0 && ( <> 最近订单
`¥${v}` }, ]} /> )} )} ); }