feat: support WPS report material workbooks
This commit is contained in:
+132
-111
@@ -1,20 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
BarChart3,
|
||||
DollarSign,
|
||||
FileCheck2,
|
||||
ShieldCheck,
|
||||
} from 'lucide-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 { 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';
|
||||
@@ -70,7 +57,11 @@ export function AdminHome() {
|
||||
enterprise: account.tenantName,
|
||||
todaySpend,
|
||||
availableBalance,
|
||||
balanceStatus: (availableBalance <= 0 ? '欠费' : availableBalance < 100 ? '紧张' : '充足') as EnterpriseSpendRank['balanceStatus'],
|
||||
balanceStatus: (availableBalance <= 0
|
||||
? '欠费'
|
||||
: availableBalance < 100
|
||||
? '紧张'
|
||||
: '充足') as EnterpriseSpendRank['balanceStatus'],
|
||||
};
|
||||
});
|
||||
}, [dashboard]);
|
||||
@@ -83,33 +74,43 @@ export function AdminHome() {
|
||||
const todayProfit = moneyUnitsToYuan(dashboard?.today.profitCents);
|
||||
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 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) ?? [] },
|
||||
],
|
||||
}),
|
||||
() =>
|
||||
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))
|
||||
)) ?? [],
|
||||
},
|
||||
}),
|
||||
() =>
|
||||
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],
|
||||
);
|
||||
|
||||
@@ -125,9 +126,23 @@ export function AdminHome() {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{ key: 'todaySpend', title: '今日消费(元)', align: 'right', render: (record) => <MoneyText>¥{formatCurrency(record.todaySpend)}</MoneyText> },
|
||||
{ key: 'availableBalance', title: '可用余额', align: 'right', render: (record) => formatCount(record.availableBalance) },
|
||||
{ key: 'balanceStatus', title: '余额状态', render: (record) => <Tag tone={balanceTone[record.balanceStatus]}>{record.balanceStatus}</Tag> },
|
||||
{
|
||||
key: 'todaySpend',
|
||||
title: '今日消费(元)',
|
||||
align: 'right',
|
||||
render: (record) => <MoneyText>¥{formatCurrency(record.todaySpend)}</MoneyText>,
|
||||
},
|
||||
{
|
||||
key: 'availableBalance',
|
||||
title: '可用余额',
|
||||
align: 'right',
|
||||
render: (record) => formatCount(record.availableBalance),
|
||||
},
|
||||
{
|
||||
key: 'balanceStatus',
|
||||
title: '余额状态',
|
||||
render: (record) => <Tag tone={balanceTone[record.balanceStatus]}>{record.balanceStatus}</Tag>,
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: '操作',
|
||||
@@ -151,9 +166,7 @@ export function AdminHome() {
|
||||
<Button icon={<FileCheck2 size={16} />} onClick={() => navigate('/admin/templates')} variant="ghost">
|
||||
处理审核
|
||||
</Button>
|
||||
<Button onClick={() => navigate('/admin/monitor')}>
|
||||
查看发送监控
|
||||
</Button>
|
||||
<Button onClick={() => navigate('/admin/monitor')}>查看发送监控</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -163,15 +176,20 @@ export function AdminHome() {
|
||||
<strong>{formatCount(totalSend)} 条</strong>
|
||||
<small>来自真实短信记录聚合</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>今日消息分片数</span>
|
||||
<strong>{formatCount(dashboard?.today.segmentCount ?? 0)} 片</strong>
|
||||
<small>来自真实分片审计记录</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>总体成功率</span>
|
||||
<strong>{averageSuccessRate.toFixed(1)}%</strong>
|
||||
<small>delivered / 今日总量</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>今日消费金额</span>
|
||||
<strong>¥{formatCurrency(todaySpend)}</strong>
|
||||
<small>来自今日消息金额聚合</small>
|
||||
<span>今日到达率</span>
|
||||
<strong>{(dashboard?.today.arrivalRate ?? 0).toFixed(1)}%</strong>
|
||||
<small>到达分片 / 发送总分片</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>今日活跃签名</span>
|
||||
@@ -179,14 +197,9 @@ export function AdminHome() {
|
||||
<small>当天有真实发送记录的签名</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>今日消息分片数</span>
|
||||
<strong>{formatCount(dashboard?.today.segmentCount ?? 0)} 片</strong>
|
||||
<small>来自真实分片审计记录</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>今日到达率</span>
|
||||
<strong>{(dashboard?.today.arrivalRate ?? 0).toFixed(1)}%</strong>
|
||||
<small>到达分片 / 发送总分片</small>
|
||||
<span>今日消费金额</span>
|
||||
<strong>¥{formatCurrency(todaySpend)}</strong>
|
||||
<small>来自今日消息金额聚合</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>今日返还金额</span>
|
||||
@@ -200,12 +213,16 @@ export function AdminHome() {
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>今日利润</span>
|
||||
<strong className={todayProfit < 0 ? 'metric-card__value--danger' : undefined}>¥{formatCurrency(todayProfit)}</strong>
|
||||
<strong className={todayProfit < 0 ? 'metric-card__value--danger' : undefined}>
|
||||
¥{formatCurrency(todayProfit)}
|
||||
</strong>
|
||||
<small>计收金额 - 成功分片通道成本</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>今日利润率</span>
|
||||
<strong className={(dashboard?.today.profitRate ?? 0) < 0 ? 'metric-card__value--danger' : undefined}>{(dashboard?.today.profitRate ?? 0).toFixed(1)}%</strong>
|
||||
<strong className={(dashboard?.today.profitRate ?? 0) < 0 ? 'metric-card__value--danger' : undefined}>
|
||||
{(dashboard?.today.profitRate ?? 0).toFixed(1)}%
|
||||
</strong>
|
||||
<small>今日利润 / 今日计收金额</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -214,12 +231,12 @@ export function AdminHome() {
|
||||
<div className="chart-grid">
|
||||
<div className="surface chart-card">
|
||||
<h2>今日发送趋势</h2>
|
||||
<p className="muted">按上海时区逐小时展示业务短信提交总条数和最终成功条数。</p>
|
||||
<p className="muted">按上海时区逐小时展示业务短信提交总条数和最终成功条数。</p>
|
||||
<Chart height={300} option={sendTrendOption} />
|
||||
</div>
|
||||
<div className="surface chart-card">
|
||||
<h2>审核处理速度</h2>
|
||||
<p className="muted">展示今日各项已处理审核数量,以及从提交到审核完成的平均时长。</p>
|
||||
<p className="muted">展示今日各项已处理审核数量,以及从提交到审核完成的平均时长。</p>
|
||||
<Chart height={300} option={auditSpeedOption} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -238,73 +255,75 @@ export function AdminHome() {
|
||||
</div>
|
||||
|
||||
<div className="surface section-stack">
|
||||
<div className="section-heading">
|
||||
<div className="section-heading">
|
||||
<div>
|
||||
<h2>运营状态</h2>
|
||||
<p className="muted">当日关键流程状态汇总。</p>
|
||||
</div>
|
||||
<BarChart3 size={20} className="status-info" />
|
||||
</div>
|
||||
<div className="overview-grid overview-grid--three">
|
||||
<Button className="mini-status-card" onClick={() => navigate('/admin/enterprise-audit')} variant="ghost">
|
||||
<FileCheck2 size={22} />
|
||||
<span>企业认证待审</span>
|
||||
<strong>{pendingAudits.enterpriseCertifications} 条</strong>
|
||||
</Button>
|
||||
<Button className="mini-status-card" onClick={() => navigate('/admin/sms-audit')} variant="ghost">
|
||||
<FileCheck2 size={22} />
|
||||
<span>短信审核待审</span>
|
||||
<strong>{pendingAudits.smsAudits} 条</strong>
|
||||
</Button>
|
||||
<Button className="mini-status-card" onClick={() => navigate('/admin/templates')} variant="ghost">
|
||||
<FileCheck2 size={22} />
|
||||
<span>模板待审</span>
|
||||
<strong>{pendingAudits.templates} 条</strong>
|
||||
</Button>
|
||||
<Button className="mini-status-card" onClick={() => navigate('/admin/signatures')} variant="ghost">
|
||||
<FileCheck2 size={22} />
|
||||
<span>签名待审</span>
|
||||
<strong>{pendingAudits.signatures} 条</strong>
|
||||
</Button>
|
||||
<Button className="mini-status-card" onClick={() => navigate('/admin/drainage-audits')} variant="ghost">
|
||||
<FileCheck2 size={22} />
|
||||
<span>引流信息待审</span>
|
||||
<strong>{pendingAudits.drainageInfos} 条</strong>
|
||||
</Button>
|
||||
<div className="mini-status-card">
|
||||
<ShieldCheck size={22} />
|
||||
<div>
|
||||
<h2>运营状态</h2>
|
||||
<p className="muted">当日关键流程状态汇总。</p>
|
||||
</div>
|
||||
<BarChart3 size={20} className="status-info" />
|
||||
</div>
|
||||
<div className="overview-grid overview-grid--three">
|
||||
<Button className="mini-status-card" onClick={() => navigate('/admin/enterprise-audit')} variant="ghost">
|
||||
<FileCheck2 size={22} />
|
||||
<span>企业认证待审</span>
|
||||
<strong>{pendingAudits.enterpriseCertifications} 条</strong>
|
||||
</Button>
|
||||
<Button className="mini-status-card" onClick={() => navigate('/admin/sms-audit')} variant="ghost">
|
||||
<FileCheck2 size={22} />
|
||||
<span>短信审核待审</span>
|
||||
<strong>{pendingAudits.smsAudits} 条</strong>
|
||||
</Button>
|
||||
<Button className="mini-status-card" onClick={() => navigate('/admin/templates')} variant="ghost">
|
||||
<FileCheck2 size={22} />
|
||||
<span>模板待审</span>
|
||||
<strong>{pendingAudits.templates} 条</strong>
|
||||
</Button>
|
||||
<Button className="mini-status-card" onClick={() => navigate('/admin/signatures')} variant="ghost">
|
||||
<FileCheck2 size={22} />
|
||||
<span>签名待审</span>
|
||||
<strong>{pendingAudits.signatures} 条</strong>
|
||||
</Button>
|
||||
<Button className="mini-status-card" onClick={() => navigate('/admin/drainage-audits')} variant="ghost">
|
||||
<FileCheck2 size={22} />
|
||||
<span>引流信息待审</span>
|
||||
<strong>{pendingAudits.drainageInfos} 条</strong>
|
||||
</Button>
|
||||
<div className="mini-status-card">
|
||||
<ShieldCheck size={22} />
|
||||
<div>
|
||||
<span>平均等待</span>
|
||||
<strong>{dashboard?.taskCount ?? 0} 任务</strong>
|
||||
<small>真实批量任务总数。</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mini-status-card">
|
||||
<ShieldCheck size={22} />
|
||||
<div>
|
||||
<span>下游投递告警</span>
|
||||
<strong>{downstreamAlertCount} 条</strong>
|
||||
<small>积压过久或近期失败。</small>
|
||||
</div>
|
||||
<span>平均等待</span>
|
||||
<strong>{dashboard?.taskCount ?? 0} 任务</strong>
|
||||
<small>真实批量任务总数。</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mini-status-card">
|
||||
<ShieldCheck size={22} />
|
||||
<div>
|
||||
<span>下游投递告警</span>
|
||||
<strong>{downstreamAlertCount} 条</strong>
|
||||
<small>积压过久或近期失败。</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
footer={(
|
||||
footer={
|
||||
<>
|
||||
<Button onClick={() => setSelectedEnterprise(null)} variant="ghost">关闭</Button>
|
||||
<Button onClick={() => setSelectedEnterprise(null)} variant="ghost">
|
||||
关闭
|
||||
</Button>
|
||||
<Button onClick={() => navigate('/admin/recharge-records')}>查看充值记录</Button>
|
||||
</>
|
||||
)}
|
||||
}
|
||||
onClose={() => setSelectedEnterprise(null)}
|
||||
open={Boolean(selectedEnterprise)}
|
||||
title={(
|
||||
title={
|
||||
<div className="ui-detail-title">
|
||||
<h2>企业消费详情</h2>
|
||||
<p>{selectedEnterprise?.id}</p>
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
>
|
||||
{selectedEnterprise ? (
|
||||
<div className="ui-detail-info-grid">
|
||||
@@ -324,7 +343,9 @@ export function AdminHome() {
|
||||
</div>
|
||||
<div className="ui-detail-info-grid__item">
|
||||
<span>今日消费</span>
|
||||
<strong><MoneyText>¥{formatCurrency(selectedEnterprise.todaySpend)}</MoneyText></strong>
|
||||
<strong>
|
||||
<MoneyText>¥{formatCurrency(selectedEnterprise.todaySpend)}</MoneyText>
|
||||
</strong>
|
||||
</div>
|
||||
<div className="ui-detail-info-grid__item">
|
||||
<span>可用余额</span>
|
||||
|
||||
Reference in New Issue
Block a user