feat(consignment): 寄卖规格与价格动态配置重构

This commit is contained in:
2026-11-05 17:09:05 +08:00
parent 2ef16ddcc7
commit d41ec44679
22 changed files with 3761 additions and 621 deletions
@@ -1,4 +1,4 @@
import { App, Alert, Button, Card, Empty, Modal, Space, Tag, Typography } from 'antd'
import { App, Alert, Button, Card, Empty, Input, Modal, Space, Tag, Typography } from 'antd'
import { useQuery } from '@tanstack/react-query'
import { useEffect, useMemo, useState } from 'react'
@@ -27,6 +27,7 @@ export default function ConsignmentWorkOrderMatchModal({
const { message } = App.useApp()
const [selectedIds, setSelectedIds] = useState<number[]>([])
const [submitting, setSubmitting] = useState(false)
const [nonExactReason, setNonExactReason] = useState('')
const candidatesQuery = useQuery({
queryKey: ['admin-consignment-match-candidates', workOrderId],
@@ -40,13 +41,24 @@ export default function ConsignmentWorkOrderMatchModal({
useEffect(() => {
if (!open) {
setSelectedIds([])
setNonExactReason('')
return
}
if (options.length === 0) return
// 默认按缺口选中价格最低的几条,客服可自行调整。
setSelectedIds(options.slice(0, needed).map((item) => item.inventoryId))
// 默认只选精确匹配;非精确库存必须人工勾选并填写原因。
const exactOptions = options.filter((item) => (item.matchType || 'exact') === 'exact')
setSelectedIds(exactOptions.slice(0, needed).map((item) => item.inventoryId))
}, [open, options, needed])
const nonExactSelected = useMemo(
() =>
selectedIds.some((id) => {
const item = options.find((option) => option.inventoryId === id)
return Boolean(item?.matchType && item.matchType !== 'exact')
}),
[options, selectedIds],
)
const totalPrice = useMemo(
() =>
selectedIds.reduce((sum, id) => {
@@ -69,7 +81,7 @@ export default function ConsignmentWorkOrderMatchModal({
<Button
type="primary"
loading={submitting}
disabled={selectedIds.length === 0}
disabled={selectedIds.length === 0 || (nonExactSelected && !nonExactReason.trim())}
onClick={async () => {
if (!workOrderId) return
setSubmitting(true)
@@ -77,6 +89,8 @@ export default function ConsignmentWorkOrderMatchModal({
const result = await matchAdminConsignmentWorkOrder({
workOrderId,
inventoryIds: selectedIds,
allowNonExact: nonExactSelected,
reason: nonExactSelected ? nonExactReason.trim() : undefined,
})
message.success(
result.data?.matched
@@ -99,7 +113,7 @@ export default function ConsignmentWorkOrderMatchModal({
>
{candidatesQuery.isLoading ? <Card loading /> : null}
{!candidatesQuery.isLoading && options.length === 0 ? (
<Empty description="没有精确匹配的在库寄卖库存" />
<Empty description="没有同配置的在库寄卖库存" />
) : null}
{options.length > 0 ? (
<Space direction="vertical" size={12} style={{ width: '100%' }}>
@@ -107,7 +121,7 @@ export default function ConsignmentWorkOrderMatchModal({
type="info"
showIcon
message={`该工单${data?.sharingEnabled ? `拼单 ${data.totalQuantity} 份,已匹配 ${data.activeQuantity} 份` : '为整单'},本次还需 ${needed} 条库存。`}
description="自动匹配只做精确规格;手动匹配可选多条,拼单工单每选一条生成一个份额。"
description="自动匹配只做精确规格;手动匹配可选多条,拼单工单每选一条生成一个份额。选择非精确库存必须填写原因。"
/>
<PercentTable<ConsignmentMatchCandidate>
rowKey="inventoryId"
@@ -133,8 +147,30 @@ export default function ConsignmentWorkOrderMatchModal({
{
title: '规格',
render: (_, item) =>
item.specText ||
`${item.system}/${item.serverZone}/${item.series}/${item.diamondLevel}钻/${item.brillianceCount}璀`,
},
{
title: '匹配',
width: 90,
render: (_, item) => {
const type = item.matchType || 'exact'
const label =
type === 'exact'
? '精确'
: type === 'partial'
? '高配'
: type === 'unknown'
? '未知'
: '冲突'
const color = type === 'exact' ? 'green' : type === 'partial' ? 'orange' : 'red'
return (
<Tag color={color} title={item.matchReasons?.join('、') || ''}>
{label}
</Tag>
)
},
},
{
title: '库存',
width: 90,
@@ -151,6 +187,21 @@ export default function ConsignmentWorkOrderMatchModal({
},
]}
/>
{nonExactSelected ? (
<Space direction="vertical" size={6} style={{ width: '100%' }}>
<Alert
type="warning"
showIcon
message="已选择非精确库存:请确认规格差异,并填写人工匹配原因。"
/>
<Input.TextArea
rows={2}
value={nonExactReason}
onChange={(event) => setNonExactReason(event.target.value)}
placeholder="例如:订单规格未写全,客服已与买家确认可用该库存"
/>
</Space>
) : null}
<Typography.Text type="secondary">
已选 {selectedIds.length} 条,合计成本 {money(totalPrice)}
</Typography.Text>