"use client" import { AppShell, Box, Burger, Divider, Flex, Menu, NavLink, ScrollArea, Space, Stack, Text, Tooltip, UnstyledButton, useComputedColorScheme, useMantineColorScheme, } from "@mantine/core" import { useDisclosure, useMediaQuery } from "@mantine/hooks" import { IconLogout, IconMoon, IconSun, IconUser } from "@tabler/icons-react" import cx from "clsx" import { Suspense, useEffect, useMemo, useState } from "react" import actionToggleClasses from "./ActionToggle.module.css" import headerLinkClasses from "./HeaderLink.module.css" import { useUserStore } from "@/app/store/user" import { usePathname, useRouter } from "next/navigation" import { usePermissionStore } from "../label/store/auth" import { deleteCookie } from "../login/api" import { MenuItem, withoutLayoutRoutes } from "./common" import { ClientIcon } from "./components/ClientIcon" import { LinksGroup } from "./components/NavbarLinks" import { useAppLayoutStore } from "./store" export default function AppLayout({ menu, children, }: { menu: MenuItem[] children: React.ReactNode }) { const [mobileOpened, { toggle: toggleMobile }] = useDisclosure() const { updateIsOpen } = useAppLayoutStore.getState() const { isOpen } = useAppLayoutStore() const [isClient, setIsClient] = useState(false) useEffect(() => { setIsClient(true) }, []) const { setColorScheme } = useMantineColorScheme() const computedColorScheme = useComputedColorScheme("light", { getInitialValueInEffect: true, }) const pathname = usePathname() const withoutLayout = useMemo( () => withoutLayoutRoutes.includes(pathname), [pathname] ) const router = useRouter() const activeHeaderMenu = useMemo(() => { return menu?.find((child) => pathname.startsWith(`/${child.url}`)) }, [pathname, menu]) const { menuDefaultRouter } = useAppLayoutStore.getState() const headItems = menu.map((item) => ( { e.preventDefault() if (activeHeaderMenu?.url === item.url) return let toPathname = menuDefaultRouter[`/${item.url}`] || `/${item.url}` window.location.href = toPathname // router.replace(toPathname) }}> {item.title} )) /** * 递归渲染 MenuItem */ const renderMenuItem = ( item: MenuItem, routerUrl?: string ): React.ReactNode => { if (item.items && item.items.length > 0) { // 有子菜单,递归渲染 return ( {item.title} {item.items.map((sub) => renderMenuItem(sub, `${routerUrl}/${item.url}`) )} ) } else { // 没有子菜单,普通 Menu.Item return ( { router.push(`/${routerUrl}/${item.url}`) }} active={pathname === `/${routerUrl}/${item.url}`} leftSection={ item.icon && ( ) } label={item.title}> ) } } const handleLogout = async () => { useUserStore.getState().removeUserInfo() process.env.NEXT_PUBLIC_OUTPUT_TYPE === "standalone" && (await deleteCookie()) router.push("/login") window.location.reload() } const matches = useMediaQuery("(min-width: 48em)") const userName = usePermissionStore((s) => s.user_name) return isClient ? ( withoutLayout ? ( }>{children} ) : ( 标注平台 {headItems} setColorScheme( computedColorScheme === "light" ? "dark" : "light" ) } aria-label="Toggle color scheme"> 用户信息 {userName || "-"} 操作 } onClick={handleLogout}> 退出登录 {activeHeaderMenu?.items?.map((item) => { if (item.items?.length) { if (matches) { if (isOpen) { return ( ) } else { return ( ) } label={item.title} /> {item.items?.map((sub) => renderMenuItem( sub, `${activeHeaderMenu.url}/${item.url}` ) )} ) } } else { return ( ) } } else { return ( { toggleMobile() router.push(`/${activeHeaderMenu.url}/${item.url}`) }} noWrap={true} active={pathname.startsWith( `/${activeHeaderMenu.url}/${item.url}` )} leftSection={ } label={item.title} /> ) } })} updateIsOpen(!isOpen)} size="sm" visibleFrom="sm" mr={isOpen ? "sm" : undefined} /> }>{children} ) ) : null }