fix(consignment): 手动匹配订单按钮点了不弹窗(弹窗挂错了位置)

用户反馈:在库待匹配列表点「手动匹配订单」没有反应。

根因:上一批把手动匹配弹窗挂到了「编辑寄售物品」弹窗**内部**,外层弹窗未打开时
该组件根本没被挂载,点击只改状态、界面无变化。

- 弹窗移到面板顶层(与其它弹窗同级),点击即弹
- 新增静态守卫测试:断言手动匹配弹窗前面不能有未闭合的 Modal(即不能嵌在别的弹窗里)、
  按钮必须真的 setState、open 由该状态驱动;已验证该守卫能拦下错误写法
- 顺带补入口缺口:订单侧原先只在"检测到系统候选"的提醒弹窗里给入口,订单没有任何候选时
  反而进不去手动匹配 —— 工单行内新增常驻「手动匹配现货」(仅待指派/待补资料,
  与后端前置校验同口径)
- 已暂停匹配的现货点按钮会直接 409,改为按钮禁用并提示先打开「允许匹配」开关
- destroyOnClose → antd 6 的 destroyOnHidden(前者已废弃)

验证:前端 typecheck/lint/format/48 例测试/build 全过。
This commit is contained in:
yml2213
2026-09-17 12:27:58 +08:00
parent a7a55d6c16
commit 0476339ec4
7 changed files with 131 additions and 18 deletions
@@ -221,7 +221,7 @@ export default function ConsignmentManualMatchModal({
</Button>
</Space>
}
destroyOnClose
destroyOnHidden
>
<Space direction="vertical" size={12} style={{ width: '100%' }}>
<Alert
@@ -554,9 +554,22 @@ export default function ConsignmentInventoryPanel() {
</Button>
{record.status === 'in_stock' && canManageConsignment ? (
<>
<Button size="small" type="link" onClick={() => setManualMatchItem(record)}>
手动匹配订单
</Button>
<Tooltip
title={
record.allowMatching
? '从待匹配订单里人工挑一单(支持规格冲突强制匹配,需填原因)'
: '该物品已暂停匹配,先打开上方「允许匹配」开关'
}
>
<Button
size="small"
type="link"
disabled={!record.allowMatching}
onClick={() => setManualMatchItem(record)}
>
手动匹配订单
</Button>
</Tooltip>
<Button
size="small"
type="link"
@@ -944,20 +957,6 @@ export default function ConsignmentInventoryPanel() {
</Form.Item>
</Card>
<ConsignmentManualMatchModal
open={Boolean(manualMatchItem)}
onClose={() => setManualMatchItem(null)}
inventory={
manualMatchItem
? {
inventoryId: manualMatchItem.id,
label: manualMatchItem.itemName,
}
: null
}
onMatched={() => void inventoriesQuery.refetch()}
/>
<Form.Item name="remark" label="备注">
<Input.TextArea rows={2} />
</Form.Item>
@@ -1162,6 +1161,21 @@ export default function ConsignmentInventoryPanel() {
</Form.Item>
</Form>
</Modal>
{/* 手动匹配订单:从现货出发挑订单(与订单侧共用同一弹窗) */}
<ConsignmentManualMatchModal
open={Boolean(manualMatchItem)}
onClose={() => setManualMatchItem(null)}
inventory={
manualMatchItem
? {
inventoryId: manualMatchItem.id,
label: manualMatchItem.itemName,
}
: null
}
onMatched={() => void inventoriesQuery.refetch()}
/>
</div>
)
}
@@ -7,6 +7,7 @@ import {
ReloadOutlined,
SearchOutlined,
SendOutlined,
ThunderboltOutlined,
TeamOutlined,
UndoOutlined,
UserAddOutlined,
@@ -82,6 +83,7 @@ import {
} from '@/services/admin/consignment'
import type { ConsignmentFulfillmentInfo, WorkOrderInventoryMatchCheck } from '@/types/consignment'
import type { UploadedFile, WorkOrder, WorkOrderShare } from '@/types/worker-platform'
import { isManuallyMatchableOrderStatus } from '@/utils/consignment-manual-match'
import ConsignmentManualMatchModal from '@/components/admin/ConsignmentManualMatchModal'
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
import dayjs, { ADMIN_DATE_FORMAT } from '@/lib/dayjs'
@@ -1539,6 +1541,19 @@ export default function WorkOrdersPanel() {
指派并核销
</Button>
) : null}
{/*
手动指定现货必须**常驻**:只在"检测到候选"的提醒弹窗里给入口,
订单没有任何系统候选时客服反而进不去——而那正是最需要人工指定的时候。
*/}
{canFulfillConsignment && isManuallyMatchableOrderStatus(row.status) ? (
<Button
icon={<ThunderboltOutlined />}
onClick={() => setManualMatchOrder(row)}
title="从全部在库现货里人工指定一件(支持规格冲突强制匹配,需填原因)"
>
手动匹配现货
</Button>
) : null}
{row.capabilities?.canCancel && (canReturnToHall || canCancelOrder) ? (
<Button danger icon={<UndoOutlined />} onClick={() => openCancelModal(row)}>
撤单
@@ -70,3 +70,13 @@ export function filterManualMatchOptions<T>(
),
)
}
/**
* 哪些订单状态可以手动指定现货。
*
* 与后端 `fulfillWorkOrderWithConsignment` 的前置校验保持一致(只允许 unassigned /
* pending_material):入口先按同一口径收敛,避免"按钮点了才报错"。
*/
export function isManuallyMatchableOrderStatus(status: string): boolean {
return status === 'unassigned' || status === 'pending_material'
}