import { Button, Dropdown, Popconfirm, Space, Tag, Tooltip, Typography } from 'antd' import { CopyOutlined, ExportOutlined, EyeOutlined, FileTextOutlined, StopOutlined, SyncOutlined, UndoOutlined, } from '@ant-design/icons' import type { ColumnsType } from 'antd/es/table' import type { RockTokenItem } from '../../api' import { formatDateTime } from '../../utils/time' import { TokenRemarkCell } from './TokenRemarkCell' import type { UidEditState } from './types' const { Text } = Typography const MS_PER_DAY = 24 * 60 * 60 * 1000 // 满 72 小时算 1 天:超时预警按实际时长计算,不按自然日跨天。 function usingElapsedDays(usingAt?: string): number | null { if (!usingAt) return null const startedAt = new Date(usingAt).getTime() if (Number.isNaN(startedAt)) return null const diff = Date.now() - startedAt return diff > 0 ? diff / MS_PER_DAY : 0 } // Token 主信息:编号/兑换码 与 包ID/提取码 上下两行展示, // 两个复制按钮各自只复制兑换码 / 提取码,编号与包 ID 不参与复制。 export function TokenInfoCell({ record }: { record: RockTokenItem }) { const redeemCode = (record.redeem_code || '').trim() const extractCode = (record.token || '').trim() const tokenNo = record.upstream_token_id || '-' const packageNo = record.package_id ? String(record.package_id) : '-' return (
#{record.id} {tokenNo}/{redeemCode || '-'}
包ID {packageNo}/{extractCode || '-'}
) } export interface TokenColumnHandlers { copyingVoucherId: number | null usingWarnDays: number usingDangerDays: number refreshingTokenId: number | null onRefreshBinding: (id: number) => void openingUpstreamId: number | null onOpenUpstream: (id: number) => void onCopyVoucher: (record: RockTokenItem) => void onPreviewVoucher: (record: RockTokenItem) => void onEditUid: (state: UidEditState) => void onViewRawData: (record: RockTokenItem) => void onResetToken: (id: number) => void onReleaseToken: (id: number) => void onUpdateRemark: (id: number, remark: string, status?: string) => void onMarkAbnormal: (id: number, remark?: string) => void } export function buildTokenColumns({ copyingVoucherId, usingWarnDays, usingDangerDays, refreshingTokenId, onRefreshBinding, openingUpstreamId, onOpenUpstream, onCopyVoucher, onPreviewVoucher, onEditUid, onViewRawData, onResetToken, onReleaseToken, onUpdateRemark, onMarkAbnormal, }: TokenColumnHandlers): ColumnsType { return [ { title: 'ID / Token(编号/兑换码 · 包ID/提取码)', key: 'token_info', width: 320, render: (_, record) => , }, { title: '规格与适用范围', dataIndex: 'color', width: 120, render: (_, record) => ( 通用黑色 Token {record.region || 'Q/V区通用'} ), }, { title: '库存状态', dataIndex: 'status', width: 165, render: (status, record) => { switch (status) { case 'available': return ( ● 可用库存 ) case 'using': { const days = usingElapsedDays(record.using_at) if (days !== null && days >= usingDangerDays) { return ( ⚠️ 使用中超时 {days.toFixed(1)} 天 ) } if (days !== null && days >= usingWarnDays) { return ( ● 扫码使用中 · {days.toFixed(1)} 天 ) } return ( ● 扫码使用中 ) } case 'bound': return ( ● 已授权绑定 ) case 'redeemed': return ( ● 已交付发货 ) case 'mismatch': return ( ⚠️ 绑定错误 (阻断) ) case 'abnormal': return ( 🚫 已上报失效 ) default: return {status} } }, }, { title: '流转时间', width: 180, render: (_, record) => { const usingDays = record.status === 'using' ? usingElapsedDays(record.using_at) : null const usingColor = usingDays === null ? '#64748b' : usingDays >= usingDangerDays ? '#dc2626' : usingDays >= usingWarnDays ? '#d97706' : '#0891b2' return (
入库: {formatDateTime(record.created_at)}
{record.status === 'using' && record.using_at && (
⏳ 使用中: {formatDateTime(record.using_at)} {usingDays !== null ? `(${usingDays.toFixed(1)} 天)` : ''}
)} {record.mismatch_at && (
⚠️ 绑错: {formatDateTime(record.mismatch_at)}
)} {record.redeemed_at && (
✅ 发货: {formatDateTime(record.redeemed_at)}
)} {!record.redeemed_at && record.bound_at && (
🔗 绑定: {formatDateTime(record.bound_at)}
)} {!record.bound_at && !record.mismatch_at && record.claimed_at && (
📦 提货: {formatDateTime(record.claimed_at)}
)}
) }, }, { title: '关联业务工单', dataIndex: 'assigned_order_no', width: 158, render: (orderNo, record) => { if (!orderNo) { return - } return ( {/* 系统履约工单号 */}
{orderNo}
{/* 创建订单时的商户单号 */} {record.client_order_no ? (
商户: {record.client_order_no}
) : null}
) }, }, { title: '预期 UID vs 扫码角色 (风控门禁)', width: 255, render: (_, record) => { const isMismatch = record.status === 'mismatch' return (
预期 UID: {record.expected_uid || '-'}
扫码反查: {record.actual_role_name ? `${record.actual_role_name} (${record.actual_role_id || '-'})` : '-'}
{isMismatch && record.mismatch_reason && (
{record.mismatch_reason}
)}
) }, }, { title: '备注', width: 44, align: 'center', render: (_, record) => ( ), }, { title: '凭证', key: 'voucher', width: 88, align: 'center', render: (_, record) => { if (record.status !== 'redeemed' && !record.upstream_fulfill_id) { return - } const isCopying = copyingVoucherId === record.id const isRockBlue = record.target_skin?.includes('蓝') const sku = isRockBlue ? 'rock_blue_sportswear' : 'rock_black_tshirt' const imgSrc = `/images/products/${sku}.webp` return (
, label: '复制发货结果图' }, { key: 'preview', icon: , label: '预览高清大图' }, ], onClick: ({ key }) => { if (key === 'copy') onCopyVoucher(record) if (key === 'preview') onPreviewVoucher(record) }, }} >
{record.target_skin
) }, }, { title: '原始', width: 44, align: 'center', render: (_, record) => record.raw_data ? ( ) const upstreamDisabled = !record.package_id const upstreamTip = upstreamDisabled ? '该 Token 未关联上游打包记录,无法跳转上游' : record.package_share_code ? '' : '本地暂无包分享码,点击时会自动向上游反查' const upstreamButton = ( ) if (record.status === 'mismatch') { return ( {refreshButton} {upstreamButton} onMarkAbnormal(record.id, '绑定错误-已上报上游 (已失效)')} > onResetToken(record.id)} > ) } if (record.status === 'abnormal') { return ( 已失效作废 {upstreamButton} onResetToken(record.id)} > ) } if (record.status === 'using') { const canRelease = !record.bound_at && !record.actual_role_id return ( {refreshButton} {upstreamButton} {canRelease && ( onReleaseToken(record.id)} > )} ) } if (record.status === 'bound') { return ( {refreshButton} {upstreamButton} ) } return ( {upstreamButton} ) }, }, ] }