fix(worker-platform): 接单平台工单详情抽屉完整展示寄售手动匹配与指派订单的卖家资料

1. 修复管理后台「接单平台 -> 接单工单」抽屉中,手动匹配或指派寄售库存履约的工单因 booster_material_json 为空导致卖家资料全部显示 '-' 的问题;
2. 后端在履约及指派时将卖家角色昵称、数字ID、联系方式、手机号、备注及上架凭证截图完整写入工单 material.consignment;
3. 后端工单查询及展示映射层增加自动回退与批量库存补充解析机制,确保历史与新增的寄售工单均能准确解析出卖家角色名、ID、区服、系统与截图;
4. 前端工单抽屉卡片优化寄售订单的资料展示与阶段标签,清晰展示卖家角色名、游戏ID、联系方式(微信/QQ/手机)、提报/仓库单号及上架截图。
This commit is contained in:
yml2213
2026-09-19 14:33:56 +08:00
parent 13a9028c66
commit 2c120bd3e5
6 changed files with 358 additions and 113 deletions
@@ -177,6 +177,16 @@ function isInternalConsignmentOrder(row: WorkOrder) {
return Boolean(consignment.inventoryId) && !row.worker
}
function isConsignmentOrder(row: WorkOrder) {
const consignment = asRecord((row.material as Record<string, unknown> | undefined)?.consignment)
return Boolean(
consignment.inventoryId ||
consignment.inventoryNo ||
consignment.fulfillmentType ||
row.gift?.boosterMaterial?.fields?.inventoryNo,
)
}
/** 工单 `material.consignment`(寄售履约信息);没有寄售履约信息时返回 null。 */
function getConsignmentFulfillment(row: WorkOrder): ConsignmentFulfillmentInfo | null {
const consignment = asRecord((row.material as Record<string, unknown> | undefined)?.consignment)
@@ -2093,112 +2103,177 @@ export default function WorkOrdersPanel() {
</div>
</Card>
{detailOrder.acceptanceMode === 'friend_gift' && detailOrder.gift ? (
<Card
title={
isInternalConsignmentOrder(detailOrder) ? '内部寄售赠送进度' : '好友赠送进度'
}
size="small"
extra={
<Space>
{isInternalConsignmentOrder(detailOrder) ? (
<Tag color="cyan">内部寄售匹配</Tag>
) : (
<Tag color="purple">{formatAcceptanceMode(detailOrder.acceptanceMode)}</Tag>
)}
<Tag color={resolveGiftPhaseColor(detailOrder.gift.phase)}>
{formatGiftPhase(
detailOrder.gift.phase,
isInternalConsignmentOrder(detailOrder),
)}
</Tag>
{detailOrder.capabilities?.canConfirmFriend && canManageProblem ? (
<Button
type="primary"
size="small"
onClick={() => confirmFriendAdded(detailOrder)}
>
确认加好友
</Button>
) : null}
{detailOrder.capabilities?.canCompleteGiftCooldown && canManageProblem ? (
<Button
type="primary"
size="small"
onClick={() => completeGiftCooldown(detailOrder)}
>
结束冷却
</Button>
) : null}
{detailOrder.capabilities?.canResetGiftFlow && canManageProblem ? (
<Button
size="small"
danger
onClick={() => requestResetGiftMaterial(detailOrder)}
>
重置打手资料
</Button>
) : null}
</Space>
}
>
<div className="worker-order-detail-stack">
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
<Descriptions.Item label="冷却时长">
{detailOrder.gift.cooldownMinutes > 0
? `${detailOrder.gift.cooldownMinutes} 分钟`
: `${detailOrder.gift.cooldownHours} 小时`}
</Descriptions.Item>
<Descriptions.Item label="打手资料提交">
{formatAdminDateTime(detailOrder.gift.boosterMaterial?.submittedAt)}
</Descriptions.Item>
<Descriptions.Item label="已加好友">
{formatAdminDateTime(detailOrder.gift.friendAddedAt)}
</Descriptions.Item>
<Descriptions.Item label="可赠送时间">
{detailOrder.gift.giftAvailableAt
? formatAdminDateTime(detailOrder.gift.giftAvailableAt)
: '-'}
</Descriptions.Item>
</Descriptions>
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
{(
[
['gameNickname', '游戏昵称'],
['gameId', '游戏ID'],
['serverZone', '区服'],
['system', '系统'],
] as const
).map(([key, label]) => (
<Descriptions.Item key={key} label={label}>
<span className="worker-order-detail-value">
{String(detailOrder.gift?.boosterMaterial?.fields?.[key] || '').trim() ||
'-'}
</span>
</Descriptions.Item>
))}
<Descriptions.Item label="打手备注" span={2}>
<span className="worker-order-detail-value">
{String(detailOrder.gift?.boosterMaterial?.note || '').trim() || '-'}
</span>
</Descriptions.Item>
</Descriptions>
<div>
<Typography.Text type="secondary">打手主页截图</Typography.Text>
<div style={{ marginTop: 8 }}>
{detailOrder.gift.boosterMaterial?.screenshots?.length ? (
<ImagePreviewList
files={detailOrder.gift.boosterMaterial.screenshots}
size={72}
/>
) : (
<Typography.Text type="secondary">未提交</Typography.Text>
)}
</div>
</div>
</div>
</Card>
) : null}
{detailOrder.acceptanceMode === 'friend_gift' && detailOrder.gift
? (() => {
const isConsignment =
isInternalConsignmentOrder(detailOrder) || isConsignmentOrder(detailOrder)
const consignmentInfo = getConsignmentFulfillment(detailOrder)
const sellerFields = detailOrder.gift.boosterMaterial?.fields || {}
const sellerContact =
sellerFields.sellerContact || String(consignmentInfo?.sellerContact || '')
const sellerPhone =
sellerFields.sellerPhone || String(consignmentInfo?.sellerPhone || '')
const inventoryNo =
sellerFields.inventoryNo || String(consignmentInfo?.inventoryNo || '')
const sourceSubmissionNo =
sellerFields.sourceSubmissionNo ||
String(consignmentInfo?.sourceSubmissionNo || '')
const effectiveNote = String(
detailOrder.gift?.boosterMaterial?.note || consignmentInfo?.remark || '',
).trim()
return (
<Card
title={isConsignment ? '寄售赠送进度' : '好友赠送进度'}
size="small"
extra={
<Space>
{isInternalConsignmentOrder(detailOrder) ? (
<Tag color="cyan">内部寄售匹配</Tag>
) : isConsignment ? (
<Tag color="blue">寄售指派匹配</Tag>
) : (
<Tag color="purple">
{formatAcceptanceMode(detailOrder.acceptanceMode)}
</Tag>
)}
<Tag color={resolveGiftPhaseColor(detailOrder.gift.phase)}>
{formatGiftPhase(detailOrder.gift.phase, isConsignment)}
</Tag>
{detailOrder.capabilities?.canConfirmFriend && canManageProblem ? (
<Button
type="primary"
size="small"
onClick={() => confirmFriendAdded(detailOrder)}
>
确认加好友
</Button>
) : null}
{detailOrder.capabilities?.canCompleteGiftCooldown && canManageProblem ? (
<Button
type="primary"
size="small"
onClick={() => completeGiftCooldown(detailOrder)}
>
结束冷却
</Button>
) : null}
{detailOrder.capabilities?.canResetGiftFlow && canManageProblem ? (
<Button
size="small"
danger
onClick={() => requestResetGiftMaterial(detailOrder)}
>
重置打手资料
</Button>
) : null}
</Space>
}
>
<div className="worker-order-detail-stack">
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
<Descriptions.Item label="冷却时长">
{detailOrder.gift.cooldownMinutes > 0
? `${detailOrder.gift.cooldownMinutes} 分钟`
: `${detailOrder.gift.cooldownHours} 小时`}
</Descriptions.Item>
<Descriptions.Item
label={isConsignment ? '卖家资料/提报时间' : '打手资料提交'}
>
{formatAdminDateTime(detailOrder.gift.boosterMaterial?.submittedAt) ||
(isConsignment ? '已自动带入卖家现货资料' : '-')}
</Descriptions.Item>
<Descriptions.Item label="已加好友">
{formatAdminDateTime(detailOrder.gift.friendAddedAt)}
</Descriptions.Item>
<Descriptions.Item label="可赠送时间">
{detailOrder.gift.giftAvailableAt
? formatAdminDateTime(detailOrder.gift.giftAvailableAt)
: '-'}
</Descriptions.Item>
</Descriptions>
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
<Descriptions.Item label={isConsignment ? '卖家角色昵称' : '游戏昵称'}>
<span className="worker-order-detail-value">
{String(
sellerFields.gameNickname || consignmentInfo?.buyerName || '',
).trim() || '-'}
</span>
</Descriptions.Item>
<Descriptions.Item label={isConsignment ? '卖家游戏ID' : '游戏ID'}>
<span className="worker-order-detail-value">
{String(
sellerFields.gameId || consignmentInfo?.sellerGameId || '',
).trim() || '-'}
</span>
</Descriptions.Item>
<Descriptions.Item label="区服">
<span className="worker-order-detail-value">
{String(
sellerFields.serverZone || consignmentInfo?.serverZone || '',
).trim() || '-'}
</span>
</Descriptions.Item>
<Descriptions.Item label="系统">
<span className="worker-order-detail-value">
{String(
sellerFields.system || consignmentInfo?.system || '',
).trim() || '-'}
</span>
</Descriptions.Item>
{isConsignment && (sellerContact || sellerPhone) ? (
<Descriptions.Item label="卖家联系方式" span={2}>
<span className="worker-order-detail-value">
{[
sellerContact ? `微信/QQ: ${sellerContact}` : '',
sellerPhone ? `手机: ${sellerPhone}` : '',
]
.filter(Boolean)
.join(' / ')}
</span>
</Descriptions.Item>
) : null}
{isConsignment && (inventoryNo || sourceSubmissionNo) ? (
<Descriptions.Item label="寄售单号" span={2}>
<span className="worker-order-detail-value">
{[
sourceSubmissionNo ? `提报单: ${sourceSubmissionNo}` : '',
inventoryNo ? `仓库单: ${inventoryNo}` : '',
]
.filter(Boolean)
.join(' / ')}
</span>
</Descriptions.Item>
) : null}
<Descriptions.Item
label={isConsignment ? '卖家备注' : '打手备注'}
span={2}
>
<span className="worker-order-detail-value">
{effectiveNote || '-'}
</span>
</Descriptions.Item>
</Descriptions>
<div>
<Typography.Text type="secondary">
{isConsignment ? '卖家上架/主页截图' : '打手主页截图'}
</Typography.Text>
<div style={{ marginTop: 8 }}>
{detailOrder.gift.boosterMaterial?.screenshots?.length ? (
<ImagePreviewList
files={detailOrder.gift.boosterMaterial.screenshots}
size={72}
/>
) : (
<Typography.Text type="secondary">未提交</Typography.Text>
)}
</div>
</div>
</div>
</Card>
)
})()
: null}
<Card title="验收信息" size="small">
<div className="worker-order-detail-stack">