import { ClockCircleOutlined } from '@ant-design/icons' import { useQuery } from '@tanstack/react-query' import { Empty, Skeleton, Tag, Timeline, Tooltip, Typography } from 'antd' import dayjs from '@/lib/dayjs' import { fetchAdminWorkOrderEvents } from '@/services/admin' import { fetchWorkerMyOrderEvents } from '@/services/worker' import type { WorkOrderEvent } from '@/types/worker-platform' type EventNodeStyle = { /** 节点标题(主文案) */ title: string /** 补充说明(次文案) */ description?: string /** Timeline 节点颜色 */ color: string } /** 工单状态中文名 */ const STATUS_LABELS: Record = { pending_material: '待完善资料', unassigned: '未分配', open: '待抢单', in_progress: '代练中', pending_acceptance: '待验收', problem: '问题单', accepted: '已验收', cancelled: '已取消', } function formatStatusLabel(status: string) { return STATUS_LABELS[status] || status || '未知' } function readPayload(event: WorkOrderEvent, key: string): string { const value = event.payload?.[key] if (value === undefined || value === null) return '' return String(value).trim() } /** 事件类型 → 节点阶段文案与颜色 */ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle { const type = event.eventType const note = readPayload(event, 'note') || readPayload(event, 'reason') switch (type) { // —— 创建节点 —— case 'source_synced': return { title: '系统收到推送的新订单,自动生成该笔订单', color: 'blue', } case 'mock_created': return { title: '本地 Mock 生成该笔订单', color: 'blue' } // —— 发布节点 —— case 'published': return { title: '将订单发单至内部接单大厅', color: 'geekblue' } case 'unpublished': return { title: '将订单下架,退回未分配状态', color: 'geekblue' } case 'pinned': return { title: '将订单置顶,抢单大厅优先展示', color: 'gold' } case 'unpinned': return { title: '取消订单置顶', color: 'gold' } // —— 接手节点 —— case 'grabbed': { const workerName = readPayload(event, 'workerName') return { title: workerName ? `通过接单大厅自助接手此单,该订单当前接手人:${workerName}` : '通过接单大厅自助接手此单', description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel( event.toStatus, )}`, color: 'green', } } case 'assigned_by_admin': { const workerId = readPayload(event, 'workerId') return { title: `指派打手接手此单${workerId ? `(打手 #${workerId})` : ''}`, description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel( event.toStatus, )}`, color: 'green', } } case 'unassigned_by_admin': return { title: '取消指派,订单回到未分配状态', color: 'green' } case 'returned_to_hall_by_admin': return { title: note ? `撤回订单到抢单大厅(${note})` : '撤回订单到抢单大厅', color: 'green', } case 'cancelled_by_worker': return { title: '打手取消接单', color: 'red' } case 'timeout_reopen': return { title: '订单超时未完成,已退回抢单大厅', color: 'orange' } // —— 资料节点 —— case 'material_submitted': return { title: '提交了订单资料', color: 'cyan' } case 'material_completed': return { title: '完善了订单资料', color: 'cyan' } case 'material_updated': return { title: '更新了订单资料', color: 'cyan' } // —— 验收节点 —— case 'acceptance_submitted': { const imageCount = readPayload(event, 'imageCount') return { title: `提交验收资料${imageCount ? `(${imageCount} 张图)` : ''}`, description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel( event.toStatus, )}`, color: 'purple', } } case 'acceptance_updated': return { title: '补充了验收资料', color: 'purple' } case 'acceptance_draft_saved': return { title: '暂存了验收草稿', color: 'purple' } case 'accepted_by_internal': return { title: '内部打手自助验收通过,订单完成', description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel( event.toStatus, )}`, color: 'purple', } case 'accepted_by_vip': return { title: 'VIP 免审核验收通过,订单完成', description: readPayload(event, 'evidenceDueAt') ? '可在 24 小时内补充验收图片' : undefined, color: 'purple', } case 'acceptance_evidence_supplemented': { const addedImageCount = readPayload(event, 'addedImageCount') return { title: `已验收后补充验收图片${addedImageCount ? `(新增 ${addedImageCount} 张)` : ''}`, color: 'purple', } } case 'sharing_acceptance_submitted': { const quantity = readPayload(event, 'quantity') return { title: `提交拼单份额验收${quantity ? `(${quantity} 份)` : ''}`, color: 'purple', } } case 'sharing_share_accepted': { const workerId = readPayload(event, 'workerId') return { title: `拼单份额验收通过并已结算${workerId ? `(打手 #${workerId})` : ''}`, color: 'purple', } } case 'sharing_acceptance_evidence_supplemented': { const addedImageCount = readPayload(event, 'addedImageCount') return { title: `拼单份额已验收后补充图片${addedImageCount ? `(新增 ${addedImageCount} 张)` : ''}`, color: 'purple', } } case 'accepted': case 'accepted_by_admin': return { title: '验收通过,订单完成', description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel( event.toStatus, )}`, color: 'purple', } // —— 问题节点 —— case 'marked_problem': return { title: note ? `将订单标记为问题单:${note}` : '将订单标记为问题单', color: 'red', } case 'problem_return_to_worker': return { title: '问题单已处置:退回打手继续处理', color: 'orange' } case 'problem_reopen': return { title: '问题单已处置:重新开工', color: 'orange' } case 'problem_cancel_release': return { title: '问题单已处置:取消订单并释放押金', color: 'orange' } case 'problem_cancel_deduct': return { title: '问题单已处置:取消订单并扣除押金', color: 'orange' } // —— 取消节点 —— case 'reopened_from_cancelled': return { title: '重新启用已取消订单,恢复为未分配状态', color: 'orange' } case 'cancelled_by_admin': return { title: note ? `取消订单(${note})` : '取消订单', description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel( event.toStatus, )}`, color: 'red', } // —— 其他 —— case 'edited': return { title: '更改了订单信息', color: 'default' } case 'sharing_config_updated': return { title: '更新了拼单配置', color: 'default' } case 'sharing_share_cancelled_by_admin': { const workerId = readPayload(event, 'workerId') const quantity = readPayload(event, 'quantity') return { title: `撤销拼单参与者${workerId ? `(打手 #${workerId})` : ''}`, description: `${quantity ? `撤销 ${quantity} 份;` : ''}${note ? `原因:${note}` : '份数已退回接单大厅'}`, color: 'red', } } case 'pending_deposit_deducted': return { title: '扣减了待解冻押金', color: 'default' } case 'voucher_backfilled': return { title: '回填了电子凭证信息', color: 'default' } case 'assign_voucher_consume_failed': case 'publish_voucher_consume_failed': return { title: '电子凭证核销失败,本次操作未生效', color: 'red' } default: // 通用状态流转兜底 if (event.fromStatus && event.toStatus && event.fromStatus !== event.toStatus) { return { title: `订单状态变更:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel( event.toStatus, )}`, color: 'default', } } return { title: type, color: 'default' } } } /** 订单详情中的流转节点时间线(管理端 / 打手端共用) */ export default function WorkOrderEventTimeline({ orderId, viewer, }: { orderId: number viewer: 'admin' | 'worker' }) { const eventsQuery = useQuery({ queryKey: ['work-order-events', viewer, orderId], queryFn: () => viewer === 'admin' ? fetchAdminWorkOrderEvents(orderId) : fetchWorkerMyOrderEvents(orderId), enabled: Boolean(orderId), retry: false, }) if (eventsQuery.isLoading) { return } if (eventsQuery.error) { return ( {eventsQuery.error instanceof Error ? eventsQuery.error.message : '读取流转记录失败'} ) } const events = eventsQuery.data?.data?.items || [] if (events.length === 0) { return } // 倒序:最新节点在上 const items = [...events].reverse().map((event) => { const style = formatWorkOrderEvent(event) return { color: style.color, children: (
{style.title} {dayjs(event.time).format('MM-DD HH:mm')}
{style.description ? ( {style.description} ) : null}
{event.actorName || event.actorType}
), } }) return }