Initial CMPP frontend prototype
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
BadgeCheck,
|
||||
BellRing,
|
||||
FileText,
|
||||
PenLine,
|
||||
Plus,
|
||||
ReceiptText,
|
||||
Send,
|
||||
WalletCards,
|
||||
} from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button, Chart, Table, Tag, type TableColumn } from '@/components/ui';
|
||||
import { channelShare, hourlySendTrend } from '@/mock/chartData';
|
||||
import { clientService, type RecentMessage, type TemplateStatus } from '@/mock';
|
||||
import { createLineOption, createPieOption } from '@/theme/chartOptions';
|
||||
|
||||
const statusLabelMap: Record<RecentMessage['status'], string> = {
|
||||
success: '发送完成',
|
||||
warning: '排队中',
|
||||
info: '发送中',
|
||||
danger: '发送失败',
|
||||
};
|
||||
|
||||
const templateStatusLabelMap: Record<TemplateStatus, string> = {
|
||||
draft: '草稿',
|
||||
pending: '待审核',
|
||||
approved: '已通过',
|
||||
rejected: '已驳回',
|
||||
};
|
||||
|
||||
const columns: Array<TableColumn<RecentMessage>> = [
|
||||
{ key: 'id', title: '批次编号', render: (record) => record.id },
|
||||
{ key: 'scene', title: '发送场景', render: (record) => record.scene },
|
||||
{ key: 'count', title: '发送量', render: (record) => `${record.count.toLocaleString('zh-CN')} 条` },
|
||||
{ key: 'channel', title: '通道', render: (record) => record.channel },
|
||||
{ key: 'createdAt', title: '创建时间', render: (record) => record.createdAt },
|
||||
{ key: 'status', title: '状态', render: (record) => <Tag tone={record.status}>{statusLabelMap[record.status]}</Tag> },
|
||||
];
|
||||
|
||||
export function ClientHome() {
|
||||
const navigate = useNavigate();
|
||||
const overview = clientService.getOverview();
|
||||
const recentMessages = clientService.getRecentMessages();
|
||||
const templates = clientService.getTemplates();
|
||||
const signatures = clientService.getSignatures();
|
||||
const invoices = clientService.getInvoices();
|
||||
|
||||
const approvedTemplates = templates.filter((item) => item.status === 'approved').length;
|
||||
const approvedSignatures = signatures.filter((item) => item.status === 'approved').length;
|
||||
const pendingTemplates = templates.filter((item) => item.status === 'pending').length;
|
||||
const pendingSignatures = signatures.filter((item) => item.status === 'pending').length;
|
||||
const latestInvoice = invoices[0];
|
||||
const quotaTotal = overview.availableMessages + overview.todaySent;
|
||||
const quotaPercent = Math.round((overview.availableMessages / quotaTotal) * 100);
|
||||
|
||||
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) },
|
||||
],
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const channelShareOption = useMemo(() => createPieOption({ data: channelShare }), []);
|
||||
|
||||
return (
|
||||
<section className="page-stack">
|
||||
<div className="overview-hero">
|
||||
<div>
|
||||
<p className="eyebrow">客户端概览</p>
|
||||
<h1>短信服务工作台</h1>
|
||||
<p className="muted">查看账户余量、审核进度、发送趋势和常用业务入口。</p>
|
||||
</div>
|
||||
<div className="page-actions">
|
||||
<Button icon={<Plus size={16} />} onClick={() => navigate('/client/templates')} variant="ghost">
|
||||
新建模板
|
||||
</Button>
|
||||
<Button icon={<Send size={16} />} onClick={() => navigate('/client/send')}>发送短信</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="dashboard-grid">
|
||||
<div className="surface metric-card metric-card--featured">
|
||||
<span>账户可用短信</span>
|
||||
<strong>{overview.availableMessages.toLocaleString('zh-CN')}</strong>
|
||||
<small>今日已发送 {overview.todaySent.toLocaleString('zh-CN')} 条</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>发送成功率</span>
|
||||
<strong>{overview.successRate}%</strong>
|
||||
<small>近 24 小时实时统计</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>待审核事项</span>
|
||||
<strong>{pendingTemplates + pendingSignatures}</strong>
|
||||
<small>模板 {pendingTemplates},签名 {pendingSignatures}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overview-grid">
|
||||
<div className="surface section-stack">
|
||||
<div className="section-heading">
|
||||
<div>
|
||||
<h2>快捷操作</h2>
|
||||
<p className="muted">覆盖概览页常用入口。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="quick-grid">
|
||||
<button className="quick-action" onClick={() => navigate('/client/send')} type="button">
|
||||
<Send size={20} />
|
||||
<span>短信发送</span>
|
||||
<small>创建发送批次</small>
|
||||
</button>
|
||||
<button className="quick-action" onClick={() => navigate('/client/templates')} type="button">
|
||||
<FileText size={20} />
|
||||
<span>模板管理</span>
|
||||
<small>{approvedTemplates} 个可用模板</small>
|
||||
</button>
|
||||
<button className="quick-action" onClick={() => navigate('/client/signatures')} type="button">
|
||||
<PenLine size={20} />
|
||||
<span>签名管理</span>
|
||||
<small>{approvedSignatures} 个可用签名</small>
|
||||
</button>
|
||||
<button className="quick-action" onClick={() => navigate('/client/billing')} type="button">
|
||||
<WalletCards size={20} />
|
||||
<span>账户充值</span>
|
||||
<small>查看套餐余量</small>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="surface section-stack">
|
||||
<div className="section-heading">
|
||||
<div>
|
||||
<h2>账户状态</h2>
|
||||
<p className="muted">企业认证与资源用量。</p>
|
||||
</div>
|
||||
<Tag tone="success">已认证</Tag>
|
||||
</div>
|
||||
<div className="summary-list">
|
||||
<div>
|
||||
<span>企业主体</span>
|
||||
<strong>上海云舟科技有限公司</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>默认签名</span>
|
||||
<strong>【云舟科技】</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>最近充值</span>
|
||||
<strong>{latestInvoice.title}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="progress-heading">
|
||||
<span>短信余量</span>
|
||||
<strong>{quotaPercent}%</strong>
|
||||
</div>
|
||||
<div className="progress-track">
|
||||
<span style={{ width: `${quotaPercent}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overview-grid overview-grid--three">
|
||||
<div className="surface mini-status-card">
|
||||
<BadgeCheck size={22} />
|
||||
<div>
|
||||
<span>模板状态</span>
|
||||
<strong>{approvedTemplates} 已通过</strong>
|
||||
<small>{templates.map((item) => templateStatusLabelMap[item.status]).join(' / ')}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="surface mini-status-card">
|
||||
<PenLine size={22} />
|
||||
<div>
|
||||
<span>签名状态</span>
|
||||
<strong>{approvedSignatures} 已通过</strong>
|
||||
<small>待审核 {pendingSignatures},需处理 {signatures.filter((item) => item.status === 'rejected').length}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="surface mini-status-card">
|
||||
<BellRing size={22} />
|
||||
<div>
|
||||
<span>服务提醒</span>
|
||||
<strong>通道运行正常</strong>
|
||||
<small>备用通道有排队批次,请关注发送详情。</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="chart-grid">
|
||||
<div className="surface chart-card">
|
||||
<h2>今日发送趋势</h2>
|
||||
<p className="muted">按 3 小时聚合提交量和成功量。</p>
|
||||
<Chart height={300} option={sendTrendOption} />
|
||||
</div>
|
||||
<div className="surface chart-card">
|
||||
<h2>通道占比</h2>
|
||||
<p className="muted">按今日提交量估算。</p>
|
||||
<Chart height={300} option={channelShareOption} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="surface section-stack">
|
||||
<div className="section-heading">
|
||||
<div>
|
||||
<h2>最近发送批次</h2>
|
||||
<p className="muted">跟踪发送状态、通道和创建时间。</p>
|
||||
</div>
|
||||
<Button icon={<ReceiptText size={16} />} onClick={() => navigate('/client/send-detail')} variant="ghost">
|
||||
查看全部
|
||||
</Button>
|
||||
</div>
|
||||
<Table columns={columns} data={recentMessages} rowKey="id" />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user