import { ArrowLeftOutlined, CheckCircleOutlined, CloseCircleOutlined, CopyOutlined, ReloadOutlined, RollbackOutlined, SendOutlined, SwapOutlined, ToolOutlined, UserSwitchOutlined, } from '@ant-design/icons' import { Alert, App, Button, Card, Descriptions, Empty, Image, Input, Skeleton, Space, Table, Tabs, Tag, Typography, } from 'antd' import type { TableColumnsType } from 'antd' import { 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, dispatchAdminTaskKuaishouCloudFulfillment, fetchAdminTaskDetail, fetchAdminTaskScreenshot, markAdminTaskManualReview, prepareAdminTaskKuaishouCloudFulfillment, rebindAdminTaskKuaishouCloudRole, refreshAdminTaskKuaishouCloudRoleInfo, resendAdminTaskKuaishouIndustryVoucherCode, retryAdminTask, returnNumberAdminTaskKuaishouCloudFulfillment, } 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 navigate = useNavigate() const { taskId = '' } = useParams() const { message, modal } = App.useApp() const [actionLoadingKey, setActionLoadingKey] = useState('') const [lastClaimUrl, setLastClaimUrl] = useState('') const [screenshotPreviewUrl, setScreenshotPreviewUrl] = 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 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, ) } return (
} /> runTaskAction( 'retry', () => retryAdminTask(resolvedDetail.task.taskId), '任务已重试', `确认重试任务 ${resolvedDetail.task.taskNo} 吗?`, ) } onRebindRole={() => runTaskAction( 'rebind-role', () => rebindAdminTaskKuaishouCloudRole(resolvedDetail.task.taskId), '角色换绑资源已准备完成', `确认为任务 ${resolvedDetail.task.taskNo} 换绑角色吗?当前虚拟号会退还,并生成新的绑定二维码。`, ) } onPrepareKuaishouCloud={() => runTaskAction( 'prepare-kuaishou-cloud', () => prepareAdminTaskKuaishouCloudFulfillment(resolvedDetail.task.taskId), '绑定资源已准备完成', `确认开始为任务 ${resolvedDetail.task.taskNo} 准备绑定资源吗?系统会自动检查库存配置并申请虚拟号。`, ) } onDispatchKuaishouCloud={() => runTaskAction( 'dispatch-kuaishou-cloud', () => dispatchAdminTaskKuaishouCloudFulfillment(resolvedDetail.task.taskId), '已完成绑定确认并发货', `确认客户已经完成绑定,并立即为任务 ${resolvedDetail.task.taskNo} 执行发货吗?这个动作会把"确认绑定完成"和"发货"合并为一步。`, ) } onReturnKuaishouCloud={() => runTaskAction( 'return-kuaishou-cloud', () => returnNumberAdminTaskKuaishouCloudFulfillment(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 || '-'} {resolvedDetail.claimToken ? ( {claimUrl ? ( {claimUrl} ) : claimLinkInvalid ? ( 领取链接已失效 ) : ( 暂无可用领取链接 )} {isExternalClaimLink ? ( 外部交付链接 ) : ( 到期:{formatAdminDateTime(resolvedDetail.claimToken.expiredAt)} )} ) : null} {resolvedDetail.task.executorKey === 'manual_dispatch' || resolvedDetail.operations.canCompleteManualDispatch || resolvedDetail.manualDispatch ? ( ) : null} runTaskAction( 'refresh-role', () => refreshAdminTaskKuaishouCloudRoleInfo(resolvedDetail.task.taskId), '角色信息已刷新', `确认刷新任务 ${resolvedDetail.task.taskNo} 当前云绑定角色信息吗?系统会重新查询 cloudtentacles 的绑定结果。`, ) } /> ) : ( ), }, { 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: , }, ]} />
) } function TaskActionPanel({ detail, canManageTaskLifecycle, canOperateIndustryVoucher, canCloseTasks, actionLoadingKey, onRetry, onRebindRole, onPrepareKuaishouCloud, onDispatchKuaishouCloud, onReturnKuaishouCloud, onResendIndustryVoucher, onConsumeIndustryVoucher, onMarkManualReview, onCloseTask, }: { detail: AdminTaskDetail canManageTaskLifecycle: boolean canOperateIndustryVoucher: boolean canCloseTasks: boolean actionLoadingKey: string onRetry: () => void onRebindRole: () => void onPrepareKuaishouCloud: () => void onDispatchKuaishouCloud: () => void onReturnKuaishouCloud: () => void onResendIndustryVoucher: () => void onConsumeIndustryVoucher: () => void onMarkManualReview: () => void onCloseTask: () => void }) { const operations = detail.operations const loading = Boolean(actionLoadingKey) const hasCloudActions = operations.canPrepareKuaishouCloudFulfillment || operations.canRebindKuaishouCloudRole || operations.canDispatchKuaishouCloudFulfillment || operations.canReturnKuaishouCloudFulfillment const hasIndustryVoucherActions = operations.canResendKuaishouIndustryVoucherCode || operations.canConsumeKuaishouIndustryVoucher || Boolean(detail.kuaishouIndustryVoucher) return (
{canManageTaskLifecycle ? (
通用
) : null} {hasCloudActions ? (
kuaishou-lewan {canManageTaskLifecycle && operations.canPrepareKuaishouCloudFulfillment ? ( ) : null} {operations.canRebindKuaishouCloudRole ? ( ) : null} {canManageTaskLifecycle && operations.canDispatchKuaishouCloudFulfillment ? ( ) : null} {canManageTaskLifecycle && operations.canReturnKuaishouCloudFulfillment ? ( ) : null}
) : null} {canOperateIndustryVoucher && hasIndustryVoucherActions ? (
电子凭证
) : null} {canCloseTasks ? (
收口
) : null} {!canManageTaskLifecycle && !canOperateIndustryVoucher && !canCloseTasks && !hasCloudActions ? ( 当前账号没有可执行的任务操作 ) : 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?.boundRoleName || detail.task.roleName || '-'} {uidMatchTag.text} {claimIdentity?.compatibilityMode === 'legacy_no_uid' ? '旧单无 UID' : 'UID 闸门'} {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 === 'kuaishou_ct_assisted') { return { label: 'kuaishou-lewan', color: 'blue' } } if (executorKey === 'kuaishou-industry') { return { label: '行业电子凭证', color: 'cyan' } } if (executorKey === 'kuaishou_feifei') { return { label: 'kuaishou-feifei', color: 'purple' } } 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, canRefresh, actionLoadingKey, onRefreshRole, }: { flow: NonNullable canRefresh: boolean actionLoadingKey: string onRefreshRole: () => void }) { 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 || '等待退号后收口', }, ] return (
} disabled={Boolean(actionLoadingKey) || !canRefresh} loading={actionLoadingKey === 'refresh-role'} onClick={onRefreshRole} > 刷新角色 } > {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 ( ) }