import React, { useState, useEffect, useMemo } from 'react'; import { Layout, Menu, Button, Avatar, Space, Drawer, Grid, Badge, Breadcrumb, Tooltip } from 'antd'; import { UserOutlined, LogoutOutlined, MenuFoldOutlined, MenuUnfoldOutlined, RightOutlined, } from '@ant-design/icons'; import { useNavigate, useLocation } from 'react-router-dom'; import menuService from '../services/menuService'; import { renderMenuIcon } from '../utils/menuIcons'; import configService, { DEFAULT_BRANDING_CONFIG } from '../utils/configService'; const { Header, Content, Sider } = Layout; const { useBreakpoint } = Grid; const MainLayout = ({ children, user, onLogout }) => { const [collapsed, setCollapsed] = useState(false); const [userMenus, setUserMenus] = useState([]); const [drawerOpen, setDrawerOpen] = useState(false); const [openKeys, setOpenKeys] = useState([]); const [activeMenuKey, setActiveMenuKey] = useState(null); const [branding, setBranding] = useState(DEFAULT_BRANDING_CONFIG); const navigate = useNavigate(); const location = useLocation(); const screens = useBreakpoint(); const isMobile = !screens.lg; useEffect(() => { const fetchMenus = async () => { try { const response = await menuService.getUserMenus(); if (response.code === '200') { setUserMenus(response.data.menus || []); } } catch (error) { console.error('Error fetching menus:', error); } }; fetchMenus(); }, []); useEffect(() => { configService.getBrandingConfig().then(setBranding).catch(() => {}); }, []); useEffect(() => { if (isMobile) { setCollapsed(false); } }, [isMobile]); const menuItems = useMemo(() => { const sortedMenus = [...userMenus] .sort((a, b) => { if ((a.parent_id || 0) !== (b.parent_id || 0)) { return (a.parent_id || 0) - (b.parent_id || 0); } if ((a.sort_order || 0) !== (b.sort_order || 0)) { return (a.sort_order || 0) - (b.sort_order || 0); } return a.menu_id - b.menu_id; }); const menuById = new Map(); sortedMenus.forEach((menu) => { menuById.set(menu.menu_id, { ...menu, key: `menu_${menu.menu_id}`, icon: renderMenuIcon(menu.menu_icon, menu.menu_code), children: [], }); }); const roots = []; menuById.forEach((menu) => { if (menu.parent_id && menuById.has(menu.parent_id)) { menuById.get(menu.parent_id).children.push(menu); } else { roots.push(menu); } }); const toMenuItem = (menu) => { const hasChildren = menu.children.length > 0; return { key: menu.key, menu_code: menu.menu_code, icon: menu.icon, label: menu.menu_name, path: hasChildren ? null : menu.menu_url, children: hasChildren ? menu.children.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)).map(toMenuItem) : undefined, onClick: () => { if (menu.menu_type === 'link' && menu.menu_url && !hasChildren) { setActiveMenuKey(`menu_${menu.menu_id}`); navigate(menu.menu_url); setDrawerOpen(false); } }, }; }; return roots .sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)) .map(toMenuItem); }, [navigate, onLogout, userMenus]); const flatMenuKeys = useMemo(() => { const keys = []; const walk = (items) => { items.forEach((item) => { keys.push(item.key); if (item.children?.length) { walk(item.children); } }); }; walk(menuItems); return keys; }, [menuItems]); const pathKeyEntries = useMemo(() => { const entries = []; const walk = (items) => { items.forEach((item) => { if (item.path && String(item.path).startsWith('/')) { entries.push({ path: item.path, key: item.key, menuCode: item.menu_code, label: item.label }); } if (item.children?.length) { walk(item.children); } }); }; walk(menuItems); return entries; }, [menuItems]); const keyParentMap = useMemo(() => { const map = new Map(); const walk = (items, parentKey = null) => { items.forEach((item) => { if (parentKey) { map.set(item.key, parentKey); } if (item.children?.length) { walk(item.children, item.key); } }); }; walk(menuItems); return map; }, [menuItems]); const menuItemByKey = useMemo(() => { const map = new Map(); const walk = (items) => { items.forEach((item) => { map.set(item.key, item); if (item.children?.length) { walk(item.children); } }); }; walk(menuItems); return map; }, [menuItems]); useEffect(() => { if (activeMenuKey && !flatMenuKeys.includes(activeMenuKey)) { setActiveMenuKey(null); } }, [activeMenuKey, flatMenuKeys]); const selectedMenuKey = useMemo(() => { if (!pathKeyEntries.length) return []; const exactMatches = pathKeyEntries.filter((item) => item.path === location.pathname); if (exactMatches.length) { if (activeMenuKey && exactMatches.some((item) => item.key === activeMenuKey)) { return [activeMenuKey]; } return [exactMatches[0].key]; } const prefixMatches = [...pathKeyEntries] .sort((a, b) => String(b.path).length - String(a.path).length) .filter((item) => location.pathname.startsWith(String(item.path))); if (prefixMatches.length) { if (activeMenuKey && prefixMatches.some((item) => item.key === activeMenuKey)) { return [activeMenuKey]; } return [prefixMatches[0].key]; } return []; }, [activeMenuKey, location.pathname, pathKeyEntries]); useEffect(() => { const currentKey = selectedMenuKey[0]; if (!currentKey) return; const ancestors = []; const visited = new Set(); let cursor = keyParentMap.get(currentKey); while (cursor && !visited.has(cursor)) { visited.add(cursor); ancestors.unshift(cursor); cursor = keyParentMap.get(cursor); } setOpenKeys((prev) => { const next = Array.from(new Set([...prev, ...ancestors])); if (next.length === prev.length && next.every((item, index) => item === prev[index])) { return prev; } return next; }); }, [keyParentMap, selectedMenuKey]); const breadcrumbItems = useMemo(() => { const create = (title, path) => ({ title, path }); const path = location.pathname; const activeMenuItem = selectedMenuKey[0] ? menuItemByKey.get(selectedMenuKey[0]) : null; if (path === '/dashboard') return [create(activeMenuItem?.label || '工作台')]; if (path === '/prompt-management') return [create('平台管理', '/admin/management'), create('提示词仓库')]; if (path === '/prompt-config') { return user?.role_id === 1 ? [create('平台管理', '/admin/management'), create('提示词配置')] : [create('会议管理'), create('提示词配置')]; } if (path === '/personal-prompts') return [create('会议管理'), create('个人提示词仓库')]; if (path === '/meetings/center' || path === '/meetings/history') return [create('会议管理'), create('会议中心')]; if (path === '/knowledge-base') return [create('知识库')]; if (path.startsWith('/knowledge-base/edit/')) return [create('知识库', '/knowledge-base'), create('编辑知识库')]; if (path === '/account-settings') return [create('个人设置')]; if (path.startsWith('/meetings/')) return [create('会议管理', '/meetings/center'), create('会议详情')]; if (path === '/downloads') return [create('客户端下载')]; if (path === '/admin/management') { return [create('平台管理')]; } if (path.startsWith('/admin/management/')) { const moduleKey = path.split('/')[3]; const moduleNameMap = { 'user-management': '用户管理', 'permission-management': '权限管理', 'dict-management': '字典管理', 'hot-word-management': '热词管理', 'client-management': '客户端管理', 'external-app-management': '外部应用管理', 'terminal-management': '终端管理', 'parameter-management': '参数管理', 'model-management': '模型管理', }; const systemModules = new Set(['user-management', 'permission-management', 'dict-management', 'parameter-management']); const parentTitle = systemModules.has(moduleKey) ? '系统管理' : '平台管理'; return [create(parentTitle, '/admin/management'), create(moduleNameMap[moduleKey] || parentTitle)]; } return [create('当前页面')]; }, [location.pathname, menuItemByKey, selectedMenuKey, user?.role_id]); const sidebar = ( <>