import { useEffect, useState } from "react"; import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom"; import { BoardRegular, DocumentTextRegular, GlobeRegular, MailRegular, PanelLeftContractRegular, PanelLeftExpandRegular, RouterRegular, SettingsRegular, SignOutRegular, } from "@fluentui/react-icons"; import { useAuth } from "../../store/auth"; import { useI18n } from "../../lib/i18n"; import { confirmDialog } from "../ui/MessageBox"; import { LanguageSwitch } from "../ui/LanguageSwitch"; import { SwitchDark } from "../ui/SwitchDark"; import { Drawer } from "../ui/Drawer"; import { ErrorBoundary } from "../ui/ErrorBoundary"; import { cx } from "../../lib/utils"; import { BrandLogo } from "./BrandLogo"; import { VersionBadge } from "./VersionBadge"; import { listPlugins, type InstalledPlugin } from "../../extensions"; const NAV = [ { to: "/", label: "仪表盘", icon: BoardRegular, end: true }, { to: "/devices", label: "设备管理", icon: RouterRegular }, { to: "/proxy", label: "代理管理", icon: GlobeRegular }, { to: "/sms", label: "短信检测", icon: MailRegular }, { to: "/logs", label: "实时日志", icon: DocumentTextRegular }, { to: "/settings", label: "系统设置", icon: SettingsRegular }, ]; export function AuthenticatedShell({ isDark, onToggleTheme, }: { isDark: boolean; onToggleTheme: () => void; }) { const [collapsed, setCollapsed] = useState(false); const [isMobile, setIsMobile] = useState(false); const [mobileOpen, setMobileOpen] = useState(false); const [plugins, setPlugins] = useState([]); const { logout, user } = useAuth(); const { t, lang } = useI18n(); const navigate = useNavigate(); const location = useLocation(); useEffect(() => { const mq = window.matchMedia("(max-width: 767px)"); const update = () => { setIsMobile(mq.matches); if (!mq.matches) setMobileOpen(false); }; update(); window.addEventListener("resize", update, { passive: true }); return () => window.removeEventListener("resize", update); }, []); useEffect(() => { let active = true; const load = () => listPlugins().then((items) => { if (active) setPlugins(items || []); }).catch(() => undefined); void load(); window.addEventListener("vocat:plugins-changed", load); return () => { active = false; window.removeEventListener("vocat:plugins-changed", load); }; }, []); useEffect(() => { setMobileOpen(false); }, [location.pathname]); async function onLogout() { const ok = await confirmDialog(t("确认退出登录?"), t("提示"), { confirmText: t("退出"), cancelText: t("取消"), type: "warning", }); if (ok) { await logout(); navigate("/login"); } } function toggle() { if (isMobile) setMobileOpen(true); else setCollapsed((value) => !value); } function menuList(collapse: boolean) { const sidebarPlugins = plugins .filter((plugin) => plugin.enabled) .flatMap((plugin) => plugin.contributions .filter((contribution) => contribution.location === "sidebar") .map((contribution) => ({ plugin, contribution }))); const navItems: Array<(typeof NAV)[number] | { to: string; label: string; icon: typeof GlobeRegular; pluginLabelZH?: string }> = []; for (const item of NAV) { navItems.push(item); if (item.to === "/sms") { for (const extension of sidebarPlugins.filter((entry) => !entry.contribution.after || entry.contribution.after === "sms")) { navItems.push({ to: `/extensions/${encodeURIComponent(extension.plugin.id)}/${encodeURIComponent(extension.contribution.id)}`, label: extension.contribution.label, pluginLabelZH: extension.contribution.labelZh, icon: GlobeRegular, }); } } } return ( ); } function userCard() { return (
{user?.username || "Admin"}
{user?.role || "Administrator"}
); } return (
{!isMobile && ( )} setMobileOpen(false)} className="mobile-drawer">
vocat
{t("高通模块测试工具")}
{menuList(false)}
{userCard()}
); }