From 29589bb9af687326922b32ecbfdebe377b664a4d Mon Sep 17 00:00:00 2001 From: yml2213 Date: Mon, 21 Sep 2026 11:40:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(worker-platform):=20=E6=8E=A5=E5=8D=95?= =?UTF-8?q?=E5=B7=A5=E5=8D=95=E8=A1=A8=E6=A0=BC=E3=80=8C=E6=89=93=E6=89=8B?= =?UTF-8?q?=E3=80=8D=E5=88=97=E6=94=AF=E6=8C=81=E5=B1=95=E7=A4=BA=E5=AF=84?= =?UTF-8?q?=E5=94=AE=E4=BE=9B=E8=B4=A7=E6=89=93=E6=89=8B=E4=B8=8E=E7=8E=B0?= =?UTF-8?q?=E8=B4=A7=E8=B5=84=E6=96=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增 resolveConsignmentWorkerDisplay 工具函数,自动解析工单寄售现货与资料字段(供货打手、游戏角色昵称、游戏ID及联系方式); 2. 优化 renderWorkOrderWorkers 渲染逻辑:当工单为内部寄售现货履约时,打手列带「供货打手」或「内部现货」专属角标,主标题展示打手名/角色名,副标题拼接游戏ID与联系方式,解决原先显示为破折号 - 的问题; 3. 新增 consignment-worker-display 单元测试,更新寄卖库存优化现状与进度追踪文档,前端 57 个测试全绿并通过类型检查。 --- .../admin/panels/work-order-panel-utils.tsx | 70 ++++++++++++++----- .../frontend/src/utils/consignment-display.ts | 58 +++++++++++++++ .../tests/consignment-worker-display.test.ts | 55 +++++++++++++++ docs/寄卖库存优化现状与进度追踪.md | 23 +++++- 4 files changed, 187 insertions(+), 19 deletions(-) create mode 100644 apps/frontend/src/utils/consignment-display.ts create mode 100644 apps/frontend/tests/consignment-worker-display.test.ts diff --git a/apps/frontend/src/pages/admin/panels/work-order-panel-utils.tsx b/apps/frontend/src/pages/admin/panels/work-order-panel-utils.tsx index 9acaa539..b31d7668 100644 --- a/apps/frontend/src/pages/admin/panels/work-order-panel-utils.tsx +++ b/apps/frontend/src/pages/admin/panels/work-order-panel-utils.tsx @@ -1,6 +1,7 @@ -import { Typography } from 'antd' +import { Space, Tag, Typography } from 'antd' import type { CollectField, WorkOrder } from '@/types/worker-platform' +import { resolveConsignmentWorkerDisplay } from '@/utils/consignment-display' import { asRecord, formatMoney } from './shared' @@ -27,25 +28,58 @@ export function formatUserPaymentAmount(order: WorkOrder): string { return paymentFen === null ? '-' : formatMoney(paymentFen) } -/** 拼单母单没有单一 assigned worker,改为展示当前参与打手及其承接份数。 */ +/** 渲染工单关联打手:普通打手 / 拼单参与打手 / 寄卖供货打手与现货资料 */ export function renderWorkOrderWorkers(order: WorkOrder) { if (order.worker) return order.worker.displayName || order.worker.username || '-' - if (!order.sharing?.enabled || !order.sharingWorkers?.length) return '-' - const names = order.sharingWorkers - .map((worker) => worker.displayName || worker.username || `打手#${worker.workerId}`) - .join('、') - const quantity = order.sharingWorkers.reduce( - (sum, worker) => sum + Number(worker.quantity || 0), - 0, - ) - return ( -
- {names} - - {order.sharingWorkers.length} 人 · {quantity} 份 - -
- ) + + if (order.sharing?.enabled && order.sharingWorkers?.length) { + const names = order.sharingWorkers + .map((worker) => worker.displayName || worker.username || `打手#${worker.workerId}`) + .join('、') + const quantity = order.sharingWorkers.reduce( + (sum, worker) => sum + Number(worker.quantity || 0), + 0, + ) + return ( +
+ {names} + + {order.sharingWorkers.length} 人 · {quantity} 份 + +
+ ) + } + + // 寄卖库存 / 内部现货履约单:回显供货打手或卖家现货资料 + const consignmentDisplay = resolveConsignmentWorkerDisplay(order) + if (consignmentDisplay) { + return ( +
+ + + {consignmentDisplay.tagText} + + + {consignmentDisplay.primaryName} + + + {consignmentDisplay.secondaryInfo ? ( + + {consignmentDisplay.secondaryInfo} + + ) : null} +
+ ) + } + + return '-' } export function getDisplayOrderNo( diff --git a/apps/frontend/src/utils/consignment-display.ts b/apps/frontend/src/utils/consignment-display.ts new file mode 100644 index 00000000..00b6de84 --- /dev/null +++ b/apps/frontend/src/utils/consignment-display.ts @@ -0,0 +1,58 @@ +import type { WorkOrder } from '@/types/worker-platform' + +function asRecord(value: unknown): Record { + return value && typeof value === 'object' && !Array.isArray(value) + ? (value as Record) + : {} +} + +export type ConsignmentWorkerDisplayInfo = { + tagText: '供货打手' | '内部现货' + primaryName: string + secondaryInfo: string +} + +/** 解析寄卖库存/内部现货履约单的供货人与现货资料信息 */ +export function resolveConsignmentWorkerDisplay( + order: WorkOrder, +): ConsignmentWorkerDisplayInfo | null { + const material = asRecord(order.material) + const consignment = asRecord(material.consignment) + const boosterFields = asRecord(order.gift?.boosterMaterial?.fields) + const hasConsignment = + Object.keys(consignment).length > 0 || + Boolean(boosterFields.sourceType === 'consignment' || boosterFields.inventoryNo) + + if (!hasConsignment) return null + + const workerName = String(consignment.workerName || consignment.worker_name || '').trim() + const sellerNickname = String( + boosterFields.gameNickname || consignment.buyerName || consignment.sellerGameNickname || '', + ).trim() + const sellerGameId = String( + boosterFields.gameId || consignment.sellerGameId || consignment.seller_game_id || '', + ).trim() + const sellerContact = String( + boosterFields.sellerPhone || + consignment.sellerPhone || + consignment.workerPhone || + boosterFields.sellerContact || + consignment.sellerContact || + '', + ).trim() + + const primaryName = workerName || sellerNickname || sellerGameId || '内部现货' + const secondaryInfo = [ + workerName && sellerNickname && sellerNickname !== workerName ? sellerNickname : '', + sellerGameId ? `ID:${sellerGameId}` : '', + !sellerGameId && sellerContact ? sellerContact : '', + ] + .filter(Boolean) + .join(' · ') + + return { + tagText: workerName ? '供货打手' : '内部现货', + primaryName, + secondaryInfo, + } +} diff --git a/apps/frontend/tests/consignment-worker-display.test.ts b/apps/frontend/tests/consignment-worker-display.test.ts new file mode 100644 index 00000000..5b9fb66e --- /dev/null +++ b/apps/frontend/tests/consignment-worker-display.test.ts @@ -0,0 +1,55 @@ +import assert from 'node:assert/strict' +import test from 'node:test' + +import { resolveConsignmentWorkerDisplay } from '../src/utils/consignment-display.ts' + +test('普通工单无寄售信息时 resolveConsignmentWorkerDisplay 返回 null', () => { + const order = { + material: {}, + } as never + assert.equal(resolveConsignmentWorkerDisplay(order), null) +}) + +test('寄售工单有供货打手时优先展示供货打手与角色ID', () => { + const order = { + material: { + consignment: { + workerName: '01邀请的', + buyerName: '客服', + sellerGameId: '草莓酱板鸭 (1234551)', + sellerPhone: '16677778888', + }, + }, + gift: { + boosterMaterial: { + fields: { + gameNickname: '客服', + gameId: '草莓酱板鸭 (1234551)', + }, + }, + }, + } as never + + const result = resolveConsignmentWorkerDisplay(order) + assert.ok(result) + assert.equal(result.tagText, '供货打手') + assert.equal(result.primaryName, '01邀请的') + assert.equal(result.secondaryInfo, '客服 · ID:草莓酱板鸭 (1234551)') +}) + +test('寄售工单无供货打手名时回退展示角色昵称与现货标签', () => { + const order = { + material: { + consignment: { + buyerName: '客服角色', + sellerGameId: '1234551', + }, + }, + } as never + + const result = resolveConsignmentWorkerDisplay(order) + assert.ok(result) + assert.equal(result.tagText, '内部现货') + assert.equal(result.primaryName, '客服角色') + assert.equal(result.secondaryInfo, 'ID:1234551') +}) diff --git a/docs/寄卖库存优化现状与进度追踪.md b/docs/寄卖库存优化现状与进度追踪.md index be3db0f7..74ec43f8 100644 --- a/docs/寄卖库存优化现状与进度追踪.md +++ b/docs/寄卖库存优化现状与进度追踪.md @@ -140,7 +140,7 @@ 1. **手动匹配弹窗收敛为单一【内部现货直接履约】**: - 在 `ConsignmentManualMatchModal.tsx` 中彻底废除 `assignWorkOrderWithConsignmentWorker`,切换为调用 `fulfillWorkOrderWithConsignment`; - 彻底解除 `selected.workerId` 强依赖与无法指派错误提示,详情卡片调整为展示供货来源(“供货打手”或“内部现货”); - - 文案统一调整为“确认内部现货履约”,明确告知“免外派佣金、不冻结重复押金、自动跳过打手资料”。 + - 文案统一调整为“确认内部现货履约”,明确告知“免外派佣金、不冻结重复押金、自动跳过打手资料”。 2. **工单发布拦截弹窗候选操作收敛为【内部现货直接履约】**: - 在 `WorkOrdersPanel.tsx` 中将卡片操作按钮替换为【内部现货直接履约】高亮主按钮(绿色),副标题标注“免外派佣金 · 不扣打手押金 · 自动进冷静期”; - 杜绝客服误点走旧指派流程。 @@ -148,4 +148,25 @@ - `consignment-manual-match-mount.test.ts` 更新为断言手动匹配走内部现货直接履约; - 前端 54 测试全绿、后端 501 测试全绿;两侧 `typecheck`、`format:check`、`build` 均 100% 通过。 +--- +## 六、 专项优化:接单工单表格「打手」列回显寄售供货打手与现货资料(2026-09-21 已闭环) + +### 1. 现象与用户反馈 +- **问题现象**:接单工单在走内部现货直接履约后,工单列表表格的【打手】列以及工单详情基本信息中的【打手】字段显示为冰冷的一横杠 `-`,客服/运营在列表视图下无法直观查看是谁供的货或对应哪个游戏角色。 + +### 2. 代码级根因定位 +- `apps/frontend/src/pages/admin/panels/work-order-panel-utils.tsx:31`: + 原 `renderWorkOrderWorkers(order)` 仅针对 `order.worker`(传统指派/抢单打手)与 `order.sharingWorkers`(拼单打手)进行了渲染,而内部现货履约单没有 `order.worker`,直接走到底部返回了 `'-'`。 + +### 3. 解决对策与落地成果 +1. **提炼寄售供货展示纯函数**: + - 在 `apps/frontend/src/utils/consignment-display.ts` 中建立 `resolveConsignmentWorkerDisplay(order: WorkOrder)`; + - 自动解析 `material.consignment` 与 `gift.boosterMaterial.fields`: + - 当存在 `workerName` 时,主标题展示供货打手名称,打上 `供货打手`,副标题自动拼接游戏角色昵称与游戏 ID; + - 当无 `workerName`(纯仓库现货)时,主标题展示卖家角色昵称/游戏 ID,打上 `内部现货`。 +2. **列表与详情无缝集成**: + - `renderWorkOrderWorkers` 统一接入 `resolveConsignmentWorkerDisplay`,使工单列表表格中的【打手】列和工单详情抽屉基本信息中的【打手】栏均能清晰、美观地回显供货人。 +3. **自动化测试与样式达标**: + - 新增前端单元测试 `apps/frontend/tests/consignment-worker-display.test.ts`,验证普通工单、有供货打手寄售单、无打手纯现货单的解析分支; + - 前端 57 个测试全绿,`format:check`、`lint:check`、`typecheck` 全部通过。