import { ArrowLeftOutlined, CopyOutlined, FileTextOutlined, PauseCircleOutlined, PlayCircleOutlined, StopOutlined, } from '@ant-design/icons' import { Alert, App, Button, Card, Descriptions, Input, Modal, Skeleton, Space, Table, Tag, Typography, } from 'antd' import type { TableColumnsType } from 'antd' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useEffect, useState } from 'react' import { useNavigate, useParams, useSearchParams } from 'react-router' import JsonPreview from '@/components/admin/JsonPreview' import ManualOrderCostModal from '@/components/admin/ManualOrderCostModal' import PageHeader from '@/components/admin/PageHeader' import StatusTag from '@/components/admin/StatusTag' import { fetchAdminOrderDetail, updateAdminOrderClaimStatus } from '@/services/admin' import type { AdminOrderDetail, AdminTaskListItem } from '@/types/admin' import { formatAdminDateTime } from '@/utils/admin-time' import { resolveOrderFulfillmentMode, resolveVoucherStateLabel } from '@/utils/admin-display' export default function AdminOrderDetailPage() { const navigate = useNavigate() const queryClient = useQueryClient() const { message, modal } = App.useApp() const { orderId = '' } = useParams() const [searchParams, setSearchParams] = useSearchParams() const [statusModalVisible, setStatusModalVisible] = useState(false) const [manualCostOpen, setManualCostOpen] = useState(false) const [targetStatus, setTargetStatus] = useState<'active' | 'paused' | 'closed'>('active') const [statusReason, setStatusReason] = useState('') useEffect(() => { if (searchParams.get('manualCost') === '1') { setManualCostOpen(true) } }, [searchParams]) function closeManualCost() { setManualCostOpen(false) if (searchParams.get('manualCost')) { const next = new URLSearchParams(searchParams) next.delete('manualCost') setSearchParams(next, { replace: true }) } } const query = useQuery({ queryKey: ['admin-order-detail', orderId], queryFn: () => fetchAdminOrderDetail(orderId), enabled: Boolean(orderId), }) const detail = query.data?.data const statusMutation = useMutation({ mutationFn: async (data: { status: 'active' | 'paused' | 'closed'; reason?: string }) => { const targetOrderId = orderId || detail?.order?.orderId if (!targetOrderId) throw new Error('订单 ID 缺失') return updateAdminOrderClaimStatus(targetOrderId, data) }, onSuccess: () => { message.success('订单兑换状态已更新') setStatusModalVisible(false) setStatusReason('') void queryClient.invalidateQueries({ queryKey: ['admin-order-detail', orderId] }) }, onError: (err: unknown) => { message.error(err instanceof Error ? err.message : '更新状态失败') }, }) const handleCopyH5Link = () => { if (!detail?.order.platformOrderId) return const h5Url = `${window.location.origin}/h5/${encodeURIComponent(detail.order.platformOrderId)}` navigator.clipboard .writeText(h5Url) .then(() => message.success('已复制 H5 领取链接: ' + h5Url)) .catch(() => message.error('复制失败,请手动复制')) } const openStatusModal = (status: 'active' | 'paused' | 'closed') => { if (status === 'active') { modal.confirm({ title: '恢复启用订单兑换', content: '恢复后,买家可正常进入 H5 页面提交 UID 领取,确认恢复吗?', onOk: async () => { await statusMutation.mutateAsync({ status: 'active', reason: '管理员恢复启用' }) }, }) return } setTargetStatus(status) setStatusReason('') setStatusModalVisible(true) } if (query.isLoading) { return (
} />
) } if (query.error || !detail) { return (
} />
) } const taskColumns: TableColumnsType = [ { title: '任务号', dataIndex: 'taskNo', minWidth: 180, render: (_, row) => ( navigate(`/admin/tasks/${row.taskId}`)}> {row.taskNo} ), }, { title: '状态', minWidth: 220, render: (_, row) => ( ), }, { title: '角色', minWidth: 160, render: (_, row) => (
{row.roleName || '-'} {row.roleId || '-'}
), }, { title: '错误', dataIndex: 'lastError', minWidth: 220, render: (value) => {value || '-'}, }, ] const allTasksCompleted = detail.tasks.length > 0 && detail.tasks.every( (t) => ['completed', 'redeemed', 'delivered', 'success'].includes(t.status) || t.deliveryStatus === 'delivered', ) const isOrderFulfilled = detail.order.orderStatus === 'completed' || allTasksCompleted const isOrderRefundedOrCancelled = detail.order.payStatus === 'refunded' || detail.order.orderStatus === 'cancelled' const canPauseOrClose = !isOrderFulfilled && !isOrderRefundedOrCancelled const isAutoClaimOrder = Boolean( detail.order.fulfillmentMode === 'affiliate_dash' || (detail.order.fulfillmentMode === 'mixed' && detail.order.canClaimH5), ) const fulfillmentMode = resolveOrderFulfillmentMode(detail.order) const voucherLabel = resolveVoucherStateLabel(detail.order.voucherState) return (
} /> {isAutoClaimOrder ? ( <> {detail.order.claimStatus === 'paused' || detail.order.claimStatus === 'closed' ? ( ) : canPauseOrClose ? ( ) : null} {canPauseOrClose && detail.order.claimStatus !== 'closed' ? ( ) : null} ) : null} } > {detail.order.orderId} {detail.order.shopName || detail.order.shopId || '-'} {detail.order.platformOrderId} {fulfillmentMode.label} {detail.order.hasVoucher && voucherLabel ? ( 🎫 {voucherLabel} ) : ( 无 )} {isAutoClaimOrder ? ( {detail.order.claimStatus === 'paused' ? ( }> 已暂停兑换 ) : detail.order.claimStatus === 'closed' ? ( }> 已关闭兑换 ) : ( }> 正常有效 )} {detail.order.claimStatusReason ? ( ({detail.order.claimStatusReason}) ) : null} ) : null} {detail.order.totalAmount} {detail.order.currency} {formatAdminDateTime(detail.order.createdAt)} {formatAdminDateTime(detail.order.updatedAt)}
{detail.order.itemTitle || detail.order.itemSummary || '-'} {detail.order.skuNick ? ( {detail.order.skuNick} ) : null}
rowKey="orderItemId" pagination={false} dataSource={detail.items} scroll={{ x: 760 }} columns={[ { title: '商品', dataIndex: 'itemTitle', minWidth: 240, render: (value) => {value || '-'}, }, { title: 'SKU / 规格', minWidth: 180, render: (_, item) => (
{item.skuNick ? ( {item.skuNick} ) : null} {/* 商品列已是内部 SKU 时不再重复展示同一串 */} {item.skuCode && item.skuCode !== item.itemTitle ? ( {item.skuCode} ) : null}
), }, { title: '数量', dataIndex: 'quantity', width: 90 }, { title: '履约模式', dataIndex: 'deliveryMode', minWidth: 140 }, ]} />
rowKey="taskId" pagination={false} dataSource={detail.tasks} columns={taskColumns} scroll={{ x: 900 }} /> setStatusModalVisible(false)} confirmLoading={statusMutation.isPending} onOk={() => { if (!statusReason.trim()) { message.warning('请填写原因或备注(如:买家申请退款)') return } statusMutation.mutate({ status: targetStatus, reason: statusReason.trim() }) }} >

{targetStatus === 'paused' ? '暂停后,买家访问 H5 链接将被拦截并提示“兑换已暂停,请联系客服”,后续可随时恢复启用。' : '关闭后,买家将无法继续进入 H5 页面兑换,适用于已退款或订单作废。'}

setStatusReason(e.target.value)} />
({ orderItemId: item.orderItemId, matchSku: item.matchSku, skuName: item.skuName, skuCode: item.skuCode, quantity: item.quantity, }))} onClose={closeManualCost} />
) } function BackButton() { const navigate = useNavigate() return ( ) }