feat(consignment): 自动匹配改为进入待抢单前一次性匹配

1) 配置改为开启自动匹配 + 匹配订单生效时间,移除内部匹配等待与定时轮询;2) 所有进入待抢单的路径统一先自动匹配一次,未命中进大厅;3) 手动匹配仅允许待抢单;4) 新增迁移 160 回填配置并迁移存量 internal_matching。
This commit is contained in:
2026-11-13 13:25:05 +08:00
parent 5bda59f94e
commit 341f8b2000
27 changed files with 596 additions and 822 deletions
@@ -19,6 +19,7 @@ import {
App,
Button,
Card,
DatePicker,
Empty,
Form,
Input,
@@ -33,6 +34,7 @@ import {
Typography,
} from 'antd'
import { useEffect, useMemo, useState, type CSSProperties } from 'react'
import dayjs, { type Dayjs } from 'dayjs'
import PercentTable from '@/components/common/PercentTable'
import ConsignmentTemplateCopyModal, {
@@ -93,11 +95,11 @@ type ConfigFormValues = {
serviceFee: { enabled: boolean; mode: 'percent' | 'fixed'; percent: number; amountYuan: number }
inventoryLimit: { enabled: boolean; maxActiveQuantity: number }
matching: {
mode: 'off' | 'manual' | 'auto'
autoMatchEnabled: boolean
exactOnly: boolean
preferLowReward: boolean
orderEffectiveFrom: Dayjs
}
internalMatch: { delayMinutes: number }
timeout: {
hours: number
policy: 'reopen' | 'cancel_release' | 'cancel_deduct' | 'rematch_release' | 'rematch_deduct'
@@ -208,19 +210,10 @@ function toFormValues(profile: ConsignmentProfile): ConfigFormValues {
},
inventoryLimit: { ...config.inventoryLimit },
matching: {
mode: config.matching.mode || 'off',
autoMatchEnabled: Boolean(config.matching.autoMatchEnabled),
exactOnly: config.matching.exactOnly,
preferLowReward: config.matching.preferLowReward,
},
internalMatch: {
// 内部等待只属于手动匹配;非手动模式切回手动时给默认 30 分钟。
// 兼容旧配置/旧缓存缺少 internalMatch 的情况,不能直接读嵌套字段。
delayMinutes:
config.matching.mode === 'manual'
? Number(config.internalMatch?.delayMinutes || 0)
: Number(config.internalMatch?.delayMinutes ?? 0) > 0
? Number(config.internalMatch?.delayMinutes)
: 30,
orderEffectiveFrom: dayjs(config.matching.orderEffectiveFrom || undefined),
},
timeout: {
hours: Math.max(0, Number(config.timeout.minutes ?? 30) / 60),
@@ -257,11 +250,11 @@ function emptyFormValues(): ConfigFormValues {
serviceFee: { enabled: false, mode: 'percent', percent: 0, amountYuan: 0 },
inventoryLimit: { enabled: false, maxActiveQuantity: 0 },
matching: {
mode: 'off',
autoMatchEnabled: false,
exactOnly: true,
preferLowReward: true,
orderEffectiveFrom: dayjs(),
},
internalMatch: { delayMinutes: 30 },
timeout: { hours: 0.5, policy: 'reopen' },
priceDrop: { enabled: true, hours: 24, mode: 'percent', value: 10 },
}
@@ -338,14 +331,11 @@ function buildPayload(
: 0,
},
matching: {
mode: values.matching.mode,
autoMatchEnabled: values.matching.autoMatchEnabled,
exactOnly: values.matching.exactOnly,
preferLowReward: values.matching.preferLowReward,
},
internalMatch: {
// 内部等待只属于手动匹配:关闭匹配不再等待;自动匹配不使用等待窗口。
delayMinutes:
values.matching.mode === 'off' ? 0 : Number(values.internalMatch?.delayMinutes || 0),
orderEffectiveFrom:
values.matching.orderEffectiveFrom?.toISOString() || new Date().toISOString(),
},
timeout: {
minutes: Math.max(0, Math.round(Number(values.timeout.hours || 0) * 60)),
@@ -1036,7 +1026,7 @@ export default function ConsignmentConfigPanel() {
const serviceFeeEnabled = Form.useWatch(['serviceFee', 'enabled'], form)
const serviceFeeMode = Form.useWatch(['serviceFee', 'mode'], form)
const inventoryLimitEnabled = Form.useWatch(['inventoryLimit', 'enabled'], form)
const matchingMode = Form.useWatch(['matching', 'mode'], form)
const autoMatchEnabled = Form.useWatch(['matching', 'autoMatchEnabled'], form)
const priceDropEnabled = Form.useWatch(['priceDrop', 'enabled'], form)
const priceDropMode = Form.useWatch(['priceDrop', 'mode'], form)
const headerStatus: ProfileStatusInfo = selectedProfile
@@ -1923,25 +1913,28 @@ export default function ConsignmentConfigPanel() {
<ConsignmentSectionTitle
index="08"
title="匹配设置"
description="匹配模式决定新单如何匹配库存;内部等待仅手动匹配使用"
description="开启后订单进入待抢单前自动匹配一次;关闭时维持人工匹配"
/>
</div>
<div style={gridStyle}>
<Form.Item
label="匹配模式"
name={['matching', 'mode']}
label="开启自动匹配"
name={['matching', 'autoMatchEnabled']}
valuePropName="checked"
style={{ marginBottom: 0 }}
>
<Select
options={[
{ value: 'off', label: '关闭自动匹配(仍可人工指定)' },
{ value: 'manual', label: '手动匹配' },
{ value: 'auto', label: '自动匹配' },
]}
/>
<Switch checkedChildren="开启" unCheckedChildren="关闭" />
</Form.Item>
{matchingMode !== 'off' ? (
{autoMatchEnabled ? (
<>
<Form.Item
label="匹配订单生效时间"
name={['matching', 'orderEffectiveFrom']}
extra="只自动匹配该时间之后创建的订单"
style={{ marginBottom: 0 }}
>
<DatePicker showTime style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label="仅精确匹配"
name={['matching', 'exactOnly']}
@@ -1951,45 +1944,30 @@ export default function ConsignmentConfigPanel() {
<Switch checkedChildren="仅精确" unCheckedChildren="允许高配" />
</Form.Item>
<Form.Item
label="低价订单优先"
label="低价库存优先"
name={['matching', 'preferLowReward']}
valuePropName="checked"
style={{ marginBottom: 0 }}
>
<Switch checkedChildren="优先" unCheckedChildren="不优先" />
</Form.Item>
{matchingMode === 'manual' ? (
<Form.Item
label="手动匹配等待(分钟)"
name={['internalMatch', 'delayMinutes']}
extra="仅手动匹配生效:工单在内部池等待运营挑库存"
style={{ marginBottom: 0 }}
>
<InputNumber
min={0}
precision={0}
style={{ width: '100%' }}
addonAfter="分钟"
/>
</Form.Item>
) : null}
</>
) : null}
</div>
{matchingMode === 'off' ? (
{!autoMatchEnabled ? (
<Alert
type="warning"
showIcon
style={{ marginTop: 12 }}
message="关闭自动匹配后:新单直接进入接单大厅,不等待内部库存、也不会自动匹配;运营仍可在后台手动指定寄卖库存。"
message="关闭自动匹配后:订单直接进入待抢单,不等待内部库存、也不会自动匹配;运营仍可在后台对待抢单手动指定寄卖库存。"
/>
) : null}
{matchingMode === 'auto' ? (
{autoMatchEnabled ? (
<Alert
type="info"
showIcon
style={{ marginTop: 12 }}
message="自动匹配:发布/补资料时立即匹配,未命中直接进大厅,不使用内部等待窗口。"
message="开启自动匹配后:订单进入待抢单前自动匹配一次,命中直接进行中,未命中才进大厅;不再后台定时补配,且只匹配生效时间之后创建的订单。"
/>
) : null}
</section>