import { useEffect, useMemo, useState } from 'react'; import { BarChart3, DollarSign, FileCheck2, ShieldCheck, } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import { Breadcrumb, Button, Modal, MoneyText, Table, Tag, type TableColumn, } from '@/components/ui'; import { Chart } from '@/components/ui/Chart'; import { adminApi, type DashboardResponse, type SendQualityResponse } from '@/api/adminApi'; import { createDualAxisBarLineOption, createLineOption } from '@/theme/chartOptions'; import { formatAmount, moneyUnitsToYuan } from '@/utils/currency'; type EnterpriseSpendRank = { id: string; enterprise: string; todaySpend: number; balanceStatus: '充足' | '紧张' | '欠费'; availableBalance: number; }; const balanceTone = { 充足: 'success', 紧张: 'warning', 欠费: 'danger', } as const; function formatCurrency(value: number) { return formatAmount(value); } function formatCount(value: number) { return value.toLocaleString('zh-CN'); } export function AdminHome() { const navigate = useNavigate(); const [dashboard, setDashboard] = useState(null); const [quality, setQuality] = useState(null); const [error, setError] = useState(''); const [selectedEnterprise, setSelectedEnterprise] = useState(null); useEffect(() => { Promise.all([adminApi.getDashboard(), adminApi.getSendQuality()]) .then(([nextDashboard, nextQuality]) => { setDashboard(nextDashboard); setQuality(nextQuality); }) .catch((err) => { setError(err instanceof Error ? err.message : '运营看板加载失败'); setDashboard(null); }); }, []); const enterpriseSpendRanks = useMemo(() => { return (dashboard?.enterpriseSpendRanks ?? []).map((account) => { const todaySpend = moneyUnitsToYuan(account.todaySpendCents); const availableBalance = moneyUnitsToYuan(account.balanceCents + account.creditCents); return { id: account.tenantId, enterprise: account.tenantName, todaySpend, availableBalance, balanceStatus: (availableBalance <= 0 ? '欠费' : availableBalance < 100 ? '紧张' : '充足') as EnterpriseSpendRank['balanceStatus'], }; }); }, [dashboard]); const totalSend = dashboard?.today.sent ?? 0; const averageSuccessRate = dashboard?.today.successRate ?? 0; const todaySpend = moneyUnitsToYuan(dashboard?.today.spendCents); const activeSignatureCount = new Set(quality?.signatures.map((item) => item.signatureId) ?? []).size; const downstreamAlertCount = dashboard?.downstreamDeliverySummary?.alertCount ?? 0; const pendingAudits = dashboard?.pendingAudits ?? { enterpriseCertifications: 0, smsAudits: 0, templates: 0, signatures: 0, drainageInfos: 0, total: 0 }; const sendTrendOption = useMemo( () => createLineOption({ labels: dashboard?.hourlySendTrend.map((item) => item.label) ?? [], series: [ { name: '提交总条数', data: dashboard?.hourlySendTrend.map((item) => item.submittedCount) ?? [] }, { name: '成功条数', data: dashboard?.hourlySendTrend.map((item) => item.successCount) ?? [] }, ], }), [dashboard], ); const auditSpeedOption = useMemo( () => createDualAxisBarLineOption({ labels: dashboard?.auditProcessingSpeed.map((item) => item.label) ?? [], bar: { name: '审核数量', data: dashboard?.auditProcessingSpeed.map((item) => item.count) ?? [], }, line: { name: '平均处理时长(分钟)', data: dashboard?.auditProcessingSpeed.map((item) => ( item.averageProcessingMs == null ? null : Number((item.averageProcessingMs / 60_000).toFixed(1)) )) ?? [], }, }), [dashboard], ); const enterpriseColumns: Array> = [ { key: 'rank', title: '排名', width: '72px', render: (_record, index) => index + 1 }, { key: 'enterprise', title: '企业名称', render: (record) => (
{record.enterprise}

{record.id}

), }, { key: 'todaySpend', title: '今日消费(元)', align: 'right', render: (record) => ¥{formatCurrency(record.todaySpend)} }, { key: 'availableBalance', title: '可用余额', align: 'right', render: (record) => formatCount(record.availableBalance) }, { key: 'balanceStatus', title: '余额状态', render: (record) => {record.balanceStatus} }, { key: 'actions', title: '操作', align: 'right', render: (record) => ( ), }, ]; return (

按业务口径查看平台发送、签名、消费和审核情况。

今日发送总量 {formatCount(totalSend)} 条 来自真实短信记录聚合
总体成功率 {averageSuccessRate.toFixed(1)}% delivered / 今日总量
今日消费金额 ¥{formatCurrency(todaySpend)} 来自今日消息金额聚合
今日活跃签名 {activeSignatureCount} 当天有真实发送记录的签名
{error ?
{error}
: null}

今日发送趋势

按上海时区逐小时展示业务短信提交总条数和最终成功条数。

审核处理速度

展示今日各项已处理审核数量,以及从提交到审核完成的平均时长。

今日企业消费排行

来自真实账户、充值和消息金额聚合。

运营状态

当日关键流程状态汇总。

平均等待 {dashboard?.taskCount ?? 0} 任务 真实批量任务总数。
下游投递告警 {downstreamAlertCount} 条 积压过久或近期失败。
)} onClose={() => setSelectedEnterprise(null)} open={Boolean(selectedEnterprise)} title={(

企业消费详情

{selectedEnterprise?.id}

)} > {selectedEnterprise ? (
企业名称 {selectedEnterprise.enterprise}
企业ID {selectedEnterprise.id}
余额状态 {selectedEnterprise.balanceStatus}
今日消费 ¥{formatCurrency(selectedEnterprise.todaySpend)}
可用余额 {formatCount(selectedEnterprise.availableBalance)}
) : null}
); }