Merge branch 'main' into dev

This commit is contained in:
2026-11-03 10:33:10 +08:00
121 changed files with 5265 additions and 1629 deletions
@@ -29,6 +29,7 @@ import {
Typography,
} from 'antd'
import PercentTable from '@/components/common/PercentTable'
import { buildAdminTablePagination } from '@/utils/admin-pagination'
import type { TableColumnsType } from 'antd'
import { useState } from 'react'
@@ -880,18 +881,16 @@ export default function ConsignmentInventoryPanel() {
dataSource={items}
loading={inventoriesQuery.isLoading}
scroll={{ x: 1590 }}
pagination={{
pagination={buildAdminTablePagination({
current: page,
pageSize,
total,
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
showTotal: (t) => `共 ${t} 件物品`,
onChange: (p, ps) => {
setPage(p)
setPageSize(ps)
},
}}
})}
/>
</Card>
@@ -26,6 +26,7 @@ import {
App,
} from 'antd'
import PercentTable from '@/components/common/PercentTable'
import { buildAdminTablePagination } from '@/utils/admin-pagination'
import type { TableColumnsType } from 'antd'
import { useState } from 'react'
@@ -474,17 +475,16 @@ export default function ConsignmentSubmissionsPanel() {
dataSource={submissions}
loading={submissionsQuery.isLoading}
scroll={{ x: 1490 }}
pagination={{
pagination={buildAdminTablePagination({
current: submissionPage,
pageSize: submissionPageSize,
total: submissionTotal,
showSizeChanger: true,
showTotal: (t) => `共 ${t} 条提报记录`,
onChange: (p, ps) => {
setSubmissionPage(p)
setSubmissionPageSize(ps)
},
}}
})}
/>
</Card>
@@ -44,7 +44,11 @@ import {
import type { KuaishouMatchSource, WorkProductMatchLog } from '@/types/worker-platform'
import type { AdminKuaishouIndustryShopOption } from '@/types/admin'
import { formatAdminDateTime } from '@/utils/admin-time'
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
import {
ADMIN_DEFAULT_PAGE_SIZE,
buildAdminLocalTablePagination,
buildAdminTablePagination,
} from '@/utils/admin-pagination'
import { formatMoney } from './shared'
export default function ProductMatchPanel() {
@@ -366,7 +370,7 @@ export default function ProductMatchPanel() {
loading={sourcesQuery.isLoading}
columns={sourceColumns}
dataSource={sources}
pagination={{ pageSize: 10, showSizeChanger: false }}
pagination={buildAdminLocalTablePagination()}
size="small"
/>
</div>
@@ -18,7 +18,6 @@ import {
Form,
Input,
InputNumber,
Modal,
Popconfirm,
Radio,
Select,
@@ -42,7 +41,6 @@ import {
fetchAdminWorkProductRules,
reprocessAdminKuaishouWorkOrderMatches,
saveAdminWorkProductRule,
syncAdminWorkProductRulePrice,
} from '@/services/admin'
import type {
CollectField,
@@ -73,7 +71,6 @@ export default function ProductRulesPanel() {
const hasInitializedRuleSelection = useRef(false)
const [editingRule, setEditingRule] = useState<WorkProductRule | null>(null)
const [syncingPrice, setSyncingPrice] = useState(false)
const [exportingRules, setExportingRules] = useState(false)
const [activeMappingId, setActiveMappingId] = useState<number | 'new' | null>(null)
const [sourcePrefill, setSourcePrefill] = useState<SourcePrefill | null>(null)
@@ -296,9 +293,7 @@ export default function ProductRulesPanel() {
itemTitleMatchType: rule.match.itemTitleMatchType,
skuNickMatchType: rule.match.skuNickMatchType,
categoryId: rule.categoryId || undefined,
pricingMode: rule.unitPriceFen > 0 ? 'unit' : 'fixed',
rewardAmount: rule.rewardAmount / 100,
unitPrice: rule.unitPriceFen / 100,
pricingMode: rule.pricingMode || 'fixed',
sharingEnabled: rule.sharing?.enabled || false,
sharingAutoFromOrder: rule.sharing?.autoFromOrder || false,
sharingTotalQuantity: hasManualSharingOverride ? rule.sharing?.totalQuantity : undefined,
@@ -342,9 +337,6 @@ export default function ProductRulesPanel() {
itemTitleMatchType?: 'contains' | 'exact'
skuNickMatchType?: 'contains' | 'exact'
categoryId?: number
rewardAmount?: number
pricingMode?: string
unitPrice?: number
sharingEnabled?: boolean
sharingAutoFromOrder?: boolean
sharingTotalQuantity?: number
@@ -358,10 +350,10 @@ export default function ProductRulesPanel() {
fieldsText?: string
enabled?: boolean
autoCreate?: boolean
pricingMode?: 'fixed' | 'unit'
sortOrder?: number
}) {
try {
const unitMode = values.pricingMode === 'unit'
const isFriendGift = values.acceptanceMode === 'friend_gift'
if (isFriendGift && values.sharingEnabled === true) {
message.error('好友赠送验收模式不支持拼单,请先关闭拼单')
@@ -372,12 +364,11 @@ export default function ProductRulesPanel() {
? Number(values.sharingTotalQuantity || 0) > 0 ||
Number(values.sharingUnitReward || 0) > 0
: hasManualSharingInput(values.sharingTotalQuantity, values.sharingUnitReward)
const { pricingMode: _pricingMode, giftCooldownUnit, giftCooldownValue, ...payload } = values
const { giftCooldownUnit, giftCooldownValue, ...payload } = values
const res = await saveAdminWorkProductRule({
...payload,
ruleId: editingRule?.ruleId,
ruleKey: editingRule?.ruleKey || '',
unitPrice: unitMode ? Number(values.unitPrice || 0) : 0,
acceptanceMode: isFriendGift ? 'friend_gift' : 'standard',
giftCooldownHours:
isFriendGift && giftCooldownUnit !== 'minutes' ? Number(giftCooldownValue || 0) : 0,
@@ -385,10 +376,16 @@ export default function ProductRulesPanel() {
isFriendGift && giftCooldownUnit === 'minutes' ? Number(giftCooldownValue || 0) : 0,
sharingEnabled: isFriendGift ? false : values.sharingEnabled === true,
sharingAutoFromOrder:
unitMode && values.sharingEnabled === true && !hasManualSharingOverride,
values.pricingMode === 'unit' &&
values.sharingEnabled === true &&
!hasManualSharingOverride,
})
message.success(editingRule ? '接单模板已更新' : '接单模板已创建')
const savedRule = res.data?.rule
if (res.data?.costConfigurationRequired) {
message.warning('模板已保存为停用。请到【商品与成本中心】配置成本后再启用')
} else {
message.success(editingRule ? '接单模板已更新' : '接单模板已创建')
}
if (savedRule) {
editRule(savedRule)
} else {
@@ -401,42 +398,15 @@ export default function ProductRulesPanel() {
queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-summary'],
}),
queryClient.invalidateQueries({
predicate: (query) => String(query.queryKey[0] || '').startsWith('admin-cost-center'),
}),
])
} catch (error) {
message.error(error instanceof Error ? error.message : '保存失败')
}
}
function confirmSyncRulePrice() {
if (!editingRule) return
Modal.confirm({
title: '同步模板价格',
content:
'将同步接单大厅中该模板的待抢单价格。部分拼单仅调整剩余份额价格,已加入份额报酬保持不变。',
okText: '确认同步',
cancelText: '取消',
onOk: async () => {
setSyncingPrice(true)
try {
const response = await syncAdminWorkProductRulePrice(editingRule.ruleId)
const result = response.data
message.success(
`已同步 ${result.updatedCount} 单:待抢单 ${result.updatedNormalCount} 单,部分拼单 ${result.updatedPartialSharingCount} 单`,
)
await Promise.all([
queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-product-rules'] }),
queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-orders'] }),
])
} catch (error) {
message.error(error instanceof Error ? error.message : '同步价格失败')
throw error
} finally {
setSyncingPrice(false)
}
},
})
}
async function toggleRuleEnabled(rule: WorkProductRule, enabled: boolean) {
try {
const hasManualSharingOverride = hasManualSharingInput(
@@ -450,11 +420,12 @@ export default function ProductRulesPanel() {
platform: rule.platform,
productName: rule.productName,
categoryId: rule.categoryId || undefined,
rewardAmount: rule.rewardAmount / 100,
unitPrice: rule.unitPriceFen / 100,
pricingMode: rule.pricingMode,
sharingEnabled: rule.sharing?.enabled || false,
sharingAutoFromOrder:
rule.unitPriceFen > 0 && rule.sharing?.enabled === true && !hasManualSharingOverride,
rule.pricingMode === 'unit' &&
rule.sharing?.enabled === true &&
!hasManualSharingOverride,
sharingTotalQuantity: hasManualSharingOverride ? rule.sharing?.totalQuantity : undefined,
sharingUnitReward: hasManualSharingOverride
? (rule.sharing?.unitReward || 0) / 100
@@ -473,6 +444,9 @@ export default function ProductRulesPanel() {
await queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-product-rules'],
})
await queryClient.invalidateQueries({
predicate: (query) => String(query.queryKey[0] || '').startsWith('admin-cost-center'),
})
} catch (error) {
message.error(error instanceof Error ? error.message : '修改状态失败')
}
@@ -782,19 +756,22 @@ export default function ProductRulesPanel() {
},
},
{
title: '接单金额',
title: '接单价格',
key: 'reward',
width: '20%',
render: (_, row) => (
<div className="cell-stack">
<Typography.Text strong style={{ color: '#3f8600', fontSize: 12 }}>
{row.sharing?.autoFromOrder ? '随订单计算' : formatMoney(row.rewardAmount)}
</Typography.Text>
{row.unitPriceFen > 0 ? (
<Typography.Text type="secondary" style={{ fontSize: 11 }}>
按件: {formatMoney(row.unitPriceFen)}
{(row.pricingMode === 'unit' ? row.unitPriceFen : row.rewardAmount) > 0 ? (
<Typography.Text strong style={{ color: '#3f8600', fontSize: 12 }}>
{row.pricingMode === 'unit'
? `${formatMoney(row.unitPriceFen)} / 件`
: formatMoney(row.rewardAmount)}
</Typography.Text>
) : null}
) : (
<Tag color="orange" style={{ width: 'fit-content', margin: 0 }}>
待成本配置
</Tag>
)}
</div>
),
},
@@ -891,11 +868,6 @@ export default function ProductRulesPanel() {
+ 新建模板
</Button>
) : null}
{editingRule ? (
<Button loading={syncingPrice} size="middle" onClick={confirmSyncRulePrice}>
同步价格
</Button>
) : null}
<Button type="primary" size="middle" htmlType="submit" form="product-rule-form">
{editingRule ? '保存模板' : '创建模板'}
</Button>
@@ -913,10 +885,9 @@ export default function ProductRulesPanel() {
matchType: 'contains',
itemTitleMatchType: 'exact',
skuNickMatchType: 'exact',
pricingMode: 'fixed',
rewardAmount: 25,
enabled: true,
enabled: false,
autoCreate: true,
pricingMode: 'fixed',
sharingEnabled: false,
acceptanceMode: 'standard',
giftCooldownUnit: 'hours',
@@ -926,23 +897,21 @@ export default function ProductRulesPanel() {
}}
onFinish={saveRule}
>
{editingRule ? (
<Alert
type="info"
showIcon
style={{ marginBottom: 16 }}
message="全系统标准价格已统一收敛至【商品与成本中心】"
description={
<span>
接单单价由商品与成本中心维护并提供同步发单功能。在此处价格为只读展示;如需改价或同步工单价格,请前往
<a href="#/admin/cost-center" style={{ marginLeft: 4, fontWeight: 500 }}>
【商品与成本中心】
</a>
进行修改。
</span>
}
/>
) : null}
<Alert
type="info"
showIcon
style={{ marginBottom: 16 }}
message="接单模板维护计价方式和履约规则"
description={
<span>
在这里选择“固定金额”或“按 SKU 按件”;具体金额与价格同步统一由
<a href="#/admin/cost-center" style={{ margin: '0 4px', fontWeight: 500 }}>
商品与成本中心
</a>
管理。新模板默认停用,配置成本并启用模板后自动出现在商品目录。
</span>
}
/>
{/* 基础信息 */}
<div className="form-grid-3col">
@@ -965,58 +934,27 @@ export default function ProductRulesPanel() {
</Form.Item>
</div>
{/* Row 2: 计价方式 + 单价/金额 + 任务时限 */}
{/* Row 2: 任务时限 */}
<div className="form-grid-3col">
<Form.Item label="计价方式" name="pricingMode">
<Form.Item label="任务时限" name="timeoutMinutes">
<InputNumber min={0} step={5} addonAfter="分钟" className="full-width" />
</Form.Item>
<Form.Item
label="计价方式"
name="pricingMode"
rules={[{ required: true, message: '请选择计价方式' }]}
extra="固定金额按整单;按 SKU 按件会把成本中心金额乘以订单件数"
>
<Radio.Group
optionType="button"
buttonStyle="solid"
className="full-width"
options={[
{ value: 'fixed', label: '固定金额' },
{ value: 'unit', label: '按件计价' },
{ value: 'unit', label: '按 SKU 按件' },
]}
/>
</Form.Item>
<Form.Item noStyle shouldUpdate>
{({ getFieldValue }) =>
getFieldValue('pricingMode') === 'unit' ? (
<Form.Item
label="按件单价"
name="unitPrice"
rules={[{ required: true, message: '请填写单价' }]}
extra={editingRule ? '标准单价由商品与成本中心统一管理' : undefined}
>
<InputNumber
min={0.01}
step={1}
addonAfter="元"
className="full-width"
disabled={Boolean(editingRule)}
/>
</Form.Item>
) : (
<Form.Item
label="接单金额"
name="rewardAmount"
extra={editingRule ? '标准单价由商品与成本中心统一管理' : undefined}
>
<InputNumber
min={0}
step={1}
addonAfter="元"
className="full-width"
disabled={Boolean(editingRule)}
/>
</Form.Item>
)
}
</Form.Item>
<Form.Item label="任务时限" name="timeoutMinutes">
<InputNumber min={0} step={5} addonAfter="分钟" className="full-width" />
</Form.Item>
</div>
{/* 验收模式:好友赠送模式与拼单互斥 */}
@@ -1126,11 +1064,19 @@ export default function ProductRulesPanel() {
const sharingEnabled = getFieldValue('sharingEnabled')
const unitMode = getFieldValue('pricingMode') === 'unit'
const friendGiftMode = getFieldValue('acceptanceMode') === 'friend_gift'
const hasConfiguredPrice =
Number(editingRule?.unitPriceFen || 0) > 0 ||
Number(editingRule?.rewardAmount || 0) > 0
return (
<div className="switches-inline-group span-2">
<Form.Item label="启用模板" name="enabled" valuePropName="checked">
<Switch size="small" />
<Form.Item
label="启用模板"
name="enabled"
valuePropName="checked"
tooltip={!hasConfiguredPrice ? '请先在商品与成本中心配置成本' : undefined}
>
<Switch size="small" disabled={!hasConfiguredPrice} />
</Form.Item>
<Form.Item label="自动建单" name="autoCreate" valuePropName="checked">
<Switch size="small" />
@@ -1139,9 +1085,15 @@ export default function ProductRulesPanel() {
label="启用拼单"
name="sharingEnabled"
valuePropName="checked"
tooltip={friendGiftMode ? '好友赠送模式不支持拼单' : undefined}
tooltip={
friendGiftMode
? '好友赠送模式不支持拼单'
: !hasConfiguredPrice
? '请先在商品与成本中心配置成本'
: undefined
}
>
<Switch size="small" disabled={friendGiftMode} />
<Switch size="small" disabled={friendGiftMode || !hasConfiguredPrice} />
</Form.Item>
{sharingEnabled ? (
@@ -14,6 +14,7 @@ import {
Tag,
} from 'antd'
import PercentTable from '@/components/common/PercentTable'
import { buildAdminTablePagination } from '@/utils/admin-pagination'
import type { ColumnsType } from 'antd/es/table'
import { useEffect, useState } from 'react'
@@ -635,16 +636,15 @@ export default function RiskControlPanel() {
dataSource={bans?.items || []}
loading={bansQuery.isLoading}
locale={{ emptyText: '暂无封禁记录' }}
pagination={{
pagination={buildAdminTablePagination({
current: banPage,
pageSize: banPageSize,
total: bans?.total || 0,
showSizeChanger: true,
onChange: (nextPage, nextPageSize) => {
setBanPage(nextPage)
setBanPageSize(nextPageSize)
},
}}
})}
/>
</Card>
@@ -690,16 +690,15 @@ export default function RiskControlPanel() {
columns={eventColumns}
dataSource={events?.items || []}
loading={eventsQuery.isLoading}
pagination={{
pagination={buildAdminTablePagination({
current: page,
pageSize,
total: events?.total || 0,
showSizeChanger: true,
onChange: (nextPage, nextPageSize) => {
setPage(nextPage)
setPageSize(nextPageSize)
},
}}
})}
/>
</Card>
</Space>