From f1575aec8537e95a24bf068c20e7a1fe2eede0d5 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Wed, 19 Aug 2026 19:31:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8B=BC=E5=8D=95=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E6=89=93=E6=89=8B=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../realtime/realtime-event-service.ts | 2 +- .../services/worker-platform/admin-service.ts | 15 ++++++++-- .../src/services/worker-platform/mappers.ts | 29 ++++++++++++++++++- .../worker-platform-service.test.ts | 15 ++++++++++ .../pages/admin/panels/WorkOrdersPanel.tsx | 22 ++++++++++++-- apps/frontend/src/types/worker-platform.ts | 7 +++++ 6 files changed, 83 insertions(+), 7 deletions(-) diff --git a/apps/backend/src/services/realtime/realtime-event-service.ts b/apps/backend/src/services/realtime/realtime-event-service.ts index 4cd17dac..e12aacb8 100644 --- a/apps/backend/src/services/realtime/realtime-event-service.ts +++ b/apps/backend/src/services/realtime/realtime-event-service.ts @@ -255,7 +255,7 @@ async function publishWorkOrderSnapshots(input: { .reduce((sum, share) => sum + Number(share.quantity || 0), 0) const pendingSubmissionCount = shares.filter((share) => share.status === 'joined').length const adminSnapshot = { - ...mapWorkOrderAdmin(workOrder), + ...mapWorkOrderAdmin(workOrder, shares), pendingUnfreezeAmount: pendingUnfreezeByOrderId.get(workOrderId) || 0, sharingProgress: { joinedQuantity, diff --git a/apps/backend/src/services/worker-platform/admin-service.ts b/apps/backend/src/services/worker-platform/admin-service.ts index 5b8abb89..ed78d3dc 100644 --- a/apps/backend/src/services/worker-platform/admin-service.ts +++ b/apps/backend/src/services/worker-platform/admin-service.ts @@ -45,6 +45,7 @@ import { listWorkerUsers, listWorkerWithdrawalAccounts, listWorkOrderShares, + listWorkOrderSharesByOrderIds, sumPendingUnfreezeByOrderIds, resolveProblemWorkOrder, reviewWorkerFinanceRequest, @@ -1300,10 +1301,18 @@ export async function listAdminWorkOrders(query: JsonObject = {}) { const pendingUnfreezeByOrderId = await sumPendingUnfreezeByOrderIds( items.map((item) => Number(item.id)), ) + const shares = await listWorkOrderSharesByOrderIds(items.map((item) => Number(item.id))) + const sharesByOrderId = new Map() + for (const share of shares) { + const workOrderId = Number(share.work_order_id) + const current = sharesByOrderId.get(workOrderId) || [] + current.push(share) + sharesByOrderId.set(workOrderId, current) + } return { items: items.map((item) => { return { - ...mapWorkOrderAdmin(item), + ...mapWorkOrderAdmin(item, sharesByOrderId.get(Number(item.id)) || []), pendingUnfreezeAmount: pendingUnfreezeByOrderId.get(Number(item.id)) || 0, } }), @@ -1377,7 +1386,7 @@ export async function getAdminWorkOrderSharing(workOrderId: number | string) { const workOrder = await getRequiredWorkOrder(workOrderId) const shares = await listWorkOrderShares(workOrder.id) return { - order: mapWorkOrderAdmin(workOrder), + order: mapWorkOrderAdmin(workOrder, shares), shares: shares.map(mapWorkOrderShare), } } @@ -2428,7 +2437,7 @@ export async function syncWorkerOrdersForSourceOrder( } return { - created: created.map(mapWorkOrderAdmin), + created: created.map((workOrder) => mapWorkOrderAdmin(workOrder)), skipped, createdCount: created.length, skippedCount: skipped.length, diff --git a/apps/backend/src/services/worker-platform/mappers.ts b/apps/backend/src/services/worker-platform/mappers.ts index 1482c6e3..257fb1bb 100644 --- a/apps/backend/src/services/worker-platform/mappers.ts +++ b/apps/backend/src/services/worker-platform/mappers.ts @@ -341,7 +341,33 @@ export function resolveWorkOrderOperationalStatus(workOrder: WorkOrderRow): stri return workOrder.status } -export function mapWorkOrderAdmin(workOrder: WorkOrderRow) { +/** 汇总拼单参与者,已撤销份额不应继续展示为当前打手。 */ +export function mapWorkOrderSharingWorkers(shares: WorkOrderShareRow[]) { + const workers = new Map< + number, + { workerId: number; username: string; displayName: string; quantity: number } + >() + for (const share of shares) { + if (share.status === 'cancelled') continue + const workerId = Number(share.worker_id || 0) + if (workerId <= 0) continue + const existing = workers.get(workerId) + if (existing) { + existing.quantity += Number(share.quantity || 0) + continue + } + const username = String(share.worker_username || '').trim() + workers.set(workerId, { + workerId, + username, + displayName: String(share.worker_display_name || username || `打手#${workerId}`).trim(), + quantity: Number(share.quantity || 0), + }) + } + return [...workers.values()] +} + +export function mapWorkOrderAdmin(workOrder: WorkOrderRow, shares?: WorkOrderShareRow[]) { const activeShareQuantity = Math.max(0, Number(workOrder.active_share_quantity || 0)) const pendingShareCount = Math.max(0, Number(workOrder.pending_share_count || 0)) const sharingTotalQuantity = Math.max(1, Number(workOrder.sharing_total_quantity || 1)) @@ -437,6 +463,7 @@ export function mapWorkOrderAdmin(workOrder: WorkOrderRow) { displayName: workOrder.worker_display_name || '', } : null, + ...(shares ? { sharingWorkers: mapWorkOrderSharingWorkers(shares) } : {}), publishedAt: workOrder.published_at, pinnedAt: workOrder.pinned_at, assignedAt: workOrder.assigned_at, diff --git a/apps/backend/src/services/worker-platform/worker-platform-service.test.ts b/apps/backend/src/services/worker-platform/worker-platform-service.test.ts index 03f054be..fa2adbd3 100644 --- a/apps/backend/src/services/worker-platform/worker-platform-service.test.ts +++ b/apps/backend/src/services/worker-platform/worker-platform-service.test.ts @@ -22,6 +22,7 @@ import { normalizeRequirementFields, mapWorkOrderForWorker, mapWorkOrderAdmin, + mapWorkOrderSharingWorkers, maskWorkerLeaderboardDisplayName, resolveCollectSubmitTargetWorkOrder, resolveAdminMaterialRewardAmount, @@ -733,6 +734,20 @@ test('后台拼单聚合状态同时收敛筛选语义和操作能力', () => { assert.equal(filled.capabilities.canUnpublish, false) }) +test('拼单打手汇总展示当前参与者并合并同一打手的份数', () => { + const workers = mapWorkOrderSharingWorkers([ + buildWorkOrderShareRow({ worker_id: 1, quantity: 2, worker_username: 'alpha' }), + buildWorkOrderShareRow({ id: 2, worker_id: 1, quantity: 3, worker_username: 'alpha' }), + buildWorkOrderShareRow({ id: 3, worker_id: 2, quantity: 1, worker_display_name: 'Beta' }), + buildWorkOrderShareRow({ id: 4, worker_id: 3, quantity: 9, status: 'cancelled' }), + ]) + + assert.deepEqual(workers, [ + { workerId: 1, username: 'alpha', displayName: 'alpha', quantity: 5 }, + { workerId: 2, username: '', displayName: 'Beta', quantity: 1 }, + ]) +}) + test('拼单份额撤单只允许未验收份额并让母工单回到大厅状态', () => { assert.equal(resolveWorkOrderShareCancellationStatus('open', 'joined'), 'open') assert.equal(resolveWorkOrderShareCancellationStatus('open', 'submitted'), 'open') diff --git a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx index 6f34bd86..be57a90a 100644 --- a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx @@ -793,7 +793,7 @@ export default function WorkOrdersPanel() { { title: '打手', width: 140, - render: (_, row) => row.worker?.displayName || row.worker?.username || '-', + render: (_, row) => renderWorkOrderWorkers(row), }, { title: '资料', @@ -1125,7 +1125,7 @@ export default function WorkOrdersPanel() { - {detailOrder.worker?.displayName || detailOrder.worker?.username || '-'} + {renderWorkOrderWorkers(detailOrder)} {formatMoney(detailOrder.rewardAmount)} @@ -1928,6 +1928,24 @@ function formatUserPaymentAmount(order: WorkOrder): string { return paymentFen === null ? '-' : formatMoney(paymentFen) } +/** 拼单母单没有单一 assigned worker,改为展示当前参与打手及其承接份数。 */ +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} 份 + +
+ ) +} + function getDisplayOrderNo(order: Pick | null | undefined): string { return String(order?.platformOrderId || '').trim() || '-' } diff --git a/apps/frontend/src/types/worker-platform.ts b/apps/frontend/src/types/worker-platform.ts index 8b02c802..0dd49eca 100644 --- a/apps/frontend/src/types/worker-platform.ts +++ b/apps/frontend/src/types/worker-platform.ts @@ -400,6 +400,13 @@ export type WorkOrder = { username: string displayName: string } + /** 拼单订单的当前参与打手,普通整单不返回该字段。 */ + sharingWorkers?: Array<{ + workerId: number + username: string + displayName: string + quantity: number + }> publishedAt: string | null /** 置顶时间;非空表示该工单在抢单大厅置顶展示 */ pinnedAt?: string | null