"use client"; import * as React from "react"; import { usePathname, useRouter } from "next/navigation"; import { useSession, getSession } from "next-auth/react"; import { SideNav, NavGroup, NavItem } from "@/components/layout/side-nav"; import { Header } from "@/components/layout/header"; import { NotificationContainer } from "@/components/ui/notification-container"; import { useUserStore } from "@/lib/stores/user-store"; import { LayoutDashboard, Sparkles, BookOpen, BarChart3, Swords, Share2, Heart, ScanSearch, TrendingUp, Code2, Settings, } from "lucide-react"; // ─── Nav Config ─────────────────────────────────────────────────────────────── const NAV_GROUPS: NavGroup[] = [ { id: "menu", title: "菜单", items: [ { id: "dashboard", label: "仪表盘", href: "/dashboard", icon: , }, { id: "content", label: "内容管理", href: "/dashboard/content", icon: , }, { id: "knowledge", label: "知识库", href: "/dashboard/knowledge", icon: , }, { id: "analytics", label: "品牌监测", href: "/dashboard/monitoring", icon: , }, { id: "competitors", label: "竞品分析", href: "/dashboard/competitors", icon: , }, { id: "health-score", label: "健康评分", href: "/dashboard/health-score", icon: , }, { id: "distribution", label: "内容分发", href: "/dashboard/distribution", icon: , }, { id: "detection", label: "检测任务", href: "/dashboard/detection", icon: , }, { id: "trends", label: "趋势洞察", href: "/dashboard/trends", icon: , }, { id: "schema", label: "Schema 建议", href: "/dashboard/schema", icon: , }, ], }, { id: "settings", title: "设置", items: [ { id: "settings", label: "设置", href: "/dashboard/settings", icon: , }, ], }, ]; // Flatten all items for activeId lookup const ALL_NAV_ITEMS: NavItem[] = NAV_GROUPS.flatMap((g) => g.items); // ─── Layout ─────────────────────────────────────────────────────────────────── export default function DashboardLayout({ children, }: { children: React.ReactNode; }) { const { status, update } = useSession(); const pathname = usePathname(); const router = useRouter(); const [verifying, setVerifying] = React.useState(false); // 同步 session 到 user store const syncFromSession = useUserStore((s) => s.syncFromSession); React.useEffect(() => { if (status === "authenticated") { getSession().then((session) => { syncFromSession(session); }); } else if (status === "unauthenticated") { syncFromSession(null); } }, [status, syncFromSession]); // 当 useSession 返回 unauthenticated 时,用 getSession 双重确认, // 避免 SessionProvider 缓存未更新导致的误判重定向 React.useEffect(() => { if (status === "unauthenticated") { setVerifying(true); getSession().then((session) => { setVerifying(false); if (session) { // SessionProvider 缓存未更新,强制刷新 update(); } else { router.replace("/login"); } }); } }, [status, router, update]); // Compute activeId: match longest href prefix const activeId = React.useMemo(() => { const sorted = [...ALL_NAV_ITEMS] .filter((item) => item.href) .sort((a, b) => (b.href?.length ?? 0) - (a.href?.length ?? 0)); return sorted.find((item) => item.href && pathname.startsWith(item.href))?.id ?? "dashboard"; }, [pathname]); // Handle nav click: navigate to href const handleNavClick = React.useCallback( (item: NavItem) => { if (item.href) { router.push(item.href); } }, [router] ); if (status === "loading" || verifying) { return (
); } if (status === "unauthenticated") { return null; } return (
{/* Fixed SideNav - left side */} {/* Main content area - offset by sidebar width */}
{/* Fixed Header at top */}
{/* Global Toast/Notification */} {/* Scrollable content area */}
{children}
); }