feat(cost-center): 记住载体/Tab 选择,补配置引导并优化表格列宽
- 新增 usePersistedState:载体工作区与 Tab 存 localStorage(admin-cost-center:carrier / :tab), 刷新后保持;存储值非法或 Tab 不适用于该载体时自动回退 - 成本项目增加配置说明与常用成本项模板(采购成本/打手报酬/人工成本/平台费/通道费/售后损失), 成本项行新增「加版本」并按成本类型预选范围;成本项/版本弹窗补字段说明 - 商品与成本中心 8 张表统一 tableLayout=fixed,名称类列定宽 + 省略 + tooltip, 表格最小宽度按列宽之和设置,避免名称列独占剩余宽度
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
/**
|
||||
* 把 UI 状态(tab、筛选等)持久化到 localStorage,刷新后保持。
|
||||
*
|
||||
* - 读取失败 / 值不合法时回退到 initial
|
||||
* - setter 支持值或函数式更新
|
||||
* - 写入失败(隐私模式、配额满)时静默忽略,不影响页面
|
||||
*/
|
||||
export function usePersistedState<T>(
|
||||
key: string,
|
||||
initial: T,
|
||||
isValid?: (value: unknown) => value is T,
|
||||
) {
|
||||
const [value, setValue] = useState<T>(() => {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(key)
|
||||
if (raw === null) return initial
|
||||
const parsed: unknown = JSON.parse(raw)
|
||||
if (isValid && !isValid(parsed)) return initial
|
||||
return parsed as T
|
||||
} catch {
|
||||
return initial
|
||||
}
|
||||
})
|
||||
|
||||
const setPersistedValue = useCallback(
|
||||
(next: T | ((prev: T) => T)) => {
|
||||
setValue((prev) => {
|
||||
const resolved = typeof next === 'function' ? (next as (previous: T) => T)(prev) : next
|
||||
try {
|
||||
window.localStorage.setItem(key, JSON.stringify(resolved))
|
||||
} catch {
|
||||
// 本地存储不可用时只保留内存状态。
|
||||
}
|
||||
return resolved
|
||||
})
|
||||
},
|
||||
[key],
|
||||
)
|
||||
|
||||
return [value, setPersistedValue] as const
|
||||
}
|
||||
@@ -32,10 +32,11 @@ import {
|
||||
} from 'antd'
|
||||
import type { TableColumnsType } from 'antd'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
|
||||
import PageHeader from '@/components/admin/PageHeader'
|
||||
import { usePersistedState } from '@/hooks/usePersistedState'
|
||||
import dayjs from '@/lib/dayjs'
|
||||
import {
|
||||
applyAdminTemplateProductBootstrap,
|
||||
@@ -193,6 +194,92 @@ const CARRIER_WORKSPACES: Array<{
|
||||
},
|
||||
]
|
||||
|
||||
function tabKeysForCarrier(carrier: CarrierScope) {
|
||||
return [
|
||||
...(carrier === 'all' || carrier === 'work_order' ? ['products', 'mappings'] : []),
|
||||
...(carrier === 'affiliate_dash' ? ['affiliate-skus'] : []),
|
||||
'cost-items',
|
||||
'cost-versions',
|
||||
'exceptions',
|
||||
'preview',
|
||||
]
|
||||
}
|
||||
|
||||
type CostItemPreset = {
|
||||
key: string
|
||||
label: string
|
||||
code: string
|
||||
name: string
|
||||
costType: CostItem['costType']
|
||||
carrierType: CostCarrierType
|
||||
pricingMode: CostItem['pricingMode']
|
||||
unitName: string
|
||||
}
|
||||
|
||||
/** 常用成本项模板:避免每次手填编码/类型/计价方式。 */
|
||||
const COST_ITEM_PRESETS: CostItemPreset[] = [
|
||||
{
|
||||
key: 'auto_purchase',
|
||||
label: '自动发货采购成本',
|
||||
code: 'auto_purchase',
|
||||
name: '自动发货采购成本',
|
||||
costType: 'auto_purchase',
|
||||
carrierType: 'affiliate_dash',
|
||||
pricingMode: 'per_item',
|
||||
unitName: '件',
|
||||
},
|
||||
{
|
||||
key: 'work_order_reward',
|
||||
label: '接单平台打手报酬',
|
||||
code: 'work_order_reward',
|
||||
name: '接单平台打手报酬',
|
||||
costType: 'work_order_reward',
|
||||
carrierType: 'work_order',
|
||||
pricingMode: 'per_item',
|
||||
unitName: '件',
|
||||
},
|
||||
{
|
||||
key: 'manual_service',
|
||||
label: '人工客服成本',
|
||||
code: 'manual_service',
|
||||
name: '人工客服成本',
|
||||
costType: 'manual_service',
|
||||
carrierType: 'manual',
|
||||
pricingMode: 'per_item',
|
||||
unitName: '单',
|
||||
},
|
||||
{
|
||||
key: 'platform_fee',
|
||||
label: '平台服务费',
|
||||
code: 'platform_fee',
|
||||
name: '平台服务费',
|
||||
costType: 'platform_fee',
|
||||
carrierType: 'shared',
|
||||
pricingMode: 'percent_of_revenue',
|
||||
unitName: '单',
|
||||
},
|
||||
{
|
||||
key: 'payment_fee',
|
||||
label: '支付通道费',
|
||||
code: 'payment_fee',
|
||||
name: '支付通道费',
|
||||
costType: 'payment_fee',
|
||||
carrierType: 'shared',
|
||||
pricingMode: 'percent_of_revenue',
|
||||
unitName: '单',
|
||||
},
|
||||
{
|
||||
key: 'after_sales_loss',
|
||||
label: '售后损失',
|
||||
code: 'after_sales_loss',
|
||||
name: '售后损失',
|
||||
costType: 'after_sales_loss',
|
||||
carrierType: 'shared',
|
||||
pricingMode: 'per_order',
|
||||
unitName: '单',
|
||||
},
|
||||
]
|
||||
|
||||
const PRICING_OPTIONS = [
|
||||
{ value: 'per_item', label: '按件' },
|
||||
{ value: 'per_order', label: '按单' },
|
||||
@@ -251,8 +338,13 @@ export default function AdminCostCenterPage() {
|
||||
const [costItemForm] = Form.useForm<CostItemFormValues>()
|
||||
const [versionForm] = Form.useForm<CostVersionFormValues>()
|
||||
|
||||
const [activeCarrier, setActiveCarrier] = useState<CarrierScope>('all')
|
||||
const [activeTab, setActiveTab] = useState('products')
|
||||
const [activeCarrier, setActiveCarrier] = usePersistedState<CarrierScope>(
|
||||
'admin-cost-center:carrier',
|
||||
'all',
|
||||
(value): value is CarrierScope =>
|
||||
value === 'all' || CARRIER_WORKSPACES.some((workspace) => workspace.key === value),
|
||||
)
|
||||
const [activeTab, setActiveTab] = usePersistedState<string>('admin-cost-center:tab', 'products')
|
||||
const [productKeyword, setProductKeyword] = useState('')
|
||||
const [productRootCategoryId, setProductRootCategoryId] = useState<number | null>(null)
|
||||
const [productSubCategoryId, setProductSubCategoryId] = useState<number | null>(null)
|
||||
@@ -305,15 +397,17 @@ export default function AdminCostCenterPage() {
|
||||
const activeCarrierLabel =
|
||||
activeCarrier === 'all' ? '全部载体' : labelOf(CARRIER_OPTIONS, activeCarrier)
|
||||
// 商品目录 / 渠道映射只服务接单平台;自动发货只需上游 SKU + 采购成本。
|
||||
const showsCatalogTabs = activeCarrier === 'all' || activeCarrier === 'work_order'
|
||||
const visibleTabKeys = [
|
||||
...(showsCatalogTabs ? ['products', 'mappings'] : []),
|
||||
...(activeCarrier === 'affiliate_dash' ? ['affiliate-skus'] : []),
|
||||
'cost-items',
|
||||
'cost-versions',
|
||||
'exceptions',
|
||||
'preview',
|
||||
]
|
||||
const visibleTabKeys = tabKeysForCarrier(activeCarrier)
|
||||
useEffect(() => {
|
||||
const keys = tabKeysForCarrier(activeCarrier)
|
||||
if (!keys.includes(activeTab)) {
|
||||
setActiveTab(keys[0] ?? 'cost-items')
|
||||
}
|
||||
}, [activeCarrier, activeTab, setActiveTab])
|
||||
const visibleCostItemPresets =
|
||||
activeCarrier === 'all'
|
||||
? COST_ITEM_PRESETS
|
||||
: COST_ITEM_PRESETS.filter((preset) => preset.carrierType === activeCarrier)
|
||||
|
||||
const productsQuery = useQuery({
|
||||
queryKey: [
|
||||
@@ -682,9 +776,22 @@ export default function AdminCostCenterPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateCostItem() {
|
||||
function openCreateCostItem(preset?: CostItemPreset) {
|
||||
setEditingCostItem(null)
|
||||
costItemForm.resetFields()
|
||||
if (preset) {
|
||||
costItemForm.setFieldsValue({
|
||||
code: preset.code,
|
||||
name: preset.name,
|
||||
costType: preset.costType,
|
||||
carrierType: preset.carrierType,
|
||||
pricingMode: preset.pricingMode,
|
||||
unitName: preset.unitName,
|
||||
status: 'active',
|
||||
})
|
||||
setCostItemModalOpen(true)
|
||||
return
|
||||
}
|
||||
const defaultCostType =
|
||||
activeCarrier === 'affiliate_dash'
|
||||
? 'auto_purchase'
|
||||
@@ -744,10 +851,20 @@ export default function AdminCostCenterPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateVersion() {
|
||||
function openCreateVersion(costItem?: CostItem) {
|
||||
versionForm.resetFields()
|
||||
const defaultScope: CostVersionFormValues['scopeType'] = costItem
|
||||
? costItem.costType === 'auto_purchase'
|
||||
? 'product'
|
||||
: costItem.costType === 'work_order_reward'
|
||||
? 'rule'
|
||||
: 'global'
|
||||
: activeCarrier === 'affiliate_dash'
|
||||
? 'product'
|
||||
: 'global'
|
||||
versionForm.setFieldsValue({
|
||||
scopeType: activeCarrier === 'affiliate_dash' ? 'product' : 'global',
|
||||
costItemId: costItem?.id,
|
||||
scopeType: defaultScope,
|
||||
effectiveFrom: dayjs(),
|
||||
status: 'active',
|
||||
})
|
||||
@@ -983,6 +1100,8 @@ export default function AdminCostCenterPage() {
|
||||
{
|
||||
title: '商品 / 分类',
|
||||
dataIndex: 'canonicalName',
|
||||
width: 460,
|
||||
ellipsis: true,
|
||||
render: (value: string, row) => (
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<Typography.Text
|
||||
@@ -1163,9 +1282,11 @@ export default function AdminCostCenterPage() {
|
||||
render: (value: string, row) => (
|
||||
<Space size={8} align="start">
|
||||
<FolderOpenOutlined style={{ color: '#1677ff', marginTop: 4 }} />
|
||||
<Space direction="vertical" size={0}>
|
||||
<Typography.Text strong>{value}</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
<Space direction="vertical" size={0} style={{ width: '100%' }}>
|
||||
<Typography.Text strong ellipsis={{ tooltip: value }}>
|
||||
{value}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" ellipsis={{ tooltip: row.canonicalSku }}>
|
||||
{row.canonicalSku} · {row.unitName}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
@@ -1269,8 +1390,8 @@ export default function AdminCostCenterPage() {
|
||||
]
|
||||
|
||||
const costItemColumns: TableColumnsType<CostItem> = [
|
||||
{ title: '编码', dataIndex: 'code', width: 180 },
|
||||
{ title: '名称', dataIndex: 'name', minWidth: 160 },
|
||||
{ title: '编码', dataIndex: 'code', width: 160 },
|
||||
{ title: '名称', dataIndex: 'name', width: 320, ellipsis: true },
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'costType',
|
||||
@@ -1302,11 +1423,14 @@ export default function AdminCostCenterPage() {
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 140,
|
||||
width: 210,
|
||||
fixed: 'right',
|
||||
render: (_, row) =>
|
||||
canConfig ? (
|
||||
<Space size={4}>
|
||||
<Button size="small" type="primary" ghost onClick={() => openCreateVersion(row)}>
|
||||
加版本
|
||||
</Button>
|
||||
<Button size="small" onClick={() => openEditCostItem(row)}>
|
||||
编辑
|
||||
</Button>
|
||||
@@ -1322,11 +1446,16 @@ export default function AdminCostCenterPage() {
|
||||
{
|
||||
title: '成本项',
|
||||
dataIndex: 'costItemName',
|
||||
minWidth: 160,
|
||||
width: 240,
|
||||
ellipsis: true,
|
||||
render: (value: string, row) => (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Typography.Text strong>{value}</Typography.Text>
|
||||
<Typography.Text type="secondary">{row.costItemCode}</Typography.Text>
|
||||
<Space direction="vertical" size={0} style={{ width: '100%' }}>
|
||||
<Typography.Text strong ellipsis={{ tooltip: value }}>
|
||||
{value}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" ellipsis={{ tooltip: row.costItemCode }}>
|
||||
{row.costItemCode}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
@@ -1341,7 +1470,8 @@ export default function AdminCostCenterPage() {
|
||||
{
|
||||
title: '范围',
|
||||
dataIndex: 'scopeType',
|
||||
width: 110,
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
render: (value: string, row) => {
|
||||
if (value === 'global') return '全局'
|
||||
if (value === 'provider') return providerLabel(row.provider)
|
||||
@@ -1417,7 +1547,7 @@ export default function AdminCostCenterPage() {
|
||||
{
|
||||
title: '原因',
|
||||
dataIndex: 'reason',
|
||||
minWidth: 220,
|
||||
width: 360,
|
||||
render: (value: string, row) => (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Typography.Text>{value}</Typography.Text>
|
||||
@@ -1688,13 +1818,14 @@ export default function AdminCostCenterPage() {
|
||||
) : null}
|
||||
|
||||
<Table
|
||||
tableLayout="fixed"
|
||||
rowKey="id"
|
||||
size="small"
|
||||
columns={productColumns}
|
||||
dataSource={products}
|
||||
loading={productsQuery.isLoading}
|
||||
rowClassName={(row) => (row.mappingCount > 0 ? 'product-rule-row-mapped' : '')}
|
||||
scroll={{ x: 1080 }}
|
||||
scroll={{ x: 1180 }}
|
||||
pagination={buildAdminTablePagination({
|
||||
current: productPage,
|
||||
pageSize: productPageSize,
|
||||
@@ -1780,12 +1911,13 @@ export default function AdminCostCenterPage() {
|
||||
<Button onClick={() => setExpandedMappingProductIds([])}>收起全部</Button>
|
||||
</Space>
|
||||
<Table<ChannelProductMappingGroup>
|
||||
tableLayout="fixed"
|
||||
rowKey={(row) => String(row.canonicalProductId)}
|
||||
size="small"
|
||||
columns={mappingGroupColumns}
|
||||
dataSource={mappingGroups}
|
||||
loading={mappingsQuery.isLoading}
|
||||
scroll={{ x: 1210 }}
|
||||
scroll={{ x: 1100 }}
|
||||
expandable={{
|
||||
expandedRowKeys: expandedMappingProductIds,
|
||||
onExpandedRowsChange: (keys) =>
|
||||
@@ -1797,6 +1929,7 @@ export default function AdminCostCenterPage() {
|
||||
该标准商品下共有 {group.mappings.length} 条渠道映射
|
||||
</Typography.Text>
|
||||
<Table<ChannelProductMapping>
|
||||
tableLayout="fixed"
|
||||
rowKey="id"
|
||||
size="small"
|
||||
columns={mappingDetailColumns}
|
||||
@@ -1866,11 +1999,12 @@ export default function AdminCostCenterPage() {
|
||||
message="自动发货只按上游货源 SKU 配采购成本:这里不维护游戏分类、渠道映射和商品上下架,那些只属于接单平台。"
|
||||
/>
|
||||
<Table
|
||||
tableLayout="fixed"
|
||||
rowKey="id"
|
||||
size="small"
|
||||
dataSource={products}
|
||||
loading={productsQuery.isLoading}
|
||||
scroll={{ x: 900 }}
|
||||
scroll={{ x: 1080 }}
|
||||
pagination={buildAdminTablePagination({
|
||||
current: productPage,
|
||||
pageSize: productPageSize,
|
||||
@@ -1885,10 +2019,20 @@ export default function AdminCostCenterPage() {
|
||||
{
|
||||
title: 'SKU 名称',
|
||||
dataIndex: 'canonicalName',
|
||||
width: 320,
|
||||
ellipsis: true,
|
||||
render: (value: string, row) => (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Typography.Text strong>{value}</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
<Space direction="vertical" size={0} style={{ width: '100%' }}>
|
||||
<Typography.Text strong ellipsis={{ tooltip: value }}>
|
||||
{value}
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
style={{ fontSize: 12 }}
|
||||
ellipsis={{
|
||||
tooltip: String(row.attributes?.skuCode || row.canonicalSku),
|
||||
}}
|
||||
>
|
||||
{String(row.attributes?.skuCode || row.canonicalSku)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
@@ -1974,12 +2118,48 @@ export default function AdminCostCenterPage() {
|
||||
}
|
||||
extra={
|
||||
canConfig ? (
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreateCostItem}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => openCreateCostItem()}
|
||||
>
|
||||
新建成本项
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 12 }}
|
||||
message="成本项目 = 有哪些成本;成本版本 = 这笔成本多少钱、对谁生效"
|
||||
description={
|
||||
<Space direction="vertical" size={2}>
|
||||
<span>
|
||||
1. 先建成本项目:如「自动发货采购成本」「接单平台打手报酬」「平台服务费」;
|
||||
</span>
|
||||
<span>
|
||||
2. 再建成本版本:同一成本项可有多条,按范围优先级 接单规则 > 标准商品
|
||||
> 店铺 > 来源 > 全局 命中;
|
||||
</span>
|
||||
<span>3. 改价只新增版本,历史订单走成本快照。</span>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
{canConfig && visibleCostItemPresets.length > 0 ? (
|
||||
<Space style={{ marginBottom: 12 }} wrap>
|
||||
<Typography.Text type="secondary">常用成本项:</Typography.Text>
|
||||
{visibleCostItemPresets.map((preset) => (
|
||||
<Button
|
||||
key={preset.key}
|
||||
size="small"
|
||||
onClick={() => openCreateCostItem(preset)}
|
||||
>
|
||||
+ {preset.label}
|
||||
</Button>
|
||||
))}
|
||||
</Space>
|
||||
) : null}
|
||||
<Space style={{ marginBottom: 16 }}>
|
||||
<Input
|
||||
allowClear
|
||||
@@ -1994,12 +2174,13 @@ export default function AdminCostCenterPage() {
|
||||
/>
|
||||
</Space>
|
||||
<Table
|
||||
tableLayout="fixed"
|
||||
rowKey="id"
|
||||
size="small"
|
||||
columns={costItemColumns}
|
||||
dataSource={costItems}
|
||||
loading={costItemsQuery.isLoading}
|
||||
scroll={{ x: 1000 }}
|
||||
scroll={{ x: 1250 }}
|
||||
pagination={buildAdminTablePagination({
|
||||
current: costItemPage,
|
||||
pageSize: costItemPageSize,
|
||||
@@ -2029,12 +2210,23 @@ export default function AdminCostCenterPage() {
|
||||
}
|
||||
extra={
|
||||
canConfig ? (
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreateVersion}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => openCreateVersion()}
|
||||
>
|
||||
新建版本
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 12 }}
|
||||
message="成本版本 = 给成本项定价:一条版本只表达「多少钱 + 何时生效 + 对谁适用」"
|
||||
description="按件/按单/按比例计价;范围优先级 接单规则 > 标准商品 > 店铺 > 来源 > 全局。改价请新建版本,不要改已生效版本;每个成本项建议只保留一个生效版本。"
|
||||
/>
|
||||
<Space style={{ marginBottom: 16 }} wrap>
|
||||
<Input
|
||||
allowClear
|
||||
@@ -2064,12 +2256,13 @@ export default function AdminCostCenterPage() {
|
||||
/>
|
||||
</Space>
|
||||
<Table
|
||||
tableLayout="fixed"
|
||||
rowKey="id"
|
||||
size="small"
|
||||
columns={versionColumns}
|
||||
dataSource={versions}
|
||||
loading={versionsQuery.isLoading}
|
||||
scroll={{ x: 1210 }}
|
||||
scroll={{ x: 1100 }}
|
||||
pagination={buildAdminTablePagination({
|
||||
current: versionPage,
|
||||
pageSize: versionPageSize,
|
||||
@@ -2108,6 +2301,7 @@ export default function AdminCostCenterPage() {
|
||||
}
|
||||
>
|
||||
<Table
|
||||
tableLayout="fixed"
|
||||
rowKey="id"
|
||||
size="small"
|
||||
columns={exceptionColumns}
|
||||
@@ -2207,27 +2401,31 @@ export default function AdminCostCenterPage() {
|
||||
) : null}
|
||||
|
||||
<Table
|
||||
tableLayout="fixed"
|
||||
rowKey={(row) => `${row.costItemId}-${row.costVersionId}`}
|
||||
size="small"
|
||||
pagination={false}
|
||||
dataSource={preview.lines}
|
||||
columns={[
|
||||
{ title: '成本项', dataIndex: 'costItemName' },
|
||||
{ title: '类型', dataIndex: 'costType' },
|
||||
{ title: '范围', dataIndex: 'scopeType' },
|
||||
{ title: '成本项', dataIndex: 'costItemName', width: 220 },
|
||||
{ title: '类型', dataIndex: 'costType', width: 120 },
|
||||
{ title: '范围', dataIndex: 'scopeType', width: 160 },
|
||||
{
|
||||
title: '数量',
|
||||
dataIndex: 'quantity',
|
||||
width: 90,
|
||||
render: (value: number) => Number(value || 0).toFixed(3),
|
||||
},
|
||||
{
|
||||
title: '单位成本',
|
||||
dataIndex: 'unitAmountFen',
|
||||
width: 110,
|
||||
render: (value: number) => formatMoney(value),
|
||||
},
|
||||
{
|
||||
title: '合计',
|
||||
dataIndex: 'totalAmountFen',
|
||||
width: 110,
|
||||
render: (value: number) => formatMoney(value),
|
||||
},
|
||||
]}
|
||||
@@ -2398,19 +2596,34 @@ export default function AdminCostCenterPage() {
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={costItemForm} layout="vertical" onFinish={submitCostItem}>
|
||||
<Form.Item name="code" label="编码" rules={[{ required: true, message: '请输入编码' }]}>
|
||||
<Form.Item
|
||||
name="code"
|
||||
label="编码"
|
||||
rules={[{ required: true, message: '请输入编码' }]}
|
||||
extra="建议英文小写 + 下划线,例如 auto_purchase、work_order_reward;创建后不可修改。"
|
||||
>
|
||||
<Input placeholder="例如:work_order_reward" disabled={Boolean(editingCostItem)} />
|
||||
</Form.Item>
|
||||
<Form.Item name="name" label="名称" rules={[{ required: true, message: '请输入名称' }]}>
|
||||
<Input placeholder="例如:接单平台打手报酬" />
|
||||
</Form.Item>
|
||||
<Form.Item name="costType" label="成本类型" rules={[{ required: true }]}>
|
||||
<Form.Item
|
||||
name="costType"
|
||||
label="成本类型"
|
||||
rules={[{ required: true }]}
|
||||
extra="决定成本归到哪一类报表:自动发货选采购成本,接单平台选打手报酬,平台/支付费选对应类型。"
|
||||
>
|
||||
<Select options={COST_TYPE_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item name="carrierType" label="适用载体" rules={[{ required: true }]}>
|
||||
<Select options={CARRIER_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item name="pricingMode" label="计价方式" rules={[{ required: true }]}>
|
||||
<Form.Item
|
||||
name="pricingMode"
|
||||
label="计价方式"
|
||||
rules={[{ required: true }]}
|
||||
extra="按件 = 数量 × 单价;按单 = 每单固定金额;按比例 = 订单金额 × 百分比;按天固定 = 每天固定成本。"
|
||||
>
|
||||
<Select options={PRICING_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item name="unitName" label="单位">
|
||||
@@ -2440,6 +2653,13 @@ export default function AdminCostCenterPage() {
|
||||
width={620}
|
||||
destroyOnClose
|
||||
>
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 12 }}
|
||||
message="一条版本 = 一个价格 + 一个生效区间 + 一个适用范围"
|
||||
description="优先级:接单规则 > 标准商品 > 店铺 > 来源 > 全局。改价请新建版本,旧版本停用即可,历史订单按快照不变。"
|
||||
/>
|
||||
<Form form={versionForm} layout="vertical" onFinish={submitVersion}>
|
||||
<Form.Item name="costItemId" label="成本项" rules={[{ required: true }]}>
|
||||
<Select showSearch options={costItemOptions} optionFilterProp="label" />
|
||||
@@ -2501,6 +2721,7 @@ export default function AdminCostCenterPage() {
|
||||
<Form.Item
|
||||
name="amountYuan"
|
||||
label="固定金额(元)"
|
||||
extra="按件时填单件成本;按单时填整单成本。"
|
||||
rules={[{ required: true, message: '请输入金额' }]}
|
||||
>
|
||||
<InputNumber min={0.01} precision={2} style={{ width: '100%' }} />
|
||||
|
||||
Reference in New Issue
Block a user