Files
hfb_sys/distribution-admin/src/pages/Sites.tsx
T
yml2213 64a4e5bb16 fix(distribution): 修正分销后台入口地址并收口主站后台隐藏路径
- 新增前端 distributionAdmin 工具:优先 VITE_DISTRIBUTION_ADMIN_URL,
  本地开发回退 5190,生产按 distribution.<主站域名> 推导
- 替换主站后台「打开独立分销后台」按钮与开通凭证中硬编码的 :5190
- 分销后台复制凭证改用 window.location.origin,去掉硬编码 http
- Caddyfile 中 /manage-7x9k2p 仅主站域名放行,分站域名统一 404
- 构建/部署链路透传 VITE_DISTRIBUTION_ADMIN_URL 与 ADMIN_BASE_PATH
2026-09-16 15:09:38 +08:00

359 lines
13 KiB
TypeScript

import { useEffect, useState } from 'react'
import {
Card,
Table,
Typography,
Space,
Tag,
Button,
Modal,
Form,
Input,
InputNumber,
Select,
Switch,
message,
Divider,
Descriptions,
} from 'antd'
import { PlusOutlined, ShopOutlined, CopyOutlined } from '@ant-design/icons'
import { listSites, onboardSite, fetchDistributionSiteDefaults, type Site, type OnboardSitePayload, type OnboardSiteResult } from '../api'
export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean }) {
const [items, setItems] = useState<Site[]>([])
const [loading, setLoading] = useState(false)
const [modalOpen, setModalOpen] = useState(false)
const [submitting, setSubmitting] = useState(false)
const [successResult, setSuccessResult] = useState<OnboardSiteResult | null>(null)
const [defaultSettlementHoldDays, setDefaultSettlementHoldDays] = useState(7)
const [form] = Form.useForm()
const loadData = () => {
setLoading(true)
listSites()
.then(setItems)
.catch(() => message.error('站点加载失败'))
.finally(() => setLoading(false))
}
useEffect(() => {
loadData()
fetchDistributionSiteDefaults()
.then((res) => {
const days = res?.default_settlement_hold_days
if (typeof days === 'number' && days >= 0 && days <= 365) {
setDefaultSettlementHoldDays(days)
}
})
.catch(() => {
// 拉取失败时保持内置默认值 7
})
}, [])
const handleOpenModal = () => {
form.resetFields()
form.setFieldsValue({
member_type: 'operator',
display_account_enabled: true,
tax_enabled: false,
tax_rate_bps: 100,
settlement_hold_days: defaultSettlementHoldDays,
})
setModalOpen(true)
}
const handleOnboardSubmit = async (values: any) => {
setSubmitting(true)
try {
const payload: OnboardSitePayload = {
site_code: values.site_code.trim(),
site_name: values.site_name.trim(),
domain: values.domain.trim(),
admin_username: values.admin_username.trim(),
admin_password: values.admin_password,
admin_nickname: values.admin_nickname?.trim() || undefined,
member_type: values.member_type,
display_account_enabled: values.display_account_enabled,
tax_enabled: values.tax_enabled,
tax_rate_bps: values.tax_enabled ? values.tax_rate_bps : 0,
settlement_hold_days: Number(values.settlement_hold_days ?? defaultSettlementHoldDays),
}
const res = await onboardSite(payload)
message.success('分销站点及分销商管理员开通成功!')
setModalOpen(false)
setSuccessResult(res)
loadData()
} catch (err: any) {
const msg = err?.response?.data?.message || '开通分销商失败,请检查站点标识或管理员用户名是否重复'
message.error(msg)
} finally {
setSubmitting(false)
}
}
return (
<Space direction="vertical" className="page" size="large" style={{ width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<Typography.Title level={3} style={{ margin: 0 }}>
分销网站管理
</Typography.Title>
<Typography.Text type="secondary">
管理全平台合作分销站点、域名配置与分销商管理员账号
</Typography.Text>
</div>
{isSuperadmin && (
<Button type="primary" icon={<PlusOutlined />} onClick={handleOpenModal}>
开通分销商
</Button>
)}
</div>
<Card>
<Table
rowKey="id"
dataSource={items}
loading={loading}
pagination={false}
scroll={{ x: 950 }}
columns={[
{
title: '站点名称',
dataIndex: 'site_name',
width: 220,
render: (v: string) => (
<Space>
<ShopOutlined style={{ color: '#1677ff' }} />
<strong>{v}</strong>
</Space>
),
},
{
title: '站点标识',
dataIndex: 'site_code',
width: 140,
render: (v: string) => <Tag color="blue">{v}</Tag>,
},
{
title: '绑定域名',
dataIndex: 'domain',
width: 220,
render: (v: string) => (
<Typography.Text copyable={{ text: v }} className="mono-code" style={{ fontSize: 13 }}>
{v}
</Typography.Text>
),
},
{
title: '运行状态',
dataIndex: 'status',
width: 110,
render: (v: string) => (
<Tag color={v === 'active' ? 'green' : 'default'}>
{v === 'active' ? '正常运营' : v}
</Tag>
),
},
{
title: '账号展示',
dataIndex: 'display_account_enabled',
width: 100,
render: (v: boolean) => (v ? <Tag color="green">开启</Tag> : <Tag>关闭</Tag>),
},
{
title: '税费设定',
width: 110,
render: (_: unknown, row: Site) =>
row.tax_enabled ? <Tag color="orange">{row.tax_rate_bps / 100}%</Tag> : <Tag>不征税</Tag>,
},
{
title: '收号下调',
width: 120,
render: (_: unknown, row: Site) =>
Number(row.receive_ratio_delta || 0) > 0 ? (
<Tag color="gold">-{Number(row.receive_ratio_delta).toFixed(1)} 点</Tag>
) : (
<Tag>与主站一致</Tag>
),
},
{
title: '创建时间',
dataIndex: 'created_at',
width: 140,
render: (v: string) => (v ? new Date(v).toLocaleDateString() : '-'),
},
]}
/>
</Card>
{/* 开通分销商 Modal */}
<Modal
title="开通分销商 (一键初始化站点与账号)"
open={modalOpen}
onCancel={() => setModalOpen(false)}
footer={null}
width={680}
destroyOnClose
>
<Form form={form} layout="vertical" onFinish={handleOnboardSubmit} style={{ marginTop: 16 }}>
<Typography.Title level={5} style={{ marginTop: 0 }}>
1. 站点基础信息
</Typography.Title>
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
<Form.Item
label="站点名称"
name="site_name"
rules={[{ required: true, message: '请输入站点名称' }]}
style={{ flex: 1 }}
>
<Input placeholder="例如:广州游戏分销站" />
</Form.Item>
<Form.Item
label="站点唯一标识"
name="site_code"
rules={[
{ required: true, message: '请输入唯一代码' },
{ pattern: /^[a-zA-Z0-9_-]+$/, message: '仅支持字母、数字与下划线' },
]}
style={{ flex: 1 }}
>
<Input placeholder="例如:gz_dist" />
</Form.Item>
</Space>
<Form.Item
label="分销专属域名"
name="domain"
rules={[{ required: true, message: '请输入分销绑定域名' }]}
>
<Input placeholder="例如:gz.distribution.hao858.com" />
</Form.Item>
<Divider style={{ margin: '16px 0' }} />
<Typography.Title level={5}>2. 分销商管理员账号</Typography.Title>
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
<Form.Item
label="登录用户名"
name="admin_username"
rules={[{ required: true, message: '请输入管理员账号' }]}
style={{ flex: 1 }}
>
<Input placeholder="例如:gz_admin" />
</Form.Item>
<Form.Item
label="初始登录密码"
name="admin_password"
rules={[
{ required: true, message: '请输入登录密码' },
{ min: 6, message: '密码至少6位' },
]}
style={{ flex: 1 }}
>
<Input.Password placeholder="至少6位" />
</Form.Item>
</Space>
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
<Form.Item label="管理员姓名/昵称" name="admin_nickname" style={{ flex: 1 }}>
<Input placeholder="例如:张站长" />
</Form.Item>
<Form.Item label="初始角色权限" name="member_type" style={{ flex: 1 }}>
<Select
options={[
{ label: '分销运营 (operator)', value: 'operator' },
{ label: '分销财务 (finance)', value: 'finance' },
{ label: '客服人员 (customer_service)', value: 'customer_service' },
]}
/>
</Form.Item>
</Space>
<Divider style={{ margin: '16px 0' }} />
<Typography.Title level={5}>3. 结算冷却期与展示</Typography.Title>
<Typography.Paragraph type="secondary" style={{ marginTop: -8 }}>
开通后四种分成模式均按默认生效:收号 35% / 卖出 40% / 服务费 25%(本站自卖 75% + 25%)。之后可在规则页调整。
</Typography.Paragraph>
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
<Form.Item
label="结算冷却期 (天)"
name="settlement_hold_days"
tooltip="订单完成后进入冷却期,期满自动释放到可提现余额;0 表示完成即释放"
style={{ flex: 1 }}
>
<InputNumber min={0} max={365} step={1} style={{ width: '100%' }} />
</Form.Item>
</Space>
<Space direction="horizontal" size="large" style={{ marginTop: 8 }}>
<Form.Item
label="允许展示主站账号"
name="display_account_enabled"
valuePropName="checked"
>
<Switch checkedChildren="开" unCheckedChildren="关" />
</Form.Item>
<Form.Item label="是否征税" name="tax_enabled" valuePropName="checked">
<Switch checkedChildren="征税" unCheckedChildren="免税" />
</Form.Item>
</Space>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, marginTop: 24 }}>
<Button onClick={() => setModalOpen(false)}>取消</Button>
<Button type="primary" htmlType="submit" loading={submitting}>
立即开通并生成分销商
</Button>
</div>
</Form>
</Modal>
{/* 开通成功信息 Modal */}
<Modal
title="分销商开通成功"
open={Boolean(successResult)}
onOk={() => setSuccessResult(null)}
cancelButtonProps={{ style: { display: 'none' } }}
width={560}
>
{successResult && (
<div>
<Typography.Paragraph type="secondary">
分销商站点与管理账号已成功创建,请妥善保管并交付给分销合作伙伴:
</Typography.Paragraph>
<Descriptions bordered column={1} size="small">
<Descriptions.Item label="分销站点">
{successResult.site.site_name} ({successResult.site.site_code})
</Descriptions.Item>
<Descriptions.Item label="分销域名">
{successResult.site.domain}
</Descriptions.Item>
<Descriptions.Item label="分销后台登录账号">
<strong>{successResult.member.username}</strong>
</Descriptions.Item>
<Descriptions.Item label="所属角色">
{successResult.member.member_type}
</Descriptions.Item>
<Descriptions.Item label="初始分成规则">
四种模式均已按默认生效:收号 35% / 卖出 40% / 服务费 25%(本站自卖 75% + 25%),可到规则页调整
</Descriptions.Item>
</Descriptions>
<div style={{ marginTop: 16, textAlign: 'right' }}>
<Button
icon={<CopyOutlined />}
onClick={() => {
const text = `分销站点: ${successResult.site.site_name}\n后台地址: ${window.location.origin}/login\n用户名: ${successResult.member.username}`
navigator.clipboard?.writeText(text)
message.success('已复制分销商开通信息到剪贴板')
}}
>
复制开通信息
</Button>
</div>
</div>
)}
</Modal>
</Space>
)
}