fix: connect remaining sms pages to real backend
This commit is contained in:
@@ -1,49 +1,42 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { BarChart3 } from 'lucide-react';
|
||||
import { adminApi, type DashboardResponse } from '@/api/adminApi';
|
||||
import { Breadcrumb, Button, Chart, Tag } from '@/components/ui';
|
||||
import { auditTrend, channelShare, customerGrowth, hourlySendTrend } from '@/mock/chartData';
|
||||
import { adminService } from '@/mock';
|
||||
import { createBarOption, createLineOption, createPieOption } from '@/theme/chartOptions';
|
||||
import { createBarOption, createPieOption } from '@/theme/chartOptions';
|
||||
|
||||
export function AdminAnalyticsPage() {
|
||||
const overview = adminService.getOverview();
|
||||
const customers = adminService.getCustomers();
|
||||
const [dashboard, setDashboard] = useState<DashboardResponse | null>(null);
|
||||
const [tenantStats, setTenantStats] = useState<Array<{ tenantId: string | null; _count: { _all: number }; _sum: { amountCents?: number | null } }>>([]);
|
||||
const [channelStats, setChannelStats] = useState<Array<{ channelId: string | null; _count: { _all: number }; _sum: { amountCents?: number | null } }>>([]);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const sendTrendOption = useMemo(
|
||||
() => createLineOption({
|
||||
labels: hourlySendTrend.map((item) => item.time),
|
||||
series: [
|
||||
{ name: '提交量', data: hourlySendTrend.map((item) => item.sent) },
|
||||
{ name: '成功量', data: hourlySendTrend.map((item) => item.success) },
|
||||
],
|
||||
}),
|
||||
[],
|
||||
);
|
||||
function loadData() {
|
||||
Promise.all([
|
||||
adminApi.getDashboard(),
|
||||
adminApi.listStatistics({ groupBy: 'tenantId' }),
|
||||
adminApi.listStatistics({ groupBy: 'channelId' }),
|
||||
])
|
||||
.then(([dashboardData, tenantData, channelData]) => {
|
||||
setDashboard(dashboardData);
|
||||
setTenantStats((Array.isArray(tenantData) ? tenantData : []) as Array<{ tenantId: string | null; _count: { _all: number }; _sum: { amountCents?: number | null } }>);
|
||||
setChannelStats((Array.isArray(channelData) ? channelData : []) as Array<{ channelId: string | null; _count: { _all: number }; _sum: { amountCents?: number | null } }>);
|
||||
setError('');
|
||||
})
|
||||
.catch((failure: Error) => setError(failure.message || '统计数据加载失败'));
|
||||
}
|
||||
|
||||
const auditTrendOption = useMemo(
|
||||
() => createBarOption({
|
||||
labels: auditTrend.map((item) => item.day),
|
||||
series: [
|
||||
{ name: '通过', data: auditTrend.map((item) => item.approved) },
|
||||
{ name: '驳回', data: auditTrend.map((item) => item.rejected) },
|
||||
{ name: '待审', data: auditTrend.map((item) => item.pending) },
|
||||
],
|
||||
}),
|
||||
[],
|
||||
);
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const customerGrowthOption = useMemo(
|
||||
() => createLineOption({
|
||||
labels: customerGrowth.map((item) => item.month),
|
||||
series: [
|
||||
{ name: '活跃客户', data: customerGrowth.map((item) => item.active) },
|
||||
{ name: '新增客户', data: customerGrowth.map((item) => item.new) },
|
||||
],
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const tenantOption = useMemo(() => createBarOption({
|
||||
labels: tenantStats.map((item) => item.tenantId ?? '未绑定企业'),
|
||||
series: [{ name: '发送量', data: tenantStats.map((item) => item._count._all) }],
|
||||
}), [tenantStats]);
|
||||
|
||||
const channelShareOption = useMemo(() => createPieOption({ data: channelShare }), []);
|
||||
const channelOption = useMemo(() => createPieOption({
|
||||
data: channelStats.map((item) => ({ name: item.channelId ?? '未分配通道', value: item._count._all })),
|
||||
}), [channelStats]);
|
||||
|
||||
return (
|
||||
<section className="page-stack">
|
||||
@@ -51,24 +44,25 @@ export function AdminAnalyticsPage() {
|
||||
<div>
|
||||
<Breadcrumb items={['数据统计']} />
|
||||
</div>
|
||||
<Button icon={<BarChart3 size={16} />} variant="ghost">导出报表</Button>
|
||||
<Button icon={<BarChart3 size={16} />} onClick={loadData} variant="ghost">刷新统计</Button>
|
||||
</div>
|
||||
{error ? <p className="form-error">{error}</p> : null}
|
||||
|
||||
<div className="dashboard-grid">
|
||||
<div className="surface metric-card">
|
||||
<span>提交量</span>
|
||||
<strong>{overview.todaySubmissions}</strong>
|
||||
<small>今日审核提交</small>
|
||||
<span>今日发送量</span>
|
||||
<strong>{dashboard?.today.sent.toLocaleString('zh-CN') ?? 0}</strong>
|
||||
<small>真实消息记录</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>活跃客户</span>
|
||||
<strong>{customers.filter((item) => item.status === 'active').length}</strong>
|
||||
<small>当前平台客户</small>
|
||||
<span>成功率</span>
|
||||
<strong>{dashboard?.today.successRate ?? 0}%</strong>
|
||||
<small>今日已回执</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>通道健康度</span>
|
||||
<strong>{overview.channelHealth}%</strong>
|
||||
<small>近 24 小时</small>
|
||||
<span>待审核</span>
|
||||
<strong>{dashboard?.pendingAuditCount ?? 0}</strong>
|
||||
<small>企业/签名/模板/风控</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,45 +70,22 @@ export function AdminAnalyticsPage() {
|
||||
<div className="surface chart-card">
|
||||
<div className="section-heading">
|
||||
<div>
|
||||
<h2>发送趋势</h2>
|
||||
<p className="muted">按 3 小时聚合短信提交与成功量。</p>
|
||||
<h2>企业发送排行</h2>
|
||||
<p className="muted">按真实短信消息记录聚合。</p>
|
||||
</div>
|
||||
<Tag tone="info">今日</Tag>
|
||||
<Tag tone="info">企业</Tag>
|
||||
</div>
|
||||
<Chart height={320} option={sendTrendOption} />
|
||||
<Chart height={320} option={tenantOption} />
|
||||
</div>
|
||||
<div className="surface chart-card">
|
||||
<div className="section-heading">
|
||||
<div>
|
||||
<h2>通道占比</h2>
|
||||
<p className="muted">按提交量估算。</p>
|
||||
<p className="muted">按通道消息记录聚合。</p>
|
||||
</div>
|
||||
<Tag tone="accent">通道</Tag>
|
||||
</div>
|
||||
<Chart height={320} option={channelShareOption} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="chart-grid">
|
||||
<div className="surface chart-card">
|
||||
<div className="section-heading">
|
||||
<div>
|
||||
<h2>审核趋势</h2>
|
||||
<p className="muted">近 7 天审核处理情况。</p>
|
||||
</div>
|
||||
<Tag tone="warning">审核</Tag>
|
||||
</div>
|
||||
<Chart height={320} option={auditTrendOption} />
|
||||
</div>
|
||||
<div className="surface chart-card">
|
||||
<div className="section-heading">
|
||||
<div>
|
||||
<h2>客户增长</h2>
|
||||
<p className="muted">平台客户增长与活跃趋势。</p>
|
||||
</div>
|
||||
<Tag tone="success">客户</Tag>
|
||||
</div>
|
||||
<Chart height={320} option={customerGrowthOption} />
|
||||
<Chart height={320} option={channelOption} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user