/** 应用框架:100vh 布局(侧栏 + 顶栏大标题 + 内容区滚动),会话守卫 */ import { useEffect, useState } from "react"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; import { Avatar, Dropdown, Layout, Menu } from "antd"; import { DashboardOutlined, MailOutlined, CloudServerOutlined, GlobalOutlined, BellOutlined, KeyOutlined, UserOutlined, LogoutOutlined, } from "@ant-design/icons"; import { health, logout, me } from "./api.ts"; const { Sider, Header, Content } = Layout; /** # 成员变量: 侧栏菜单项 **/ const MENU_ITEMS = [ { key: "/", icon: , label: "概览" }, { key: "/nodes", icon: , label: "节点 Nodes" }, { key: "/domains", icon: , label: "域名 Domains" }, { key: "/mailboxes", icon: , label: "邮箱 Mailboxes" }, { key: "/emails", icon: , label: "邮件 Emails" }, { key: "/webhooks", icon: , label: "Webhooks" }, { key: "/keys", icon: , label: "API / MCP Keys" }, ]; /** # 成员变量: 路由 -> 页面标题/副标题 **/ const PAGE_META: Record = { "/": { title: "概览", subtitle: "节点、邮件与投递总览" }, "/nodes": { title: "节点管理", subtitle: "通过 Cloudflare OAuth 自动安装的转发节点" }, "/domains": { title: "域名管理", subtitle: "接入 Email Routing 的收件域名" }, "/mailboxes": { title: "邮箱管理", subtitle: "收件地址与保留策略" }, "/emails": { title: "邮件", subtitle: "已接收邮件(默认保留 1 天)" }, "/webhooks": { title: "Webhooks", subtitle: "邮件事件推送与投递日志" }, "/keys": { title: "API / MCP Keys", subtitle: "wmh_ 前缀凭据,仅创建时可见" }, }; /** # 函数 * 应用框架组件:未登录跳 /login;顶栏展示页面标题 + 版本号(package.json 来源) */ export function App() { const navigate = useNavigate(); const location = useLocation(); const [ready, setReady] = useState(false); const [username, setUsername] = useState(""); const [version, setVersion] = useState(""); useEffect(() => { void (async () => { try { const [healthInfo, meInfo] = await Promise.all([health(), me()]); setVersion(healthInfo.version); setUsername(meInfo.user.username); setReady(true); } catch { navigate("/login", { replace: true }); } })(); }, [navigate]); if (!ready) return null; const meta = PAGE_META[location.pathname] ?? { title: "WmailHub", subtitle: "" }; return ( WmailHub navigate(key)} style={{ borderInlineEnd: "none" }} /> {meta.title} {meta.subtitle} v{version} {/* Dropdown 子元素必须是 DOM 元素(antd v6 坑),所以外包 div */} , label: "退出登录", onClick: async () => { await logout().catch(() => undefined); window.location.href = "/login"; }, }, ], }} > } /> {username} ); }