Files
hfb_sys/distribution-admin/src/pages/Rules.tsx
T
yml2213andCursor 768902f2f7 feat(distribution): 补齐退款加价回退、本站自卖与开通默认规则
退款时压缩卖方加价,人工/自动批次仅完结单可入账,号主收入进主站钱包;仲裁后重算释放并支持作废未入账 DS。开通时四种模式按默认生效,对外口径合并为 35/40/25,不再单独展示通道费。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 18:13:46 +08:00

307 lines
11 KiB
TypeScript

import { useEffect, useState, useCallback } from 'react'
import { Card, Table, Typography, Space, Select, Tag, message, Row, Col, Divider, Alert, Button } from 'antd'
import {
SafetyCertificateOutlined,
DollarCircleOutlined,
ThunderboltOutlined,
CheckCircleOutlined,
LockOutlined,
} from '@ant-design/icons'
import { listRules, listSites, updateRuleStatus, getMe, type Rule, type Site } from '../api'
import { useMember } from '../MemberContext'
const MODE_LABELS: Record<string, string> = {
two_party_distributor_receive: '分销收号模式 (主站售出 / 散户在本站上传)',
two_party_platform_receive: '分销卖号模式 (分站售出 / 主站共享号源池)',
three_party: '分销跨站协同模式 (分站A收号 + 分站B卖出)',
site_self_sell: '分站自收自卖模式',
}
export default function Rules() {
const member = useMember()
const [sites, setSites] = useState<Site[]>([])
const [siteID, setSiteID] = useState<number | undefined>(member?.site_id)
const [rules, setRules] = useState<Rule[]>([])
const [loading, setLoading] = useState(false)
const load = useCallback(async (site?: number) => {
setLoading(true)
try {
setRules(await listRules(site))
} catch {
message.error('分成规则加载失败')
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
async function init() {
let currentMember = member
if (!currentMember) {
try {
currentMember = await getMe()
} catch {
// ignore
}
}
const activeSiteID = currentMember?.site_id
if (activeSiteID) {
setSiteID(activeSiteID)
void load(activeSiteID)
}
if (currentMember?.is_superadmin) {
try {
const rows = await listSites()
setSites(rows)
if (!activeSiteID) {
void load(undefined)
}
} catch {
// ignore
}
}
}
void init()
}, [member, load])
async function toggleStatus(rule: Rule) {
const next = rule.status === 'active' ? 'disabled' : 'active'
try {
await updateRuleStatus(rule.id, next)
message.success(`规则已${next === 'active' ? '启用' : '停用'}`)
void load(siteID)
} catch {
message.error('状态更新失败')
}
}
const isSuperadmin = member?.is_superadmin
return (
<Space direction="vertical" className="page" style={{ width: '100%' }} size="middle">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<Typography.Title level={3} style={{ marginBottom: 4 }}>
{isSuperadmin ? '分成规则配置与管理' : '分销商合作协议与分成政策'}
</Typography.Title>
<Typography.Text type="secondary">
{isSuperadmin
? '主站超管可维护各交易模式的分账比例、服务费与税费政策'
: '查看当前分销站签约的收益权益、分成政策与资金结算恒等式说明'}
</Typography.Text>
</div>
{isSuperadmin && sites.length > 1 && (
<Select
allowClear
placeholder="切换分销网站"
style={{ width: 220 }}
value={siteID}
options={sites.map((site) => ({ label: `${site.site_name} (${site.site_code})`, value: site.id }))}
onChange={(value) => {
setSiteID(value)
void load(value)
}}
/>
)}
</div>
{/* 4 Core Rights Cards */}
<Card title="分销商核心权益与收益构成">
<Row gutter={[16, 16]}>
<Col xs={24} md={6}>
<div
style={{
background: '#f6ffed',
border: '1px solid #b7eb8f',
borderRadius: 8,
padding: '16px',
height: '100%',
}}
>
<Space direction="vertical" style={{ width: '100%' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<ThunderboltOutlined style={{ color: '#52c41a', fontSize: 20 }} />
<Typography.Text strong style={{ fontSize: 16, color: '#389e0d' }}>
自主加价 100% 独享
</Typography.Text>
</div>
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
分销站在主站基准价上自行上浮的全部溢价差额,<strong>100% 独立结算至商户账户</strong>,主站不参与任何分润抽成。
</Typography.Text>
</Space>
</div>
</Col>
<Col xs={24} md={6}>
<div
style={{
background: '#e6f4ff',
border: '1px solid #91caff',
borderRadius: 8,
padding: '16px',
height: '100%',
}}
>
<Space direction="vertical" style={{ width: '100%' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<DollarCircleOutlined style={{ color: '#1677ff', fontSize: 20 }} />
<Typography.Text strong style={{ fontSize: 16, color: '#0958d9' }}>
卖号分成 40%
</Typography.Text>
</div>
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
租客通过本分销站点租赁主站共享号源池账号,获得扣除号主底价后的<strong>主站提价利润池 40% 分成</strong>。
</Typography.Text>
</Space>
</div>
</Col>
<Col xs={24} md={6}>
<div
style={{
background: '#f9f0ff',
border: '1px solid #d3adf7',
borderRadius: 8,
padding: '16px',
height: '100%',
}}
>
<Space direction="vertical" style={{ width: '100%' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<SafetyCertificateOutlined style={{ color: '#722ed1', fontSize: 20 }} />
<Typography.Text strong style={{ fontSize: 16, color: '#531dab' }}>
收号分成 35%
</Typography.Text>
</div>
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
散户号主通过本站上传账号并上架,在全网产生租赁订单后,本站长期享受该账号产生的<strong>主站提价利润 35% 分成</strong>。
</Typography.Text>
</Space>
</div>
</Col>
<Col xs={24} md={6}>
<div
style={{
background: '#fffbe6',
border: '1px solid #ffe58f',
borderRadius: 8,
padding: '16px',
height: '100%',
}}
>
<Space direction="vertical" style={{ width: '100%' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<CheckCircleOutlined style={{ color: '#fa8c16', fontSize: 20 }} />
<Typography.Text strong style={{ fontSize: 16, color: '#d46b08' }}>
底价与结算保障
</Typography.Text>
</div>
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
号主本金 100% 刚兑保障。利润按收号 / 卖出 / 服务费切分,订单完成并过冷却期后系统自动释放入账至商户钱包。
</Typography.Text>
</Space>
</div>
</Col>
</Row>
</Card>
{/* Rules Table */}
<Card title="当前生效的交易分账规则版本">
<Table
rowKey="id"
loading={loading}
dataSource={rules}
pagination={false}
scroll={{ x: 950 }}
columns={[
{
title: '业务交易模式',
dataIndex: 'mode',
width: 280,
render: (v: string) => (
<div>
<strong>{MODE_LABELS[v] ?? v}</strong>
<div style={{ fontSize: 11, color: '#8c8c8c' }}>模式标识: {v}</div>
</div>
),
},
{
title: '版本号',
dataIndex: 'version',
width: 90,
render: (v: number) => <Tag color="blue">v{v}</Tag>,
},
{
title: '状态',
dataIndex: 'status',
width: 100,
render: (v: string) => (
<Tag color={v === 'active' ? 'success' : 'default'}>
{v === 'active' ? '生效中' : '已停用'}
</Tag>
),
},
{
title: '分成权益比例',
render: (_: unknown, r: Rule) => {
const parts: string[] = []
if (r.distributor_receive_ratio > 0) parts.push(`分销收号: ${r.distributor_receive_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.platform_sell_ratio > 0) parts.push(`主站卖出: ${r.platform_sell_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 (
<Space direction="vertical" size={2}>
{parts.map((p, idx) => (
<Tag color="cyan" key={idx}>
{p}
</Tag>
))}
</Space>
)
},
},
{
title: '平台服务费',
width: 110,
render: (_: unknown, r: Rule) => <span>{r.service_fee_value}%</span>,
},
{
title: '生效时间',
dataIndex: 'effective_from',
width: 160,
render: (v: string) => (
<span style={{ fontSize: 12, color: '#595959' }}>
{v ? new Date(v).toLocaleString('zh-CN', { hour12: false }) : '-'}
</span>
),
},
...(isSuperadmin
? [
{
title: '操作',
width: 100,
render: (_: unknown, r: Rule) => (
<Button
type="link"
size="small"
danger={r.status === 'active'}
onClick={() => void toggleStatus(r)}
>
{r.status === 'active' ? '停用' : '启用'}
</Button>
),
},
]
: []),
]}
/>
</Card>
</Space>
)
}