import { useMemo, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import {
Alert,
App,
Button,
Col,
Form,
Input,
InputNumber,
Modal,
Row,
Select,
Space,
Switch,
Tag,
Typography,
type TableColumnsType,
} from 'antd'
import {
EditOutlined,
ExportOutlined,
EyeOutlined,
ReloadOutlined,
ShopOutlined,
} from '@ant-design/icons'
import { fetchAdminDistributionSites, updateAdminDistributionSite } from '@/features/admin/api'
import { AdminPageHeader } from '@/features/admin/components/AdminPageHeader'
import { DataTable } from '@/features/admin/components/DataTable'
import { DetailDrawer, type DetailSection } from '@/features/admin/components/DetailDrawer'
import { FilterBar, FilterField } from '@/features/admin/components/FilterBar'
import { StatusTag } from '@/features/admin/components/StatusTag'
import {
announcementModeMap,
distributionMemberTypeMap,
distributionSiteStatusMap,
distributionSiteStatusOptions,
readStatusMeta,
} from '@/features/admin/labels'
import type {
AdminDistributionSite,
AdminDistributionSiteUpdatePayload,
} from '@/features/admin/types'
import { useAdminSessionStore } from '@/stores/adminSession'
import { formatDateTime, formatText } from '@/shared/utils/format'
interface SiteFormValues {
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
}
function formatBps(bps?: number | null) {
if (bps == null) return '—'
return `${(bps / 100).toFixed(2)}%`
}
function hasJson(value?: string) {
const text = value?.trim()
return Boolean(text && text !== '{}' && text !== '[]' && text !== 'null')
}
function siteUrl(domain?: string) {
const text = domain?.trim()
if (!text) return ''
return /^https?:\/\//.test(text) ? text : `https://${text}`
}
function adminName(site: AdminDistributionSite) {
if (site.admin_nickname || site.admin_username) {
return site.admin_nickname || site.admin_username || ''
}
return site.admin_user_id != null ? `管理员 #${site.admin_user_id}` : '未绑定站长'
}
function buildSections(site: AdminDistributionSite): DetailSection[] {
const brandFields = [
{ key: 'logo', label: 'Logo', value: site.logo_url },
{ key: 'favicon', label: 'Favicon', value: site.favicon_url },
{ key: 'qrcode', label: '发布二维码', value: site.listing_qrcode_url },
].filter(field => field.value)
return [
{
key: 'overview',
title: '站点概览',
icon: ,
fields: [
{ key: 'name', label: '站点名称', value: site.site_name },
{ key: 'code', label: '站点标识', value: {site.site_code} },
{
key: 'domain',
label: '专属域名',
value: site.domain ? (
{site.domain}
) : (
'—'
),
},
{
key: 'status',
label: '站点状态',
value: ,
},
{
key: 'display_account',
label: '展示账号',
value: site.display_account_enabled ? '开启' : '关闭',
},
{
key: 'announcement_mode',
label: '公告模式',
value: announcementModeMap[site.announcement_mode] ?? formatText(site.announcement_mode),
},
{ key: 'created', label: '创建时间', value: formatDateTime(site.created_at) },
{ key: 'updated', label: '更新时间', value: formatDateTime(site.updated_at) },
],
},
{
key: 'manager',
title: '站长与成员',
fields: [
{
key: 'admin',
label: '绑定站长',
value: (
{adminName(site)}
{site.admin_user_id != null ? `ID ${site.admin_user_id}` : '—'}
),
},
{
key: 'member_type',
label: '成员角色',
value: site.member_type
? distributionMemberTypeMap[site.member_type] ?? site.member_type
: '平台全量可见',
},
{
key: 'support_group',
label: '默认客服群',
value:
site.default_support_group_id != null
? `群 #${site.default_support_group_id}`
: '未绑定',
},
{
key: 'counts',
label: '用户 / 下单用户',
value: `${site.user_count} / ${site.order_user_count}`,
},
],
},
{
key: 'settlement',
title: '分成与结算',
fields: [
{
key: 'ratio_delta',
label: '收号比例系数',
value: {site.receive_ratio_delta} 个点,
},
{
key: 'cost_delta',
label: '收号成本系数',
value: {site.receive_cost_delta} 个点,
},
{
key: 'hold_days',
label: '结算冷却',
value: `${site.settlement_hold_days} 天`,
},
{
key: 'tax',
label: '税费',
value: site.tax_enabled ? formatBps(site.tax_rate_bps) : '未启用',
},
],
},
{
key: 'assets',
title: '品牌与页面配置',
fields: [
...(brandFields.length > 0
? brandFields.map(field => ({
key: field.key,
label: field.label,
value: (
{field.value}
),
}))
: [
{
key: 'brand_empty',
label: '品牌素材',
value: '沿用主站品牌素材',
},
]),
{
key: 'configs',
label: '页面配置',
span: 2,
value: (
轮播
公告
页脚
回收入口
合作反馈
卖场文案
),
},
],
},
]
}
function buildColumns(
onOpen: (site: AdminDistributionSite) => void,
onEdit: (site: AdminDistributionSite) => void,
canManage: boolean
): TableColumnsType {
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 (
}
width={760}
onCancel={closeEditor}
maskClosable={!saving}
closable={!saving}
destroyOnHidden
footer={
}
>
{editing ? (
) : null}
)
}