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
+104
View File
@@ -0,0 +1,104 @@
import {
Activity,
BarChart3,
Building2,
FileCheck2,
Gauge,
Hash,
ImageIcon,
Layers3,
MessageSquare,
Phone,
TrendingUp,
RadioTower,
ReceiptText,
Send,
Settings,
Shield,
ShieldOff,
ShieldCheck,
Users,
UserX,
} from 'lucide-react';
import { AppShell } from '@/layouts/AppShell';
export function AdminLayout() {
return (
<AppShell
title="CMPP 运营端"
subtitle="平台运营管理中心"
workspaceName="平台运营工作区"
userName="运营"
userRole="平台管理员"
navSections={[
{
title: '运营概览',
icon: Gauge,
items: [
{ label: '运营看板', to: '/admin', icon: Gauge },
{ label: '发送监控', to: '/admin/monitor', icon: Activity },
{ label: '数据统计', to: '/admin/analytics', icon: BarChart3 },
{ label: '客户管理', to: '/admin/customers', icon: Building2 },
],
},
{
title: '审核中心',
icon: FileCheck2,
items: [
{ label: '企业认证审核', to: '/admin/enterprise-audit', icon: ShieldCheck },
{ label: '短信审核', to: '/admin/sms-audit', icon: MessageSquare },
{ label: '短信模板审核', to: '/admin/templates', icon: FileCheck2 },
{ label: '彩信模板审核', to: '/admin/signatures', icon: ImageIcon },
],
},
{
title: '通道管理',
icon: RadioTower,
items: [
{ label: '短信通道管理', to: '/admin/channels', icon: RadioTower },
{ label: '彩信通道管理', to: '/admin/mms-channels', icon: ImageIcon },
{ label: '短信通道组管理', to: '/admin/channel-groups', icon: Layers3 },
],
},
{
title: '发送任务',
icon: Send,
items: [
{ label: '短信任务进度', to: '/admin/sms-task-progress', icon: TrendingUp },
{ label: '彩信任务进度', to: '/admin/mms-task-progress', icon: BarChart3 },
],
},
{
title: '数据详单',
icon: ReceiptText,
items: [
{ label: '短信记录', to: '/admin/sms-records', icon: MessageSquare },
{ label: '彩信记录', to: '/admin/mms-records', icon: ImageIcon },
{ label: '短信上行记录', to: '/admin/sms-uplink-records', icon: MessageSquare },
{ label: '充值记录', to: '/admin/recharge-records', icon: ReceiptText },
{ label: '账单流水', to: '/admin/billing', icon: ReceiptText },
],
},
{
title: '安全控制',
icon: Shield,
items: [
{ label: '企业黑名单', to: '/admin/enterprise-blacklist', icon: UserX },
{ label: '全局黑名单', to: '/admin/global-blacklist', icon: ShieldOff },
{ label: '敏感词管理', to: '/admin/sensitive-words', icon: Shield },
],
},
{
title: '系统管理',
icon: Settings,
items: [
{ label: '用户管理', to: '/admin/users', icon: Users },
{ label: '手机号段库', to: '/admin/phone-segments', icon: Phone },
{ label: '引流信息字段库', to: '/admin/drainage-fields', icon: Hash },
{ label: '系统配置', to: '/admin/settings', icon: Settings },
],
},
]}
/>
);
}
+145
View File
@@ -0,0 +1,145 @@
import type { ComponentType } from 'react';
import { useState } from 'react';
import {
Bell,
ChevronDown,
ChevronRight,
CircleHelp,
PanelLeftClose,
PanelLeftOpen,
Search,
Sparkles,
} from 'lucide-react';
import { NavLink, Outlet } from 'react-router-dom';
export type ShellNavItem = {
label: string;
to: string;
icon: ComponentType<{ size?: number; strokeWidth?: number }>;
};
export type ShellNavSection = {
title: string;
icon?: ComponentType<{ size?: number; strokeWidth?: number }>;
items: ShellNavItem[];
};
type AppShellProps = {
title: string;
subtitle: string;
workspaceName: string;
userName: string;
userRole: string;
navSections: ShellNavSection[];
};
export function AppShell({
title,
subtitle,
workspaceName,
userName,
userRole,
navSections,
}: AppShellProps) {
const [collapsed, setCollapsed] = useState(false);
const [closedSections, setClosedSections] = useState<Record<string, boolean>>({});
const ToggleIcon = collapsed ? PanelLeftOpen : PanelLeftClose;
return (
<div className={['app-shell', collapsed ? 'app-shell--collapsed' : ''].filter(Boolean).join(' ')}>
<aside className="sidebar">
<div className="brand-block">
<div className="brand-icon">
<Sparkles size={18} />
</div>
<div className="brand-copy">
<div className="brand-mark">{title}</div>
<div className="brand-subtitle">{subtitle}</div>
</div>
</div>
<nav className="side-nav" aria-label="主导航">
{navSections.map((section) => (
<section className="side-nav-section" key={section.title}>
{section.icon ? (
<button
aria-expanded={!closedSections[section.title]}
className="side-nav-group-toggle"
onClick={() => setClosedSections((current) => ({ ...current, [section.title]: !current[section.title] }))}
type="button"
>
<section.icon size={18} strokeWidth={2.1} />
<span className="side-nav-group-label">{section.title}</span>
{closedSections[section.title] ? (
<ChevronRight className="side-nav-group-chevron" size={16} />
) : (
<ChevronDown className="side-nav-group-chevron" size={16} />
)}
</button>
) : (
<p>{section.title}</p>
)}
<div className={['side-nav-list', closedSections[section.title] ? 'side-nav-list--closed' : ''].join(' ')}>
{section.items.map((item) => {
const Icon = item.icon;
return (
<NavLink key={item.to} to={item.to} end title={item.label}>
<Icon size={17} strokeWidth={2.1} />
<span>{item.label}</span>
</NavLink>
);
})}
</div>
</section>
))}
</nav>
<div className="sidebar-footer">
<span></span>
<strong>{workspaceName}</strong>
</div>
</aside>
<main className="main-area">
<header className="topbar">
<div className="topbar-left">
<button
className="icon-button"
onClick={() => setCollapsed((value) => !value)}
type="button"
aria-label={collapsed ? '展开导航' : '收起导航'}
>
<ToggleIcon size={18} />
</button>
<label className="topbar-search">
<Search size={17} />
<input placeholder="搜索模板、客户、发送记录" />
</label>
</div>
<div className="topbar-actions">
<button className="icon-button" type="button" aria-label="帮助中心">
<CircleHelp size={18} />
</button>
<button className="icon-button has-dot" type="button" aria-label="通知">
<Bell size={18} />
</button>
<button className="user-menu" type="button">
<span className="user-avatar">{userName.slice(0, 1)}</span>
<span>
<strong>{userName}</strong>
<small>{userRole}</small>
</span>
<ChevronDown size={16} />
</button>
</div>
</header>
<div className="page-content">
<Outlet />
</div>
</main>
</div>
);
}
+76
View File
@@ -0,0 +1,76 @@
import {
BadgeDollarSign,
ClipboardList,
FileText,
Home,
ImageIcon,
MessageSquareText,
PenLine,
ReceiptText,
Settings,
ShieldCheck,
Users,
} from 'lucide-react';
import { AppShell } from '@/layouts/AppShell';
export function ClientLayout() {
return (
<AppShell
title="CMPP 客户端"
subtitle="短信服务控制台"
workspaceName="华东一区客户空间"
userName="赵先生"
userRole="企业管理员"
navSections={[
{
title: '概览',
items: [{ label: '工作台', to: '/client', icon: Home }],
},
{
title: '短信业务',
items: [
{ label: '短信发送', to: '/client/send', icon: MessageSquareText },
{ label: '查看批量任务', to: '/client/batch-tasks', icon: ClipboardList },
{ label: '短信发送详情', to: '/client/send-detail', icon: ClipboardList },
{ label: '查看上行短信', to: '/client/uplink-messages', icon: MessageSquareText },
],
},
{
title: '短信基础配置',
items: [
{ label: '短信应用', to: '/client/applications', icon: FileText },
{ label: '模板管理', to: '/client/templates', icon: FileText },
{ label: '签名与引流信息', to: '/client/signatures', icon: PenLine },
],
},
{
title: '彩信服务',
items: [
{ label: '签名报备', to: '/client/mms-signatures', icon: PenLine },
{ label: '彩信模板管理', to: '/client/mms-templates', icon: ImageIcon },
{ label: '发送彩信', to: '/client/mms-send', icon: MessageSquareText },
{ label: '查看批量任务', to: '/client/mms-batch-tasks', icon: ClipboardList },
{ label: '彩信发送详情', to: '/client/mms-send-detail', icon: ClipboardList },
{ label: '查看上行彩信', to: '/client/mms-uplink-messages', icon: ImageIcon },
],
},
{
title: '账户',
items: [
{ label: '充值套餐', to: '/client/billing', icon: BadgeDollarSign },
{ label: '账单流水', to: '/client/invoices', icon: ReceiptText },
{ label: '账号设置', to: '/client/settings', icon: Settings },
],
},
{
title: '系统管理',
items: [
{ label: '企业认证', to: '/client/enterprise-auth', icon: ShieldCheck },
{ label: '用户管理', to: '/client/users', icon: Users },
{ label: '系统日志', to: '/client/system-logs', icon: FileText },
],
},
]}
/>
);
}