refactor(distribution): 重写分销迁移并落地利润百分比分成与货架比例
将 000064-000078 合并为 064/065/066,默认规则改为收号 35%、卖出 40%、服务费 25%。 货架调价改为增加分站 1 元兑换哈夫币比例。sql-guard 仅保护 main 已发布迁移。
This commit is contained in:
@@ -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 },
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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' }}>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user