/**
* # 路由 /admin 后台布局
* 左侧 Sider 菜单 + 顶部 Header(当前用户 / 退出)。
* 未登录或无后台权限:整页渲染登录页(不再展示后台骨架)。
*/
import { createFileRoute, Outlet, useNavigate, useRouter, useRouterState } from "@tanstack/react-router";
import { App, Avatar, Button, Card, ConfigProvider, Form, Input, Layout, Menu, Space, Tag } from "antd";
import { LockKeyhole } from "lucide-react";
import {
DatabaseBackup,
Files,
Inbox,
Image as ImageIcon,
Languages,
LayoutDashboard,
List,
LogOut,
Package,
Route as RouteIcon,
ScrollText,
Settings,
ShoppingCart,
SlidersHorizontal,
UserRound,
} from "lucide-react";
import { useState, useEffect, type ReactNode } from "react";
import { StyleProvider } from "@ant-design/cssinjs";
import { getCurrentUser, loginFn, logoutFn } from "~/server/auth";
import { getLoginSecurityPublic } from "~/server/security";
import { RecaptchaField, useRecaptcha, type LoginSecurityPublic } from "~/components/admin/Recaptcha";
/** # 类型 后台菜单分组(分组标签不可点;key 即目标路由路径) **/
const MENU_GROUPS: { label: string; children: { key: string; icon: ReactNode; label: string }[] }[] = [
{
label: "概览",
children: [{ key: "/admin/", icon: , label: "Dashboard" }],
},
{
label: "内容",
children: [
{ key: "/admin/contents/", icon: , label: "页面 / 文章" },
{ key: "/admin/menus/", icon:
, label: "菜单" },
{ key: "/admin/media/", icon: , label: "媒体库" },
],
},
{
label: "商务",
children: [
{ key: "/admin/products/", icon: , label: "产品" },
{ key: "/admin/orders/", icon: , label: "订单" },
{ key: "/admin/inquiries/", icon: , label: "询盘" },
],
},
{
label: "系统",
children: [
{ key: "/admin/filters/", icon: , label: "筛选" },
{ key: "/admin/languages/", icon: , label: "语言" },
{ key: "/admin/seo/", icon: , label: "SEO / 重定向" },
{ key: "/admin/users/", icon: , label: "用户" },
{ key: "/admin/settings/", icon: , label: "系统设置" },
{ key: "/admin/backups/", icon: , label: "备份" },
{ key: "/admin/audit/", icon: , label: "审计日志" },
],
},
];
/** # 类型 后台菜单项(扁平视图,用于路径高亮匹配) **/
const MENU_ITEMS = MENU_GROUPS.flatMap((g) => g.children);
/** # 类型 合法菜单路径(用于类型安全的导航) **/
type MenuKey = string;
/** # 类型 拥有后台权限的角色 **/
const ADMIN_ROLES = ["admin", "editor", "manager"];
export const Route = createFileRoute("/admin")({
loader: () => getCurrentUser(),
component: AdminLayout,
// AntD 烘焙静态样式(scripts/gen-antd-css.tsx 生成到 public/):
// 用 而非 JS import —— dev 下 vite 会把 import 的 CSS 注入成