/** * # 路由 /admin 后台布局 * 左侧 Sider 菜单(按角色权限过滤)+ 顶部 Header(语言选择 / 主题切换 / 用户头像下拉)。 * 未登录或无后台权限:整页渲染登录页。 * v0.16.0:后台 i18n(默认英文)、浅/深主题、个人资料入口、菜单权限。 */ import { createFileRoute, Outlet, useNavigate, useRouter, useRouterState } from "@tanstack/react-router"; import { App, Avatar, Button, Card, Dropdown, Form, Input, Layout, Menu, Select, Space, Tooltip } from "antd"; import { LockKeyhole, LogOut, Moon, Settings, Sun, UserRound, Languages } from "lucide-react"; import { DatabaseBackup, Files, Inbox, Image as ImageIcon, Languages as LanguagesIcon, LayoutDashboard, List, Package, Route as RouteIcon, ScrollText, ShoppingCart, ShieldCheck, SlidersHorizontal, UserRound as UserRoundIcon, } from "lucide-react"; import { useEffect, useState, type ReactNode } from "react"; import { loginFn, logoutFn } from "~/server/auth"; import { getAdminLayoutData } from "~/server/permissions"; import { getLoginSecurityPublic } from "~/server/security"; import { RecaptchaField, useRecaptcha, type LoginSecurityPublic } from "~/components/admin/Recaptcha"; import { AdminLocaleProvider, useAdminI18n, useAdminT, type AdminLocaleDef } from "~/admin-i18n"; import { ADMIN_LOCALES } from "~/admin-i18n/shared"; import { AdminThemeProvider, useAdminTheme } from "~/admin-theme"; import { ADMIN_MENU_GROUPS } from "~/admin-menus"; /** # 成员变量 菜单 key → 图标(菜单定义在 admin-menus.ts,图标在此补充) **/ const MENU_ICONS: Record = { "/admin/": , "/admin/contents/": , "/admin/menus/": , "/admin/media/": , "/admin/products/": , "/admin/orders/": , "/admin/inquiries/": , "/admin/filters/": , "/admin/languages/": , "/admin/seo/": , "/admin/users/": , "/admin/permissions/": , "/admin/settings/": , "/admin/backups/": , "/admin/audit/": , }; /** # 类型 拥有后台权限的角色 **/ const ADMIN_ROLES = ["admin", "editor", "manager"]; /** # 类型 loader 返回(用户 + 该角色授权菜单) **/ type AdminLoaderData = { user: { displayName: string | null; email: string; role: string; avatar: string; locale: string; theme: string } | null; /** # 成员变量 当前角色可见的菜单 key(未登录为空) **/ menus: string[]; }; export const Route = createFileRoute("/admin")({ loader: async (): Promise => { return await getAdminLayoutData(); }, component: AdminLayout, // AntD 烘焙静态样式(scripts/gen-antd-css.tsx 生成到 public/): // 用 而非 JS import —— dev 下 vite 会把 import 的 CSS 注入成