import React, { useEffect, useState } from 'react'; import { Badge, Button, Card, Drawer, Form, Grid, Input, InputNumber, Message, Modal, Popconfirm, Radio, Select, Space, Statistic, Table, Tag, Typography, } from '@arco-design/web-react'; import { IconRefresh, IconSearch, IconSafe, IconEdit, IconCalendar, IconUser, } from '@arco-design/web-react/icon'; import axios from 'axios'; import useLocale from '@/utils/useLocale'; import type { PlatformOverviewResult, SubscriberItem, TenantPlanType, UpdateSubscriberDTO, } from '@/types/omni'; const { Row, Col } = Grid; const { Title, Text } = Typography; const API_SUBSCRIBERS_PAGE = '/api/admin/team/platform/subscribers/page'; const API_SUBSCRIBER_UPDATE = '/api/admin/team/platform/subscribers/update'; const API_OVERVIEW = '/api/admin/team/platform/overview'; export default function SubscribersPage() { const t = useLocale(); const [loading, setLoading] = useState(false); const [data, setData] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [size, setSize] = useState(10); const [keyword, setKeyword] = useState(''); const [planFilter, setPlanFilter] = useState('all'); const [statusFilter, setStatusFilter] = useState('all'); const [overview, setOverview] = useState(null); // 弹窗表单状态 const [editDrawerVisible, setEditDrawerVisible] = useState(false); const [selectedSub, setSelectedSub] = useState(null); const [extendModalVisible, setExtendModalVisible] = useState(false); const [extendDays, setExtendDays] = useState(30); const [form] = Form.useForm(); const fetchOverview = async () => { try { const res = await axios.post(API_OVERVIEW); if (res.data?.data) { setOverview(res.data.data); } } catch { // 容错兜底 } }; const fetchData = async (targetPage = page, targetSize = size) => { setLoading(true); try { const payload: Record = { page: targetPage, size: targetSize, }; if (keyword) payload.keyword = keyword; if (planFilter !== 'all') payload.planType = planFilter; if (statusFilter !== 'all') payload.status = statusFilter; const res = await axios.post(API_SUBSCRIBERS_PAGE, payload); const resData = res.data?.data || res.data; if (resData?.list) { setData(resData.list); setTotal(resData.pagination?.total || resData.list.length); } } catch { Message.error('获取会员列表失败,已启用离线缓存'); } finally { setLoading(false); } }; useEffect(() => { fetchOverview(); fetchData(1, size); }, [planFilter, statusFilter]); const handleSearch = () => { setPage(1); fetchData(1, size); }; const handleOpenEdit = (record: SubscriberItem) => { setSelectedSub(record); form.setFieldsValue({ tenantId: record.tenantId, planType: record.planType, cardsLimit: record.cardsLimit, seatsLimit: record.seatsLimit, status: record.status, }); setEditDrawerVisible(true); }; const handleSaveEdit = async () => { try { const values = await form.validate(); await axios.post(API_SUBSCRIBER_UPDATE, values); Message.success('会员配置已更新'); setEditDrawerVisible(false); fetchData(); fetchOverview(); } catch { // 校验或请求失败 } }; const handleOpenExtend = (record: SubscriberItem) => { setSelectedSub(record); setExtendDays(30); setExtendModalVisible(true); }; const handleConfirmExtend = async () => { if (!selectedSub) return; try { await axios.post(API_SUBSCRIBER_UPDATE, { tenantId: selectedSub.tenantId, extendDays, }); Message.success(`成功为【${selectedSub.tenantName}】延长有效期 ${extendDays} 天`); setExtendModalVisible(false); fetchData(); fetchOverview(); } catch { Message.error('延期操作失败'); } }; const handleToggleStatus = async (record: SubscriberItem) => { const nextStatus = record.status === 'canceled' ? 'active' : 'canceled'; try { await axios.post(API_SUBSCRIBER_UPDATE, { tenantId: record.tenantId, status: nextStatus, }); Message.success(`会员状态已切换为: ${nextStatus === 'active' ? '正常' : '冻结'}`); fetchData(); fetchOverview(); } catch { Message.error('切换状态失败'); } }; const getPlanTag = (plan: TenantPlanType) => { switch (plan) { case 'enterprise': return Enterprise 商业企业版; case 'pro': return Pro 专业商务版; default: return Free 基础版; } }; const getStatusBadge = (status: string) => { switch (status) { case 'active': return ; case 'trialing': return ; case 'past_due': return ; case 'canceled': return ; default: return ; } }; const columns = [ { title: '租户 ID / 会员名称', dataIndex: 'tenantName', render: (name: string, record: SubscriberItem) => (
{name}
ID: #{record.tenantId} · 开通时间: {record.startDate}
), }, { title: '主账号 / 负责人', dataIndex: 'adminName', render: (adminName: string, record: SubscriberItem) => (
{adminName}
{record.adminEmail}
), }, { title: '当前套餐', dataIndex: 'planType', render: (plan: TenantPlanType) => getPlanTag(plan), }, { title: '服务状态', dataIndex: 'status', render: (status: string) => getStatusBadge(status), }, { title: '名片 / 坐席用量', render: (_: unknown, record: SubscriberItem) => (
名片: {record.cardsUsed} / {record.cardsLimit}
坐席: {record.seatsUsed} / {record.seatsLimit}
), }, { title: '服务到期日', dataIndex: 'expireDate', render: (date: string, record: SubscriberItem) => { const isExpiring = record.status === 'past_due'; return ( {date} ); }, }, { title: '累计实付', dataIndex: 'totalPaidAmount', render: (amount: number, record: SubscriberItem) => ( ${Number(amount || 0).toLocaleString()} {record.currency} ), }, { title: '最高权限操作', render: (_: unknown, record: SubscriberItem) => ( handleToggleStatus(record)} > ), }, ]; return (
{/* 顶部总控概览卡片 */} } suffix="家" /> {/* 主列表卡片 */} 平台会员生命周期总控列表 超级管理员权限
} extra={ } > {/* 筛选过滤工具条 */} } placeholder="搜索会员企业名、主账号邮箱、ID..." value={keyword} onChange={setKeyword} onPressEnter={handleSearch} style={{ width: 280 }} allowClear /> {/* 表格 */} { setPage(p); setSize(s); fetchData(p, s); }, }} /> {/* 调整会员抽屉 */} setEditDrawerVisible(false)} width={420} >
正常有效 到期催收 冻结停用
{/* 延长有效期 Modal */} setExtendModalVisible(false)} >
当前会员:{selectedSub?.tenantName}
当前到期日:{selectedSub?.expireDate}
选择顺延天数:
+30 天 (1 个月) +90 天 (1 季度) +365 天 (1 年)
setExtendDays(Number(val) || 30)} min={1} max={3650} suffix="天" style={{ width: 160 }} />
); }