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 AdminOrderRow, type Paginated } from '../lib/api'; const STATUS_LABELS: Record = { PENDING_PAY: '待付款', PENDING_SHIP: '待发货', OUT_WAREHOUSE: '已出库', SHIPPING: '配送中', PENDING_RECEIVE: '待收货', COMPLETED: '已完成', CANCELLED: '已取消', REFUNDING: '退款中', REFUNDED: '已退款', }; type OrderDetail = AdminOrderRow & { receiverAddress?: string; receiverProvince?: string; receiverCity?: string; receiverDistrict?: string; clientIp?: string | null; ipProvince?: string | null; ipCity?: string | null; ipDistrict?: string | null; gpsProvince?: string | null; gpsCity?: string | null; gpsDistrict?: string | null; gpsLatitude?: number | null; gpsLongitude?: number | null; gpsAddress?: string | null; productAmount?: number; freightAmount?: number; benefitAmount?: number; paidAt?: string | null; payExpireAt?: string | null; items?: Array>; payment?: Record | null; statusLogs?: Array<{ fromStatus: string | null; toStatus: string; createdAt: string }>; benefitCoupons?: Array>; city?: { name: string; code: string }; }; export default function OrdersPage() { 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.orderNo) qs.set('orderNo', values.orderNo); if (values.status) qs.set('status', values.status); if (values.receiverPhone) qs.set('receiverPhone', values.receiverPhone); const res = await request>(`/admin/orders?${qs}`); setData(res); } finally { setLoading(false); } }, [form, page, pageSize]); useEffect(() => { void load(); }, [load]); async function openDetail(id: string) { const res = await request(`/admin/orders/${id}`); setDetail(res); setDrawerOpen(true); } const columns: ColumnsType = [ { title: '订单号', dataIndex: 'orderNo', width: 180 }, { title: '状态', dataIndex: 'status', width: 100, render: (s) => {STATUS_LABELS[s] || s}, }, { title: '配送', dataIndex: 'deliveryType', width: 90, render: (v) => (v === 'LOCAL' ? '同城' : '跨城'), }, { title: '实付', dataIndex: 'payAmount', width: 90, render: (v) => `¥${v}` }, { title: '收货人', dataIndex: 'receiverName', width: 90 }, { title: '手机', dataIndex: 'receiverPhone', width: 120 }, { title: '用户', dataIndex: ['user', 'userNo'], width: 110, render: (_, row) => row.user?.userNo || '—', }, { title: '快递', width: 100, render: (_, row) => row.delivery?.trackingNo || row.delivery?.provider || '—', }, { 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.orderNo} {STATUS_LABELS[detail.status] || detail.status} {detail.user?.userNo} / {detail.user?.nickname} ¥{detail.productAmount} ¥{detail.payAmount} ¥{detail.benefitAmount} {new Date(detail.createdAt).toLocaleString('zh-CN')} {detail.receiverName} {detail.receiverPhone} {detail.receiverAddress} {detail.clientIp || '—'} {[detail.ipProvince, detail.ipCity, detail.ipDistrict].filter(Boolean).join(' / ') || '—'} {[detail.gpsProvince, detail.gpsCity, detail.gpsDistrict].filter(Boolean).join(' / ') || '—'} {detail.gpsLatitude != null ? `${detail.gpsLatitude}, ${detail.gpsLongitude}` : '—'} {detail.delivery && ( {detail.delivery.provider} {detail.delivery.trackingNo || '—'} )} {detail.statusLogs && detail.statusLogs.length > 0 && ( <> 状态流转
v || '—' }, { title: '到', dataIndex: 'toStatus', render: (v) => STATUS_LABELS[v] || v }, { title: '时间', dataIndex: 'createdAt', render: (v) => new Date(v).toLocaleString('zh-CN') }, ]} /> )} )} ); }