diff --git a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx
index a6d56b6d..9a96f2e3 100644
--- a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx
+++ b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx
@@ -76,7 +76,6 @@ import {
updateAdminWorkOrderSharing,
} from '@/services/admin'
import {
- assignWorkOrderWithConsignmentWorker,
checkWorkOrderConsignmentMatches,
fulfillWorkOrderWithConsignment,
revokeWorkOrderConsignmentFulfillment,
@@ -85,6 +84,7 @@ import { notifyVoucherConsume } from '@/utils/voucher-consume'
import type { ConsignmentFulfillmentInfo, WorkOrderInventoryMatchCheck } from '@/types/consignment'
import type { UploadedFile, WorkOrder, WorkOrderShare } from '@/types/worker-platform'
import { isManuallyMatchableOrderStatus } from '@/utils/consignment-manual-match'
+import { resolveConsignmentScreenshotUrls } from '@/utils/consignment-display'
import ConsignmentManualMatchModal from '@/components/admin/ConsignmentManualMatchModal'
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
import dayjs, { ADMIN_DATE_FORMAT } from '@/lib/dayjs'
@@ -798,26 +798,21 @@ export default function WorkOrdersPanel() {
/** 非精确匹配(订单规格信息不全 / 高配充低配)必须人工确认,后端也会二次拦截 */
/**
- * 匹配弹窗里的两个动作**都会立刻占用现货**,点错就得走「撤销寄售履约」才能退回来,
- * 而且两个按钮挨在一起很容易点错,所以一律二次确认,并把后果写清楚:
- * 用哪个号占用、给谁、要不要冻结押金、能不能回退。
+ * 内部现货履约会立刻占用库存,点错就得走「撤销寄售履约」才能退回来,
+ * 所以二次确认写清楚:用哪个号占用、结算价、工单会停在待加好友。
*/
function confirmConsignmentMatchAction(input: {
- kind: 'assign_worker' | 'internal_stock'
matchType?: string
consignmentNo: string
- workerName?: string
- depositFen?: number | null
costPriceFen?: number | null
onOk: () => void
}) {
const isPartial = input.matchType !== 'exact'
- const isAssignWorker = input.kind === 'assign_worker'
- const depositText =
- input.depositFen != null ? `履约押金 ¥${(input.depositFen / 100).toFixed(2)}` : '履约押金'
+ const costText =
+ input.costPriceFen != null ? `结算价 ¥${(input.costPriceFen / 100).toFixed(2)}` : ''
modal.confirm({
- title: isAssignWorker ? '确认走接单平台指派打手?' : '确认用内部现货履约?',
+ title: '确认用内部现货履约?',
width: 460,
content: (
@@ -828,14 +823,11 @@ export default function WorkOrdersPanel() {
- {isAssignWorker
- ? `动作:把订单指派给供货打手${input.workerName ? `「${input.workerName}」` : ''}接单,需冻结${depositText}`
- : '动作:不向外部打手派单,直接用这件现货履约'}
+ 动作:不向外部打手派单,直接用这件现货履约
+ {costText ? `(${costText})` : ''}
- {isAssignWorker
- ? '结果:立刻占用该现货,订单转入打手履约,其它订单不能再匹配它。'
- : '结果:立刻占用该现货,跳过「待打手提交资料」直接进入赠送冷却期,其它订单不能再匹配它。'}
+ 结果:立刻占用该现货,跳过「待打手提交资料」,工单进入「待加好友」;加好后再点确认加好友才会进入赠送冷却期。其它订单不能再匹配它。
退回需要走「撤销寄售履约」,请先核对商品与买家订单一致。
@@ -848,7 +840,7 @@ export default function WorkOrdersPanel() {
) : null}
),
- okText: isAssignWorker ? '确认指派打手' : '确认占用并履约',
+ okText: '确认占用并履约',
okButtonProps: isPartial ? { danger: true } : undefined,
cancelText: '再想想',
onOk: input.onOk,
@@ -864,7 +856,7 @@ export default function WorkOrdersPanel() {
inventoryId,
allowPartial,
})
- message.success('已占用内部库存,跳过打手资料,进入赠送冷静期')
+ message.success('已占用内部库存,跳过打手资料,已进入待加好友阶段')
notifyVoucherConsume(res?.data?.voucherConsume)
setConsignmentInterceptModal({ open: false, order: null, matchData: null, submitting: false })
await refreshAll()
@@ -874,30 +866,6 @@ export default function WorkOrdersPanel() {
}
}
- async function handleAssignWithConsignmentWorker(
- inventoryId: number,
- workerId?: number | null,
- allowPartial = false,
- ) {
- if (!consignmentInterceptModal.order) return
- setConsignmentInterceptModal((prev) => ({ ...prev, submitting: true }))
- try {
- const res = await assignWorkOrderWithConsignmentWorker({
- workOrderId: consignmentInterceptModal.order.workOrderId,
- inventoryId,
- workerId,
- allowPartial,
- })
- message.success('已通过接单平台指派打手!系统已自动冻结履约押金,等待打手交货核销')
- notifyVoucherConsume(res?.data?.voucherConsume)
- setConsignmentInterceptModal({ open: false, order: null, matchData: null, submitting: false })
- await refreshAll()
- } catch (err: any) {
- message.error(err.message || '走接单平台指派打手失败')
- setConsignmentInterceptModal((prev) => ({ ...prev, submitting: false }))
- }
- }
-
function openRevokeConsignmentModal(row: WorkOrder) {
setRevokeConsignmentModal({
open: true,
@@ -2207,6 +2175,9 @@ export default function WorkOrdersPanel() {
const effectiveNote = String(
detailOrder.gift?.boosterMaterial?.note || consignmentInfo?.remark || '',
).trim()
+ const consignmentScreenshotUrls = resolveConsignmentScreenshotUrls(
+ consignmentInfo?.screenshots,
+ )
return (
- ) : Array.isArray(consignmentInfo?.screenshots) &&
- consignmentInfo.screenshots.length > 0 ? (
- typeof item === 'string' && item.trim(),
- )}
- size={72}
- />
+ ) : consignmentScreenshotUrls.length > 0 ? (
+
) : (
未提交
)}
@@ -3329,8 +3294,7 @@ export default function WorkOrdersPanel() {
若使用内部寄售库存履约,无需向外部打手支付佣金(打手支出 ¥0.00)
- 。系统会立刻占用库存,并跳过「待打手提交资料」,直接进入该商品规则设定的赠送冷静期(未单独配置时为
- 72 小时);冷静期结束后再用库存账号赠送并验收。
+ 。系统会立刻占用库存,并跳过「待打手提交资料」,工单进入「待加好友」。加好买家好友后再点确认加好友,才会进入赠送冷却期。
}
@@ -3446,7 +3410,6 @@ export default function WorkOrdersPanel() {
loading={consignmentInterceptModal.submitting}
onClick={() =>
confirmConsignmentMatchAction({
- kind: 'internal_stock',
matchType: m.matchType,
consignmentNo: inv.sourceSubmissionNo || '客服手工入库',
costPriceFen: inv.costPriceFen,
@@ -3466,7 +3429,7 @@ export default function WorkOrdersPanel() {
textAlign: 'center',
}}
>
- 免外派佣金 · 不扣打手押金 · 自动进冷静期
+ 免外派佣金 · 不扣打手押金 · 进入待加好友
diff --git a/apps/frontend/src/pages/worker/WorkerOrderDetailDrawer.tsx b/apps/frontend/src/pages/worker/WorkerOrderDetailDrawer.tsx
index e60f796a..6cd53ade 100644
--- a/apps/frontend/src/pages/worker/WorkerOrderDetailDrawer.tsx
+++ b/apps/frontend/src/pages/worker/WorkerOrderDetailDrawer.tsx
@@ -5,6 +5,7 @@ import WorkOrderEventTimeline from '@/components/WorkOrderEventTimeline'
import ImagePreviewList from '@/components/files/ImagePreviewList'
import type { WorkOrder } from '@/types/worker-platform'
import { formatDateTime } from '@/utils/date-time'
+import { resolveOrderConsignmentScreenshotUrls } from '@/utils/consignment-display'
import {
canEditAcceptanceImages,
@@ -183,6 +184,9 @@ function GiftProgressCard({ order }: { order: WorkOrder }) {
const boosterItems = Object.entries(boosterFields).filter(([, value]) =>
String(value || '').trim(),
)
+ const consignmentScreenshotUrls = resolveOrderConsignmentScreenshotUrls(order)
+ const hasBoosterScreenshots = (gift.boosterMaterial?.screenshots || []).length > 0
+ const hasConsignmentScreenshots = consignmentScreenshotUrls.length > 0
return (
@@ -228,7 +232,7 @@ function GiftProgressCard({ order }: { order: WorkOrder }) {
)}
- {boosterItems.length > 0 || (gift.boosterMaterial?.screenshots || []).length > 0 ? (
+ {boosterItems.length > 0 || hasBoosterScreenshots || hasConsignmentScreenshots ? (
已提交的本人资料
@@ -241,20 +245,13 @@ function GiftProgressCard({ order }: { order: WorkOrder }) {
))}
) : null}
- {(gift.boosterMaterial?.screenshots || []).length > 0 ? (
+ {hasBoosterScreenshots ? (
-
+
- ) : Array.isArray(
- (order.material as Record
| undefined)?.consignment?.screenshots,
- ) &&
- ((order.material as Record).consignment.screenshots as unknown[])
- .length > 0 ? (
+ ) : hasConsignmentScreenshots ? (
- ).consignment.screenshots}
- size={64}
- />
+
) : null}
diff --git a/apps/frontend/src/utils/consignment-display.ts b/apps/frontend/src/utils/consignment-display.ts
index 00b6de84..241de48f 100644
--- a/apps/frontend/src/utils/consignment-display.ts
+++ b/apps/frontend/src/utils/consignment-display.ts
@@ -6,6 +6,27 @@ function asRecord(value: unknown): Record
{
: {}
}
+/** 寄售截图可能是字符串 URL,也可能是 UploadedFile 对象。 */
+export function resolveConsignmentScreenshotUrls(screenshots: unknown): string[] {
+ if (!Array.isArray(screenshots)) return []
+ return screenshots
+ .map((item) => {
+ if (typeof item === 'string') return item.trim()
+ if (item && typeof item === 'object' && !Array.isArray(item)) {
+ const rec = item as Record
+ return String(rec.url || rec.mediumUrl || rec.thumbnailUrl || '').trim()
+ }
+ return ''
+ })
+ .filter(Boolean)
+}
+
+export function resolveOrderConsignmentScreenshotUrls(order: { material?: unknown }): string[] {
+ return resolveConsignmentScreenshotUrls(
+ asRecord(asRecord(order.material).consignment).screenshots,
+ )
+}
+
export type ConsignmentWorkerDisplayInfo = {
tagText: '供货打手' | '内部现货'
primaryName: string
diff --git a/apps/frontend/tests/consignment-manual-match-mount.test.ts b/apps/frontend/tests/consignment-manual-match-mount.test.ts
index 4059b651..c6a63ca3 100644
--- a/apps/frontend/tests/consignment-manual-match-mount.test.ts
+++ b/apps/frontend/tests/consignment-manual-match-mount.test.ts
@@ -100,3 +100,18 @@ test('手动匹配弹窗:数据过期的 409 要清选择并刷新,走内部
assert.ok(source.includes("from '@/utils/voucher-consume'"), '核销反馈必须用共享出口')
assert.ok(source.includes('keyword: keyword.trim() || undefined'), '库存侧关键字要透传后端')
})
+
+test('发布拦截弹窗:只走内部现货履约,文案停在待加好友而不是冷静期', () => {
+ const source = readFileSync(
+ new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url).pathname,
+ 'utf8',
+ )
+ assert.ok(source.includes('fulfillWorkOrderWithConsignment'), '拦截弹窗必须调用内部现货履约')
+ assert.ok(
+ !source.includes('assignWorkOrderWithConsignmentWorker'),
+ '拦截弹窗不得再调用旧的指派打手接口',
+ )
+ assert.ok(source.includes('进入待加好友'), '拦截弹窗必须写明工单进入待加好友')
+ assert.ok(!source.includes('自动进冷静期'), '拦截弹窗不得再写自动进冷静期')
+ assert.ok(!source.includes('进入赠送冷静期'), '成功提示不得再写成进入赠送冷静期')
+})
diff --git a/apps/frontend/tests/consignment-worker-display.test.ts b/apps/frontend/tests/consignment-worker-display.test.ts
index 5b9fb66e..8dbebc06 100644
--- a/apps/frontend/tests/consignment-worker-display.test.ts
+++ b/apps/frontend/tests/consignment-worker-display.test.ts
@@ -1,7 +1,10 @@
import assert from 'node:assert/strict'
import test from 'node:test'
-import { resolveConsignmentWorkerDisplay } from '../src/utils/consignment-display.ts'
+import {
+ resolveConsignmentWorkerDisplay,
+ resolveConsignmentScreenshotUrls,
+} from '../src/utils/consignment-display.ts'
test('普通工单无寄售信息时 resolveConsignmentWorkerDisplay 返回 null', () => {
const order = {
@@ -53,3 +56,18 @@ test('寄售工单无供货打手名时回退展示角色昵称与现货标签',
assert.equal(result.primaryName, '客服角色')
assert.equal(result.secondaryInfo, 'ID:1234551')
})
+
+test('寄售截图兼容字符串 URL 与 UploadedFile 对象', () => {
+ assert.deepEqual(
+ resolveConsignmentScreenshotUrls([
+ '/api/v1/files/object?key=consignment_screenshot%2Fa.webp',
+ { url: '/api/v1/files/object?key=consignment_screenshot%2Fb.webp' },
+ { objectKey: 'x', url: '' },
+ 12,
+ ]),
+ [
+ '/api/v1/files/object?key=consignment_screenshot%2Fa.webp',
+ '/api/v1/files/object?key=consignment_screenshot%2Fb.webp',
+ ],
+ )
+})
diff --git a/docs/寄卖库存优化现状与进度追踪.md b/docs/寄卖库存优化现状与进度追踪.md
index 74ec43f8..37cc444c 100644
--- a/docs/寄卖库存优化现状与进度追踪.md
+++ b/docs/寄卖库存优化现状与进度追踪.md
@@ -142,7 +142,7 @@
- 彻底解除 `selected.workerId` 强依赖与无法指派错误提示,详情卡片调整为展示供货来源(“供货打手”或“内部现货”);
- 文案统一调整为“确认内部现货履约”,明确告知“免外派佣金、不冻结重复押金、自动跳过打手资料”。
2. **工单发布拦截弹窗候选操作收敛为【内部现货直接履约】**:
- - 在 `WorkOrdersPanel.tsx` 中将卡片操作按钮替换为【内部现货直接履约】高亮主按钮(绿色),副标题标注“免外派佣金 · 不扣打手押金 · 自动进冷静期”;
+ - 在 `WorkOrdersPanel.tsx` 中将卡片操作按钮替换为【内部现货直接履约】高亮主按钮(绿色),副标题标注“免外派佣金 · 不扣打手押金 · 进入待加好友”;
- 杜绝客服误点走旧指派流程。
3. **测试套件与类型风格全面达标**:
- `consignment-manual-match-mount.test.ts` 更新为断言手动匹配走内部现货直接履约;