NapCatQQ/packages/napcat-webui-frontend/src/layouts/default.tsx
时瑾 3c612e03ff
Some checks are pending
Build NapCat Artifacts / Build-Framework (push) Waiting to run
Build NapCat Artifacts / Build-Shell (push) Waiting to run
feat: close #1394
2025-11-24 12:47:04 +08:00

158 lines
4.8 KiB
TypeScript

import { BreadcrumbItem, Breadcrumbs } from '@heroui/breadcrumbs';
import { Button } from '@heroui/button';
import { useLocalStorage } from '@uidotdev/usehooks';
import clsx from 'clsx';
import { AnimatePresence, motion } from 'motion/react';
import { useEffect, useMemo, useRef } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { MdMenu, MdMenuOpen } from 'react-icons/md';
import { useLocation, useNavigate } from 'react-router-dom';
import key from '@/const/key';
import errorFallbackRender from '@/components/error_fallback';
// import PageLoading from "@/components/Loading/PageLoading";
import SideBar from '@/components/sidebar';
import useAuth from '@/hooks/auth';
import type { MenuItem } from '@/config/site';
import { siteConfig } from '@/config/site';
import QQManager from '@/controllers/qq_manager';
const menus: MenuItem[] = siteConfig.navItems;
const findTitle = (menus: MenuItem[], pathname: string): string[] => {
const paths: string[] = [];
if (pathname) {
for (const item of menus) {
if (item.href === pathname) {
paths.push(item.label);
} else if (item.items) {
const title = findTitle(item.items, pathname);
if (title.length > 0) {
paths.push(item.label);
paths.push(...title);
}
}
}
}
return paths;
};
const Layout: React.FC<{ children: React.ReactNode; }> = ({ children }) => {
const location = useLocation();
const contentRef = useRef<HTMLDivElement>(null);
const [openSideBar, setOpenSideBar] = useLocalStorage(key.sideBarOpen, true);
const [b64img] = useLocalStorage(key.backgroundImage, '');
const navigate = useNavigate();
const { isAuth } = useAuth();
const checkIsQQLogin = async () => {
try {
const result = await QQManager.checkQQLoginStatus();
if (!result.isLogin) {
if (isAuth) {
navigate('/qq_login', { replace: true });
} else {
navigate('/web_login', { replace: true });
}
}
} catch (_error) {
navigate('/web_login', { replace: true });
}
};
useEffect(() => {
contentRef?.current?.scrollTo?.({
top: 0,
behavior: 'smooth',
});
}, [location.pathname]);
useEffect(() => {
if (isAuth) {
checkIsQQLogin();
}
}, [isAuth]);
const title = useMemo(() => {
return findTitle(menus, location.pathname);
}, [location.pathname]);
return (
<div
className='h-screen relative flex bg-primary-50 dark:bg-black items-stretch'
style={{
backgroundImage: `url(${b64img})`,
backgroundSize: 'cover',
}}
>
<SideBar
items={menus}
open={openSideBar}
onClose={() => setOpenSideBar(false)}
/>
<div
ref={contentRef}
className={clsx(
'overflow-y-auto flex-1 rounded-md m-1 bg-content1 pb-10 md:pb-0',
openSideBar ? 'ml-0' : 'ml-1',
!b64img && 'shadow-inner',
b64img && '!bg-opacity-50 backdrop-blur-none dark:bg-background',
'overflow-x-hidden'
)}
>
<div
className={clsx(
'h-10 flex items-center font-bold text-xl backdrop-blur-lg rounded-full',
'dark:bg-background dark:shadow-primary-100',
'bg-background !bg-opacity-50',
'shadow-sm shadow-primary-50',
'z-30 m-2 mb-0 sticky top-2 left-0'
)}
>
<motion.div
className={clsx(
'mr-1 ease-in-out ml-0 md:relative z-50 md:z-auto',
openSideBar && 'pl-2 absolute',
'md:!ml-0 md:pl-0'
)}
transition={{ type: 'spring', stiffness: 150, damping: 15 }}
initial={{ marginLeft: 0 }}
animate={{ marginLeft: openSideBar ? '15rem' : 0 }}
>
<Button
isIconOnly
radius='full'
variant='light'
onPress={() => setOpenSideBar(!openSideBar)}
>
{openSideBar ? <MdMenuOpen size={24} /> : <MdMenu size={24} />}
</Button>
</motion.div>
<Breadcrumbs isDisabled size='lg'>
{title?.map((item, index) => (
<BreadcrumbItem key={index}>
<AnimatePresence mode='wait'>
<motion.div
key={item}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.3 }}
>
{item}
</motion.div>
</AnimatePresence>
</BreadcrumbItem>
))}
</Breadcrumbs>
</div>
<ErrorBoundary fallbackRender={errorFallbackRender}>
{children}
</ErrorBoundary>
</div>
</div>
);
};
export default Layout;