import { useEffect, useState } from 'react'; import { Layout, Menu, Avatar, Space, Typography, Button, Modal, Dropdown } from 'antd'; import type { MenuProps } from 'antd'; import { DashboardOutlined, UserOutlined, LogoutOutlined, CloudServerOutlined, TeamOutlined, ApiOutlined, KeyOutlined, MenuFoldOutlined, MenuUnfoldOutlined, SwapOutlined, SunOutlined, MoonOutlined, DesktopOutlined, GiftOutlined, ShoppingCartOutlined, ApartmentOutlined, MobileOutlined, BookOutlined, TrophyOutlined, SafetyCertificateOutlined, } from '@ant-design/icons'; import { useNavigate, useLocation, Outlet } from 'react-router-dom'; import { getUser, clearAuth, type AuthUser } from '../store/auth'; import { authApi } from '../api/modules'; import { type ThemeMode } from '../store/theme'; import { useTheme } from '../store/useTheme'; import { usePermissions } from '../hooks/usePermissions'; import { useAppInfo } from '../hooks/useAppInfo'; const { Sider, Content } = Layout; const { Text } = Typography; const ROLE_LABELS: Record = { super_admin: '超级管理员', operation: '运营', support: '客服', }; const THEME_OPTIONS: { key: ThemeMode; label: string; icon: React.ReactNode }[] = [ { key: 'light', label: '亮色', icon: }, { key: 'dark', label: '暗色', icon: }, { key: 'system', label: '跟随系统', icon: }, ]; export default function MainLayout({ onLogout }: { onLogout?: () => void }) { const navigate = useNavigate(); const location = useLocation(); const [user] = useState(getUser()); const [collapsed, setCollapsed] = useState(false); const { mode, isDark, setMode } = useTheme(); const { can, canAny } = usePermissions(user); const appInfo = useAppInfo(); useEffect(() => { if (!user) navigate('/login'); }, [user, navigate]); if (!user) return null; const menuItems: MenuProps['items'] = []; const douyuItems: NonNullable = []; const huyaItems: NonNullable = []; const systemItems: NonNullable = []; // Dashboard - 所有人可见 menuItems.push({ key: '/', label: '概览', icon: }); // 斗鱼 if (canAny(['account:view_all', 'account:view_assigned'])) { douyuItems.push({ key: '/accounts', label: '账号管理', icon: }); } if (canAny(['account:check', 'login:batch'])) { douyuItems.push({ key: '/account-check', label: '账号检测', icon: }); } if (can('account:assign')) { douyuItems.push({ key: '/assignments', label: '分配管理', icon: }); } if (canAny(['login:batch', 'login:view_all'])) { douyuItems.push({ key: '/login-tasks', label: '登录任务', icon: }); } if (can('cookie:view')) { douyuItems.push({ key: '/cookies', label: 'Cookie 管理', icon: }); } if (can('douyu:task')) { douyuItems.push({ key: '/douyu/elite', label: '精英宝典', icon: }); douyuItems.push({ key: '/douyu/esports', label: '电竞手册', icon: }); } // 虎牙 if (canAny(['huya:account', 'huya:view_all', 'huya:view_assigned'])) { huyaItems.push({ key: '/huya/accounts', label: '账号管理', icon: }); } if (canAny(['huya:account', 'huya:import'])) { huyaItems.push({ key: '/huya/register', label: '自动注册', icon: }); } if (canAny(['huya:account', 'huya:assign'])) { huyaItems.push({ key: '/huya/assignments', label: '分配管理', icon: }); } if (canAny(['huya:account', 'huya:cookie:view', 'huya:cookie:export'])) { huyaItems.push({ key: '/huya/cookies', label: 'Cookie 管理', icon: }); } if (can('huya:task')) { huyaItems.push({ key: '/huya/tasks', label: '任务操作台', icon: }); } if (douyuItems.length > 0) { menuItems.push({ key: 'group-douyu', type: 'group', label: '斗鱼', children: douyuItems }); } if (huyaItems.length > 0) { menuItems.push({ key: 'group-huya', type: 'group', label: '虎牙', children: huyaItems }); } // 系统 if (can('proxy:manage')) { systemItems.push({ key: '/proxy', label: '代理配置', icon: }); } if (can('user:view')) { systemItems.push({ key: '/users', label: '用户管理', icon: }); } if (systemItems.length > 0) { menuItems.push({ key: 'group-system', type: 'group', label: '系统', children: systemItems }); } const handleLogout = () => { Modal.confirm({ title: '确认退出', content: '确定要退出登录吗?', okText: '退出', cancelText: '取消', okButtonProps: { type: 'primary' }, centered: true, onOk: async () => { try { await authApi.logout(); } catch { // 忽略服务端登出失败,仍继续清理本地登录态。 } clearAuth(); onLogout?.(); navigate('/login', { replace: true }); }, }); }; const siderTheme = isDark ? 'dark' : 'light'; const siderTextColor = isDark ? '#fff' : undefined; const secondaryTextColor = isDark ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,0.45)'; const versionText = appInfo?.version ? `v${appInfo.version}` : 'v--'; return (
{collapsed ? : '直播运营后台'}
navigate(key)} style={{ flex: 1, overflow: 'auto', marginTop: 8 }} />
} size="small" /> {!collapsed && ( <> {user.username} ({ROLE_LABELS[user.role] || user.role}) )} {!collapsed && ( 当前版本 {versionText} )} {collapsed ? ( <> ({ key: o.key, label: o.label, icon: o.icon, onClick: () => setMode(o.key) })) }} trigger={['click']} > )}
); }