import { useState, useCallback, useEffect, useMemo } from 'react' import { Outlet, useNavigate, useLocation } from 'react-router-dom' import { DashboardOutlined, TeamOutlined, CloudServerOutlined, BarChartOutlined, SwapOutlined, SettingOutlined, FileTextOutlined, MessageOutlined, RobotOutlined, LogoutOutlined, MenuFoldOutlined, MenuUnfoldOutlined, SunOutlined, MoonOutlined, ApiOutlined, } from '@ant-design/icons' import { Avatar, Dropdown, Tooltip, Drawer } from 'antd' import { useAuthStore } from '@/stores/authStore' import { useThemeStore, setThemeMode } from '@/stores/themeStore' import type { ReactNode } from 'react' // ============================================================ // Navigation Configuration // ============================================================ interface NavItem { path: string name: string icon: ReactNode permission?: string group: string } const navItems: NavItem[] = [ { path: '/', name: '仪表盘', icon: , group: '核心' }, { path: '/accounts', name: '账号管理', icon: , permission: 'account:admin', group: '资源管理' }, { path: '/model-services', name: '模型服务', icon: , permission: 'provider:manage', group: '资源管理' }, { path: '/agent-templates', name: 'Agent 模板', icon: , permission: 'model:read', group: '资源管理' }, { path: '/api-keys', name: 'API 密钥', icon: , permission: 'provider:manage', group: '资源管理' }, { path: '/usage', name: '用量统计', icon: , permission: 'admin:full', group: '运维' }, { path: '/relay', name: '中转任务', icon: , permission: 'relay:use', group: '运维' }, { path: '/logs', name: '操作日志', icon: , permission: 'admin:full', group: '运维' }, { path: '/config', name: '系统配置', icon: , permission: 'config:read', group: '系统' }, { path: '/prompts', name: '提示词管理', icon: , permission: 'prompt:read', group: '系统' }, ] // ============================================================ // Sidebar Component // ============================================================ function Sidebar({ collapsed, onNavigate, activePath, }: { collapsed: boolean onNavigate: (path: string) => void activePath: string }) { const { hasPermission } = useAuthStore() const visibleItems = navItems.filter( (item) => !item.permission || hasPermission(item.permission), ) const groups = useMemo(() => { const map = new Map() for (const item of visibleItems) { const list = map.get(item.group) || [] list.push(item) map.set(item.group, list) } return map }, [visibleItems]) return ( {/* Logo */} Z {!collapsed && ( ZCLAW )} {/* Navigation Items */} {Array.from(groups.entries()).map(([groupName, items]) => ( {!collapsed && ( {groupName} )} {items.map((item) => { const isActive = item.path === '/' ? activePath === '/' : activePath.startsWith(item.path) const btn = ( onNavigate(item.path)} className={` w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-150 ease-in-out cursor-pointer border-none bg-transparent ${ isActive ? 'text-brand-purple bg-brand-purple/8 dark:text-brand-purple dark:bg-brand-purple/12' : 'text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-neutral-100 hover:bg-neutral-100 dark:hover:bg-neutral-800' } ${collapsed ? 'justify-center' : ''} `} aria-current={isActive ? 'page' : undefined} > {item.icon} {!collapsed && {item.name}} {isActive && ( )} ) return collapsed ? ( {btn} ) : ( {btn} ) })} ))} {/* Bottom section */} {!collapsed && ( ZCLAW Admin v2 )} ) } // ============================================================ // Mobile Drawer Sidebar // ============================================================ function MobileDrawer({ open, onClose, onNavigate, activePath, }: { open: boolean onClose: () => void onNavigate: (path: string) => void activePath: string }) { return ( ) } // ============================================================ // Breadcrumb // ============================================================ const breadcrumbMap: Record = { '/': '仪表盘', '/accounts': '账号管理', '/model-services': '模型服务', '/providers': '模型服务', '/models': '模型服务', '/api-keys': 'API 密钥', '/agent-templates': 'Agent 模板', '/usage': '用量统计', '/relay': '中转任务', '/config': '系统配置', '/prompts': '提示词管理', '/logs': '操作日志', } // ============================================================ // Main Layout // ============================================================ export default function AdminLayout() { const navigate = useNavigate() const location = useLocation() const { account, logout } = useAuthStore() const themeState = useThemeStore() const [collapsed, setCollapsed] = useState(false) const [mobileOpen, setMobileOpen] = useState(false) const [isMobile, setIsMobile] = useState(false) // Responsive detection useEffect(() => { const mq = window.matchMedia('(max-width: 768px)') setIsMobile(mq.matches) const handler = (e: MediaQueryListEvent) => setIsMobile(e.matches) mq.addEventListener('change', handler) return () => mq.removeEventListener('change', handler) }, []) const handleNavigate = useCallback( (path: string) => { navigate(path) setMobileOpen(false) }, [navigate], ) const handleLogout = useCallback(() => { logout() navigate('/login', { replace: true }) }, [logout, navigate]) const toggleTheme = useCallback(() => { setThemeMode(themeState.resolved === 'dark' ? 'light' : 'dark') }, [themeState.resolved]) const currentPage = breadcrumbMap[location.pathname] || '页面' return ( {/* Desktop Sidebar */} {!isMobile && ( )} {/* Mobile Drawer */} {isMobile && ( setMobileOpen(false)} onNavigate={handleNavigate} activePath={location.pathname} /> )} {/* Main Area */} {/* Header */} {/* Mobile menu button */} {isMobile && ( setMobileOpen(true)} className="p-2 rounded-lg text-neutral-600 dark:text-neutral-400 hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors" aria-label="打开菜单" > )} {/* Collapse toggle (desktop) */} {!isMobile && ( setCollapsed(!collapsed)} className="p-2 rounded-lg text-neutral-600 dark:text-neutral-400 hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors" aria-label={collapsed ? '展开侧边栏' : '收起侧边栏'} > {collapsed ? ( ) : ( )} )} {/* Breadcrumb */} ZCLAW / {currentPage} {/* Right actions */} {/* Theme toggle */} {themeState.resolved === 'dark' ? ( ) : ( )} {/* User avatar */} , label: '退出登录', onClick: handleLogout, }, ], }} > {(account?.display_name || account?.username || 'A')[0].toUpperCase()} {!isMobile && ( {account?.display_name || account?.username || 'Admin'} )} {/* Content */} ) }