{
+ return [
+ {
+ title: '站点',
+ dataIndex: 'site_name',
+ width: 280,
+ fixed: 'left',
+ render: (_, row) => (
+
+
+ {row.site_code}
+ {row.domain || '未绑定专属域名'}
+
+ ),
+ },
+ {
+ title: '站长',
+ key: 'admin',
+ width: 190,
+ render: (_, row) => (
+
+ {adminName(row)}
+
+ {row.member_type
+ ? distributionMemberTypeMap[row.member_type] ?? row.member_type
+ : '平台账号'}
+
+
+ {row.admin_user_id != null ? `ID ${row.admin_user_id}` : '—'}
+
+
+ ),
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ width: 100,
+ render: value => ,
+ },
+ {
+ title: '用户',
+ key: 'users',
+ width: 130,
+ align: 'right',
+ render: (_, row) => (
+
+ {row.user_count}
+ 下单 {row.order_user_count}
+
+ ),
+ },
+ {
+ title: '分成 / 结算',
+ key: 'settlement',
+ width: 190,
+ render: (_, row) => (
+
+ 比例 {row.receive_ratio_delta} 点
+ 成本 {row.receive_cost_delta} 点
+
+ 冷却 {row.settlement_hold_days} 天 ·{' '}
+ {row.tax_enabled ? formatBps(row.tax_rate_bps) : '无税'}
+
+
+ ),
+ },
+ {
+ title: '创建时间',
+ dataIndex: 'created_at',
+ width: 160,
+ render: value => formatDateTime(value),
+ },
+ {
+ title: '操作',
+ key: 'actions',
+ width: 150,
+ fixed: 'right',
+ render: (_, row) => (
+
+ } onClick={() => onOpen(row)}>
+ 详情
+
+ {canManage ? (
+ } onClick={() => onEdit(row)}>
+ 编辑
+
+ ) : null}
+
+ ),
+ },
+ ]
+}
+
+export function AdminDistributionSitesPage() {
+ const { message, modal } = App.useApp()
+ const [form] = Form.useForm()
+ const [keyword, setKeyword] = useState('')
+ const [status, setStatus] = useState('')
+ const [selected, setSelected] = useState(null)
+ const [detailOpen, setDetailOpen] = useState(false)
+ const [editing, setEditing] = useState(null)
+ const [saving, setSaving] = useState(false)
+ const permissions = useAdminSessionStore(state => state.admin?.permissions)
+ const canView = Boolean(
+ permissions?.includes('*') ||
+ permissions?.includes('distribution:site_view') ||
+ permissions?.includes('qrcode:view')
+ )
+ const canManage = Boolean(
+ permissions?.includes('*') || permissions?.includes('distribution:site_manage')
+ )
+
+ const { data, isPending, isFetching, isError, error, refetch } = useQuery({
+ queryKey: ['admin-distribution-sites'],
+ queryFn: fetchAdminDistributionSites,
+ enabled: canView,
+ })
+
+ const sites = data ?? []
+
+ const filtered = useMemo(() => {
+ const text = keyword.trim().toLowerCase()
+ return sites.filter(site => {
+ if (status && site.status !== status) return false
+ if (!text) return true
+ return [site.site_name, site.site_code, site.domain, site.admin_username, site.admin_nickname]
+ .filter(Boolean)
+ .some(value => String(value).toLowerCase().includes(text))
+ })
+ }, [sites, keyword, status])
+
+ const limited = Boolean(editing?.member_type)
+ const errorText = error instanceof Error ? error.message : '请稍后重试或重新登录后台。'
+ const distributionAdminUrl = import.meta.env.VITE_DISTRIBUTION_ADMIN_URL
+ const hasActiveFilters = Boolean(keyword || status)
+
+ const openDetail = (site: AdminDistributionSite) => {
+ setSelected(site)
+ setDetailOpen(true)
+ }
+
+ const openEditor = (site: AdminDistributionSite) => {
+ setEditing(site)
+ form.setFieldsValue({
+ site_name: site.site_name,
+ domain: site.domain,
+ status: site.status === 'disabled' ? 'disabled' : 'active',
+ display_account_enabled: site.display_account_enabled,
+ listing_qrcode_url: site.listing_qrcode_url,
+ receive_ratio_delta: site.receive_ratio_delta,
+ receive_cost_delta: site.receive_cost_delta,
+ tax_enabled: site.tax_enabled,
+ tax_rate_bps: site.tax_rate_bps,
+ settlement_hold_days: site.settlement_hold_days,
+ })
+ }
+
+ const closeEditor = () => {
+ if (saving) return
+ setEditing(null)
+ }
+
+ const applyUpdated = (updated: AdminDistributionSite) => {
+ setSelected(current => (current?.id === updated.id ? updated : current))
+ }
+
+ const saveSite = async () => {
+ if (!editing) return
+ let values: SiteFormValues
+ try {
+ values = await form.validateFields()
+ } catch {
+ return
+ }
+
+ const payload: AdminDistributionSiteUpdatePayload = limited
+ ? {
+ receive_ratio_delta: Number(values.receive_ratio_delta),
+ receive_cost_delta: Number(values.receive_cost_delta),
+ }
+ : {
+ site_name: values.site_name?.trim(),
+ domain: values.domain?.trim(),
+ status: values.status,
+ display_account_enabled: values.display_account_enabled,
+ listing_qrcode_url: values.listing_qrcode_url?.trim(),
+ receive_ratio_delta: Number(values.receive_ratio_delta),
+ receive_cost_delta: Number(values.receive_cost_delta),
+ tax_enabled: values.tax_enabled,
+ tax_rate_bps: values.tax_enabled ? Number(values.tax_rate_bps) : 0,
+ settlement_hold_days: Number(values.settlement_hold_days),
+ }
+
+ modal.confirm({
+ title: `确认更新「${editing.site_name}」?`,
+ content: limited
+ ? '站长账号只能调整本站分成系数,其他站点资料不会被修改。'
+ : '站点名称、域名、分成系数与结算冷却会立即影响前台展示和后续订单结算,请确认已核对。',
+ okText: '确认更新',
+ cancelText: '再检查一下',
+ onOk: async () => {
+ setSaving(true)
+ try {
+ const updated = await updateAdminDistributionSite(editing.id, payload)
+ message.success('分销站点已更新')
+ applyUpdated(updated)
+ setEditing(null)
+ await refetch()
+ } catch (err) {
+ message.error(err instanceof Error ? err.message : '更新失败,请稍后重试')
+ } finally {
+ setSaving(false)
+ }
+ },
+ })
+ }
+
+ const toggleStatus = (site: AdminDistributionSite) => {
+ const next = site.status === 'active' ? 'disabled' : 'active'
+ const actionText = next === 'active' ? '启用' : '停用'
+ modal.confirm({
+ title: `确认${actionText}「${site.site_name}」?`,
+ content:
+ next === 'active'
+ ? '启用后站点域名与前台页面会恢复对外服务。'
+ : '停用后站点前台会停止服务,请先确认站内订单与结算已经处理完毕。',
+ okText: `确认${actionText}`,
+ okButtonProps: { danger: next === 'disabled' },
+ cancelText: '取消',
+ onOk: async () => {
+ try {
+ const updated = await updateAdminDistributionSite(site.id, { status: next })
+ message.success(`站点已${actionText}`)
+ applyUpdated(updated)
+ await refetch()
+ } catch (err) {
+ message.error(err instanceof Error ? err.message : `${actionText}失败,请稍后重试`)
+ }
+ },
+ })
+ }
+
+ const columns = useMemo(
+ () => buildColumns(openDetail, openEditor, canManage),
+ [canManage]
+ )
+
+ const detailFooter = selected ? (
+
+
+ {canManage ? (
+ } onClick={() => openEditor(selected)}>
+ 编辑站点
+
+ ) : null}
+ {canManage && !selected.member_type ? (
+
+ ) : null}
+
+ ) : null
+
+ return (
+
+
+ 共 {sites.length} 个站点
+
+ 运营中 {sites.filter(item => item.status === 'active').length}
+
+ {isFetching && !isPending ? 正在更新 : null}
+
+ }
+ extra={
+
+ {distributionAdminUrl ? (
+ }
+ href={distributionAdminUrl}
+ target="_blank"
+ rel="noreferrer"
+ >
+ 前往分销后台
+
+ ) : null}
+ }
+ onClick={() => void refetch()}
+ loading={isFetching}
+ disabled={!canView}
+ >
+ 刷新数据
+
+
+ }
+ />
+
+ {!canView ? (
+
+ ) : null}
+
+ {
+ setKeyword('')
+ setStatus('')
+ }
+ : undefined
+ }
+ loading={isFetching}
+ extra={
+
+ {filtered.length === sites.length
+ ? '显示全部站点'
+ : `筛选出 ${filtered.length} / ${sites.length} 个站点`}
+
+ }
+ >
+
+ setKeyword(event.target.value)}
+ />
+
+
+
+
+
+ {isError ? (
+ void refetch()}>
+ 重试
+
+ }
+ />
+ ) : null}
+
+
+ title={站点列表}
+ extra={
+
+ {isPending ? '加载中' : `当前 ${filtered.length} 个站点`}
+
+ }
+ rowKey="id"
+ columns={columns}
+ dataSource={filtered}
+ loading={isFetching}
+ onRowClick={openDetail}
+ emptyDescription={
+ canView
+ ? '还没有分销站点;需要开通时请前往分销后台创建站点与站长账号'
+ : '当前账号没有查看权限'
+ }
+ />
+
+ setDetailOpen(false)}
+ title={selected?.site_name ?? '分销站点'}
+ subtitle={
+ selected
+ ? `${selected.site_code} · ${selected.domain || '未绑定域名'} · ${adminName(selected)}`
+ : undefined
+ }
+ tags={
+ selected ? (
+
+
+ {selected.member_type ? (
+ {distributionMemberTypeMap[selected.member_type] ?? selected.member_type}
+ ) : (
+ 平台全量
+ )}
+
+ ) : undefined
+ }
+ sections={selected ? buildSections(selected) : []}
+ footer={detailFooter}
+ width={820}
+ emptyDescription="没有可展示的站点详情"
+ />
+
+
+
+ {limited ? '调整分成系数' : '编辑分销站点'}
+
+ {editing ? (
+
+ {editing.site_name} · {editing.site_code}
+
+ ) : null}
+
+ }
+ width={760}
+ onCancel={closeEditor}
+ maskClosable={!saving}
+ closable={!saving}
+ destroyOnHidden
+ footer={
+
+
+
+
+ }
+ >
+ {editing ? (
+
+ ) : null}
+
+
+ )
+}
diff --git a/frontend/src/features/admin/api.ts b/frontend/src/features/admin/api.ts
index 4f60031..2674b5f 100644
--- a/frontend/src/features/admin/api.ts
+++ b/frontend/src/features/admin/api.ts
@@ -12,6 +12,8 @@ import type {
AdminDispute,
AdminDisputePage,
AdminDisputeQuery,
+ AdminDistributionSite,
+ AdminDistributionSiteUpdatePayload,
AdminListing,
AdminListingPage,
AdminListingQuery,
@@ -442,3 +444,21 @@ export async function updateAdminSystemConfig(
)
return data.data
}
+
+export async function fetchAdminDistributionSites() {
+ const { data } = await adminClient.get>(
+ '/admin/distribution/sites'
+ )
+ return data.data.items ?? []
+}
+
+export async function updateAdminDistributionSite(
+ id: number | string,
+ payload: AdminDistributionSiteUpdatePayload
+) {
+ const { data } = await adminClient.put>(
+ `/admin/distribution/sites/${id}`,
+ payload
+ )
+ return data.data
+}
diff --git a/frontend/src/features/admin/labels.ts b/frontend/src/features/admin/labels.ts
index 144e7dd..1374163 100644
--- a/frontend/src/features/admin/labels.ts
+++ b/frontend/src/features/admin/labels.ts
@@ -336,3 +336,21 @@ export function systemConfigCategoryLabel(key: string) {
const category = systemConfigCategory(key)
return systemConfigCategoryMap[category] ?? category
}
+
+export const distributionSiteStatusMap: Record = {
+ active: { label: '运营中', tone: 'success' },
+ disabled: { label: '已停用', tone: 'default' },
+}
+
+export const distributionSiteStatusOptions = toOptions(distributionSiteStatusMap)
+
+export const distributionMemberTypeMap: Record = {
+ operator: '运营',
+ finance: '财务',
+ customer_service: '客服',
+}
+
+export const announcementModeMap: Record = {
+ inherit: '继承主站公告',
+ own_only: '仅本站公告',
+}
diff --git a/frontend/src/features/admin/types.ts b/frontend/src/features/admin/types.ts
index 2f1a1a9..f53bce3 100644
--- a/frontend/src/features/admin/types.ts
+++ b/frontend/src/features/admin/types.ts
@@ -622,3 +622,49 @@ export interface AdminSystemConfigUpdatePayload {
value: string
description: string
}
+
+export interface AdminDistributionSite {
+ id: number
+ site_code: string
+ site_name: string
+ domain: string
+ status: string
+ display_account_enabled: boolean
+ logo_url: string
+ favicon_url: string
+ banners_json: string
+ announcements_json: string
+ footer_info_json: string
+ aw_recycle_json: string
+ cooperation_feedback_json: string
+ storefront_text_json: string
+ announcement_mode: string
+ default_support_group_id?: number | null
+ listing_qrcode_url: string
+ receive_ratio_delta: number
+ receive_cost_delta: number
+ tax_enabled: boolean
+ tax_rate_bps: number
+ settlement_hold_days: number
+ admin_user_id?: number | null
+ admin_username?: string
+ admin_nickname?: string
+ member_type?: string
+ user_count: number
+ order_user_count: number
+ created_at: string
+ updated_at: string
+}
+
+export interface AdminDistributionSiteUpdatePayload {
+ site_name?: string
+ domain?: string
+ status?: 'active' | 'disabled'
+ display_account_enabled?: boolean
+ listing_qrcode_url?: string
+ receive_ratio_delta?: number
+ receive_cost_delta?: number
+ tax_enabled?: boolean
+ tax_rate_bps?: number
+ settlement_hold_days?: number
+}