refactor(distribution): 重写分销迁移并落地利润百分比分成与货架比例

将 000064-000078 合并为 064/065/066,默认规则改为收号 35%、卖出 40%、服务费 25%。
货架调价改为增加分站 1 元兑换哈夫币比例。sql-guard 仅保护 main 已发布迁移。
This commit is contained in:
yml2213
2026-09-08 19:02:58 +08:00
parent 48a2961245
commit d762b33f54
48 changed files with 745 additions and 1073 deletions
+8 -4
View File
@@ -176,6 +176,9 @@ export interface AccountDisplay {
listing_id: number
display_status: string
markup_amount_cent?: number
shelf_ratio_delta?: number
base_ratio?: number
site_ratio?: number
listing_no?: string
title?: string
game_name?: string
@@ -184,6 +187,7 @@ export interface AccountDisplay {
rank_level?: string
haf_coin_amount?: number
price_cent?: number
site_price_cent?: number
deposit_amount_cent?: number
listing_status?: string
in_transaction?: boolean
@@ -541,17 +545,17 @@ export async function updateSiteBranding(siteID: number, payload: SiteBrandingPa
return extract(resp)
}
export async function updateAccountMarkup(id: number, markupCent: number): Promise<AccountDisplay> {
export async function updateAccountMarkup(id: number, shelfRatioDelta: number): Promise<AccountDisplay> {
const resp = await api.put<ApiResponse<AccountDisplay>>(`/admin/distribution/accounts/${id}/markup`, {
markup_amount_cent: markupCent,
shelf_ratio_delta: shelfRatioDelta,
})
return extract(resp)
}
export async function batchUpdateAccountMarkup(siteID: number, listingIDs: number[], markupCent: number): Promise<void> {
export async function batchUpdateAccountMarkup(siteID: number, listingIDs: number[], shelfRatioDelta: number): Promise<void> {
await api.post(`/admin/distribution/accounts/batch-markup`, {
listing_ids: listingIDs,
markup_amount_cent: markupCent,
shelf_ratio_delta: shelfRatioDelta,
}, {
params: { site_id: siteID },
})
+25 -26
View File
@@ -33,7 +33,7 @@ export default function Accounts() {
// Markup modal state
const [markupModalVisible, setMarkupModalVisible] = useState(false)
const [targetAccount, setTargetAccount] = useState<AccountDisplay | null>(null)
const [markupYuan, setMarkupYuan] = useState<number>(0)
const [markupRatio, setMarkupRatio] = useState<number>(0)
const [markupSaving, setMarkupSaving] = useState(false)
async function load(site?: number, statusFilter?: string) {
@@ -122,30 +122,29 @@ export default function Accounts() {
function handleOpenMarkup(item?: AccountDisplay) {
if (item) {
setTargetAccount(item)
setMarkupYuan((item.markup_amount_cent ?? 0) / 100)
setMarkupRatio(item.shelf_ratio_delta ?? 0)
} else {
if (selectedRowKeys.length === 0) {
message.warning('请先勾选需要批量加价的账号商品')
return
}
setTargetAccount(null)
setMarkupYuan(0)
setMarkupRatio(0)
}
setMarkupModalVisible(true)
}
async function handleSaveMarkup() {
if (!siteID) return
const markupCent = Math.round(markupYuan * 100)
if (markupCent < 0) {
message.error('加价金额不能小于0')
if (markupRatio < 0) {
message.error('加价比例不能小于0')
return
}
setMarkupSaving(true)
try {
if (targetAccount) {
await updateAccountMarkup(targetAccount.id, markupCent)
message.success('商品加价设置成功!')
await updateAccountMarkup(targetAccount.id, markupRatio)
message.success('商品加价比例设置成功')
} else {
const listingIDs = items
.filter((it) => selectedRowKeys.includes(it.id))
@@ -154,7 +153,7 @@ export default function Accounts() {
message.warning('请先勾选要加价的商品')
return
}
await batchUpdateAccountMarkup(siteID, listingIDs, markupCent)
await batchUpdateAccountMarkup(siteID, listingIDs, markupRatio)
message.success(`已成功为选中的 ${listingIDs.length} 个商品批量设置加价!`)
setSelectedRowKeys([])
}
@@ -340,8 +339,7 @@ export default function Accounts() {
render: (_: unknown, row: AccountDisplay) => (
<div>
<Typography.Text strong style={{ color: '#cf1322' }}>
{formatMoney(row.price_cent)}
<span style={{ fontSize: 12, fontWeight: 'normal', color: '#888' }}> /小时</span>
{formatMoney(row.site_price_cent || row.price_cent)}
</Typography.Text>
<div style={{ fontSize: 12, color: '#666' }}>
押金: {formatMoney(row.deposit_amount_cent)}
@@ -350,14 +348,14 @@ export default function Accounts() {
),
},
{
title: '自主加价',
dataIndex: 'markup_amount_cent',
title: '加价比例',
dataIndex: 'shelf_ratio_delta',
width: 120,
render: (v: number | undefined) => {
if (v && v > 0) {
return (
<Tag color="gold" style={{ fontWeight: 'bold' }}>
+{formatMoney(v)}
+{v}
</Tag>
)
}
@@ -422,28 +420,29 @@ export default function Accounts() {
>
<Space direction="vertical" style={{ width: '100%', padding: '12px 0' }}>
<Typography.Paragraph type="secondary" style={{ marginBottom: 8 }}>
自主加价金额将直接叠加在分销前台展示的每小时租金上,该部分溢价产生的收益将在成单结算时归当前分销商所有。
加价是增加该站账号「1 元兑换哈夫币」的比例,不是叠加人民币。比例越大,同样币数售价越低。
</Typography.Paragraph>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<Typography.Text strong>每小时加价金额:</Typography.Text>
<Typography.Text strong>增加兑换比例:</Typography.Text>
<InputNumber
min={0}
max={1000}
precision={2}
max={100}
precision={1}
style={{ width: 180 }}
value={markupYuan}
onChange={(val) => setMarkupYuan(val ?? 0)}
addonAfter="元/小时"
placeholder="0.00"
value={markupRatio}
onChange={(val) => setMarkupRatio(val ?? 0)}
addonAfter="比例"
placeholder="0"
/>
</div>
{markupYuan > 0 && targetAccount && (
{targetAccount && (
<div style={{ marginTop: 8, padding: '8px 12px', background: '#f6ffed', borderRadius: 4, border: '1px solid #b7eb8f', fontSize: 12 }}>
原租金 {formatMoney(targetAccount.price_cent)}/时 ➔ 分销前台标价为{' '}
主站比例 {targetAccount.base_ratio || '-'} → 分站{' '}
{((targetAccount.base_ratio || 0) + markupRatio).toFixed(1)},标价{' '}
<strong style={{ color: '#cf1322' }}>
¥{(((targetAccount.price_cent ?? 0) + Math.round(markupYuan * 100)) / 100).toFixed(2)}
{formatMoney(targetAccount.site_price_cent || targetAccount.price_cent)}
</strong>
/时
(保存后按新比例重算)
</div>
)}
</Space>
+1 -1
View File
@@ -275,7 +275,7 @@ export default function Orders({ kind }: { kind: 'all' | 'sold' | 'receive' }) {
render: (_: unknown, row: ReceivedListing) => (
<div>
<Typography.Text strong style={{ color: '#cf1322' }}>
{formatMoney(row.price_cent)}/时
{formatMoney(row.price_cent)}
</Typography.Text>
<div style={{ fontSize: 12, color: '#888' }}>
押金: {formatMoney(row.deposit_amount_cent)}
+7 -7
View File
@@ -80,16 +80,16 @@ export default function Rules() {
title: '比例',
render: (_: unknown, r: Rule) => {
const parts: string[] = []
if (r.distributor_receive_ratio > 0) parts.push(`分销收 ${r.distributor_receive_ratio}`)
if (r.platform_sell_ratio > 0) parts.push(`主站卖 ${r.platform_sell_ratio}`)
if (r.distributor_sell_ratio > 0) parts.push(`分销卖 ${r.distributor_sell_ratio}`)
if (r.platform_receive_ratio > 0) parts.push(`主站收 ${r.platform_receive_ratio}`)
if (r.distributor_a_ratio > 0) parts.push(`分销A ${r.distributor_a_ratio}`)
if (r.distributor_b_ratio > 0) parts.push(`分销B ${r.distributor_b_ratio}`)
if (r.distributor_receive_ratio > 0) parts.push(`分销收 ${r.distributor_receive_ratio}%`)
if (r.platform_sell_ratio > 0) parts.push(`主站卖 ${r.platform_sell_ratio}%`)
if (r.distributor_sell_ratio > 0) parts.push(`分销卖 ${r.distributor_sell_ratio}%`)
if (r.platform_receive_ratio > 0) parts.push(`主站收 ${r.platform_receive_ratio}%`)
if (r.distributor_a_ratio > 0) parts.push(`分销A ${r.distributor_a_ratio}%`)
if (r.distributor_b_ratio > 0) parts.push(`分销B ${r.distributor_b_ratio}%`)
return parts.join(' / ') || '-'
},
},
{ title: '服务费', render: (_: unknown, r: Rule) => `${r.service_fee_value} (${r.service_fee_unit})` },
{ title: '服务费', render: (_: unknown, r: Rule) => `${r.service_fee_value}%` },
{ title: '手续费', dataIndex: 'payment_fee_bps', render: (v: number) => `${v / 100}%` },
{ title: '税费', render: (_: unknown, r: Rule) => (r.tax_enabled ? `${r.tax_rate_bps / 100}%` : '关闭') },
{ title: '生效时间', dataIndex: 'effective_from' },
+13 -13
View File
@@ -43,9 +43,9 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
form.resetFields()
form.setFieldsValue({
member_type: 'operator',
distributor_receive_ratio: 3.5,
platform_sell_ratio: 4.0,
service_fee_value: 2.5,
distributor_receive_ratio: 35,
platform_sell_ratio: 40,
service_fee_value: 25,
display_account_enabled: true,
tax_enabled: false,
tax_rate_bps: 100,
@@ -237,28 +237,28 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
<Typography.Title level={5}>3. 分销比例与佣金规则初始化</Typography.Title>
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
<Form.Item
label="分销商收号分成比例 (%)"
label="分销商收号分成 (%)"
name="distributor_receive_ratio"
tooltip="分销模式下分销商收号收益率"
tooltip="卖出后利润的 35%"
style={{ flex: 1 }}
>
<InputNumber min={0} max={100} step={0.1} style={{ width: '100%' }} />
<InputNumber min={0} max={100} step={1} style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label="平台卖号分成比例 (%)"
label="平台卖号分成 (%)"
name="platform_sell_ratio"
tooltip="平台卖号提取分成率"
tooltip="卖出后利润的 40%"
style={{ flex: 1 }}
>
<InputNumber min={0} max={100} step={0.1} style={{ width: '100%' }} />
<InputNumber min={0} max={100} step={1} style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label="单笔服务费 (元)"
label="平台服务费 (%)"
name="service_fee_value"
tooltip="按单扣除的固定服务费"
tooltip="卖出后利润的 25%"
style={{ flex: 1 }}
>
<InputNumber min={0} step={0.5} style={{ width: '100%' }} />
<InputNumber min={0} max={100} step={1} style={{ width: '100%' }} />
</Form.Item>
</Space>
@@ -313,7 +313,7 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
<Descriptions.Item label="初始规则版本">
收号分成: {successResult.rule.distributor_receive_ratio}% · 卖号分成:{' '}
{successResult.rule.platform_sell_ratio}% · 服务费:{' '}
¥{successResult.rule.service_fee_value}
{successResult.rule.service_fee_value}%
</Descriptions.Item>
</Descriptions>
<div style={{ marginTop: 16, textAlign: 'right' }}>
+1 -1
View File
@@ -390,7 +390,7 @@ export default function Storefront() {
<div>
<div style={{ color: '#ef4444', fontSize: 18, fontWeight: 700, lineHeight: 1 }}>
{formatMoney(item.price_cent)}
<span style={{ fontSize: 12, color: '#94a3b8', fontWeight: 400 }}> /时</span>
</div>
<div style={{ color: '#64748b', fontSize: 11, marginTop: 4 }}>
押金:{formatMoney(item.deposit_amount_cent)}