feat(distribution): 分销站点管理支持完整 CRUD、安全删除保护与管理员密码重置
- 后端支持分销站点编辑(域名唯一性校验、收号压价点位及税率更新) - 后端支持分销站点安全删除校验(阻断存在业务订单、财务流水、结算批次或钱包余额的站点删除;对干净站点连带清理分成规则、展示账号绑定、支付通道配置及专有站长账号) - 后端支持站长账号展示与超级管理员重置站长密码 - 主管理后台与分销后台前端全面支持筛选搜索、编辑模态窗、状态快捷切换、站长密码重置与安全删除确认 - 补充后端 CRUD、删除保护与密码重置覆盖率测试用例
This commit is contained in:
@@ -130,6 +130,10 @@ export interface Site {
|
||||
listing_qrcode_url?: string
|
||||
receive_ratio_delta?: number
|
||||
receive_cost_delta?: number
|
||||
admin_user_id?: number
|
||||
admin_username?: string
|
||||
admin_nickname?: string
|
||||
member_type?: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
@@ -341,9 +345,35 @@ export async function listSites(): Promise<Site[]> {
|
||||
|
||||
export async function updateSite(
|
||||
siteID: number,
|
||||
payload: Partial<Pick<Site, 'receive_ratio_delta' | 'receive_cost_delta'>>
|
||||
payload: Partial<
|
||||
Pick<
|
||||
Site,
|
||||
| 'site_name'
|
||||
| 'domain'
|
||||
| 'status'
|
||||
| 'display_account_enabled'
|
||||
| 'tax_enabled'
|
||||
| 'tax_rate_bps'
|
||||
| 'settlement_hold_days'
|
||||
| 'listing_qrcode_url'
|
||||
| 'receive_ratio_delta'
|
||||
| 'receive_cost_delta'
|
||||
>
|
||||
>
|
||||
): Promise<Site> {
|
||||
const resp = await api.put<ApiResponse<Site>>(`/distribution/sites/${siteID}`, payload)
|
||||
const resp = await api.put<ApiResponse<Site>>(`/admin/distribution/sites/${siteID}`, payload)
|
||||
return extract(resp)
|
||||
}
|
||||
|
||||
export async function deleteSite(siteID: number): Promise<{ deleted: boolean }> {
|
||||
const resp = await api.delete<ApiResponse<{ deleted: boolean }>>(`/admin/distribution/sites/${siteID}`)
|
||||
return extract(resp)
|
||||
}
|
||||
|
||||
export async function resetSiteAdminPassword(siteID: number, newPassword: string): Promise<{ message: string }> {
|
||||
const resp = await api.put<ApiResponse<{ message: string }>>(`/admin/distribution/sites/${siteID}/admin-password`, {
|
||||
new_password: newPassword,
|
||||
})
|
||||
return extract(resp)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useState, useMemo } from 'react'
|
||||
import {
|
||||
Card,
|
||||
Table,
|
||||
@@ -12,12 +12,34 @@ import {
|
||||
InputNumber,
|
||||
Select,
|
||||
Switch,
|
||||
Radio,
|
||||
message,
|
||||
Divider,
|
||||
Descriptions,
|
||||
Popconfirm,
|
||||
Tooltip,
|
||||
} from 'antd'
|
||||
import { PlusOutlined, ShopOutlined, CopyOutlined } from '@ant-design/icons'
|
||||
import { listSites, onboardSite, fetchDistributionSiteDefaults, type Site, type OnboardSitePayload, type OnboardSiteResult } from '../api'
|
||||
import {
|
||||
PlusOutlined,
|
||||
ShopOutlined,
|
||||
CopyOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
KeyOutlined,
|
||||
SearchOutlined,
|
||||
ReloadOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import {
|
||||
listSites,
|
||||
onboardSite,
|
||||
updateSite,
|
||||
deleteSite,
|
||||
resetSiteAdminPassword,
|
||||
fetchDistributionSiteDefaults,
|
||||
type Site,
|
||||
type OnboardSitePayload,
|
||||
type OnboardSiteResult,
|
||||
} from '../api'
|
||||
|
||||
export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean }) {
|
||||
const [items, setItems] = useState<Site[]>([])
|
||||
@@ -28,6 +50,22 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
|
||||
const [defaultSettlementHoldDays, setDefaultSettlementHoldDays] = useState(7)
|
||||
const [form] = Form.useForm()
|
||||
|
||||
// 筛选检索
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [statusFilter, setStatusFilter] = useState<string>('')
|
||||
|
||||
// 编辑弹窗
|
||||
const [editModalOpen, setEditModalOpen] = useState(false)
|
||||
const [editSubmitting, setEditSubmitting] = useState(false)
|
||||
const [editingSite, setEditingSite] = useState<Site | null>(null)
|
||||
const [editForm] = Form.useForm()
|
||||
|
||||
// 重置密码弹窗
|
||||
const [resetPwdModalOpen, setResetPwdModalOpen] = useState(false)
|
||||
const [resetPwdSubmitting, setResetPwdSubmitting] = useState(false)
|
||||
const [resetPwdSite, setResetPwdSite] = useState<Site | null>(null)
|
||||
const [resetPwdForm] = Form.useForm()
|
||||
|
||||
const loadData = () => {
|
||||
setLoading(true)
|
||||
listSites()
|
||||
@@ -50,6 +88,25 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
|
||||
})
|
||||
}, [])
|
||||
|
||||
const filteredItems = useMemo(() => {
|
||||
return items.filter((site) => {
|
||||
if (statusFilter && site.status !== statusFilter) {
|
||||
return false
|
||||
}
|
||||
if (searchQuery.trim()) {
|
||||
const q = searchQuery.trim().toLowerCase()
|
||||
const matchName = site.site_name?.toLowerCase().includes(q)
|
||||
const matchCode = site.site_code?.toLowerCase().includes(q)
|
||||
const matchDomain = site.domain?.toLowerCase().includes(q)
|
||||
const matchAdmin = site.admin_username?.toLowerCase().includes(q)
|
||||
if (!matchName && !matchCode && !matchDomain && !matchAdmin) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}, [items, searchQuery, statusFilter])
|
||||
|
||||
const handleOpenModal = () => {
|
||||
form.resetFields()
|
||||
form.setFieldsValue({
|
||||
@@ -91,6 +148,90 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenEdit = (site: Site) => {
|
||||
setEditingSite(site)
|
||||
editForm.resetFields()
|
||||
editForm.setFieldsValue({
|
||||
site_name: site.site_name,
|
||||
domain: site.domain,
|
||||
status: site.status || 'active',
|
||||
display_account_enabled: site.display_account_enabled,
|
||||
settlement_hold_days: site.settlement_hold_days ?? defaultSettlementHoldDays,
|
||||
receive_cost_delta: Number(site.receive_cost_delta || 0),
|
||||
tax_enabled: site.tax_enabled,
|
||||
tax_rate_bps: site.tax_rate_bps ?? 100,
|
||||
})
|
||||
setEditModalOpen(true)
|
||||
}
|
||||
|
||||
const handleEditSubmit = async (values: any) => {
|
||||
if (!editingSite) return
|
||||
setEditSubmitting(true)
|
||||
try {
|
||||
await updateSite(editingSite.id, {
|
||||
site_name: values.site_name.trim(),
|
||||
domain: values.domain.trim(),
|
||||
status: values.status,
|
||||
display_account_enabled: values.display_account_enabled,
|
||||
settlement_hold_days: Number(values.settlement_hold_days),
|
||||
receive_cost_delta: Number(values.receive_cost_delta),
|
||||
tax_enabled: values.tax_enabled,
|
||||
tax_rate_bps: values.tax_enabled ? values.tax_rate_bps : 0,
|
||||
})
|
||||
message.success(`【${values.site_name}】分销站点更新成功`)
|
||||
setEditModalOpen(false)
|
||||
loadData()
|
||||
} catch (err: any) {
|
||||
const msg = err?.response?.data?.message || '更新分销站点失败'
|
||||
message.error(msg)
|
||||
} finally {
|
||||
setEditSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleStatus = async (site: Site) => {
|
||||
const isCurrentlyActive = site.status === 'active'
|
||||
const newStatus = isCurrentlyActive ? 'disabled' : 'active'
|
||||
const actionText = isCurrentlyActive ? '停用' : '启用'
|
||||
try {
|
||||
await updateSite(site.id, { status: newStatus })
|
||||
message.success(`分销站点【${site.site_name}】已${actionText}`)
|
||||
loadData()
|
||||
} catch (err: any) {
|
||||
message.error(err?.response?.data?.message || `${actionText}失败`)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteSite = async (site: Site) => {
|
||||
try {
|
||||
await deleteSite(site.id)
|
||||
message.success(`分销站点【${site.site_name}】已删除`)
|
||||
loadData()
|
||||
} catch (err: any) {
|
||||
message.error(err?.response?.data?.message || '删除分销站点失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenResetPwd = (site: Site) => {
|
||||
setResetPwdSite(site)
|
||||
resetPwdForm.resetFields()
|
||||
setResetPwdModalOpen(true)
|
||||
}
|
||||
|
||||
const handleResetPwdSubmit = async (values: any) => {
|
||||
if (!resetPwdSite) return
|
||||
setResetPwdSubmitting(true)
|
||||
try {
|
||||
await resetSiteAdminPassword(resetPwdSite.id, values.new_password)
|
||||
message.success(`【${resetPwdSite.site_name}】管理员密码已成功重置`)
|
||||
setResetPwdModalOpen(false)
|
||||
} catch (err: any) {
|
||||
message.error(err?.response?.data?.message || '密码重置失败')
|
||||
} finally {
|
||||
setResetPwdSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Space direction="vertical" className="page" size="large" style={{ width: '100%' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
@@ -99,28 +240,65 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
|
||||
分销网站管理
|
||||
</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
管理全平台合作分销站点、域名配置与分销商管理员账号
|
||||
管理全平台合作分销站点、域名配置、结算规则与分销商管理员账号
|
||||
</Typography.Text>
|
||||
</div>
|
||||
{isSuperadmin && (
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleOpenModal}>
|
||||
开通分销商
|
||||
一键开通分销商
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
{/* 筛选检索区 */}
|
||||
<div style={{ display: 'flex', gap: 12, marginBottom: 16, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
<Input
|
||||
placeholder="搜索站点名称 / 标识 / 域名 / 管理员"
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
allowClear
|
||||
style={{ width: 280 }}
|
||||
/>
|
||||
<Select
|
||||
placeholder="运行状态"
|
||||
value={statusFilter}
|
||||
onChange={setStatusFilter}
|
||||
style={{ width: 130 }}
|
||||
options={[
|
||||
{ label: '全部状态', value: '' },
|
||||
{ label: '正常运营', value: 'active' },
|
||||
{ label: '已停用', value: 'disabled' },
|
||||
]}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setSearchQuery('')
|
||||
setStatusFilter('')
|
||||
}}
|
||||
>
|
||||
重置筛选
|
||||
</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={loadData}>
|
||||
刷新
|
||||
</Button>
|
||||
<div style={{ marginLeft: 'auto', color: '#666', fontSize: 13 }}>
|
||||
共 <strong>{filteredItems.length}</strong> 个分销站点
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
rowKey="id"
|
||||
dataSource={items}
|
||||
dataSource={filteredItems}
|
||||
loading={loading}
|
||||
pagination={false}
|
||||
scroll={{ x: 950 }}
|
||||
scroll={{ x: 1100 }}
|
||||
columns={[
|
||||
{
|
||||
title: '站点名称',
|
||||
dataIndex: 'site_name',
|
||||
width: 220,
|
||||
width: 180,
|
||||
render: (v: string) => (
|
||||
<Space>
|
||||
<ShopOutlined style={{ color: '#1677ff' }} />
|
||||
@@ -131,61 +309,249 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
|
||||
{
|
||||
title: '站点标识',
|
||||
dataIndex: 'site_code',
|
||||
width: 140,
|
||||
width: 120,
|
||||
render: (v: string) => <Tag color="blue">{v}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '绑定域名',
|
||||
dataIndex: 'domain',
|
||||
width: 220,
|
||||
width: 200,
|
||||
render: (v: string) => (
|
||||
<Typography.Text copyable={{ text: v }} className="mono-code" style={{ fontSize: 13 }}>
|
||||
{v}
|
||||
</Typography.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '分销管理员',
|
||||
width: 140,
|
||||
render: (_: unknown, row: Site) =>
|
||||
row.admin_username ? (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Tag color="cyan">{row.admin_username}</Tag>
|
||||
{row.admin_nickname && (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 11 }}>
|
||||
({row.admin_nickname})
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Space>
|
||||
) : (
|
||||
<span style={{ color: '#999' }}>未关联</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '运行状态',
|
||||
dataIndex: 'status',
|
||||
width: 110,
|
||||
render: (v: string) => (
|
||||
<Tag color={v === 'active' ? 'green' : 'default'}>
|
||||
{v === 'active' ? '正常运营' : v}
|
||||
</Tag>
|
||||
),
|
||||
render: (v: string, row: Site) =>
|
||||
isSuperadmin ? (
|
||||
<Tooltip title={v === 'active' ? '点击停用站点' : '点击启用站点'}>
|
||||
<Switch
|
||||
checked={v === 'active'}
|
||||
checkedChildren="正常"
|
||||
unCheckedChildren="停用"
|
||||
onChange={() => handleToggleStatus(row)}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tag color={v === 'active' ? 'green' : 'default'}>
|
||||
{v === 'active' ? '正常' : '已停用'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '账号展示',
|
||||
dataIndex: 'display_account_enabled',
|
||||
width: 100,
|
||||
width: 90,
|
||||
render: (v: boolean) => (v ? <Tag color="green">开启</Tag> : <Tag>关闭</Tag>),
|
||||
},
|
||||
{
|
||||
title: '税费设定',
|
||||
width: 110,
|
||||
width: 100,
|
||||
render: (_: unknown, row: Site) =>
|
||||
row.tax_enabled ? <Tag color="orange">{row.tax_rate_bps / 100}%</Tag> : <Tag>不征税</Tag>,
|
||||
row.tax_enabled ? <Tag color="orange">{row.tax_rate_bps / 100}%</Tag> : <Tag>免税</Tag>,
|
||||
},
|
||||
{
|
||||
title: '结算冷却',
|
||||
width: 90,
|
||||
render: (_: unknown, row: Site) =>
|
||||
row.settlement_hold_days && row.settlement_hold_days > 0 ? (
|
||||
<Tag color="blue">{row.settlement_hold_days}天</Tag>
|
||||
) : (
|
||||
<Tag color="green">立即</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '收号压价',
|
||||
width: 120,
|
||||
width: 110,
|
||||
render: (_: unknown, row: Site) =>
|
||||
Number(row.receive_cost_delta || 0) > 0 ? (
|
||||
<Tag color="gold">低 {Number(row.receive_cost_delta).toFixed(1)} 点</Tag>
|
||||
) : (
|
||||
<Tag>与主站一致</Tag>
|
||||
<Tag>同主站</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 140,
|
||||
width: 120,
|
||||
render: (v: string) => (v ? new Date(v).toLocaleDateString() : '-'),
|
||||
},
|
||||
...(isSuperadmin
|
||||
? [
|
||||
{
|
||||
title: '操作',
|
||||
width: 220,
|
||||
fixed: 'right' as const,
|
||||
render: (_: unknown, row: Site) => (
|
||||
<Space size="small">
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleOpenEdit(row)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<KeyOutlined />}
|
||||
onClick={() => handleOpenResetPwd(row)}
|
||||
>
|
||||
重置密码
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="确认删除此分销站点?"
|
||||
description="此操作将清理该站点及其规则与专属账号。若已产生订单则无法删除。"
|
||||
onConfirm={() => handleDeleteSite(row)}
|
||||
okText="确认删除"
|
||||
cancelText="取消"
|
||||
okButtonProps={{ danger: true }}
|
||||
>
|
||||
<Button type="link" size="small" danger icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 编辑分销站点 Modal */}
|
||||
<Modal
|
||||
title={editingSite ? `编辑分销站点: ${editingSite.site_name}` : '编辑分销站点'}
|
||||
open={editModalOpen}
|
||||
onCancel={() => setEditModalOpen(false)}
|
||||
footer={null}
|
||||
width={600}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={editForm} layout="vertical" onFinish={handleEditSubmit} style={{ marginTop: 16 }}>
|
||||
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
|
||||
<Form.Item label="站点唯一标识" style={{ flex: 1 }}>
|
||||
<Tag color="blue" style={{ fontSize: 13, padding: '4px 8px' }}>
|
||||
{editingSite?.site_code}
|
||||
</Tag>
|
||||
</Form.Item>
|
||||
<Form.Item label="管理员账号" style={{ flex: 1 }}>
|
||||
<Tag style={{ fontSize: 13, padding: '4px 8px' }}>
|
||||
{editingSite?.admin_username || '未关联'}
|
||||
</Tag>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
|
||||
<Form.Item
|
||||
label="分销站点名称"
|
||||
name="site_name"
|
||||
rules={[{ required: true, message: '请输入分销站点名称' }]}
|
||||
>
|
||||
<Input placeholder="例如:广州游戏分销站" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="绑定独立域名"
|
||||
name="domain"
|
||||
rules={[{ required: true, message: '请输入绑定专属域名' }]}
|
||||
>
|
||||
<Input placeholder="例如:gz.hao858.com" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="运行状态" name="status">
|
||||
<Radio.Group>
|
||||
<Radio value="active">正常运营</Radio>
|
||||
<Radio value="disabled">暂停服务 (已停用)</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
|
||||
<Form.Item label="展示主站账号" name="display_account_enabled" valuePropName="checked" style={{ flex: 1 }}>
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭" />
|
||||
</Form.Item>
|
||||
<Form.Item label="是否征税" name="tax_enabled" valuePropName="checked" style={{ flex: 1 }}>
|
||||
<Switch checkedChildren="征税" unCheckedChildren="免税" />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
|
||||
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
|
||||
<Form.Item label="结算冷却期 (天)" name="settlement_hold_days" style={{ flex: 1 }}>
|
||||
<InputNumber min={0} max={365} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item label="收号压价 (点)" name="receive_cost_delta" style={{ flex: 1 }}>
|
||||
<InputNumber min={0} max={20} step={0.1} precision={1} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, marginTop: 24 }}>
|
||||
<Button onClick={() => setEditModalOpen(false)}>取消</Button>
|
||||
<Button type="primary" htmlType="submit" loading={editSubmitting}>
|
||||
保存设置
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 重置管理员密码 Modal */}
|
||||
<Modal
|
||||
title={`重置管理员密码: ${resetPwdSite?.site_name}`}
|
||||
open={resetPwdModalOpen}
|
||||
onCancel={() => setResetPwdModalOpen(false)}
|
||||
footer={null}
|
||||
width={450}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={resetPwdForm} layout="vertical" onFinish={handleResetPwdSubmit} style={{ marginTop: 16 }}>
|
||||
<Descriptions column={1} size="small" bordered style={{ marginBottom: 16 }}>
|
||||
<Descriptions.Item label="站点名称">{resetPwdSite?.site_name}</Descriptions.Item>
|
||||
<Descriptions.Item label="管理员账号">
|
||||
<Tag color="cyan">{resetPwdSite?.admin_username || '专属管理员'}</Tag>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<Form.Item
|
||||
label="新登录密码"
|
||||
name="new_password"
|
||||
rules={[
|
||||
{ required: true, message: '请输入新密码' },
|
||||
{ min: 6, message: '密码长度至少6位' },
|
||||
]}
|
||||
>
|
||||
<Input.Password placeholder="至少6位新登录密码" />
|
||||
</Form.Item>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, marginTop: 24 }}>
|
||||
<Button onClick={() => setResetPwdModalOpen(false)}>取消</Button>
|
||||
<Button type="primary" htmlType="submit" loading={resetPwdSubmitting}>
|
||||
确认重置
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 开通分销商 Modal */}
|
||||
<Modal
|
||||
title="开通分销商 (一键初始化站点与账号)"
|
||||
@@ -255,47 +621,58 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
|
||||
</Space>
|
||||
|
||||
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
|
||||
<Form.Item label="管理员姓名/昵称" name="admin_nickname" style={{ flex: 1 }}>
|
||||
<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' },
|
||||
]}
|
||||
/>
|
||||
<Select>
|
||||
<Select.Option value="operator">分销运营 (operator)</Select.Option>
|
||||
<Select.Option value="finance">分销财务 (finance)</Select.Option>
|
||||
<Select.Option value="customer_service">分销客服 (customer_service)</Select.Option>
|
||||
</Select>
|
||||
</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.Title level={5}>3. 结算冷却期与展示配置</Typography.Title>
|
||||
<Typography.Paragraph type="secondary" style={{ fontSize: 13, marginBottom: 12 }}>
|
||||
开通后四种模式均按默认生效:收号 35% / 卖出 40% / 服务费 25%(自卖为 75% + 25%)。之后可在「分销规则」页单独调整。
|
||||
</Typography.Paragraph>
|
||||
|
||||
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
|
||||
<Form.Item label="结算冷却期 (天)" name="settlement_hold_days" style={{ flex: 1 }}>
|
||||
<InputNumber min={0} max={365} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="结算冷却期 (天)"
|
||||
name="settlement_hold_days"
|
||||
tooltip="订单完成后进入冷却期,期满自动释放到可提现余额;0 表示完成即释放"
|
||||
label="展示主站账号"
|
||||
name="display_account_enabled"
|
||||
valuePropName="checked"
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<InputNumber min={0} max={365} step={1} style={{ width: '100%' }} />
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭" />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
|
||||
<Space direction="horizontal" size="large" style={{ marginTop: 8 }}>
|
||||
<Form.Item
|
||||
label="允许展示主站账号"
|
||||
name="display_account_enabled"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch checkedChildren="开" unCheckedChildren="关" />
|
||||
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
|
||||
<Form.Item label="是否征税" name="tax_enabled" valuePropName="checked" style={{ flex: 1 }}>
|
||||
<Switch checkedChildren="征税" unCheckedChildren="不征税" />
|
||||
</Form.Item>
|
||||
<Form.Item label="是否征税" name="tax_enabled" valuePropName="checked">
|
||||
<Switch checkedChildren="征税" unCheckedChildren="免税" />
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prev, curr) => prev.tax_enabled !== curr.tax_enabled}
|
||||
>
|
||||
{({ getFieldValue }) =>
|
||||
getFieldValue('tax_enabled') ? (
|
||||
<Form.Item
|
||||
label="综合税率 (基点,100 = 1%)"
|
||||
name="tax_rate_bps"
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<InputNumber min={0} max={5000} step={50} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
) : null
|
||||
}
|
||||
</Form.Item>
|
||||
</Space>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user