feat: wire admin workflows to real APIs

This commit is contained in:
hectorzhao
2026-07-02 13:50:09 +08:00
parent f8c9b78c21
commit ab421cf8a7
42 changed files with 2596 additions and 250 deletions
+7 -2
View File
@@ -34,6 +34,12 @@ export function AdminLayout() {
workspaceName="平台运营工作区"
userName="运营"
userRole="平台管理员"
auditNotifications={[
{ label: '企业认证审核', count: 3, to: '/admin/enterprise-audit' },
{ label: '短信审核', count: 8, to: '/admin/sms-audit' },
{ label: '短信模板审核', count: 5, to: '/admin/templates' },
{ label: '签名审核', count: 2, to: '/admin/enterprise-signatures' },
]}
navSections={[
{
title: '运营概览',
@@ -42,7 +48,6 @@ export function AdminLayout() {
{ label: '运营看板', to: '/admin', icon: Gauge },
{ label: '发送监控', to: '/admin/monitor', icon: Activity },
{ label: '数据统计', to: '/admin/analytics', icon: BarChart3 },
{ label: '客户管理', to: '/admin/customers', icon: Building2 },
],
},
{
@@ -116,7 +121,7 @@ export function AdminLayout() {
items: [
{ label: '用户管理', to: '/admin/users', icon: Users },
{ label: '手机号段库', to: '/admin/phone-segments', icon: Phone },
{ label: '引流信息字段库', to: '/admin/drainage-fields', icon: Hash },
{ label: '报备字段库', to: '/admin/drainage-fields', icon: Hash },
{ label: '系统日志', to: '/admin/system-logs', icon: FileText },
],
},
+87 -12
View File
@@ -1,10 +1,12 @@
import type { ComponentType } from 'react';
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import {
Bell,
ChevronDown,
ChevronRight,
CircleHelp,
KeyRound,
LogOut,
PanelLeftClose,
PanelLeftOpen,
Search,
@@ -25,6 +27,12 @@ export type ShellNavSection = {
items: ShellNavItem[];
};
export type AuditNotificationItem = {
label: string;
count: number;
to: string;
};
type AppShellProps = {
title: string;
subtitle: string;
@@ -32,6 +40,7 @@ type AppShellProps = {
userName: string;
userRole: string;
navSections: ShellNavSection[];
auditNotifications?: AuditNotificationItem[];
};
export function AppShell({
@@ -41,10 +50,32 @@ export function AppShell({
userName,
userRole,
navSections,
auditNotifications = [],
}: AppShellProps) {
const [collapsed, setCollapsed] = useState(false);
const [closedSections, setClosedSections] = useState<Record<string, boolean>>({});
const [userMenuOpen, setUserMenuOpen] = useState(false);
const [noticeOpen, setNoticeOpen] = useState(false);
const ToggleIcon = collapsed ? PanelLeftOpen : PanelLeftClose;
const auditTotal = useMemo(
() => auditNotifications.reduce((sum, item) => sum + item.count, 0),
[auditNotifications],
);
useEffect(() => {
if (auditTotal <= 0 || typeof window === 'undefined') {
return;
}
const alertedKey = `cmpp-audit-alert-${title}`;
if (window.sessionStorage.getItem(alertedKey)) {
return;
}
window.sessionStorage.setItem(alertedKey, '1');
const timer = window.setTimeout(() => {
window.alert(`有新的审核任务进入待办,共 ${auditTotal} 条。`);
}, 1200);
return () => window.clearTimeout(timer);
}, [auditTotal, title]);
return (
<div className={['app-shell', collapsed ? 'app-shell--collapsed' : ''].filter(Boolean).join(' ')}>
@@ -126,17 +157,61 @@ export function AppShell({
<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 className="notice-menu-wrap">
<button
aria-expanded={noticeOpen}
aria-haspopup="menu"
className={['icon-button', auditTotal > 0 ? 'has-dot' : ''].filter(Boolean).join(' ')}
onClick={() => setNoticeOpen((open) => !open)}
type="button"
aria-label="通知"
>
<Bell size={18} />
{auditTotal > 0 ? <span className="notice-count">{auditTotal}</span> : null}
</button>
{noticeOpen ? (
<div className="notice-popover" role="menu">
<div className="notice-popover__header">
<strong></strong>
<span>{auditTotal} </span>
</div>
{auditNotifications.length ? auditNotifications.map((item) => (
<NavLink key={item.to} onClick={() => setNoticeOpen(false)} role="menuitem" to={item.to}>
<span>{item.label}</span>
<strong>{item.count}</strong>
</NavLink>
)) : <p></p>}
</div>
) : null}
</div>
<div className="user-menu-wrap">
<button
aria-expanded={userMenuOpen}
aria-haspopup="menu"
className="user-menu"
onClick={() => setUserMenuOpen((open) => !open)}
type="button"
>
<span className="user-avatar">{userName.slice(0, 1)}</span>
<span>
<strong>{userName}</strong>
<small>{userRole}</small>
</span>
<ChevronDown size={16} />
</button>
{userMenuOpen ? (
<div className="user-menu-popover" role="menu">
<button onClick={() => setUserMenuOpen(false)} role="menuitem" type="button">
<KeyRound size={16} />
</button>
<button onClick={() => setUserMenuOpen(false)} role="menuitem" type="button">
<LogOut size={16} />
退
</button>
</div>
) : null}
</div>
</div>
</header>
-2
View File
@@ -7,7 +7,6 @@ import {
MessageSquareText,
PenLine,
ReceiptText,
Settings,
ShieldCheck,
Users,
} from 'lucide-react';
@@ -66,7 +65,6 @@ export function ClientLayout() {
items: [
{ label: '企业认证', to: '/client/enterprise-auth', icon: ShieldCheck },
{ label: '用户管理', to: '/client/users', icon: Users },
{ label: '账号设置', to: '/client/settings', icon: Settings },
{ label: '系统日志', to: '/client/system-logs', icon: FileText },
],
},