refactor(cost-center): 统一人工标准商品与成本配置
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 人工 SKU 配置 Tab:扫描 → 配置 → 重新匹配。
|
||||
* 人工 SKU 配置 Tab:按人工标准商品配置渠道与成本。
|
||||
*
|
||||
* 口径(2026-10-24 确认):
|
||||
* * 配置主键是**内部 SKU**(快手 skuNick / 拼多多 outSkuSn),商品名称仅供识别;
|
||||
@@ -16,8 +16,6 @@ import {
|
||||
MinusCircleOutlined,
|
||||
PlusOutlined,
|
||||
ReloadOutlined,
|
||||
ScanOutlined,
|
||||
SyncOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import {
|
||||
Alert,
|
||||
@@ -25,7 +23,6 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
DatePicker,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
@@ -48,9 +45,7 @@ import {
|
||||
fetchAdminManualPlatforms,
|
||||
fetchAdminManualSkuOverview,
|
||||
fetchAdminManualSkus,
|
||||
rematchAdminManualFulfillments,
|
||||
saveAdminManualConfig,
|
||||
scanAdminManualSkus,
|
||||
} from '@/services/admin'
|
||||
import type { ManualPlatformKey, ManualSkuItem } from '@/types/admin'
|
||||
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
||||
@@ -59,8 +54,6 @@ import {
|
||||
formatCreditUnitPriceLabel,
|
||||
formatCreditUnitPriceYuan,
|
||||
} from '@/utils/credit-unit-price'
|
||||
import dayjs from '@/lib/dayjs'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
|
||||
type ConfigFormValues = {
|
||||
channels: Array<{
|
||||
@@ -102,10 +95,6 @@ export function ManualSkuConfigPanel() {
|
||||
const [platformKey, setPlatformKey] = useState('')
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
|
||||
const [scanRange, setScanRange] = useState<[Dayjs, Dayjs]>([
|
||||
dayjs().startOf('month'),
|
||||
dayjs().endOf('month'),
|
||||
])
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const [editing, setEditing] = useState<ManualSkuItem | null>(null)
|
||||
|
||||
@@ -133,25 +122,6 @@ export function ManualSkuConfigPanel() {
|
||||
const platforms = platformQuery.data?.data.items || []
|
||||
const platformMap = new Map(platforms.map((item) => [item.key, item]))
|
||||
|
||||
const scanMutation = useMutation({
|
||||
mutationFn: () =>
|
||||
scanAdminManualSkus({
|
||||
fromDate: scanRange[0].format('YYYY-MM-DD'),
|
||||
toDate: scanRange[1].format('YYYY-MM-DD'),
|
||||
}),
|
||||
onSuccess: (response) => {
|
||||
const result = response.data
|
||||
message.success(`扫描完成,登记 ${result.registered} 个内部 SKU`)
|
||||
void queryClient.invalidateQueries({ queryKey: ['admin-cost-center-manual-skus'] })
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ['admin-cost-center-manual-skus-overview'],
|
||||
})
|
||||
},
|
||||
onError: (error) => {
|
||||
message.error(error instanceof Error ? error.message : '扫描失败')
|
||||
},
|
||||
})
|
||||
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: (values: ConfigFormValues) =>
|
||||
saveAdminManualConfig({
|
||||
@@ -183,7 +153,7 @@ export function ManualSkuConfigPanel() {
|
||||
mutationFn: (skuKey: string) => deleteAdminManualSku(skuKey),
|
||||
onSuccess: (response) => {
|
||||
message.success(
|
||||
`人工分类已移除;${response.data.affectedItems} 个订单项已退回待分类,原始订单保留`,
|
||||
`人工标准商品已归档;${response.data.affectedItems} 个订单项已退回待分类,原始订单保留`,
|
||||
)
|
||||
void queryClient.invalidateQueries({ queryKey: ['admin-cost-center-manual-skus'] })
|
||||
void queryClient.invalidateQueries({
|
||||
@@ -197,17 +167,6 @@ export function ManualSkuConfigPanel() {
|
||||
},
|
||||
})
|
||||
|
||||
const rematchMutation = useMutation({
|
||||
mutationFn: () => rematchAdminManualFulfillments(),
|
||||
onSuccess: (response) => {
|
||||
message.success(`重新匹配完成,回填 ${response.data.updatedCount} 个订单项`)
|
||||
void queryClient.invalidateQueries({ queryKey: ['admin-cost-center-manual-skus'] })
|
||||
},
|
||||
onError: (error) => {
|
||||
message.error(error instanceof Error ? error.message : '重新匹配失败')
|
||||
},
|
||||
})
|
||||
|
||||
function openConfig(item: ManualSkuItem) {
|
||||
setEditing(item)
|
||||
const configuredChannels = item.config?.channels?.length
|
||||
@@ -232,16 +191,18 @@ export function ManualSkuConfigPanel() {
|
||||
|
||||
const columns: TableColumnsType<ManualSkuItem> = [
|
||||
{
|
||||
title: '内部 SKU(配置主键)',
|
||||
title: '人工标准商品',
|
||||
dataIndex: 'skuKey',
|
||||
width: 280,
|
||||
width: 300,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Typography.Text strong copyable={{ text: row.skuKey }}>
|
||||
{row.skuKey}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{row.displayName || '(无名称)'}
|
||||
<Typography.Text strong>{row.displayName || row.skuKey}</Typography.Text>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
copyable={{ text: row.skuKey }}
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
SKU:{row.skuKey}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
),
|
||||
@@ -321,14 +282,14 @@ export function ManualSkuConfigPanel() {
|
||||
{row.configured ? '编辑' : '配置'}
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="移除该人工分类?"
|
||||
description="会删除人工登记与配置、撤销人工渠道规则、清除已生成的人工成本快照,并把关联订单项退回待分类;原始订单不会删除。"
|
||||
okText="确认移除"
|
||||
title="归档该人工标准商品?"
|
||||
description="会归档标准商品、删除人工配置与渠道规则、清除已生成的人工成本快照,并把关联订单项退回待分类;原始订单不会删除。"
|
||||
okText="确认归档"
|
||||
cancelText="取消"
|
||||
onConfirm={() => deleteMutation.mutate(row.skuKey)}
|
||||
>
|
||||
<Button size="small" danger icon={<DeleteOutlined />}>
|
||||
移除
|
||||
归档
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
@@ -347,13 +308,13 @@ export function ManualSkuConfigPanel() {
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 14 }}
|
||||
message="人工履约只需在这里配置一次"
|
||||
description="保存时会自动创建或关联 man: 标准商品、同步统一渠道规则、回填最近 30 天订单,并重试待处理的人工渠道订单。消耗积分 × 平台换算率就是这单的人工成本,不需要再去标准商品或渠道规则页重复配置;消耗积分填 0 表示成本不固定,由客服在确认履约时手填。"
|
||||
message="人工标准商品与成本配置一一对应"
|
||||
description="这里直接列出人工客服范围内的 man: 标准商品。点“配置”选择实际履约渠道并填写每单成本即可;保存后会同步渠道规则、回填最近 30 天订单并重试待处理订单。消耗积分填 0 表示成本不固定,由客服确认时手填。"
|
||||
/>
|
||||
|
||||
<Row gutter={12} style={{ marginBottom: 12 }}>
|
||||
<Col span={6}>
|
||||
<Statistic title="内部 SKU 总数" value={overview?.totalSku ?? 0} />
|
||||
<Statistic title="人工标准商品" value={overview?.totalSku ?? 0} />
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
@@ -380,29 +341,8 @@ export function ManualSkuConfigPanel() {
|
||||
</Row>
|
||||
|
||||
<Space wrap style={{ marginBottom: 12 }}>
|
||||
<DatePicker.RangePicker
|
||||
value={scanRange}
|
||||
onChange={(value) => {
|
||||
if (value?.[0] && value?.[1]) setScanRange([value[0], value[1]])
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ScanOutlined />}
|
||||
loading={scanMutation.isPending}
|
||||
onClick={() => scanMutation.mutate()}
|
||||
>
|
||||
扫描内部 SKU
|
||||
</Button>
|
||||
<Button
|
||||
icon={<SyncOutlined />}
|
||||
loading={rematchMutation.isPending}
|
||||
onClick={() => rematchMutation.mutate()}
|
||||
>
|
||||
重新匹配已配置
|
||||
</Button>
|
||||
<Input.Search
|
||||
placeholder="搜索内部 SKU 或名称"
|
||||
placeholder="搜索标准商品或 SKU"
|
||||
allowClear
|
||||
style={{ width: 220 }}
|
||||
onSearch={(value) => {
|
||||
|
||||
Reference in New Issue
Block a user