diff --git a/web/src/components/Layout.tsx b/web/src/components/Layout.tsx index 4e7e46d..5f08883 100644 --- a/web/src/components/Layout.tsx +++ b/web/src/components/Layout.tsx @@ -1,8 +1,10 @@ import { Outlet, useLocation, useNavigate } from '@tanstack/react-router' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { Activity, Bell, + ChevronLeft, + ChevronRight, FileCode, Globe, KeyRound, @@ -12,17 +14,20 @@ import { Menu, Moon, Network, + PanelLeftClose, + PanelLeftOpen, ScrollText, Server, Settings, Sun, Terminal, + UserRound, Variable, X, } from 'lucide-react' import { useTheme } from '@/context/ThemeContext' import { api } from '@/api' -import { usePublicSite, useSentence } from '@/api/hooks' +import { usePublicSite, useSentence, useUser } from '@/api/hooks' import { NodeSwitcher } from '@/components/business/NodeSwitcher' import { SystemNotice } from '@/components/business/SystemNotice' import { ToastHost } from '@/components/business/ToastHost' @@ -46,6 +51,8 @@ const NAV_ITEMS = [ { path: '/settings', icon: Settings, label: '系统设置', exact: true }, ] as const +const COLLAPSE_KEY = 'taskpool.sidebar.collapsed' + function isActive(pathname: string, path: string, exact: boolean) { if (exact) return pathname === path return pathname === path || pathname.startsWith(path + '/') @@ -57,11 +64,23 @@ export default function Layout() { const { theme, toggleTheme } = useTheme() const { data: site } = usePublicSite() const { data: sentenceData } = useSentence() + const { data: user } = useUser() const [menuOpen, setMenuOpen] = useState(false) const [isMobile, setIsMobile] = useState(false) + const [collapsed, setCollapsed] = useState(() => { + try { + return localStorage.getItem(COLLAPSE_KEY) === '1' + } catch { + return false + } + }) + const [userMenuOpen, setUserMenuOpen] = useState(false) + const userMenuRef = useRef(null) const title = site?.title || '任务池' const sentence = sentenceData?.sentence || '欢迎使用任务池' + const username = user?.username || '管理员' + const sidebarCollapsed = !isMobile && collapsed useEffect(() => { document.title = title @@ -86,12 +105,32 @@ export default function Layout() { useEffect(() => { setMenuOpen(false) + setUserMenuOpen(false) }, [location.pathname]) useEffect(() => { eventBus.init() }, []) + useEffect(() => { + try { + localStorage.setItem(COLLAPSE_KEY, collapsed ? '1' : '0') + } catch { + // ignore + } + }, [collapsed]) + + useEffect(() => { + function onDocClick(e: MouseEvent) { + if (!userMenuRef.current) return + if (!userMenuRef.current.contains(e.target as Node)) { + setUserMenuOpen(false) + } + } + document.addEventListener('mousedown', onDocClick) + return () => document.removeEventListener('mousedown', onDocClick) + }, []) + async function logout() { try { await api.auth.logout() @@ -104,103 +143,176 @@ export default function Layout() { return (
- {isMobile && menuOpen && ( -
setMenuOpen(false)} /> - )} + {isMobile && menuOpen && ( +
setMenuOpen(false)} /> + )} - + +
+
+
+ {isMobile ? ( + + ) : ( + )} +

+ {sentence} +

- - -
- -
- -
-
-
- {isMobile && ( - - )} -

- {sentence} -

-
-
- - - -
-
+ + + + {username} + -
- + {userMenuOpen && ( +
+
+
{username}
+
已登录
+
+ +
+ )} +
- - + + +
+ +
+ +
) }