import { ArrowLeftOutlined, CheckCircleOutlined, CloseCircleOutlined, CopyOutlined, LinkOutlined, PauseCircleOutlined, PlayCircleOutlined, ReloadOutlined, StopOutlined, UserSwitchOutlined, } from '@ant-design/icons' import { Alert, App, Button, Card, Descriptions, Divider, Empty, Image, Input, Modal, Skeleton, Space, Table, Tabs, Tag, Typography, } from 'antd' import type { TableColumnsType } from 'antd' import { useMutation, useQuery } from '@tanstack/react-query' import { useEffect, useMemo, useState } from 'react' import { useNavigate, useParams } from 'react-router' import JsonPreview from '@/components/admin/JsonPreview' import PageHeader from '@/components/admin/PageHeader' import StatusTag from '@/components/admin/StatusTag' import { closeAdminTask, completeAdminTaskManualDispatch, consumeAdminTaskKuaishouIndustryVoucher, fetchAdminTaskDetail, fetchAdminTaskScreenshot, markAdminTaskManualReview, resendAdminTaskKuaishouIndustryVoucherCode, retryAdminTask, updateAdminOrderClaimStatus, } from '@/services/admin' import type { AdminTaskActionResponse, AdminTaskDetail } from '@/types/admin' import { hasAdminRole } from '@/utils/admin-auth' import { formatKuaishouRoleInfoLabel, formatRedeemOutcomeLabel, formatRedeemResolutionStatus, formatTaskEventPayload, formatTaskEventType, } from '@/utils/admin-display' import { formatAdminDateTime } from '@/utils/admin-time' type TaskEvent = AdminTaskDetail['events'][number] type KuaishouIndustryVoucher = NonNullable type ManualDispatchForm = { deliveryReference: string deliveredCredential: string resultMessage: string } export default function AdminTaskDetailPage() { const { taskId = '' } = useParams() const { message, modal } = App.useApp() const [actionLoadingKey, setActionLoadingKey] = useState('') const [lastClaimUrl, setLastClaimUrl] = useState('') const [screenshotPreviewUrl, setScreenshotPreviewUrl] = useState('') const [statusModalVisible, setStatusModalVisible] = useState(false) const [targetStatus, setTargetStatus] = useState<'active' | 'paused' | 'closed'>('active') const [statusReason, setStatusReason] = useState('') const [manualDispatchForm, setManualDispatchForm] = useState({ deliveryReference: '', deliveredCredential: '', resultMessage: '', }) const query = useQuery({ queryKey: ['admin-task-detail', taskId], queryFn: () => fetchAdminTaskDetail(taskId), enabled: Boolean(taskId), }) const detail = query.data?.data const orderId = detail?.order?.orderId ? String(detail.order.orderId) : '' const statusMutation = useMutation({ mutationFn: async (data: { status: 'active' | 'paused' | 'closed'; reason?: string }) => { const resolvedOrderId = query.data?.data?.order?.orderId || detail?.order?.orderId if (!resolvedOrderId) throw new Error('当前任务无关联订单信息') return updateAdminOrderClaimStatus(resolvedOrderId, data) }, onSuccess: () => { message.success('订单兑换状态已更新') setStatusModalVisible(false) setStatusReason('') void query.refetch() }, onError: (err: unknown) => { message.error(err instanceof Error ? err.message : '更新状态失败') }, }) const canManageTaskLifecycle = hasAdminRole('operator') const canOperateIndustryVoucher = hasAdminRole('support') const canCloseTasks = hasAdminRole('support') const claimTokenStatus = String(detail?.claimToken?.status || '').trim() const isExternalClaimLink = claimTokenStatus.toLowerCase() === 'external' const claimUrl = useMemo(() => { const tokenStatus = String(detail?.claimToken?.status || '').trim() const taskStatus = String(detail?.task.status || '').trim() if (!isUsableClaimLinkStatus(tokenStatus) || ['closed', 'expired'].includes(taskStatus)) { return '' } return lastClaimUrl || detail?.claimToken?.claimUrl || '' }, [detail?.claimToken?.claimUrl, detail?.claimToken?.status, detail?.task.status, lastClaimUrl]) const claimLinkInvalid = Boolean( detail?.claimToken?.claimUrl && claimTokenStatus && !isUsableClaimLinkStatus(claimTokenStatus), ) useEffect(() => { if (!detail) return setManualDispatchForm({ deliveryReference: detail.manualDispatch?.deliveryReference || '', deliveredCredential: detail.manualDispatch?.deliveredCredential || '', resultMessage: detail.manualDispatch?.resultMessage || detail.task.resultMessage || '', }) }, [detail]) useEffect(() => { let objectUrl = '' setScreenshotPreviewUrl('') if (!detail?.screenshotUrl) { return undefined } fetchAdminTaskScreenshot(detail.task.taskId) .then((blob) => { objectUrl = URL.createObjectURL(blob) setScreenshotPreviewUrl(objectUrl) }) .catch(() => { setScreenshotPreviewUrl('') }) return () => { if (objectUrl) { URL.revokeObjectURL(objectUrl) } } }, [detail?.screenshotUrl, detail?.task.taskId]) if (query.isLoading) { return (
} />
) } if (query.error || !detail) { return (
} />
) } const resolvedDetail = detail const flow = resolvedDetail.kuaishouCloudFulfillment const eventColumns: TableColumnsType = [ { title: '时间', dataIndex: 'createdAt', width: 180, render: (value) => formatAdminDateTime(value), }, { title: '事件', dataIndex: 'eventType', width: 180, render: (value) => formatTaskEventType(value), }, { title: '摘要', render: (_, row) => formatTaskEventPayload(row.payload), }, ] async function copyClaimUrl() { if (!claimUrl) return await navigator.clipboard.writeText(claimUrl) message.success('领取链接已复制') } function runTaskAction( actionKey: string, action: () => Promise<{ data: AdminTaskActionResponse }>, successMessage: string, confirmText: string, ) { modal.confirm({ title: '确认操作', content: confirmText, okText: '继续执行', cancelText: '取消', centered: true, onOk: async () => { setActionLoadingKey(actionKey) try { const response = await action() if (response.data.claimUrl) { setLastClaimUrl(response.data.claimUrl) } message.success(successMessage) await query.refetch() } catch (error) { message.error(error instanceof Error ? error.message : '操作失败') } finally { setActionLoadingKey('') } }, }) } function submitManualDispatch(outcome: 'delivered' | 'failed') { const actionLabel = outcome === 'failed' ? '标记履约失败' : '标记已完成履约' const confirmText = outcome === 'failed' ? `确认把任务 ${resolvedDetail.task.taskNo} 回写为人工履约失败吗?这会把任务直接收口并保留失败原因。` : `确认把任务 ${resolvedDetail.task.taskNo} 回写为人工履约完成吗?这会把任务直接标记为已发放。` runTaskAction( `manual-${outcome}`, () => completeAdminTaskManualDispatch(resolvedDetail.task.taskId, { outcome, resultMessage: manualDispatchForm.resultMessage, deliveryReference: manualDispatchForm.deliveryReference, deliveredCredential: manualDispatchForm.deliveredCredential, }), actionLabel, confirmText, ) } const platformOrderId = resolvedDetail.order?.platformOrderId || resolvedDetail.task.platformOrderId || '' const h5ClaimUrl = platformOrderId ? `${window.location.origin}/h5/${encodeURIComponent(platformOrderId)}` : '' const claimStatus = resolvedDetail.order?.claimStatus || 'active' const claimStatusReason = resolvedDetail.order?.claimStatusReason || '' const isTaskFulfilled = ['completed', 'redeemed', 'delivered', 'success'].includes(resolvedDetail.task.status) || resolvedDetail.task.deliveryStatus === 'delivered' const isOrderRefundedOrCancelled = resolvedDetail.order?.payStatus === 'refunded' || resolvedDetail.order?.orderStatus === 'cancelled' const isOrderCompleted = resolvedDetail.order?.orderStatus === 'completed' const canPauseOrClose = !isTaskFulfilled && !isOrderCompleted && !isOrderRefundedOrCancelled const handleCopyH5Link = () => { if (!h5ClaimUrl) return navigator.clipboard .writeText(h5ClaimUrl) .then(() => message.success('已复制 H5 领取链接: ' + h5ClaimUrl)) .catch(() => message.error('复制失败,请手动复制')) } const openStatusModal = (status: 'active' | 'paused' | 'closed') => { if (!orderId) { message.warning('当前任务无关联订单 ID,无法更改兑换状态') return } if (status === 'active') { modal.confirm({ title: '恢复启用订单兑换', content: '恢复后,买家可正常进入 H5 页面提交 UID 领取,确认恢复吗?', onOk: () => statusMutation.mutateAsync({ status: 'active', reason: '客服在任务详情中恢复启用', }), }) return } setTargetStatus(status) setStatusReason('') setStatusModalVisible(true) } return (
} /> runTaskAction( 'retry', () => retryAdminTask(resolvedDetail.task.taskId), '任务已重试', `确认重试任务 ${resolvedDetail.task.taskNo} 吗?`, ) } onResendIndustryVoucher={() => runTaskAction( 'resend-industry-voucher', () => resendAdminTaskKuaishouIndustryVoucherCode(resolvedDetail.task.taskId), '电子凭证发码回调已重发', `确认重新发送任务 ${resolvedDetail.task.taskNo} 的电子凭证发码回调吗?`, ) } onConsumeIndustryVoucher={() => runTaskAction( 'consume-industry-voucher', () => consumeAdminTaskKuaishouIndustryVoucher(resolvedDetail.task.taskId), '电子凭证已执行核销', `确认立即核销任务 ${resolvedDetail.task.taskNo} 绑定的电子凭证吗?`, ) } onMarkManualReview={() => runTaskAction( 'manual-review', () => markAdminTaskManualReview(resolvedDetail.task.taskId), '任务已转人工处理', `确认将任务 ${resolvedDetail.task.taskNo} 转为人工处理吗?`, ) } onCloseTask={() => runTaskAction( 'close', () => closeAdminTask(resolvedDetail.task.taskId), '任务已关闭', `确认关闭任务 ${resolvedDetail.task.taskNo} 吗?关闭后不会自动继续推进。`, ) } /> {resolvedDetail.task.taskId} {resolvedDetail.task.taskNo} {resolvedDetail.task.platformOrderId || resolvedDetail.order?.platformOrderId || '-'} {resolvedDetail.orderItem?.skuName || resolvedDetail.task.skuName || '-'} {resolvedDetail.task.roleName || '-'}{' '} {resolvedDetail.task.roleId ? `(${resolvedDetail.task.roleId})` : ''} {formatAdminDateTime(resolvedDetail.task.updatedAt)} {resolvedDetail.task.lastError || '-'} {h5ClaimUrl || resolvedDetail.claimToken ? ( 领取链接与兑换控制 {resolvedDetail.order ? ( claimStatus === 'active' ? ( H5兑换有效 ) : claimStatus === 'paused' ? ( H5兑换已暂停 ) : ( H5兑换已关闭 ) ) : null} } > {h5ClaimUrl ? (
固定 H5 链接 {h5ClaimUrl} {orderId ? ( <> {canPauseOrClose && claimStatus !== 'paused' && ( )} {canPauseOrClose && claimStatus !== 'closed' && ( )} {claimStatus !== 'active' && ( )} ) : null}
{claimStatusReason ? ( 状态备注:{claimStatusReason} ) : null}
) : null} {resolvedDetail.claimToken ? (
历史/备用 Token 链接: {claimUrl ? ( {claimUrl} ) : claimLinkInvalid ? ( 链接已失效 ) : ( 暂无可用链接 )} {isExternalClaimLink ? ( 外部交付链接 ) : ( {resolvedDetail.claimToken.expiredAt ? `到期:${formatAdminDateTime(resolvedDetail.claimToken.expiredAt)}` : '长期有效'} )}
) : null}
) : null} {resolvedDetail.task.executorKey === 'manual_dispatch' || resolvedDetail.operations.canCompleteManualDispatch || resolvedDetail.manualDispatch ? ( ) : null} ) : ( ), }, { key: 'industry-voucher', label: '电子凭证', children: resolvedDetail.kuaishouIndustryVoucher ? ( ) : ( ), }, { key: 'resolution', label: '兑换重试', children: resolvedDetail.redeemResolution ? ( ) : ( ), }, { key: 'screenshot', label: '截图', children: resolvedDetail.screenshotUrl ? ( ) : ( ), }, { key: 'events', label: '事件流水', children: ( rowKey="eventId" pagination={false} columns={eventColumns} dataSource={resolvedDetail.events} scroll={{ x: 760 }} /> ), }, { key: 'raw', label: '原始数据', children: , }, ]} /> statusMutation.mutateAsync({ status: targetStatus, reason: statusReason.trim() || undefined, }) } onCancel={() => setStatusModalVisible(false)} okText="确认变更" cancelText="取消" centered >
原因 / 备注(可选): setStatusReason(e.target.value)} style={{ marginTop: 6 }} />
) } function TaskActionPanel({ detail, canManageTaskLifecycle, canOperateIndustryVoucher, canCloseTasks, actionLoadingKey, onRetry, onResendIndustryVoucher, onConsumeIndustryVoucher, onMarkManualReview, onCloseTask, }: { detail: AdminTaskDetail canManageTaskLifecycle: boolean canOperateIndustryVoucher: boolean canCloseTasks: boolean actionLoadingKey: string onRetry: () => void onResendIndustryVoucher: () => void onConsumeIndustryVoucher: () => void onMarkManualReview: () => void onCloseTask: () => void }) { const operations = detail.operations const loading = Boolean(actionLoadingKey) const hasIndustryVoucherActions = operations.canResendKuaishouIndustryVoucherCode || operations.canConsumeKuaishouIndustryVoucher || Boolean(detail.kuaishouIndustryVoucher) return (
{canManageTaskLifecycle ? (
通用
) : null} {canOperateIndustryVoucher && hasIndustryVoucherActions ? (
电子凭证
) : null} {canCloseTasks ? (
收口
) : null} {!canManageTaskLifecycle && !canOperateIndustryVoucher && !canCloseTasks ? ( 当前账号没有可执行的任务操作 ) : null}
{loading ? ( 操作执行中,请稍候。 ) : null}
) } function isUsableClaimLinkStatus(value: unknown) { const normalized = String(value || '') .trim() .toLowerCase() return normalized === 'active' || normalized === 'external' } function FulfillmentOverviewPanel({ detail, claimUrl, claimLinkInvalid, }: { detail: AdminTaskDetail claimUrl: string claimLinkInvalid: boolean }) { const executor = resolveTaskExecutorDisplay(detail.task.executorKey) const steps = buildFulfillmentOverviewSteps(detail, claimUrl, claimLinkInvalid) const claimIdentity = detail.claimIdentity const currentBlocker = resolveTaskBlocker(detail.task, claimUrl, claimLinkInvalid, claimIdentity) const uidMatchTag = claimIdentity?.uidMatched === true ? { color: 'success' as const, text: 'UID 已匹配' } : claimIdentity?.uidMatched === false ? { color: 'error' as const, text: 'UID 不一致' } : claimIdentity?.ready ? { color: 'processing' as const, text: '已填 UID' } : { color: 'warning' as const, text: '未填 UID' } return ( {executor.label}}> {claimIdentity?.expectedUid || 未提交} {claimIdentity?.boundUid || detail.task.roleId || '-'} {claimIdentity?.displaySource === 'shipped' ? ( 发货快照 ) : null} {claimIdentity?.boundRoleName || detail.task.roleName || '-'} {uidMatchTag.text} {claimIdentity?.compatibilityMode === 'legacy_no_uid' ? '旧单无 UID' : 'UID 闸门'} {claimIdentity?.shippedUid ? ( {claimIdentity.shippedUid} {claimIdentity.shippedRoleName ? ` / ${claimIdentity.shippedRoleName}` : ''} {claimIdentity.shippedAt ? ( {claimIdentity.shippedAt} ) : null} ) : null} {claimIdentity?.note || '暂无 UID 信息'}
{steps.map((step) => (
{step.label} {step.detail}
))}
) } function buildFulfillmentOverviewSteps( detail: AdminTaskDetail, claimUrl: string, claimLinkInvalid: boolean, ) { const task = detail.task const executor = resolveTaskExecutorDisplay(task.executorKey) const voucher = detail.kuaishouIndustryVoucher const manualDispatch = detail.manualDispatch const claimStatus = claimUrl ? 'success' : claimLinkInvalid ? 'failed' : detail.claimToken?.status || 'pending' return [ { key: 'source', label: '订单来源', status: detail.order?.payStatus || task.status, detail: `${detail.order?.provider || '-'} / ${detail.order?.platform || '-'} / ${task.platformOrderId || '-'}`, }, { key: 'product', label: '商品解析', status: detail.orderItem || task.skuName ? 'success' : 'pending_config', detail: detail.orderItem?.skuName || task.skuName || task.skuCode || '未命中履约商品', }, { key: 'planner', label: '履约计划', status: task.executorKey ? 'success' : 'pending_config', detail: executor.label, }, { key: 'prepare', label: '任务准备', status: task.resourceStatus || task.status, detail: resolveTaskPrepareSummary(detail), }, { key: 'delivery', label: '平台执行', status: resolveDeliveryStepStatus(detail), detail: resolveDeliveryStepSummary(detail), }, { key: 'voucher', label: '电子凭证', status: voucher ? voucher.sendCallbackStatus || voucher.status : 'skipped', detail: voucher ? `发码 ${voucher.sendCallbackStatus || '-'} / 凭证 ${voucher.status || '-'}` : '当前任务未绑定电子凭证', }, { key: 'claim', label: '交付链接', status: task.executorKey === 'manual_dispatch' ? 'skipped' : claimStatus, detail: task.executorKey === 'manual_dispatch' ? '人工履约不需要客户领取链接' : claimUrl ? '已有可用链接' : claimLinkInvalid ? '链接已失效' : '等待生成或同步链接', }, { key: 'finish', label: '履约收口', status: manualDispatch?.outcome || task.deliveryStatus || task.status, detail: task.resultMessage || task.lastError || '等待履约完成', }, ] } function resolveTaskBlocker( task: AdminTaskDetail['task'], claimUrl: string, claimLinkInvalid: boolean, claimIdentity?: AdminTaskDetail['claimIdentity'], ): { level: 'success' | 'info' | 'warning' | 'error'; title: string; detail: string } { if ( ['completed', 'redeemed', 'delivered'].includes(task.status) || task.deliveryStatus === 'delivered' ) { return { level: 'success', title: '履约已完成', detail: task.resultMessage || '任务已经收口。', } } if (['manual_review', 'retry_pending', 'failed'].includes(task.status)) { return { level: 'error', title: '当前卡在异常处理', detail: task.lastError || task.resultMessage || '需要人工检查后重试或收口。', } } if (task.executorKey === 'kuaishou_ct_assisted' && claimIdentity && !claimIdentity.ready) { return { level: 'warning', title: '等待用户填写 UID', detail: '旧单或新单均需用户打开领取页提交游戏 UID,否则禁止自动发货。', } } if (task.executorKey === 'kuaishou_ct_assisted' && claimIdentity?.uidMatched === false) { return { level: 'error', title: 'UID 与绑定角色不一致', detail: `填写 ${claimIdentity.expectedUid || '-'},绑定 ${claimIdentity.boundUid || '-'},请用户换绑或更正 UID。`, } } if (claimLinkInvalid) { return { level: 'warning', title: '领取链接不可用', detail: '链接状态不是 active,可重新生成或检查平台交付链接。', } } if (!claimUrl && task.executorKey !== 'manual_dispatch') { return { level: 'warning', title: '等待交付链接', detail: '任务尚未拿到可交付链接,先检查任务准备和平台执行状态。', } } if (task.customerStatus === 'waiting_customer') { return { level: 'info', title: '等待客户操作', detail: '资源已准备,客户还没有完成绑定或领取动作。', } } return { level: 'info', title: '履约推进中', detail: task.lastError || '当前任务没有明确异常,可按链路状态继续观察。', } } function resolveTaskPrepareSummary(detail: AdminTaskDetail) { const flow = detail.kuaishouCloudFulfillment if (flow) { return flow.binding.bindPreparedAt ? `绑定资源 ${flow.binding.vnPhone || flow.binding.vnId || '-'}` : '等待准备 Cloud 资源' } if (detail.task.executorKey === 'manual_dispatch') { return '人工履约任务无需自动准备资源' } return detail.task.resultMessage || detail.task.lastError || '等待执行器准备' } function resolveDeliveryStepStatus(detail: AdminTaskDetail) { const flow = detail.kuaishouCloudFulfillment if (flow) { return flow.dispatch.status || detail.task.deliveryStatus || detail.task.status } if (detail.manualDispatch) { return detail.manualDispatch.outcome || detail.task.deliveryStatus } return detail.task.deliveryStatus || detail.task.status } function resolveDeliveryStepSummary(detail: AdminTaskDetail) { const flow = detail.kuaishouCloudFulfillment if (flow) { if (flow.dispatch.dispatchAt) { return `已于 ${formatAdminDateTime(flow.dispatch.dispatchAt)} 发货` } return flow.dispatch.note || '等待平台发货执行' } if (detail.manualDispatch) { return ( detail.manualDispatch.resultMessage || detail.manualDispatch.deliveryReference || '人工履约已回写' ) } return detail.task.resultMessage || detail.task.lastError || '等待执行器推进' } function resolveTaskExecutorDisplay(value: unknown) { const executorKey = String(value || '').trim() if (executorKey === 'affiliate_dash') { return { label: '联盟发货', color: 'green' } } if (executorKey === 'kuaishou_ct_assisted') { return { label: '乐玩云(已下线)', color: 'default' } } if (executorKey === 'kuaishou-industry') { return { label: '行业电子凭证', color: 'cyan' } } if (executorKey === 'kuaishou_feifei') { return { label: '飞飞(已下线)', color: 'default' } } if (executorKey === 'manual_dispatch') { return { label: '人工履约', color: 'orange' } } return { label: executorKey || '未分配执行器', color: 'default' } } function ManualDispatchPanel({ detail, form, canSubmit, actionLoadingKey, onChange, onSubmit, }: { detail: AdminTaskDetail form: ManualDispatchForm canSubmit: boolean actionLoadingKey: string onChange: (form: ManualDispatchForm) => void onSubmit: (outcome: 'delivered' | 'failed') => void }) { return ( ) : null } >
履约单号 / 回执 onChange({ ...form, deliveryReference: event.target.value })} />
发放凭据 onChange({ ...form, deliveredCredential: event.target.value })} />
处理备注 onChange({ ...form, resultMessage: event.target.value })} />
{detail.manualDispatch ? ( {formatAdminDateTime(detail.manualDispatch.completedAt)} {detail.manualDispatch.completedBy?.username || '-'} /{' '} {detail.manualDispatch.completedBy?.role || '-'} {detail.manualDispatch.deliveryReference || '-'} {detail.manualDispatch.deliveredCredential || '-'} {detail.manualDispatch.resultMessage || '-'} ) : null}
) } function KuaishouIndustryVoucherPanel({ voucher }: { voucher: KuaishouIndustryVoucher }) { const consumeDetails = Array.isArray(voucher.consumeDetails) ? voucher.consumeDetails : [] return ( {voucher.oid || '-'} {voucher.voucherCode || '-'} {voucher.unitIndex || '-'} {voucher.sendCallbackAttemptCount} {formatAdminDateTime(voucher.sendCallbackSentAt)} {voucher.consumeSerialNum || '-'} {formatAdminDateTime(voucher.consumedAt)} {voucher.sendCallbackLastError || '-'} {voucher.sendCallbackLastError ? ( ) : null} {consumeDetails.length > 0 ? ( > className="platform-section-gap" rowKey={(_, index) => String(index ?? 0)} pagination={false} dataSource={consumeDetails} scroll={{ x: 760 }} columns={[ { title: '核销明细', render: (_, row) => , }, ]} /> ) : null} ) } function KuaishouCloudPanel({ flow, }: { flow: NonNullable }) { const roleInfoEntries = flow.role.rawInfo ? Object.entries(flow.role.rawInfo).filter( ([, value]) => value !== null && value !== undefined && String(value).trim() !== '', ) : [] const checklist = [ { key: 'ticket', label: '凭证确认', status: flow.ticket.status || 'pending', detail: flow.ticket.verifiedAt ? `已于 ${formatAdminDateTime(flow.ticket.verifiedAt)} 确认` : '等待电子凭证同步确认', }, { key: 'bind', label: '绑定资源', status: flow.binding.prepareStatus || 'pending', detail: flow.binding.bindPreparedAt ? `绑定资源已准备,VN ${flow.binding.vnId || '-'}` : '等待准备 Cloud 绑定资源', }, { key: 'role', label: '角色识别', status: flow.role.status || 'pending', detail: flow.role.name || flow.role.rid ? `${flow.role.name || '-'} / ${flow.role.rid || '-'}` : '客户绑定后刷新角色信息', }, { key: 'dispatch', label: '发货执行', status: flow.dispatch.status || 'pending', detail: flow.dispatch.dispatchAt ? `已于 ${formatAdminDateTime(flow.dispatch.dispatchAt)} 发货` : '等待客服确认绑定并发货', }, { key: 'return', label: '退还号码', status: flow.returnNumber.status || 'pending', detail: flow.returnNumber.returnedAt ? `已于 ${formatAdminDateTime(flow.returnNumber.returnedAt)} 退号` : '发货后执行退还号码', }, { key: 'consume', label: '电子凭证核销', status: flow.consume.status || 'pending', detail: flow.consume.consumedAt ? `已于 ${formatAdminDateTime(flow.consume.consumedAt)} 核销(链接交付或履约收口)` : flow.consume.errorMessage || '等待 91 交付链接时核销,或发货收口时补核销', }, ] return (
{flow.binding.resolvedSourceLabel || flow.binding.resolvedSourceKey || '-'} {flow.binding.vnPhone || flow.binding.vnId || '-'} {flow.role.name || '-'} {flow.role.rid ? `(${flow.role.rid})` : ''} {flow.binding.bindUrl ? ( {flow.binding.bindUrl} ) : ( '-' )}
{checklist.map((item) => (
{item.label} {item.detail}
))}
{roleInfoEntries.length > 0 ? ( {roleInfoEntries.map(([key, value]) => ( {typeof value === 'object' ? JSON.stringify(value) : String(value)} ))} ) : null} {flow.rebind.history.length > 0 ? ( `${row.attempt}-${row.requestedAt || ''}`} pagination={false} dataSource={flow.rebind.history} scroll={{ x: 860 }} columns={[ { title: '次数', dataIndex: 'attempt', width: 80 }, { title: '时间', dataIndex: 'requestedAt', width: 180, render: (value) => formatAdminDateTime(value), }, { title: '状态', dataIndex: 'status', width: 120, render: (value) => ( {String(value || '-')} ), }, { title: '旧角色', render: (_, row) => `${row.oldBinding.roleName || '-'} / ${row.oldBinding.roleId || '-'}`, }, { title: '旧虚拟号', render: (_, row) => row.oldBinding.vnPhone || row.oldBinding.vnId || row.oldBinding.vnKey || '-', }, { title: '异常', dataIndex: 'errorMessage', render: (value) => value || '-', }, ]} /> ) : null} ) } function RedeemResolutionPanel({ resolution, }: { resolution: NonNullable }) { return ( {formatRedeemResolutionStatus(resolution.status)} {resolution.taskStatus || '-'} {resolution.replacementCount} {formatAdminDateTime(resolution.finishedAt)}
`${row.attempt}-${row.codeMasked}`} pagination={false} dataSource={resolution.attempts} scroll={{ x: 760 }} columns={[ { title: '次数', dataIndex: 'attempt', width: 80 }, { title: '凭据', dataIndex: 'codeMasked', minWidth: 160 }, { title: '类型', dataIndex: 'credentialType', minWidth: 120 }, { title: '结果', dataIndex: 'outcome', minWidth: 160, render: (value) => formatRedeemOutcomeLabel(value), }, { title: '代码', dataIndex: 'resultCode', minWidth: 120 }, { title: '说明', dataIndex: 'resultMessage', minWidth: 220 }, ]} /> ) } function ScreenshotPanel({ screenshotPreviewUrl }: { screenshotPreviewUrl: string }) { return ( {screenshotPreviewUrl ? (
) : ( )}
) } function BackButton() { const navigate = useNavigate() return ( ) }