Initial CMPP frontend prototype

This commit is contained in:
hectorzhao
2026-06-30 16:09:46 +08:00
commit 2f3c274a30
98 changed files with 25255 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
+50
View File
@@ -0,0 +1,50 @@
import type { AdminOverview, AuditItem, Channel, Customer } from '@/mock/types';
export const adminOverview: AdminOverview = {
todaySubmissions: 128,
pendingAudits: 36,
averageWaitMinutes: 12,
channelHealth: 99.2,
};
export const auditItems: AuditItem[] = [
{
id: 'AUD-2401',
customer: '上海云舟科技',
type: '模板',
content: '验证码 ${code}5 分钟内有效。',
risk: 'low',
status: 'pending',
submittedAt: '2026-06-16 09:12',
},
{
id: 'AUD-2402',
customer: '杭州星澜商贸',
type: '签名',
content: '星澜会员中心',
risk: 'medium',
status: 'pending',
submittedAt: '2026-06-16 09:04',
},
{
id: 'AUD-2403',
customer: '深圳北辰出行',
type: '模板',
content: '您的优惠券已到账,点击链接查看。',
risk: 'high',
status: 'pending',
submittedAt: '2026-06-16 08:46',
},
];
export const customers: Customer[] = [
{ id: 'CUS-1001', name: '上海云舟科技', contact: '赵先生', balance: 286420, status: 'active' },
{ id: 'CUS-1002', name: '杭州星澜商贸', contact: '王女士', balance: 94220, status: 'active' },
{ id: 'CUS-1003', name: '深圳北辰出行', contact: '陈先生', balance: 0, status: 'suspended' },
];
export const channels: Channel[] = [
{ id: 'CH-01', name: '华东主通道', region: '华东', successRate: 99.1, latencyMs: 128, enabled: true },
{ id: 'CH-02', name: '华南通道', region: '华南', successRate: 98.6, latencyMs: 164, enabled: true },
{ id: 'CH-03', name: '备用通道', region: '全国', successRate: 97.8, latencyMs: 210, enabled: true },
];
+48
View File
@@ -0,0 +1,48 @@
import { adminOverview, auditItems, channels, customers } from '@/mock/adminData';
import { readLocalData, writeLocalData } from '@/mock/storage';
import type { AuditItem, AuditStatus, Channel, Customer } from '@/mock/types';
const keys = {
audits: 'admin:audits',
customers: 'admin:customers',
channels: 'admin:channels',
};
export const adminService = {
getOverview() {
return adminOverview;
},
getAudits(status?: AuditStatus) {
const records = readLocalData<AuditItem[]>(keys.audits, auditItems);
return status ? records.filter((item) => item.status === status) : records;
},
updateAuditStatus(id: string, status: AuditStatus) {
const records = readLocalData<AuditItem[]>(keys.audits, auditItems);
const nextRecords = records.map((item) => (item.id === id ? { ...item, status } : item));
writeLocalData(keys.audits, nextRecords);
return nextRecords;
},
getCustomers() {
return readLocalData<Customer[]>(keys.customers, customers);
},
getChannels() {
return readLocalData<Channel[]>(keys.channels, channels);
},
saveChannels(nextChannels: Channel[]) {
writeLocalData(keys.channels, nextChannels);
},
toggleChannel(id: string) {
const records = readLocalData<Channel[]>(keys.channels, channels);
const nextRecords = records.map((item) => (
item.id === id ? { ...item, enabled: !item.enabled } : item
));
writeLocalData(keys.channels, nextRecords);
return nextRecords;
},
};
+35
View File
@@ -0,0 +1,35 @@
export const hourlySendTrend = [
{ time: '00:00', sent: 8200, success: 8020 },
{ time: '03:00', sent: 5400, success: 5280 },
{ time: '06:00', sent: 9600, success: 9410 },
{ time: '09:00', sent: 28600, success: 28110 },
{ time: '12:00', sent: 21400, success: 21030 },
{ time: '15:00', sent: 24600, success: 24180 },
{ time: '18:00', sent: 31800, success: 31260 },
{ time: '21:00', sent: 17600, success: 17270 },
];
export const channelShare = [
{ name: '华东主通道', value: 46 },
{ name: '华南通道', value: 31 },
{ name: '备用通道', value: 23 },
];
export const auditTrend = [
{ day: '06-10', pending: 18, approved: 86, rejected: 7 },
{ day: '06-11', pending: 24, approved: 92, rejected: 9 },
{ day: '06-12', pending: 22, approved: 88, rejected: 6 },
{ day: '06-13', pending: 29, approved: 104, rejected: 12 },
{ day: '06-14', pending: 31, approved: 116, rejected: 10 },
{ day: '06-15', pending: 27, approved: 108, rejected: 8 },
{ day: '06-16', pending: 36, approved: 128, rejected: 11 },
];
export const customerGrowth = [
{ month: '1月', active: 42, new: 8 },
{ month: '2月', active: 51, new: 9 },
{ month: '3月', active: 63, new: 12 },
{ month: '4月', active: 74, new: 11 },
{ month: '5月', active: 86, new: 12 },
{ month: '6月', active: 101, new: 15 },
];
+145
View File
@@ -0,0 +1,145 @@
import type { BatchTask, BillingPlan, ClientOverview, Invoice, RecentMessage, Signature, SmsTemplate } from '@/mock/types';
export const clientOverview: ClientOverview = {
availableMessages: 286420,
todaySent: 74166,
successRate: 98.7,
pendingAudits: 12,
};
export const recentMessages: RecentMessage[] = [
{
id: 'MSG-1024',
scene: '登录验证码',
count: 18240,
channel: '华东主通道',
status: 'success',
createdAt: '2026-06-16 09:24',
},
{
id: 'MSG-1023',
scene: '营销活动通知',
count: 46800,
channel: '备用通道',
status: 'warning',
createdAt: '2026-06-16 08:40',
},
{
id: 'MSG-1022',
scene: '订单状态提醒',
count: 9126,
channel: '华南通道',
status: 'info',
createdAt: '2026-06-15 18:12',
},
];
export const smsTemplates: SmsTemplate[] = [
{
id: 'TPL-301',
name: '登录验证码',
type: '验证码',
content: '您的验证码为 ${code}5 分钟内有效。',
status: 'approved',
updatedAt: '2026-06-15 13:20',
},
{
id: 'TPL-302',
name: '订单发货提醒',
type: '通知短信',
content: '您的订单 ${orderNo} 已发货,请留意物流信息。',
status: 'pending',
updatedAt: '2026-06-16 08:18',
},
{
id: 'TPL-303',
name: '会员权益通知',
type: '营销短信',
content: '会员专属权益已到账,登录小程序查看详情。',
status: 'draft',
updatedAt: '2026-06-14 17:05',
},
];
export const signatures: Signature[] = [
{ id: 'SIG-101', name: '云舟科技', company: '上海云舟科技有限公司', status: 'approved' },
{ id: 'SIG-102', name: '星澜会员中心', company: '杭州星澜商贸有限公司', status: 'pending' },
{ id: 'SIG-103', name: '北辰出行', company: '深圳北辰出行服务有限公司', status: 'rejected' },
];
export const billingPlans: BillingPlan[] = [
{ id: 'PLAN-01', name: '基础包', messages: 10000, price: 680 },
{ id: 'PLAN-02', name: '增长包', messages: 50000, price: 2980, highlight: true },
{ id: 'PLAN-03', name: '企业包', messages: 200000, price: 10800 },
];
export const invoices: Invoice[] = [
{ id: 'INV-20260601', title: '增长包充值', amount: 2980, messages: 50000, status: 'paid', createdAt: '2026-06-01 10:12' },
{ id: 'INV-20260518', title: '基础包充值', amount: 680, messages: 10000, status: 'paid', createdAt: '2026-05-18 14:26' },
{ id: 'INV-20260502', title: '企业包充值', amount: 10800, messages: 200000, status: 'pending', createdAt: '2026-05-02 09:48' },
];
export const batchTasks: BatchTask[] = [
{
id: 'TASK20260312001',
applicationName: '营销推广平台',
submittedAt: '2026-03-12 10:30:15',
templateContent: '【启瑞物业】尊敬的业主,您本月物业费500元,请及时缴纳。感谢您的配合!',
phoneCount: 1500,
wordCount: 45,
sendType: 'immediate',
sentCount: 1500,
totalCount: 1500,
status: 'completed',
},
{
id: 'TASK20260312002',
applicationName: '客服系统',
submittedAt: '2026-03-12 11:15:30',
templateContent: '【客服中心】尊敬的张三,您已成功预约上门维修服务,时间:2026-03-15 14:00,地点:XX小区1号楼。',
phoneCount: 300,
wordCount: 58,
sendType: 'scheduled',
scheduledAt: '2026-03-13 09:00:00',
sentCount: 158,
totalCount: 300,
status: 'sending',
},
{
id: 'TASK20260312003',
applicationName: '验证码服务',
submittedAt: '2026-03-12 14:20:45',
templateContent: '【验证码】您的验证码是123456,5分钟内有效,请勿泄露给他人。',
phoneCount: 5000,
wordCount: 34,
sendType: 'immediate',
sentCount: 3200,
totalCount: 5000,
status: 'sending',
},
{
id: 'TASK20260311001',
applicationName: '营销推广平台',
submittedAt: '2026-03-11 16:45:00',
templateContent: '【启瑞物业】您好!春季业主大会将于2026-03-20在小区会议室举行,欢迎参加。咨询电话:400-8888-8888',
phoneCount: 2000,
wordCount: 62,
sendType: 'scheduled',
scheduledAt: '2026-03-12 08:00:00',
sentCount: 2000,
totalCount: 2000,
status: 'completed',
},
{
id: 'TASK20260311002',
applicationName: '营销推广平台',
submittedAt: '2026-03-11 09:30:20',
templateContent: '【启瑞物业】尊敬的客户,值此佳节之际,祝您节日快乐,阖家幸福!',
phoneCount: 800,
wordCount: 38,
sendType: 'immediate',
sentCount: 500,
totalCount: 800,
status: 'terminated',
},
];
+70
View File
@@ -0,0 +1,70 @@
import { batchTasks, billingPlans, clientOverview, invoices, recentMessages, signatures, smsTemplates } from '@/mock/clientData';
import { readLocalData, writeLocalData } from '@/mock/storage';
import type { BatchTask, RecentMessage, Signature, SmsTemplate } from '@/mock/types';
const keys = {
recentMessages: 'client:recent-messages',
templates: 'client:templates',
signatures: 'client:signatures',
batchTasks: 'client:batch-tasks',
};
export const clientService = {
getOverview() {
return clientOverview;
},
getRecentMessages() {
return readLocalData<RecentMessage[]>(keys.recentMessages, recentMessages);
},
saveRecentMessages(nextMessages: RecentMessage[]) {
writeLocalData(keys.recentMessages, nextMessages);
},
addRecentMessage(message: RecentMessage) {
const records = readLocalData<RecentMessage[]>(keys.recentMessages, recentMessages);
const nextRecords = [message, ...records];
writeLocalData(keys.recentMessages, nextRecords);
return nextRecords;
},
getTemplates() {
return readLocalData<SmsTemplate[]>(keys.templates, smsTemplates);
},
saveTemplates(nextTemplates: SmsTemplate[]) {
writeLocalData(keys.templates, nextTemplates);
},
getSignatures() {
return readLocalData<Signature[]>(keys.signatures, signatures);
},
saveSignatures(nextSignatures: Signature[]) {
writeLocalData(keys.signatures, nextSignatures);
},
getBillingPlans() {
return billingPlans;
},
getInvoices() {
return invoices;
},
getBatchTasks() {
return readLocalData<BatchTask[]>(keys.batchTasks, batchTasks);
},
terminateBatchTask(id: string) {
const records = readLocalData<BatchTask[]>(keys.batchTasks, batchTasks);
const nextRecords = records.map((item) => (
item.id === id && item.status === 'sending'
? { ...item, status: 'terminated' as const }
: item
));
writeLocalData(keys.batchTasks, nextRecords);
return nextRecords;
},
};
+22
View File
@@ -0,0 +1,22 @@
export { adminService } from './adminService';
export { clientService } from './clientService';
export type {
AdminOverview,
AuditItem,
AuditStatus,
AuditType,
BatchSendType,
BatchTask,
BatchTaskStatus,
BillingPlan,
Channel,
ClientOverview,
Customer,
Invoice,
MessageStatus,
RecentMessage,
RiskLevel,
Signature,
SmsTemplate,
TemplateStatus,
} from './types';
+35
View File
@@ -0,0 +1,35 @@
const STORAGE_PREFIX = 'cmpp-platform-v2';
export function readLocalData<T>(key: string, fallback: T): T {
if (typeof window === 'undefined') {
return fallback;
}
const rawValue = window.localStorage.getItem(`${STORAGE_PREFIX}:${key}`);
if (!rawValue) {
return fallback;
}
try {
return JSON.parse(rawValue) as T;
} catch {
return fallback;
}
}
export function writeLocalData<T>(key: string, value: T) {
if (typeof window === 'undefined') {
return;
}
window.localStorage.setItem(`${STORAGE_PREFIX}:${key}`, JSON.stringify(value));
}
export function resetLocalData(key: string) {
if (typeof window === 'undefined') {
return;
}
window.localStorage.removeItem(`${STORAGE_PREFIX}:${key}`);
}
+107
View File
@@ -0,0 +1,107 @@
export type MessageStatus = 'success' | 'warning' | 'info' | 'danger';
export type RecentMessage = {
id: string;
scene: string;
count: number;
channel: string;
status: MessageStatus;
createdAt: string;
};
export type ClientOverview = {
availableMessages: number;
todaySent: number;
successRate: number;
pendingAudits: number;
};
export type TemplateStatus = 'draft' | 'pending' | 'approved' | 'rejected';
export type SmsTemplate = {
id: string;
name: string;
type: '验证码' | '通知短信' | '营销短信';
content: string;
status: TemplateStatus;
updatedAt: string;
};
export type Signature = {
id: string;
name: string;
company: string;
status: TemplateStatus;
};
export type RiskLevel = 'low' | 'medium' | 'high';
export type AuditType = '模板' | '签名';
export type AuditStatus = 'pending' | 'approved' | 'rejected';
export type AuditItem = {
id: string;
customer: string;
type: AuditType;
content: string;
risk: RiskLevel;
status: AuditStatus;
submittedAt: string;
};
export type AdminOverview = {
todaySubmissions: number;
pendingAudits: number;
averageWaitMinutes: number;
channelHealth: number;
};
export type Customer = {
id: string;
name: string;
contact: string;
balance: number;
status: 'active' | 'suspended';
};
export type Channel = {
id: string;
name: string;
region: string;
successRate: number;
latencyMs: number;
enabled: boolean;
};
export type BillingPlan = {
id: string;
name: string;
messages: number;
price: number;
highlight?: boolean;
};
export type Invoice = {
id: string;
title: string;
amount: number;
messages: number;
status: 'paid' | 'pending' | 'failed';
createdAt: string;
};
export type BatchTaskStatus = 'completed' | 'sending' | 'terminated';
export type BatchSendType = 'immediate' | 'scheduled';
export type BatchTask = {
id: string;
applicationName: string;
submittedAt: string;
templateContent: string;
phoneCount: number;
wordCount: number;
sendType: BatchSendType;
scheduledAt?: string;
sentCount: number;
totalCount: number;
status: BatchTaskStatus;
};