feat(distribution): 优化一键同步主站商品功能(并发防重、分批入库、失效商品联动下架与初始加价)
- 后端优化: - 引入 Redis + 内存 sync.Map 双重互斥锁,避免并发/连击导致唯一索引冲突 (ErrSyncInProgress) - 使用 CreateInBatches(500) 进行大数据量批量插入,杜绝超长 SQL 与锁表超时 - 增加 CleanStaleListings 支持,自动排查并联动下架主站非在售的货架商品,清理历史幽灵数据 - 增加 DefaultShelfRatioDelta 支持,新同步商品直接继承初始加价比例,免除二次手动改价 - 前端优化: - distribution-admin 货架管理新增一键同步配置弹窗(可勾选自动下架失效商品、配置初始加价) - 同步完成后展示结构化统计弹窗(新增、保持、联动下架、主站在售总量) - 前后端类型定义与 API 客户端对齐升级 - 测试覆盖: - 补充 TestSyncSiteAccountsOptimizations 单元测试与 handler 集成测试 - 全工程测试与门禁 check.sh 100% 通过
This commit is contained in:
@@ -222,9 +222,17 @@ export interface AccountDisplay {
|
||||
source_channel?: string
|
||||
}
|
||||
|
||||
export interface SyncSiteAccountsRequest {
|
||||
listing_ids?: number[]
|
||||
clean_stale_listings?: boolean
|
||||
default_shelf_ratio_delta?: number
|
||||
}
|
||||
|
||||
export interface SyncSiteAccountsResult {
|
||||
synced_count: number
|
||||
skipped_count: number
|
||||
updated_count?: number
|
||||
disabled_stale_count?: number
|
||||
total_active: number
|
||||
}
|
||||
|
||||
@@ -405,8 +413,8 @@ export async function updateAccountDisplay(id: number, displayStatus: string): P
|
||||
return extract(resp)
|
||||
}
|
||||
|
||||
export async function syncSiteAccounts(siteID: number): Promise<SyncSiteAccountsResult> {
|
||||
const resp = await api.post<ApiResponse<SyncSiteAccountsResult>>(`/admin/distribution/sites/${siteID}/sync-accounts`)
|
||||
export async function syncSiteAccounts(siteID: number, req?: SyncSiteAccountsRequest): Promise<SyncSiteAccountsResult> {
|
||||
const resp = await api.post<ApiResponse<SyncSiteAccountsResult>>(`/admin/distribution/sites/${siteID}/sync-accounts`, req ?? {})
|
||||
return extract(resp)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
Radio,
|
||||
Row,
|
||||
Col,
|
||||
Checkbox,
|
||||
Statistic,
|
||||
} from 'antd'
|
||||
import {
|
||||
SyncOutlined,
|
||||
@@ -42,6 +44,7 @@ import {
|
||||
getMe,
|
||||
type AccountDisplay,
|
||||
type Site,
|
||||
type SyncSiteAccountsResult,
|
||||
} from '../api'
|
||||
import { useMember } from '../MemberContext'
|
||||
|
||||
@@ -199,18 +202,32 @@ export default function Accounts() {
|
||||
void init()
|
||||
}, [member])
|
||||
|
||||
async function handleSync() {
|
||||
// 同步主站商品配置与结果弹窗状态
|
||||
const [syncModalVisible, setSyncModalVisible] = useState(false)
|
||||
const [syncCleanStale, setSyncCleanStale] = useState(true)
|
||||
const [syncDefaultRatioDelta, setSyncDefaultRatioDelta] = useState<number>(0)
|
||||
const [syncResultModalVisible, setSyncResultModalVisible] = useState(false)
|
||||
const [syncResult, setSyncResult] = useState<SyncSiteAccountsResult | null>(null)
|
||||
|
||||
function handleOpenSyncModal() {
|
||||
if (!siteID) {
|
||||
message.warning('无法获取当前分销站点信息')
|
||||
return
|
||||
}
|
||||
setSyncModalVisible(true)
|
||||
}
|
||||
|
||||
async function handleExecuteSync() {
|
||||
if (!siteID) return
|
||||
setSyncing(true)
|
||||
try {
|
||||
const res = await syncSiteAccounts(siteID)
|
||||
message.success(
|
||||
`同步完成!成功同步新增商品 ${res.synced_count} 个,已在货架 ${res.skipped_count} 个(主站共享池在售共 ${res.total_active} 个)`,
|
||||
5,
|
||||
)
|
||||
const res = await syncSiteAccounts(siteID, {
|
||||
clean_stale_listings: syncCleanStale,
|
||||
default_shelf_ratio_delta: Math.max(0, syncDefaultRatioDelta),
|
||||
})
|
||||
setSyncResult(res)
|
||||
setSyncModalVisible(false)
|
||||
setSyncResultModalVisible(true)
|
||||
await load(siteID)
|
||||
} catch (error: unknown) {
|
||||
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
|
||||
@@ -578,7 +595,7 @@ export default function Accounts() {
|
||||
type="primary"
|
||||
icon={<SyncOutlined spin={syncing} />}
|
||||
loading={syncing}
|
||||
onClick={handleSync}
|
||||
onClick={handleOpenSyncModal}
|
||||
style={{ background: '#1677ff' }}
|
||||
>
|
||||
一键同步主站商品
|
||||
@@ -728,7 +745,7 @@ export default function Accounts() {
|
||||
type="primary"
|
||||
icon={<SyncOutlined spin={syncing} />}
|
||||
loading={syncing}
|
||||
onClick={handleSync}
|
||||
onClick={handleOpenSyncModal}
|
||||
>
|
||||
一键同步主站商品
|
||||
</Button>
|
||||
@@ -1243,6 +1260,136 @@ export default function Accounts() {
|
||||
)}
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
{/* 同步配置弹窗 */}
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
<SyncOutlined style={{ color: '#1677ff' }} />
|
||||
<span>一键同步主站商品设置</span>
|
||||
</Space>
|
||||
}
|
||||
open={syncModalVisible}
|
||||
onCancel={() => !syncing && setSyncModalVisible(false)}
|
||||
onOk={handleExecuteSync}
|
||||
confirmLoading={syncing}
|
||||
okText="立即开始同步"
|
||||
cancelText="取消"
|
||||
width={540}
|
||||
>
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%', marginTop: 12 }}>
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="货架铺货说明"
|
||||
description="系统将自动拉取主站及全网审核通过的在售精品账号,极速批量补充到本分站货架,已在货架的商品不会重复生成。"
|
||||
/>
|
||||
|
||||
<Card size="small" style={{ background: '#f8fafc', borderColor: '#e2e8f0' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={syncCleanStale}
|
||||
onChange={(e) => setSyncCleanStale(e.target.checked)}
|
||||
>
|
||||
<Typography.Text strong>自动下架主站已失效/已出租商品</Typography.Text>
|
||||
</Checkbox>
|
||||
<div style={{ paddingLeft: 24, fontSize: 12, color: '#64748b', marginTop: 4 }}>
|
||||
推荐勾选。同步时自动将主站已下架、已出租或被删除的商品标记为已下架,避免历史幽灵商品滞留货架。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
|
||||
<div>
|
||||
<Typography.Text strong>新同步商品初始加价比例(下浮比例点数)</Typography.Text>
|
||||
<div style={{ marginTop: 6, display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={50}
|
||||
step={0.5}
|
||||
value={syncDefaultRatioDelta}
|
||||
onChange={(val) => setSyncDefaultRatioDelta(val ?? 0)}
|
||||
style={{ width: 160 }}
|
||||
addonAfter="点比例"
|
||||
/>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{syncDefaultRatioDelta === 0
|
||||
? '保持主站原价上架'
|
||||
: `预计标价上浮 ${(syncDefaultRatioDelta * 1.1).toFixed(1)}% 左右,溢价利润 100% 归本站`}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: '#94a3b8', marginTop: 6 }}>
|
||||
仅对本次<strong>新增入货架</strong>的商品生效,已在货架的商品保持其原有加价不变。
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
{/* 同步结果展示弹窗 */}
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
<CheckCircleOutlined style={{ color: '#52c41a' }} />
|
||||
<span>主站商品同步完成</span>
|
||||
</Space>
|
||||
}
|
||||
open={syncResultModalVisible}
|
||||
onCancel={() => setSyncResultModalVisible(false)}
|
||||
footer={[
|
||||
<Button key="ok" type="primary" onClick={() => setSyncResultModalVisible(false)}>
|
||||
我知道了
|
||||
</Button>,
|
||||
]}
|
||||
width={500}
|
||||
>
|
||||
<div style={{ padding: '16px 0' }}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Card size="small" style={{ background: '#f6ffed', borderColor: '#b7eb8f', textAlign: 'center' }}>
|
||||
<Statistic
|
||||
title="新增上架商品"
|
||||
value={syncResult?.synced_count ?? 0}
|
||||
valueStyle={{ color: '#52c41a', fontWeight: 'bold' }}
|
||||
suffix="个"
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card size="small" style={{ background: '#f0f5ff', borderColor: '#adc6ff', textAlign: 'center' }}>
|
||||
<Statistic
|
||||
title="已在货架保持"
|
||||
value={syncResult?.skipped_count ?? 0}
|
||||
valueStyle={{ color: '#1677ff', fontWeight: 'bold' }}
|
||||
suffix="个"
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card size="small" style={{ background: '#fffbe6', borderColor: '#ffe58f', textAlign: 'center' }}>
|
||||
<Statistic
|
||||
title="联动下架失效商品"
|
||||
value={syncResult?.disabled_stale_count ?? 0}
|
||||
valueStyle={{ color: '#faad14', fontWeight: 'bold' }}
|
||||
suffix="个"
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card size="small" style={{ background: '#fafafa', borderColor: '#d9d9d9', textAlign: 'center' }}>
|
||||
<Statistic
|
||||
title="主站共享池在售"
|
||||
value={syncResult?.total_active ?? 0}
|
||||
valueStyle={{ color: '#595959', fontWeight: 'bold' }}
|
||||
suffix="个"
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Modal>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user