import { useState, useEffect } from 'react' import { useNavigate, useLocation } from 'react-router-dom' import { Layout as AntLayout, Menu, Drawer, Button, Modal } from 'antd' import { useTranslation } from 'react-i18next' import { useMediaQuery } from 'react-responsive' import { WalletOutlined, UserOutlined, UnorderedListOutlined, BarChartOutlined, MenuOutlined, FileTextOutlined, LinkOutlined, AppstoreOutlined, TeamOutlined, LogoutOutlined, SettingOutlined, GithubOutlined, TwitterOutlined, GlobalOutlined, CheckCircleOutlined, NotificationOutlined, KeyOutlined } from '@ant-design/icons' import type { MenuProps } from 'antd' import type { ReactNode } from 'react' import { removeToken } from '../utils' import { wsManager } from '../services/websocket' import Logo from './Logo' const { Header, Content, Sider } = AntLayout interface LayoutProps { children: ReactNode } const Layout: React.FC = ({ children }) => { const { t } = useTranslation() const navigate = useNavigate() const location = useLocation() const isMobile = useMediaQuery({ maxWidth: 768 }) const [mobileMenuOpen, setMobileMenuOpen] = useState(false) // 获取当前选中的菜单项 const getSelectedKeys = (): string[] => { return [location.pathname] } // 获取当前应该打开的父菜单 const getInitialOpenKeys = (): string[] => { const path = location.pathname const keys: string[] = [] if (path.startsWith('/leaders') || path.startsWith('/templates') || path.startsWith('/copy-trading')) { keys.push('/copy-trading-management') } if (path.startsWith('/system-settings')) { keys.push('/system-settings') } return keys } const [openKeys, setOpenKeys] = useState(getInitialOpenKeys()) // 当路径变化时,自动打开对应的父菜单 useEffect(() => { const path = location.pathname const keys: string[] = [] if (path.startsWith('/leaders') || path.startsWith('/templates') || path.startsWith('/copy-trading')) { keys.push('/copy-trading-management') } if (path.startsWith('/system-settings')) { keys.push('/system-settings') } setOpenKeys(keys) }, [location.pathname]) const menuItems: MenuProps['items'] = [ { key: '/accounts', icon: , label: t('menu.accounts') }, { key: '/copy-trading-management', icon: , label: t('menu.copyTrading'), children: [ { key: '/copy-trading', icon: , label: t('menu.copyTradingConfig') }, { key: '/leaders', icon: , label: t('menu.leaders') }, { key: '/templates', icon: , label: t('menu.templates') } ] }, { key: '/positions', icon: , label: t('menu.positions') }, { key: '/statistics', icon: , label: t('menu.statistics') }, { key: '/users', icon: , label: t('menu.users') }, { key: '/system-settings', icon: , label: t('menu.systemSettings'), children: [ { key: '/system-settings', icon: , label: t('menu.systemOverview') || '概览' }, { key: '/system-settings/language', icon: , label: t('menu.language') }, { key: '/system-settings/api-health', icon: , label: t('menu.apiHealth') }, { key: '/system-settings/proxy', icon: , label: t('menu.proxy') }, { key: '/system-settings/builder-api-key', icon: , label: t('menu.builderApiKey') || 'Builder API Key' }, { key: '/system-settings/notifications', icon: , label: t('menu.notifications') } ] }, { key: 'logout', icon: , label: t('menu.logout') } ] const handleLogout = () => { removeToken() // 断开 WebSocket 连接 wsManager.disconnect() navigate('/login', { replace: true }) } const handleLogoutConfirm = () => { Modal.confirm({ title: t('menu.logoutConfirm'), content: t('menu.logoutConfirmDesc'), okText: t('common.confirm'), cancelText: t('common.cancel'), onOk: () => { handleLogout() if (isMobile) { setMobileMenuOpen(false) } } }) } const handleMenuClick = ({ key }: { key: string }) => { // 如果是父菜单,不导航(但 /system-settings 作为子菜单项时可以导航) if (key === '/copy-trading-management') { return } // 处理退出登录 if (key === 'logout') { handleLogoutConfirm() return } navigate(key) if (isMobile) { setMobileMenuOpen(false) } } const handleOpenChange = (keys: string[]) => { setOpenKeys(keys) } if (isMobile) { // 移动端布局 return (
{children} setMobileMenuOpen(false)} open={mobileMenuOpen} bodyStyle={{ padding: 0 }} > ) } // 桌面端布局 return (
PolyHermes
{children} ) } export default Layout