| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607 |
- import React, { useState, useMemo, useEffect, useRef, useCallback } from 'react';
- import { useNavigate, useLocation } from 'react-router-dom';
- import { useTranslation } from 'react-i18next';
- import { Shield, Settings, Users, Cpu, MapPin, Layers, Bell, User, LogOut, ChevronDown, Activity, Radio, Lock, AlertCircle, CheckCircle, Clock, Menu, Building2, UserCog, BookOpen, Server, Globe, Gauge, Briefcase, MessageSquare, FileText, FolderTree, KeyRound, HardDrive, Database, Network, Workflow, ClipboardList, Wrench, Archive, Package, Box, Grid3x3, List, LayoutGrid, FolderOpen, FileCheck, FileEdit, FileSearch } from 'lucide-react';
- import SystemConfig from './components/SystemConfig';
- import UserManagement from './components/UserManagement';
- import HardwareManagement from './components/HardwareManagement';
- import SegregationPointManagement from './components/SegregationPointManagement';
- import IsolationWork from './components/IsolationWork';
- import ProfileSettings from './components/ProfileSettings';
- import DashboardComponent from './components/Dashboard';
- import ExecutorDashboard from './components/ExecutorDashboard';
- import NotificationManagement from './components/NotificationManagement';
- import FormManagement from './components/FormManagement';
- import MyTask from './components/MyTask';
- import TaskManagement from './components/TaskManagement';
- import MessageNotification from './components/MessageNotification';
- import { authApi } from './api';
- import { toast } from 'sonner';
- import { Toaster } from 'sonner';
- import { env } from './utils/env';
- import { getMenus, hasMenuPathPermission, mapMenuPathToKey, getPermissionUser, hasRole } from './utils/permission';
- import { Dropdown } from 'antd';
- import type { MenuProps } from 'antd';
- export default function Dashboard() {
- const navigate = useNavigate();
- const location = useLocation();
- const { t, i18n } = useTranslation();
-
- // 监听语言变化,强制组件重新渲染
- useEffect(() => {
- // 当语言切换时,这个 effect 会重新执行,从而触发组件更新
- }, [i18n.language]);
-
- // 从 URL hash 解析菜单状态
- const parseHashToMenuState = (hash: string): { menu: string; subMenu: string } | null => {
- if (!hash || hash === '#') return null;
- const cleanHash = hash.replace(/^#/, '');
- const parts = cleanHash.split('-');
- if (parts.length >= 2) {
- return { menu: parts[0], subMenu: parts.slice(1).join('-') };
- } else if (parts.length === 1 && parts[0]) {
- return { menu: parts[0], subMenu: '' };
- }
- return null;
- };
-
- // 初始化时从 hash 读取菜单状态
- const initialMenuState = parseHashToMenuState(window.location.hash);
- const [activeMenu, setActiveMenu] = useState(initialMenuState?.menu || 'dashboard');
- const [activeSubMenu, setActiveSubMenu] = useState(initialMenuState?.subMenu || 'menuManagement');
-
- // 更新 URL hash 的函数(不触发页面刷新)
- const updateUrlHash = useCallback((menu: string, subMenu: string) => {
- const newHash = subMenu ? `#${menu}-${subMenu}` : `#${menu}`;
- // 使用 pushState 添加历史记录,这样浏览器回退按钮可以工作
- if (window.location.hash !== newHash) {
- window.history.pushState(null, '', newHash);
- }
- }, []);
-
- // 封装 setActiveMenu 和 setActiveSubMenu,同时更新 URL hash
- const handleMenuChange = useCallback((menu: string, subMenu: string) => {
- setActiveMenu(menu);
- setActiveSubMenu(subMenu);
- updateUrlHash(menu, subMenu);
- }, [updateUrlHash]);
-
- // 监听浏览器回退/前进按钮(popstate 事件)
- useEffect(() => {
- const handlePopState = () => {
- const menuState = parseHashToMenuState(window.location.hash);
- if (menuState) {
- console.log('浏览器回退/前进,恢复菜单状态:', menuState);
- setActiveMenu(menuState.menu);
- setActiveSubMenu(menuState.subMenu);
- } else {
- // 如果没有 hash,恢复到默认状态(dashboard)
- setActiveMenu('dashboard');
- setActiveSubMenu('');
- }
- };
-
- window.addEventListener('popstate', handlePopState);
- return () => {
- window.removeEventListener('popstate', handlePopState);
- };
- }, []);
-
- const [showUserMenu, setShowUserMenu] = useState(false);
- const [showDropdownMenu, setShowDropdownMenu] = useState<string | null>(null); // 当前显示下拉菜单的菜单key
- const [dropdownTimer, setDropdownTimer] = useState<NodeJS.Timeout | null>(null);
- const [showProfileSettings, setShowProfileSettings] = useState(false);
- const [avatarError, setAvatarError] = useState(false);
- // 获取用户信息
- const userInfo = useMemo(() => getPermissionUser(), []);
- // 切换语言
- const changeLanguage = (lang: 'zh' | 'en') => {
- i18n.changeLanguage(lang).then(() => {
- // 语言切换后,强制组件更新以确保菜单重新渲染
- window.dispatchEvent(new Event('languagechange'));
- });
- };
- // 语言下拉菜单项
- const languageMenuItems: MenuProps['items'] = [
- {
- key: 'zh',
- label: '中文',
- onClick: () => changeLanguage('zh'),
- },
- {
- key: 'en',
- label: 'English',
- onClick: () => changeLanguage('en'),
- },
- ];
- // 登出处理
- const handleLogout = async () => {
- try {
- await authApi.logout();
- localStorage.removeItem('token');
- localStorage.removeItem('tenant');
- // 清除所有菜单相关的 sessionStorage
- sessionStorage.removeItem('lastActiveMenu');
- sessionStorage.removeItem('navigateToMenu');
- sessionStorage.removeItem('cabinetDetailSource');
- // 退出登录不再弹出全局成功提示(避免频繁/停留过久影响体验)
- navigate('/login');
- } catch (error: any) {
- // 即使API失败也清除本地数据并跳转
- localStorage.removeItem('token');
- localStorage.removeItem('tenant');
- // 清除所有菜单相关的 sessionStorage
- sessionStorage.removeItem('lastActiveMenu');
- sessionStorage.removeItem('navigateToMenu');
- sessionStorage.removeItem('cabinetDetailSource');
- navigate('/login');
- }
- };
- // 后端菜单路径到前端菜单key的映射函数
- const mapBackendPathToFrontendKey = (path: string): string | null => {
- // ===== 通知管理子菜单:优先兜底映射(有些后端路径不包含 /notification)=====
- // App 通知
- if (path && (path.includes('app-notify-message') || path.includes('appNotify') || path.includes('app_notify'))) {
- return 'app';
- }
- // 站内信
- if (path && (path.includes('notify-message') || path.includes('in_site') || path.includes('in-site'))) {
- return 'webmail';
- }
- // 短信日志
- if (path && (path.includes('sms-log') || path.includes('smsLog') || path.includes('sms_log'))) {
- return 'sms';
- }
- // 邮件提醒/邮件模板(归到邮件 Tab)- 只匹配明确的通知相关邮件路径
- if (path && (
- path.includes('mail-notify') ||
- path.includes('mailNotify') ||
- path.includes('mail_notify') ||
- path.includes('email-notify') ||
- path.includes('emailNotify') ||
- path.includes('email_notify') ||
- path.includes('mail-template') ||
- path.includes('mailTemplate') ||
- path.includes('mail_template')
- )) {
- return 'email';
- }
- // 处理简短路径(不带斜杠的路径,如 "menuManagement", "dept", "post" 等)
- // 这些通常是系统配置下的子菜单路径
- if (!path.startsWith('/')) {
- // 系统配置子菜单路径
- if (path === 'menuManagement' || path === 'menu') return 'menuManagement';
- if (path === 'dept' || path === 'department') return 'departmentManagement';
- if (path === 'post' || path === 'marsdept' || path === 'position') return 'positionManagement';
- if (path === 'role') return 'roleManagement';
- if (path === 'dict' || path === 'dictionary') return 'dictionaryManagement';
- if (path === 'cabinet') return 'cabinetManagement';
-
- // 硬件管理子菜单路径
- if (path === 'hwcabinet' || path === 'cabinet') return 'cabinet';
- if (path === 'key') return 'key';
- if (path === 'lock' || path === 'padlock') return 'padlock';
- if (path === 'portability' || path === 'portable') return 'portable';
-
- // 隔离作业子菜单路径
- if (path === 'processTemplate' || path === 'processTemplate') return 'processTemplate';
- if (path === 'job') return 'workManagement';
- if (path === 'form') return 'formManagement';
- if (path === 'taskmanagement' || path === 'taskManagement') return 'taskManagement';
- if (path === 'mytask' || path === 'myTask') return 'myTask';
- if (path === 'sop') return 'sopManagement';
- }
-
- // 客户端系统路径(支持 /clientSystem/xxx 格式)
- if (path.startsWith('/clientSystem')) {
- // 隔离作业子菜单路径(优先处理)
- if (path.startsWith('/clientSystem/IsolationWork/')) {
- if (path.includes('/processDesign') || path.endsWith('/processDesign')) return 'processDesign';
- if (path.includes('/sop') || path.endsWith('/sop')) return 'sopManagement';
- if (path.includes('/job') || path.endsWith('/job')) return 'workManagement';
- if (path.includes('/form') || path.endsWith('/form')) return 'formManagement';
- return 'isolationWork';
- }
- // 其他客户端系统路径
- if (path.includes('SystemConfig') || path.endsWith('/SystemConfig')) return 'systemConfig';
- if (path.includes('UserManagement') || path.endsWith('/UserManagement')) return 'userManagement';
- if (path.includes('HardwareManagement') || path.endsWith('/HardwareManagement')) return 'hardwareManagement';
- if (path.includes('LocationManagement') || path.endsWith('/LocationManagement')) return 'locationManagement';
- if (path.includes('IsolationWork') || path.endsWith('/IsolationWork')) return 'isolationWork';
- if (path.includes('notification') || path.endsWith('/notification')) return 'notificationManagement';
- if (path.includes('TaskManagement') || path.endsWith('/TaskManagement') || path.includes('taskManagement') || path.endsWith('/taskManagement')) return 'taskManagement';
- }
-
- // 系统管理相关
- if (path === '/system' || path.startsWith('/system')) {
- if (path.includes('menu') || path.endsWith('/menu') || path.includes('/menuManagement')) return 'menuManagement';
- if (path.includes('dept') || path.endsWith('/dept') || path.includes('/department')) return 'departmentManagement';
- if (path.includes('post') || path.includes('marsdept') || path.endsWith('/post') || path.endsWith('/marsdept') || path.includes('/position')) return 'positionManagement';
- if (path.includes('role') || path.endsWith('/role')) return 'roleManagement';
- if (path.includes('dict') || path.endsWith('/dict') || path.includes('/dictionary')) return 'dictionaryManagement';
- if (path.includes('cabinet') || path.endsWith('/cabinet')) return 'cabinetManagement';
- return 'systemConfig';
- }
-
- // 用户管理相关
- if (path === '/users' || path.startsWith('/users') || path === '/user' || path.startsWith('/user')) {
- if (path.includes('list') || path.endsWith('/list')) return 'userList';
- if (path.includes('notification') || path.endsWith('/notification')) return 'notificationManagement';
- return 'userManagement';
- }
-
- // 通知管理相关(clientSystem 路径)
- if (path.includes('/notification') || path.includes('/clientSystem/notification')) {
- const p = path.toLowerCase();
- // 通知管理二级菜单(优先返回稳定 key,便于 i18n)
- if (p.includes('webmail') || p.includes('in_site') || p.includes('in-site') || p.includes('站内信')) return 'webmail';
- if (p.includes('sms') || p.includes('短信')) return 'sms';
- if (p.includes('email') || p.includes('邮件') || p.includes('mail')) return 'email';
- if (p.includes('app') || p.includes('app-notify') || p.includes('app_notify') || p.includes('app通知')) return 'app';
- return 'notificationManagement';
- }
-
- // 硬件管理相关
- if (path === '/hw' || path.startsWith('/hw') || path === '/hardware' || path.startsWith('/hardware')) {
- if (path.includes('hwcabinet') || (path.includes('cabinet') && !path.includes('system')) || path.includes('work')) return 'cabinet';
- if (path.includes('key') || path.includes('keys')) return 'key';
- if (path.includes('lock') || path.includes('lk') || path.includes('padlock')) return 'padlock';
- if (path.includes('portability') || path.includes('portable') || path.includes('rfid')) return 'portable';
- return 'hardwareManagement';
- }
-
- // 任务管理相关(优先处理,避免被隔离作业匹配)
- if (path.includes('/task-management') || path.includes('/taskManagement') || path.includes('/admin-work') || path.includes('/adminWork') || path.includes('TaskManagement')) {
- return 'taskManagement';
- }
-
- // 我的任务相关(优先处理,避免被隔离作业匹配)
- if (path.includes('/my-task') || path.includes('/myTask') || path.includes('/my-work') || path.includes('/myWork')) {
- return 'myTask';
- }
-
- // 隔离作业相关
- if (path === '/jobTicket' || path.startsWith('/jobTicket') || path === '/isolation' || path.startsWith('/isolation') || path === '/CustomWorkflow' || path.startsWith('/CustomWorkflow') || path === '/sopm' || path.startsWith('/sopm')) {
- if (path.includes('/form') || path.endsWith('/form')) return 'formManagement';
- if (path.includes('job') || path.endsWith('/job')) return 'workManagement';
- if (path.includes('sop') || path.endsWith('/sop')) return 'sopManagement';
- if (path.includes('design') || path.endsWith('/design')) return 'processDesign';
- if (path.includes('step') || path.includes('template') || path.includes('process') || path.includes('CW') || path.includes('CS')) return 'processTemplate';
- return 'isolationWork';
- }
-
- // 我的任务相关
- if (path.includes('/my-task') || path.includes('/myTask') || path.endsWith('/my-task') || path.endsWith('/myTask')) {
- return 'myTask';
- }
-
- // 点位管理
- if (path === '/points' || path.startsWith('/points') || path === '/Basicdata' || path.startsWith('/Basicdata')) {
- return 'locationManagement';
- }
-
- // 驾驶舱
- if (path === '/cockpit' || path === '/dashboard' || path === '/') {
- return 'dashboard';
- }
-
- return null;
- };
- // 图标名称到组件的映射(用于备用)
- const getIconByPath = (path: string): any => {
- if (path.includes('system')) return Settings;
- if (path.includes('user')) return Users;
- if (path.includes('hw') || path.includes('hardware')) return Cpu;
- if (path.includes('point') || path.includes('Basicdata')) return MapPin;
- if (path.includes('job') || path.includes('isolation') || path.includes('sop') || path.includes('CustomWorkflow')) return Layers;
- if (path.includes('cockpit') || path === '/') return Gauge;
- if (path.includes('infra')) return Settings;
- return Settings; // 默认
- };
- // 从后端菜单动态生成前端菜单配置
- const { mainMenus, subMenuConfig } = useMemo(() => {
- const backendMenus = getMenus();
-
- // 调试:打印后端菜单数据
- // console.log('后端菜单数据:', backendMenus);
-
- // 如果menus为空数组(接口明确返回空数组),不渲染任何菜单
- if (backendMenus !== null && Array.isArray(backendMenus) && backendMenus.length === 0) {
- // console.log('menus为空数组,不渲染任何菜单');
- return {
- mainMenus: [],
- subMenuConfig: {}
- };
- }
-
- // 如果权限信息不存在或menus字段不存在,使用默认菜单(兼容性处理)
- if (!backendMenus) {
- // console.log('权限信息不存在,使用默认菜单');
- return {
- mainMenus: [
- { key: 'dashboard', icon: Gauge, path: '/dashboard', name: '驾驶舱' },
- { key: 'systemConfig', icon: Settings, path: '/system', name: '系统配置' },
- { key: 'userManagement', icon: Users, path: '/users', name: '用户管理' },
- { key: 'hardwareManagement', icon: Cpu, path: '/hardware', name: '硬件管理' },
- { key: 'locationManagement', icon: MapPin, path: '/points', name: '点位管理' },
- { key: 'isolationWork', icon: Layers, path: '/isolation', name: '隔离作业' },
- ],
- subMenuConfig: {
- systemConfig: [
- { key: 'menuManagement', icon: Menu, path: '/system/menu', name: '菜单管理' },
- { key: 'departmentManagement', icon: Building2, path: '/system/dept', name: '部门管理' },
- { key: 'positionManagement', icon: Briefcase, path: '/system/post', name: '岗位管理' },
- { key: 'roleManagement', icon: UserCog, path: '/system/role', name: '角色管理' },
- { key: 'dictionaryManagement', icon: BookOpen, path: '/system/dict', name: '字典管理' },
- { key: 'cabinetManagement', icon: Server, path: '/system/cabinet', name: '机柜管理' },
- ],
- userManagement: [
- { key: 'userList', icon: User, path: '/users/list', name: '用户列表' },
- { key: 'notificationManagement', icon: Bell, path: '/users/notification', name: '通知管理' },
- ],
- hardwareManagement: [
- { key: 'cabinet', icon: Cpu, path: '/hardware/cabinet', name: '机柜' },
- { key: 'key', icon: Lock, path: '/hardware/key', name: '钥匙' },
- { key: 'padlock', icon: Lock, path: '/hardware/lock', name: '挂锁' },
- { key: 'portable', icon: Radio, path: '/hardware/portable', name: '便携式' },
- ],
- locationManagement: [],
- isolationWork: [
- { key: 'processTemplate', icon: Layers, path: '/isolation/approval', name: '流程模板' },
- { key: 'sopManagement', icon: BookOpen, path: '/isolation/record', name: 'SOP管理' },
- { key: 'workManagement', icon: Activity, path: '/isolation/list', name: '作业管理' },
- { key: 'processDesign', icon: Workflow, path: '/jobTicket/design', name: '流程设计' },
- { key: 'formManagement', icon: FileText, path: '/jobTicket/form', name: '表单管理' },
- ],
- }
- };
- }
- // 从后端菜单动态生成
- const mainMenuMap: { [key: string]: { key: string; icon: any; path: string; name: string } } = {};
- const subMenuMap: { [key: string]: { key: string; icon: any; path: string; name: string }[] } = {};
- const processedSubMenuKeys = new Set<string>(); // 用于去重,避免重复添加子菜单
- // 根据前端key或菜单名称获取对应的图标
- const getIconByKey = (key: string, menuName?: string): any => {
- // 首先尝试根据 key 精确匹配
- const iconMap: { [key: string]: any } = {
- // 主菜单
- 'dashboard': Gauge,
- 'systemConfig': Settings,
- 'userManagement': Users,
- 'hardwareManagement': Cpu,
- 'locationManagement': MapPin,
- 'isolationWork': Layers,
- // 系统配置子菜单
- 'menuManagement': Menu,
- 'departmentManagement': Building2,
- 'positionManagement': Briefcase,
- 'roleManagement': UserCog,
- 'dictionaryManagement': BookOpen,
- 'cabinetManagement': Server,
- // 用户管理子菜单
- 'userList': User,
- 'notificationManagement': Bell,
- // 硬件管理子菜单
- 'cabinet': Server,
- 'key': KeyRound,
- 'padlock': Lock,
- 'portable': Radio,
- // 隔离作业子菜单
- 'processTemplate': Workflow,
- 'sopManagement': ClipboardList,
- 'workManagement': Activity,
- 'processDesign': Workflow,
- 'formManagement': FileText,
- };
-
- if (iconMap[key]) {
- return iconMap[key];
- }
-
- // 如果 key 无法匹配,尝试根据菜单名称推断
- if (menuName) {
- const name = menuName.toLowerCase();
- // 根据名称关键词匹配图标
- if (name.includes('菜单') || name.includes('menu')) return Menu;
- if (name.includes('部门') || name.includes('dept')) return Building2;
- if (name.includes('岗位') || name.includes('position') || name.includes('post')) return Briefcase;
- if (name.includes('角色') || name.includes('role')) return UserCog;
- if (name.includes('字典') || name.includes('dict')) return BookOpen;
- if (name.includes('机柜') || name.includes('cabinet')) return Server;
- if (name.includes('用户') || name.includes('user')) return User;
- if (name.includes('通知') || name.includes('notification')) return Bell;
- if (name.includes('钥匙') || name.includes('key')) return KeyRound;
- if (name.includes('挂锁') || name.includes('padlock') || name.includes('lock')) return Lock;
- if (name.includes('便携') || name.includes('portable')) return Radio;
- if (name.includes('流程') || name.includes('process') || name.includes('template')) return Workflow;
- if (name.includes('sop') || name.includes('标准')) return ClipboardList;
- if (name.includes('作业') || name.includes('work') || name.includes('job')) return Activity;
- if (name.includes('列表') || name.includes('list')) return List;
- if (name.includes('管理') || name.includes('manage')) return Settings;
- if (name.includes('配置') || name.includes('config')) return Settings;
- if (name.includes('数据') || name.includes('data')) return Database;
- if (name.includes('文件') || name.includes('file')) return FileText;
- if (name.includes('文件夹') || name.includes('folder')) return FolderOpen;
- if (name.includes('网络') || name.includes('network')) return Network;
- if (name.includes('硬件') || name.includes('hardware')) return Cpu;
- if (name.includes('设备') || name.includes('device')) return HardDrive;
- if (name.includes('位置') || name.includes('location') || name.includes('point')) return MapPin;
- if (name.includes('隔离') || name.includes('isolation')) return Layers;
- }
-
- // 最后尝试根据 key 中的关键词推断
- const keyLower = key.toLowerCase();
- if (keyLower.includes('menu')) return Menu;
- if (keyLower.includes('dept') || keyLower.includes('department')) return Building2;
- if (keyLower.includes('position') || keyLower.includes('post')) return Briefcase;
- if (keyLower.includes('role')) return UserCog;
- if (keyLower.includes('dict')) return BookOpen;
- if (keyLower.includes('cabinet')) return Server;
- if (keyLower.includes('user')) return User;
- if (keyLower.includes('notification')) return Bell;
- if (keyLower.includes('key')) return KeyRound;
- if (keyLower.includes('lock') || keyLower.includes('padlock')) return Lock;
- if (keyLower.includes('portable')) return Radio;
- if (keyLower.includes('process') || keyLower.includes('template')) return Workflow;
- if (keyLower.includes('sop')) return ClipboardList;
- if (keyLower.includes('work') || keyLower.includes('job')) return Activity;
- if (keyLower.includes('list')) return List;
- if (keyLower.includes('hardware') || keyLower.includes('hw')) return Cpu;
- if (keyLower.includes('location') || keyLower.includes('point')) return MapPin;
- if (keyLower.includes('isolation')) return Layers;
-
- // 默认使用更合适的图标而不是 Settings
- return Layers; // 使用 Layers 作为默认图标,比 Settings 更通用
- };
- // 递归处理菜单
- const processMenu = (menu: any, parentKey: string | null = null, isClientParent: boolean = false) => {
- // 检查是否是客户端菜单(当前菜单包含"客户端"或者是客户端菜单的子菜单)
- const isClientMenu = menu.name && menu.name.includes('客户端');
- const shouldProcess = isClientMenu || isClientParent;
-
- // 对于客户端菜单,即使 visible 为 false 也显示;对于非客户端菜单,如果 visible 为 false 则跳过
- if (!shouldProcess && menu.visible === false) {
- // 如果不是客户端菜单且不可见,跳过
- if (menu.children && menu.children.length > 0) {
- menu.children.forEach((child: any) => {
- processMenu(child, parentKey, false);
- });
- }
- return;
- }
-
- // 调试:打印菜单处理信息
- // console.log('处理菜单:', {
- // name: menu.name,
- // path: menu.path,
- // parentId: menu.parentId,
- // isClientMenu,
- // isClientParent,
- // shouldProcess
- // });
-
- // 只处理客户端菜单或其子菜单
- if (!shouldProcess) {
- // 如果不是客户端菜单,但可能有子菜单是客户端菜单,继续递归处理子菜单
- if (menu.children && menu.children.length > 0) {
- menu.children.forEach((child: any) => {
- processMenu(child, parentKey, false);
- });
- }
- return;
- }
-
- // 尝试映射到已知的key,如果无法映射则使用path作为key(支持客户端菜单)
- let frontendKey: string = mapBackendPathToFrontendKey(menu.path) || '';
- if (!frontendKey) {
- // 如果无法映射,使用path作为key(去除特殊字符)
- frontendKey = menu.path.replace(/[^a-zA-Z0-9]/g, '_') || `menu_${menu.id}`;
- }
-
- // 获取图标组件 - 使用原来的图标配置,根据key来映射
- const IconComponent = getIconByKey(frontendKey);
-
- // 如果是顶级菜单(parentId === 0)
- // 排除通知管理,它应该显示在右侧功能区
- if (menu.parentId === 0 && frontendKey !== 'notificationManagement') {
- if (!mainMenuMap[frontendKey]) {
- // 去掉"客户端-"前缀
- const displayName = menu.name.replace(/^客户端[-_]\s*/i, '');
- mainMenuMap[frontendKey] = {
- key: frontendKey,
- icon: IconComponent,
- path: menu.path,
- name: displayName
- };
- }
-
- // 处理子菜单 - 只在这里处理,避免在递归时重复添加
- if (menu.children && menu.children.length > 0) {
- if (!subMenuMap[frontendKey]) {
- subMenuMap[frontendKey] = [];
- }
- // console.log(`处理父菜单 "${menu.name}" 的子菜单,共 ${menu.children.length} 个`);
- menu.children.forEach((child: any) => {
- // 子菜单也需要检查是否包含"客户端",或者父菜单是客户端菜单
- const isChildClientMenu = child.name && child.name.includes('客户端');
- const isChildShouldProcess = isChildClientMenu || isClientMenu;
-
- // console.log(` 子菜单: ${child.name}, visible: ${child.visible}, isChildShouldProcess: ${isChildShouldProcess}`);
-
- // 对于客户端菜单的子菜单,即使 visible 为 false 也显示
- if (!isChildShouldProcess && child.visible === false) {
- // console.log(` 跳过: 不是客户端菜单且不可见`);
- return;
- }
-
- if (!isChildShouldProcess) {
- // 如果子菜单不是客户端菜单,且父菜单也不是客户端菜单,跳过
- // console.log(` 跳过: 不是客户端菜单的子菜单`);
- return;
- }
- let childKey: string = mapBackendPathToFrontendKey(child.path) || '';
- console.log('处理子菜单:', { path: child.path, mappedKey: childKey });
- if (!childKey) {
- // 如果无法映射,尝试根据路径推断
- // 优先处理 clientSystem/IsolationWork 路径
- if (child.path.startsWith('/clientSystem/IsolationWork/')) {
- if (child.path.includes('/processDesign') || child.path.endsWith('/processDesign')) {
- childKey = 'processDesign';
- } else if (child.path.includes('/sop') || child.path.endsWith('/sop')) {
- childKey = 'sopManagement';
- } else if (child.path.includes('/job') || child.path.endsWith('/job')) {
- childKey = 'workManagement';
- } else if (child.path.includes('/form') || child.path.endsWith('/form')) {
- childKey = 'formManagement';
- } else {
- childKey = 'isolationWork';
- }
- } else if (child.path.includes('/jobTicket') || child.path.startsWith('/jobTicket')) {
- // 处理 /jobTicket 路径
- if (child.path.includes('/form') || child.path.endsWith('/form')) {
- childKey = 'formManagement';
- } else if (child.path.includes('/processDesign') || child.path.endsWith('/design')) {
- childKey = 'processDesign';
- } else if (child.path.includes('/sop') || child.path.endsWith('/sop')) {
- childKey = 'sopManagement';
- } else if (child.path.includes('/job') || child.path.endsWith('/job')) {
- childKey = 'workManagement';
- } else if (child.path.includes('/step') || child.path.includes('/template')) {
- childKey = 'processTemplate';
- } else {
- childKey = 'isolationWork';
- }
- } else if (child.path.includes('/dept') || child.path === 'dept' || child.path === 'department') {
- childKey = 'departmentManagement';
- } else if (child.path.includes('/menu') || child.path === 'menuManagement' || child.path === 'menu') {
- childKey = 'menuManagement';
- } else if (child.path.includes('/post') || child.path.includes('/marsdept') || child.path === 'post' || child.path === 'marsdept' || child.path === 'position') {
- childKey = 'positionManagement';
- } else if (child.path.includes('/role') || child.path === 'role') {
- childKey = 'roleManagement';
- } else if (child.path.includes('/dict') || child.path === 'dict' || child.path === 'dictionary') {
- childKey = 'dictionaryManagement';
- } else if (child.path.includes('/cabinet') || child.path === 'cabinet') {
- childKey = 'cabinetManagement';
- } else if (child.path.includes('/hw') || child.path.includes('/hardware') || child.path === 'hwcabinet' || child.path === 'cabinet') {
- // 硬件管理子菜单
- if (child.path.includes('hwcabinet') || child.path === 'hwcabinet' || (child.path.includes('cabinet') && !child.path.includes('system'))) {
- childKey = 'cabinet';
- } else if (child.path.includes('key') || child.path === 'key') {
- childKey = 'key';
- } else if (child.path.includes('lock') || child.path === 'lock' || child.path.includes('padlock') || child.path === 'padlock') {
- childKey = 'padlock';
- } else if (child.path.includes('portability') || child.path === 'portability' || child.path.includes('portable') || child.path === 'portable') {
- childKey = 'portable';
- } else {
- childKey = 'cabinet'; // 默认
- }
- } else if (child.path === 'processTemplate' || child.path === 'processTemplate' || child.path.includes('processTemplate')) {
- // 隔离作业子菜单路径推断
- childKey = 'processTemplate';
- } else if (child.path === 'job' || child.path.includes('/job') || child.path.endsWith('/job')) {
- childKey = 'workManagement';
- } else if (child.path === 'form' || child.path.includes('/form') || child.path.endsWith('/form')) {
- childKey = 'formManagement';
- } else if (child.path === 'taskmanagement' || child.path === 'taskManagement' || child.path.includes('taskmanagement') || child.path.includes('taskManagement')) {
- childKey = 'taskManagement';
- } else if (child.path === 'mytask' || child.path === 'myTask' || child.path.includes('mytask') || child.path.includes('myTask')) {
- childKey = 'myTask';
- } else if (child.path === 'sop' || child.path.includes('/sop') || child.path.endsWith('/sop')) {
- childKey = 'sopManagement';
- } else if (child.path.includes('/notification') || child.path.startsWith('/clientSystem/notification')) {
- // 通知管理子菜单
- if (child.path.includes('appNotification') || child.path.includes('app-notify') || child.path.includes('app_notify')) {
- childKey = 'appNotification';
- } else if (child.path.includes('webmail') || child.path.includes('站内信')) {
- childKey = 'webmail';
- } else if (child.path.includes('sms') || child.path.includes('短信')) {
- childKey = 'sms';
- } else if (child.path.includes('email') || child.path.includes('邮件')) {
- childKey = 'email';
- } else {
- childKey = 'email'; // 默认
- }
- } else {
- // 如果无法推断,使用path作为key(去除特殊字符)
- childKey = child.path.replace(/[^a-zA-Z0-9]/g, '_') || `child_${child.id}`;
- }
- }
- console.log('最终 childKey:', childKey);
-
- // 检查是否已经添加过(去重)
- const subMenuKey = `${frontendKey}_${childKey}`;
- if (processedSubMenuKeys.has(subMenuKey)) {
- // console.log(` 跳过: 子菜单 ${childKey} 已存在`);
- return;
- }
- processedSubMenuKeys.add(subMenuKey);
-
- // 去掉"客户端-"前缀
- const childDisplayName = child.name ? child.name.replace(/^客户端[-_]\s*/i, '') : child.name;
-
- // 使用原来的图标配置,根据key和名称来映射(传入名称以便智能推断)
- const ChildIcon = getIconByKey(childKey, childDisplayName || child.name);
- const subMenuItem = {
- key: childKey,
- icon: ChildIcon,
- path: child.path,
- name: childDisplayName
- };
- // console.log(` 添加子菜单到 ${frontendKey}:`, subMenuItem);
- subMenuMap[frontendKey].push(subMenuItem);
- });
- // console.log(`父菜单 "${menu.name}" 的子菜单数量: ${subMenuMap[frontendKey].length}`);
- }
- }
- // 注意:不再在这里处理子菜单,避免重复添加
- // 子菜单已经在 parentId === 0 的分支中处理了
-
- // 递归处理子菜单(传递是否是客户端父菜单的标志)
- if (menu.children && menu.children.length > 0) {
- menu.children.forEach((child: any) => {
- processMenu(child, frontendKey, isClientMenu);
- });
- }
- };
-
- backendMenus.forEach(menu => {
- processMenu(menu);
- });
-
- const result = {
- mainMenus: Object.values(mainMenuMap),
- subMenuConfig: subMenuMap
- };
-
- // 调试:打印最终生成的菜单
- // console.log('=== 菜单生成结果 ===');
- // console.log('主菜单数量:', result.mainMenus.length);
- // console.log('主菜单列表:', result.mainMenus.map(m => ({ key: m.key, name: m.name })));
- // console.log('子菜单配置:', result.subMenuConfig);
- // console.log('==================');
-
- return result;
- }, [i18n.language]);
- // 获取通知管理菜单信息(从后端菜单中查找)
- const notificationMenu = useMemo(() => {
- const backendMenus = getMenus();
- // 如果menus为null或空数组,返回null
- if (!backendMenus || backendMenus.length === 0) {
- return null;
- }
- const findNotificationMenu = (menus: any[]): any => {
- for (const menu of menus) {
- if (menu.name && menu.name.includes('客户端') && menu.name.includes('通知')) {
- return menu;
- }
- if (menu.children && menu.children.length > 0) {
- const found = findNotificationMenu(menu.children);
- if (found) return found;
- }
- }
- return null;
- };
- return findNotificationMenu(backendMenus);
- }, []);
- // 将通知管理的子菜单也加入到 subMenuConfig 中,以便在 tab 标签中显示
- const enhancedSubMenuConfig = useMemo(() => {
- const config = { ...subMenuConfig };
-
- // 如果找到通知管理菜单,将其子菜单添加到配置中
- if (notificationMenu && notificationMenu.children) {
- const notificationSubMenus = notificationMenu.children
- // 放宽过滤:即使后端标记不可见,只要是通知子项(尤其 APP 通知)也加入 Tab
- .filter((child: any) => {
- const name: string = child?.name || '';
- const path: string = child?.path || '';
- const lower = `${name} ${path}`.toLowerCase();
- const isAppNotify = lower.includes('app') || lower.includes('app-notify') || lower.includes('app_notify') || lower.includes('app通知');
- return child.visible !== false || (name && name.includes('客户端')) || isAppNotify;
- })
- .map((child: any) => {
- const rawKey = mapBackendPathToFrontendKey(child.path) || child.path.replace(/[^a-zA-Z0-9]/g, '_') || `child_${child.id}`;
- const childDisplayName = child.name ? child.name.replace(/^客户端[-_]\s*/i, '') : child.name;
-
- // 通知管理二级菜单 key 归一化:确保 i18n 命中(英文切换时不会回退到中文 name)
- const lower = `${childDisplayName || ''} ${child.path || ''}`.toLowerCase();
- let childKey = rawKey;
- if (lower.includes('app') || lower.includes('app-notify') || lower.includes('app_notify') || lower.includes('app通知')) {
- childKey = 'app';
- } else if (lower.includes('webmail') || lower.includes('in_site') || lower.includes('in-site') || lower.includes('站内信')) {
- childKey = 'webmail';
- } else if (lower.includes('sms') || lower.includes('sms-log') || lower.includes('短信')) {
- childKey = 'sms';
- } else if (lower.includes('email') || lower.includes('mail') || lower.includes('邮件')) {
- childKey = 'email';
- }
- // 获取图标 - 需要在 useMemo 外部定义 getIconByKey,或者在这里重新定义
- // 由于 getIconByKey 在 useMemo 内部,我们需要在这里重新实现图标映射逻辑
- let ChildIcon = Bell; // 默认图标
- const iconMap: { [key: string]: any } = {
- 'userList': User,
- 'notificationManagement': Bell,
- 'menuManagement': Menu,
- 'departmentManagement': Building2,
- 'positionManagement': Briefcase,
- 'roleManagement': UserCog,
- 'dictionaryManagement': BookOpen,
- 'cabinetManagement': Server,
- 'cabinet': Server,
- 'key': KeyRound,
- 'padlock': Lock,
- 'portable': Radio,
- 'processTemplate': Workflow,
- 'sopManagement': ClipboardList,
- 'workManagement': Activity,
- 'processDesign': Workflow,
- 'formManagement': FileText,
- };
- if (iconMap[childKey]) {
- ChildIcon = iconMap[childKey];
- } else if (childDisplayName) {
- const name = childDisplayName.toLowerCase();
- if (name.includes('用户') || name.includes('user')) ChildIcon = User;
- else if (name.includes('通知') || name.includes('notification')) ChildIcon = Bell;
- else if (name.includes('列表') || name.includes('list')) ChildIcon = List;
- else if (name.includes('流程') || name.includes('process')) ChildIcon = Workflow;
- else if (name.includes('作业') || name.includes('work')) ChildIcon = Activity;
- }
- return {
- key: childKey,
- icon: ChildIcon,
- name: childDisplayName,
- path: child.path
- };
- });
-
- if (notificationSubMenus.length > 0) {
- // 去重:后端可能存在多个“邮件/APP”等子菜单,归一化后会出现重复 key(例如两个 Email)
- const uniqueNotificationSubMenus = Array.from(
- new Map(notificationSubMenus.map((item) => [item.key, item])).values()
- );
- config['notificationManagement'] = uniqueNotificationSubMenus;
- }
- }
-
- return config;
- }, [subMenuConfig, notificationMenu]);
- // 过滤后的主菜单(已经根据后端菜单过滤了)
- const filteredMainMenus = mainMenus;
- // 过滤后的二级菜单配置(已经根据后端菜单过滤了,包含通知管理)
- // 隐藏隔离作业模块下的SOP管理功能tab
- const filteredSubMenuConfig = useMemo(() => {
- const config = { ...enhancedSubMenuConfig };
- if (config.isolationWork) {
- config.isolationWork = config.isolationWork.filter(item => item.key !== 'sopManagement');
- }
- return config;
- }, [enhancedSubMenuConfig]);
- // 监听路径变化,处理详情页显示和菜单状态更新
- useEffect(() => {
-
- // 如果路径是 /dashboard,检查是否有从其他页面跳转过来的菜单信息(navigateToMenu)
- // 注意:只有在有 navigateToMenu 时才恢复菜单状态,避免新登录用户读取到旧的 lastActiveMenu
- if (location.pathname === '/dashboard') {
- // 首先检查 URL hash,如果有 hash,说明用户是直接访问带 hash 的 URL 或浏览器回退
- const hashState = parseHashToMenuState(window.location.hash);
- if (hashState && hashState.menu) {
- console.log('从 URL hash 恢复菜单状态:', hashState);
- // 不需要重新设置状态,因为 useState 初始化时已经从 hash 读取了
- // 但如果是 useEffect 触发的(比如路由变化),需要设置
- if (activeMenu !== hashState.menu || activeSubMenu !== hashState.subMenu) {
- setActiveMenu(hashState.menu);
- setActiveSubMenu(hashState.subMenu);
- }
- return;
- }
-
- // 优先检查 navigateToMenu(登录后跳转首菜单或从其他页面跳转)
- const navigateToMenu = sessionStorage.getItem('navigateToMenu');
- if (navigateToMenu) {
- try {
- const menuInfo = JSON.parse(navigateToMenu);
- if (menuInfo.menu && menuInfo.subMenu) {
- console.log('从其他页面跳转,恢复菜单状态:', menuInfo);
- setActiveMenu(menuInfo.menu);
- setActiveSubMenu(menuInfo.subMenu);
- // 同时更新 URL hash
- updateUrlHash(menuInfo.menu, menuInfo.subMenu);
- sessionStorage.removeItem('navigateToMenu');
- return;
- }
- } catch (e) {
- console.error('解析菜单信息失败:', e);
- sessionStorage.removeItem('navigateToMenu');
- }
- }
- // 若接口未返回 /dashboard 路由,则不强制展示驾驶舱:切到接口返回的第一个菜单
- const hasDashboardMenu = filteredMainMenus.some((m: { key: string }) => m.key === 'dashboard');
- if (!hasDashboardMenu && filteredMainMenus.length > 0 && activeMenu === 'dashboard') {
- const first = filteredMainMenus[0];
- const subs = filteredSubMenuConfig[first.key];
- const sub = (subs && subs.length > 0) ? subs[0].key : first.key;
- setActiveMenu(first.key);
- setActiveSubMenu(sub);
- // 同时更新 URL hash
- updateUrlHash(first.key, sub);
- return;
- }
- return;
- }
-
- // 根据路径更新菜单状态
- const path = location.pathname;
- const menuKey = mapBackendPathToFrontendKey(path);
-
- console.log('路径变化处理:', { path, menuKey, filteredSubMenuConfig: Object.keys(filteredSubMenuConfig) });
-
- if (menuKey) {
- console.log('根据路径更新菜单:', { path, menuKey });
-
- // 如果是系统配置的子菜单,需要设置对应的子菜单key
- if (menuKey === 'systemConfig' || menuKey === 'departmentManagement' || menuKey === 'menuManagement' ||
- menuKey === 'positionManagement' || menuKey === 'roleManagement' || menuKey === 'dictionaryManagement' ||
- menuKey === 'cabinetManagement') {
- // 确定是哪个子菜单
- let subMenuKey = 'menuManagement'; // 默认值
-
- if (path.includes('/dept') || menuKey === 'departmentManagement') {
- subMenuKey = 'departmentManagement';
- } else if (path.includes('/menu') || menuKey === 'menuManagement') {
- subMenuKey = 'menuManagement';
- } else if (path.includes('/post') || menuKey === 'positionManagement') {
- subMenuKey = 'positionManagement';
- } else if (path.includes('/role') || menuKey === 'roleManagement') {
- subMenuKey = 'roleManagement';
- } else if (path.includes('/dict') || menuKey === 'dictionaryManagement') {
- subMenuKey = 'dictionaryManagement';
- } else if (path.includes('/cabinet') || menuKey === 'cabinetManagement') {
- subMenuKey = 'cabinetManagement';
- }
-
- console.log('设置系统配置子菜单:', { menuKey: 'systemConfig', subMenuKey });
- setActiveMenu('systemConfig');
- setActiveSubMenu(subMenuKey);
- } else if (menuKey === 'cabinet' || menuKey === 'key' || menuKey === 'padlock' || menuKey === 'portable') {
- // 硬件管理的子菜单
- setActiveMenu('hardwareManagement');
- setActiveSubMenu(menuKey);
- } else if (menuKey === 'myTask') {
- // 我的任务
- console.log('设置我的任务菜单:', { menuKey: 'myTask' });
- setActiveMenu('isolationWork');
- setActiveSubMenu('myTask');
- } else if (menuKey === 'taskManagement' || menuKey === 'task-management' || menuKey === 'taskmanagement') {
- // 任务管理
- console.log('设置任务管理菜单:', { menuKey });
- setActiveMenu('taskManagement');
- setActiveSubMenu('taskManagement');
- } else if (menuKey === 'formManagement' || menuKey === 'processDesign' || menuKey === 'sopManagement' ||
- menuKey === 'workManagement' || menuKey === 'processTemplate') {
- // 隔离作业的子菜单
- setActiveMenu('isolationWork');
- setActiveSubMenu(menuKey);
- } else if (menuKey === 'processDesign' || menuKey === 'sopManagement' || menuKey === 'workManagement' || menuKey === 'formManagement') {
- // 隔离作业的子菜单
- console.log('设置隔离作业子菜单:', { menuKey: 'isolationWork', subMenuKey: menuKey });
- setActiveMenu('isolationWork');
- setActiveSubMenu(menuKey);
- } else if (menuKey === 'notificationManagement') {
- // 通知管理的子菜单
- let subMenuKey = '';
- // 根据路径判断是哪个子菜单
- if (path.includes('appNotification') || path.includes('app-notify') || path.includes('app_notify')) {
- // 查找 appNotification 相关的子菜单
- const appSubMenu = filteredSubMenuConfig[menuKey]?.find(item =>
- item.key.toLowerCase().includes('app') ||
- item.path.toLowerCase().includes('appnotification') ||
- item.name?.toLowerCase().includes('app')
- );
- subMenuKey = appSubMenu?.key || 'appNotification';
- } else if (path.includes('webmail') || path.includes('站内信')) {
- const webmailSubMenu = filteredSubMenuConfig[menuKey]?.find(item =>
- item.key.toLowerCase().includes('webmail') ||
- item.name?.toLowerCase().includes('站内信')
- );
- subMenuKey = webmailSubMenu?.key || 'webmail';
- } else if (path.includes('sms') || path.includes('短信')) {
- const smsSubMenu = filteredSubMenuConfig[menuKey]?.find(item =>
- item.key.toLowerCase().includes('sms') ||
- item.name?.toLowerCase().includes('短信')
- );
- subMenuKey = smsSubMenu?.key || 'sms';
- } else if (path.includes('email') || path.includes('邮件')) {
- const emailSubMenu = filteredSubMenuConfig[menuKey]?.find(item =>
- item.key.toLowerCase().includes('email') ||
- item.name?.toLowerCase().includes('邮件')
- );
- subMenuKey = emailSubMenu?.key || 'email';
- }
-
- // 如果找到了子菜单,设置它;否则设置第一个子菜单
- if (subMenuKey && filteredSubMenuConfig[menuKey]?.find(item => item.key === subMenuKey)) {
- setActiveMenu(menuKey);
- setActiveSubMenu(subMenuKey);
- } else if (filteredSubMenuConfig[menuKey] && filteredSubMenuConfig[menuKey].length > 0) {
- setActiveMenu(menuKey);
- setActiveSubMenu(filteredSubMenuConfig[menuKey][0].key);
- }
- } else if (filteredSubMenuConfig[menuKey] && filteredSubMenuConfig[menuKey].length > 0) {
- // 其他菜单,设置第一个子菜单
- setActiveMenu(menuKey);
- setActiveSubMenu(filteredSubMenuConfig[menuKey][0].key);
- } else if (menuKey === 'isolationWork') {
- // 如果只是隔离作业主菜单,设置第一个子菜单
- if (filteredSubMenuConfig.isolationWork && filteredSubMenuConfig.isolationWork.length > 0) {
- setActiveMenu('isolationWork');
- setActiveSubMenu(filteredSubMenuConfig.isolationWork[0].key);
- }
- }
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [location.pathname, filteredSubMenuConfig, filteredMainMenus, activeMenu]);
- // 监听自定义事件,用于在同一页面切换菜单
- useEffect(() => {
- const handleSwitchToMenu = (event: CustomEvent) => {
- const { menu, subMenu } = event.detail;
- if (menu && subMenu) {
- console.log('收到切换菜单事件:', { menu, subMenu });
- // 使用 handleMenuChange 更新菜单状态并同步更新 URL hash
- handleMenuChange(menu, subMenu);
- }
- };
- window.addEventListener('switchToMenu', handleSwitchToMenu as EventListener);
-
- return () => {
- window.removeEventListener('switchToMenu', handleSwitchToMenu as EventListener);
- };
- }, [handleMenuChange]);
- // 根据URL路径初始化菜单状态(仅在首次加载时执行)
- useEffect(() => {
-
- // 检查是否有从其他页面跳转过来的菜单信息
- const navigateToMenu = sessionStorage.getItem('navigateToMenu');
- if (navigateToMenu) {
- try {
- const menuInfo = JSON.parse(navigateToMenu);
- if (menuInfo.menu && menuInfo.subMenu) {
- setActiveMenu(menuInfo.menu);
- setActiveSubMenu(menuInfo.subMenu);
- sessionStorage.removeItem('navigateToMenu');
- return;
- }
- } catch (e) {
- console.error('解析菜单信息失败:', e);
- sessionStorage.removeItem('navigateToMenu');
- }
- }
-
- // 只在路径不是 /dashboard 时才根据路径初始化(因为应用使用状态管理而非路由)
- // 如果路径是 /dashboard,则使用默认状态
- if (location.pathname === '/dashboard') {
- return; // 使用默认状态
- }
-
- const path = location.pathname;
- const menuKey = mapBackendPathToFrontendKey(path);
-
- if (menuKey) {
- console.log('根据路径初始化菜单:', { path, menuKey });
-
- // 如果是系统配置的子菜单,需要设置对应的子菜单key
- if (menuKey === 'systemConfig' || menuKey === 'departmentManagement' || menuKey === 'menuManagement' ||
- menuKey === 'positionManagement' || menuKey === 'roleManagement' || menuKey === 'dictionaryManagement' ||
- menuKey === 'cabinetManagement') {
- // 确定是哪个子菜单
- let subMenuKey = 'menuManagement'; // 默认值
-
- if (path.includes('/dept') || menuKey === 'departmentManagement') {
- subMenuKey = 'departmentManagement';
- } else if (path.includes('/menu') || menuKey === 'menuManagement') {
- subMenuKey = 'menuManagement';
- } else if (path.includes('/post') || menuKey === 'positionManagement') {
- subMenuKey = 'positionManagement';
- } else if (path.includes('/role') || menuKey === 'roleManagement') {
- subMenuKey = 'roleManagement';
- } else if (path.includes('/dict') || menuKey === 'dictionaryManagement') {
- subMenuKey = 'dictionaryManagement';
- } else if (path.includes('/cabinet') || menuKey === 'cabinetManagement') {
- subMenuKey = 'cabinetManagement';
- }
-
- console.log('设置系统配置子菜单:', { menuKey: 'systemConfig', subMenuKey });
- setActiveMenu('systemConfig');
- setActiveSubMenu(subMenuKey);
- } else if (menuKey === 'cabinet' || menuKey === 'key' || menuKey === 'padlock' || menuKey === 'portable') {
- // 硬件管理的子菜单
- setActiveMenu('hardwareManagement');
- setActiveSubMenu(menuKey);
- } else if (menuKey === 'processDesign' || menuKey === 'sopManagement' || menuKey === 'workManagement' || menuKey === 'formManagement') {
- // 隔离作业的子菜单
- console.log('设置隔离作业子菜单:', { menuKey: 'isolationWork', subMenuKey: menuKey });
- setActiveMenu('isolationWork');
- setActiveSubMenu(menuKey);
- } else if (menuKey === 'formManagement' || menuKey === 'processDesign' || menuKey === 'sopManagement' ||
- menuKey === 'workManagement' || menuKey === 'processTemplate') {
- // 隔离作业的子菜单
- setActiveMenu('isolationWork');
- setActiveSubMenu(menuKey);
- } else if (filteredSubMenuConfig[menuKey] && filteredSubMenuConfig[menuKey].length > 0) {
- // 其他菜单,设置第一个子菜单
- setActiveMenu(menuKey);
- setActiveSubMenu(filteredSubMenuConfig[menuKey][0].key);
- }
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []); // 只在组件挂载时执行一次
- // 监听从用户管理跳转到岗位管理的事件
- useEffect(() => {
- const handleSwitchToPostManagement = (event: CustomEvent) => {
- const userId = event.detail?.userId;
- if (userId) {
- // 将用户ID存储到 sessionStorage
- sessionStorage.setItem('selectedUserId', String(userId));
- }
- // 切换到岗位管理页面,使用 handleMenuChange 更新菜单状态并同步更新 URL hash
- handleMenuChange('systemConfig', 'positionManagement');
- };
- window.addEventListener('switchToPostManagement', handleSwitchToPostManagement as EventListener);
- return () => {
- window.removeEventListener('switchToPostManagement', handleSwitchToPostManagement as EventListener);
- };
- }, [handleMenuChange]);
- return (
- <div className="h-screen flex flex-col bg-gradient-to-br from-gray-50 via-blue-50/30 to-gray-50 overflow-hidden">
- {/* 顶部导航栏 */}
- <nav className="bg-white/80 backdrop-blur-xl border-b border-gray-200/50 flex-shrink-0 z-50 shadow-sm">
- <div className="px-6 py-4 min-w-0">
- <div className="flex items-center justify-between gap-4 min-w-0">
- {/* Logo区域 */}
- <div className="flex items-center gap-4 lg:gap-8 flex-shrink-0 min-w-0">
- <div className="flex items-center gap-2 lg:gap-3 flex-shrink-0">
- <div className="relative flex-shrink-0">
- <div className="w-11 h-11 rounded-xl flex items-center justify-center shadow-lg shadow-blue-400/40" style={{ background: 'linear-gradient(135deg, #4d79f8 0%, #6b8ffb 100%)' }}>
- <Shield className="w-6 h-6 text-white" strokeWidth={2.5} />
- </div>
- <div className="absolute -top-0.5 -right-0.5 w-3 h-3 bg-green-400 rounded-full border-2 border-white"></div>
- </div>
- <div className="min-w-0">
- <h1 className="text-lg text-gray-900 truncate">{env.appTitle}</h1>
- <p className="text-xs text-gray-500 truncate">{t('login.subtitle')}</p>
- </div>
- </div>
- {/* 主菜单 */}
- <div className="hidden lg:flex items-center gap-2 ml-2 lg:ml-4 flex-shrink min-w-0">
- {filteredMainMenus.map((item) => {
- const Icon = item.icon;
- const isActive = activeMenu === item.key;
-
- // 只有系统配置显示下拉菜单,其他菜单的二级菜单在页面内用 tab 标签显示
- if (item.key === 'systemConfig' && filteredSubMenuConfig[item.key] && filteredSubMenuConfig[item.key].length > 0) {
- const isDropdownOpen = showDropdownMenu === item.key;
- const buttonRef = useRef<HTMLButtonElement>(null);
- const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number } | null>(null);
-
- // 计算下拉菜单位置
- useEffect(() => {
- if (isDropdownOpen && buttonRef.current) {
- const updatePosition = () => {
- if (buttonRef.current) {
- const rect = buttonRef.current.getBoundingClientRect();
- setDropdownPosition({
- top: rect.bottom + 8,
- left: rect.left,
- });
- }
- };
- updatePosition();
- window.addEventListener('scroll', updatePosition, true);
- window.addEventListener('resize', updatePosition);
- return () => {
- window.removeEventListener('scroll', updatePosition, true);
- window.removeEventListener('resize', updatePosition);
- };
- } else {
- setDropdownPosition(null);
- }
- }, [isDropdownOpen]);
-
- return (
- <>
- <div
- key={item.key}
- className="relative"
- onMouseEnter={() => {
- if (dropdownTimer) clearTimeout(dropdownTimer);
- setShowDropdownMenu(item.key);
- }}
- onMouseLeave={() => {
- // 鼠标离开按钮时立即隐藏(如果鼠标进入悬浮框,悬浮框的onMouseEnter会保持显示)
- if (dropdownTimer) clearTimeout(dropdownTimer);
- setShowDropdownMenu(null);
- }}
- >
- <button
- ref={buttonRef}
- className={`flex items-center gap-2 px-4 py-2.5 rounded-xl transition-all duration-300 ${
- isActive
- ? 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg shadow-blue-400/40'
- : 'text-gray-600 hover:bg-blue-50 hover:text-blue-600'
- }`}
- >
- <Icon className={`w-4 h-4 ${isActive ? 'text-white' : ''}`} strokeWidth={2.5} />
- <span className={`text-sm ${isActive ? 'text-white' : ''}`}>
- {t(`nav.${item.key}`, item.name || item.key)}
- </span>
- <ChevronDown className={`w-3 h-3 transition-transform ${isDropdownOpen ? 'rotate-180' : ''} ${isActive ? 'text-white' : ''}`} />
- </button>
- </div>
-
- {/* 下拉菜单 - 使用 fixed 定位,避免被父容器裁剪 */}
- {isDropdownOpen && dropdownPosition && (
- <>
- {/* 连接区域 - 填充按钮和下拉菜单之间的间隙 */}
- <div
- className="fixed z-[99]"
- style={{
- top: `${dropdownPosition.top - 8}px`,
- left: `${dropdownPosition.left}px`,
- width: '340px',
- height: '8px',
- }}
- onMouseEnter={() => {
- // 鼠标进入连接区域时保持显示
- if (dropdownTimer) clearTimeout(dropdownTimer);
- setShowDropdownMenu(item.key);
- }}
- onMouseLeave={() => {
- // 鼠标离开连接区域时立即隐藏
- if (dropdownTimer) clearTimeout(dropdownTimer);
- setShowDropdownMenu(null);
- }}
- />
- <div
- className="fixed bg-white rounded-xl shadow-xl border border-gray-200/50 p-3 animate-in fade-in slide-in-from-top-2 duration-200 z-[100]"
- style={{
- top: `${dropdownPosition.top}px`,
- left: `${dropdownPosition.left}px`,
- width: '340px',
- }}
- onMouseEnter={() => {
- // 鼠标进入悬浮框时保持显示
- if (dropdownTimer) clearTimeout(dropdownTimer);
- setShowDropdownMenu(item.key);
- }}
- onMouseLeave={() => {
- // 鼠标离开悬浮框时立即隐藏
- if (dropdownTimer) clearTimeout(dropdownTimer);
- setShowDropdownMenu(null);
- }}
- >
- <div className="grid grid-cols-2 gap-2">
- {(filteredSubMenuConfig[item.key] || []).map((subItem) => {
- const IconComponent = subItem.icon;
- const isActive = activeMenu === item.key && activeSubMenu === subItem.key;
- return (
- <button
- key={subItem.key}
- onClick={() => {
- console.log('点击子菜单:', { menuKey: item.key, subMenuKey: subItem.key });
- // 如果当前在个人中心页面,先关闭个人中心
- if (showProfileSettings) {
- setShowProfileSettings(false);
- }
- // 如果当前在详情页,导航回对应的菜单页面
- if (location.pathname.startsWith('/lock-cabinet/detail')) {
- navigate('/dashboard');
- }
- // 保存菜单状态到 sessionStorage
- sessionStorage.setItem('lastActiveMenu', JSON.stringify({
- menu: item.key,
- subMenu: subItem.key
- }));
- // 使用 handleMenuChange 更新菜单状态并同步更新 URL hash
- handleMenuChange(item.key, subItem.key);
- setShowDropdownMenu(null);
- }}
- className={`flex items-center gap-2.5 px-3 py-3 rounded-lg text-sm transition-all ${
- isActive
- ? 'bg-blue-50 text-blue-600 shadow-sm'
- : 'text-gray-700 hover:bg-blue-50 hover:text-blue-600'
- }`}
- >
- <div className={`w-8 h-8 rounded-lg flex items-center justify-center ${
- isActive ? 'bg-blue-100' : 'bg-gray-100'
- }`}>
- <IconComponent className="w-4 h-4" strokeWidth={2.5} />
- </div>
- <span>
- {t(`systemConfig.${subItem.key}`, subItem.name || subItem.key)}
- </span>
- </button>
- );
- })}
- </div>
- </div>
- </>
- )}
- </>
- );
- }
-
- // 其他普通菜单(用户管理、硬件管理、隔离作业等)- 它们的二级菜单在页面内用 tab 标签显示
- return (
- <button
- key={item.key}
- onClick={() => {
- console.log('点击主菜单:', { menuKey: item.key, hasSubMenus: !!filteredSubMenuConfig[item.key] });
- // 如果当前在个人中心页面,先关闭个人中心
- if (showProfileSettings) {
- setShowProfileSettings(false);
- }
- // 保存菜单状态到 sessionStorage
- const firstSubMenu = filteredSubMenuConfig[item.key]?.[0];
- if (firstSubMenu) {
- sessionStorage.setItem('lastActiveMenu', JSON.stringify({
- menu: item.key,
- subMenu: firstSubMenu.key
- }));
- }
- // 如果当前在详情页,导航回对应的菜单页面
- if (location.pathname.startsWith('/lock-cabinet/detail')) {
- navigate('/dashboard');
- }
- // 任务管理菜单如果没有子菜单,设置 activeSubMenu 为 'taskManagement'
- // 使用 handleMenuChange 更新菜单状态并同步更新 URL hash
- if (item.key === 'taskManagement' && !firstSubMenu) {
- handleMenuChange('taskManagement', 'taskManagement');
- } else {
- handleMenuChange(item.key, firstSubMenu?.key || '');
- }
- }}
- className={`flex items-center gap-2 px-4 py-2.5 rounded-xl transition-all duration-300 ${
- isActive
- ? 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg shadow-blue-400/40'
- : 'text-gray-600 hover:bg-blue-50 hover:text-blue-600'
- }`}
- >
- <Icon className={`w-4 h-4 ${isActive ? 'text-white' : ''}`} strokeWidth={2.5} />
- <span className={`text-sm ${isActive ? 'text-white' : ''}`}>
- {t(`nav.${item.key}`, item.name || item.key)}
- </span>
- </button>
- );
- })}
- </div>
- </div>
- {/* 右侧功能区 */}
- <div className="flex items-center gap-2 lg:gap-4 flex-shrink-0">
- {/* 语言切换 - 鼠标悬停显示,移开自动收起 */}
- <Dropdown
- menu={{ items: languageMenuItems }}
- trigger={['hover']}
- placement="bottomRight"
- mouseEnterDelay={0}
- mouseLeaveDelay={0.15}
- >
- <button
- className="flex items-center gap-2 px-3 py-2 hover:bg-gray-100 rounded-xl transition-colors group"
- >
- {/* <Globe className="w-4 h-4 text-gray-600 group-hover:rotate-12 transition-transform" /> */}
- {/* <span className="text-sm text-gray-700">{i18n.language === 'zh' ? '中文' : 'English'}</span> */}
- {/* <span className="text-sm text-gray-700">语言切换</span> */}
- {/* <ChevronDown className="w-3 h-3 text-gray-500" /> */}
- <svg t="1770183289750" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5073" width="24" height="24" >
- <path d="M789.312 426.688L977.088 896H885.12l-51.2-128h-174.528l-51.2 128H516.352L704 426.688h85.312zM426.688 85.312v85.376h256V256H598.72A777.472 777.472 0 0 1 444.16 524.8c30.784 27.52 64.192 51.84 99.712 72.896l-32 80.128A728.96 728.96 0 0 1 384 585.664a711.68 711.68 0 0 1-264.512 151.296l-22.976-82.304A627.2 627.2 0 0 0 323.84 524.928a771.328 771.328 0 0 1-120.448-183.616H298.88c23.808 43.968 52.352 85.12 85.056 122.816C437.312 402.56 479.36 332.16 508.16 256H85.312V170.688h256V85.312h85.376z m320 464.448l-53.184 132.928h106.24l-53.12-132.928z" fill="#515151" p-id="5074"></path>
- </svg>
-
- </button>
- </Dropdown>
-
- {/* 消息通知 - 显示未读消息红点和消息列表 */}
- <MessageNotification />
- {/* 通知管理 - 点击后进入页面,二级菜单在 tab 标签中显示 */}
- {notificationMenu && (
- <div className="relative group">
- <button
- className="relative p-2.5 hover:bg-gray-100 rounded-xl transition-colors"
- title={t('nav.notificationManagement', '通知管理')}
- // title=""
- onClick={() => {
- // 如果当前在个人中心页面,先关闭个人中心
- if (showProfileSettings) {
- setShowProfileSettings(false);
- }
- const firstSubMenu = filteredSubMenuConfig['notificationManagement']?.[0]?.key || '';
- // 使用 handleMenuChange 更新菜单状态并同步更新 URL hash
- handleMenuChange('notificationManagement', firstSubMenu);
- }}
- >
- <MessageSquare className="w-5 h-5 text-gray-600" />
- </button>
- {/* Tooltip - 显示菜单名称 */}
- <div className="absolute right-0 top-full mt-2 px-3 py-1.5 bg-gray-900 text-white text-xs rounded-lg opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-50">
- {t('nav.notificationManagement', '通知管理')}
- </div>
- </div>
- )}
- {/* 用户菜单 */}
- <div
- className="relative"
- onMouseEnter={() => {
- // 清除可能存在的隐藏定时器
- if (dropdownTimer) {
- clearTimeout(dropdownTimer);
- setDropdownTimer(null);
- }
- setShowUserMenu(true);
- }}
- onMouseLeave={() => {
- // 延迟隐藏,给用户时间移动到下拉菜单
- const timer = setTimeout(() => {
- setShowUserMenu(false);
- }, 200);
- setDropdownTimer(timer);
- }}
- >
- <button
- onClick={() => setShowUserMenu(!showUserMenu)}
- className="flex items-center gap-3 px-3 py-2 hover:bg-gray-100 rounded-xl transition-colors"
- >
- {userInfo?.avatar && !avatarError ? (
- <img
- src={userInfo.avatar}
- alt={userInfo.nickname || userInfo.username || '用户'}
- className="w-9 h-9 rounded-lg object-cover shadow-lg border-2 border-white"
- onError={() => setAvatarError(true)}
- />
- ) : (
- <div className="w-9 h-9 bg-gradient-to-br from-blue-400 to-blue-500 rounded-lg flex items-center justify-center shadow-lg shadow-blue-400/30">
- <User className="w-5 h-5 text-white" strokeWidth={2.5} />
- </div>
- )}
- <div className="text-left hidden md:block">
- <div className="text-sm text-gray-900">{userInfo?.nickname || userInfo?.username || '用户'}</div>
- <div className="text-xs text-gray-500">{userInfo?.username || 'User'}</div>
- </div>
- <ChevronDown className={`w-4 h-4 text-gray-500 transition-transform ${showUserMenu ? 'rotate-180' : ''}`} />
- </button>
- {/* 下拉菜单 */}
- {showUserMenu && (
- <div
- className="absolute right-0 mt-2 w-48 bg-white rounded-xl shadow-xl border border-gray-200/50 py-2 animate-in fade-in slide-in-from-top-2 duration-200 z-50"
- onMouseEnter={() => {
- // 鼠标进入下拉菜单时,清除隐藏定时器
- if (dropdownTimer) {
- clearTimeout(dropdownTimer);
- setDropdownTimer(null);
- }
- setShowUserMenu(true);
- }}
- onMouseLeave={() => {
- // 鼠标离开下拉菜单时,延迟隐藏
- const timer = setTimeout(() => {
- setShowUserMenu(false);
- }, 200);
- setDropdownTimer(timer);
- }}
- >
- <button
- onClick={() => {
- setShowProfileSettings(true);
- setShowUserMenu(false);
- }}
- className="w-full px-4 py-2.5 text-left text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600 flex items-center gap-2 transition-colors"
- >
- <User className="w-4 h-4" />
- {t('nav.profile')}
- </button>
- <hr className="my-2 border-gray-200" />
- <button className="w-full px-4 py-2.5 text-left text-sm text-red-600 hover:bg-red-50 flex items-center gap-2 transition-colors" onClick={handleLogout}>
- <LogOut className="w-4 h-4" />
- {t('nav.logout')}
- </button>
- </div>
- )}
- </div>
- </div>
- </div>
- </div>
- </nav>
- {/* 主内容区 */}
- <div className="flex-1 flex flex-col overflow-hidden">
- <div className="px-6 pt-6 flex-shrink-0">
- {/* 二级菜单 Tab - 只要有子菜单(>=1)且不是系统配置时显示,并且不在个人资料页面时显示 */}
- {!showProfileSettings &&
- filteredSubMenuConfig[activeMenu]?.length >= 1 &&
- activeMenu !== 'systemConfig' &&
- activeMenu !== 'userManagement' && (
- <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm p-2 mb-6">
- <div className="flex items-center gap-2 overflow-x-auto min-w-0">
- {filteredSubMenuConfig[activeMenu]?.map((item) => {
- const isActive = activeSubMenu === item.key;
- // 根据主菜单key确定翻译键前缀
- let menuKey = '';
- if (activeMenu === 'userManagement') {
- menuKey = 'userManagement';
- } else if (activeMenu === 'hardwareManagement') {
- menuKey = 'hardwareManagement';
- } else if (activeMenu === 'isolationWork' || item.key === 'processTemplate' || item.key === 'sopManagement' || item.key === 'workManagement' || item.key === 'processDesign' || item.key === 'formManagement' || item.key === 'myTask' || item.key === 'taskManagement') {
- menuKey = 'isolationWork';
- } else if (activeMenu === 'notificationManagement') {
- menuKey = 'notificationManagement';
- } else if (activeMenu === 'systemConfig') {
- menuKey = 'systemConfig';
- } else {
- // 默认尝试使用 activeMenu 作为翻译键前缀
- menuKey = activeMenu;
- }
- return (
- <button
- key={item.key}
- onClick={() => {
- // 保存菜单状态到 sessionStorage
- sessionStorage.setItem('lastActiveMenu', JSON.stringify({
- menu: activeMenu,
- subMenu: item.key
- }));
- // 使用 handleMenuChange 更新菜单状态并同步更新 URL hash
- handleMenuChange(activeMenu, item.key);
- }}
- className={`px-3 lg:px-4 py-2 lg:py-2.5 rounded-xl text-xs lg:text-sm transition-all duration-200 whitespace-nowrap flex-shrink-0 ${
- isActive
- ? 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg shadow-blue-400/40'
- : 'text-gray-700 hover:bg-blue-50 hover:text-blue-600'
- }`}
- >
- <span className={isActive ? 'text-white' : ''}>
- {(() => {
- // 优先尝试使用对应的翻译键
- let translationKey = '';
- if (menuKey) {
- translationKey = `${menuKey}.${item.key}`;
- } else if (activeMenu === 'isolationWork') {
- // 如果是隔离作业的子菜单,使用 isolationWork
- translationKey = `isolationWork.${item.key}`;
- } else if (activeMenu === 'notificationManagement') {
- // 如果是通知管理的子菜单,尝试使用 userManagement(因为通知管理通常在用户管理下)
- translationKey = `userManagement.${item.key}`;
- } else {
- // 默认尝试使用 activeMenu 作为前缀
- translationKey = `${activeMenu}.${item.key}`;
- }
- // 尝试翻译,如果翻译不存在则使用 item.name
- const translation = t(translationKey, item.name || item.key);
- // 如果翻译结果等于键本身(说明翻译不存在),使用后备值
- return translation === translationKey ? (item.name || item.key) : translation;
- })()}
- </span>
- </button>
- );
- })}
- </div>
- </div>
- )}
- </div>
- {/* 主内容区域 - 可滚动 */}
- <div className="flex-1 px-6 pb-6 overflow-auto">
- {showProfileSettings ? (
- <ProfileSettings onBack={() => setShowProfileSettings(false)} />
- ) : activeMenu === 'dashboard' ? (
- hasRole('super_admin') ? <DashboardComponent /> : <ExecutorDashboard />
- ) : activeMenu === 'systemConfig' ? (
- <SystemConfig subMenu={activeSubMenu} />
- ) : activeMenu === 'userManagement' ? (
- <UserManagement subMenu={activeSubMenu} />
- ) : activeMenu === 'hardwareManagement' ? (
- <HardwareManagement subMenu={activeSubMenu} />
- ) : activeMenu === 'locationManagement' ? (
- <SegregationPointManagement />
- ) : activeMenu === 'isolationWork' ? (
- (() => {
- // 调试信息
- console.log('检查隔离作业子菜单:', { activeMenu, activeSubMenu, location: location.pathname, filteredSubMenuConfig: filteredSubMenuConfig[activeMenu] });
- // 支持 'formManagement' 和 'form' 两种 key
- if (activeSubMenu === 'formManagement' || activeSubMenu === 'form' || location.pathname.includes('/form')) {
- console.log('✅ 渲染 FormManagement 组件', { activeMenu, activeSubMenu });
- return <FormManagement />;
- } else if (activeSubMenu === 'myTask' || activeSubMenu === 'mytask' || location.pathname.includes('/my-task') || location.pathname.includes('/myTask')) {
- console.log('✅ 渲染 MyTask 组件', { activeMenu, activeSubMenu, pathname: location.pathname });
- return <MyTask />;
- } else if (activeSubMenu === 'taskManagement' || activeSubMenu === 'taskmanagement' || activeSubMenu === 'task-management' || location.pathname.includes('/task-management') || location.pathname.includes('/taskManagement')) {
- console.log('✅ 渲染 TaskManagement 组件(从隔离作业子菜单)', { activeMenu, activeSubMenu, pathname: location.pathname });
- return <TaskManagement />;
- } else {
- const subMenuName = filteredSubMenuConfig[activeMenu]?.find(item => item.key === activeSubMenu)?.name || activeSubMenu;
- console.log('⚠️ 渲染 IsolationWork 组件', { activeMenu, activeSubMenu, subMenuName });
- return <IsolationWork subMenu={subMenuName} />;
- }
- })()
- ) : activeMenu === 'myTask' || location.pathname.includes('/my-task') ? (
- <MyTask />
- ) : activeMenu === 'taskManagement' || activeSubMenu === 'taskManagement' || location.pathname.includes('/task-management') || location.pathname.includes('/taskManagement') ? (
- (() => {
- console.log('✅ 渲染 TaskManagement 组件', { activeMenu, activeSubMenu, pathname: location.pathname });
- return <TaskManagement />;
- })()
- ) : activeMenu === 'notificationManagement' ? (
- (() => {
- const subMenuName = filteredSubMenuConfig[activeMenu]?.find(item => item.key === activeSubMenu)?.name || activeSubMenu;
- return <NotificationManagement subMenu={subMenuName} />;
- })()
- ) : (
- // 无法映射的菜单(如客户端菜单)显示占位内容
- <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm p-8">
- <div className="text-center">
- <div className="text-gray-400 mb-4">
- <Settings className="w-16 h-16 mx-auto" />
- </div>
- <h3 className="text-lg text-gray-900 mb-2">
- {mainMenus.find(m => m.key === activeMenu)?.name || '功能开发中'}
- </h3>
- <p className="text-sm text-gray-500">
- 该功能正在开发中,敬请期待
- </p>
- </div>
- </div>
- )}
- </div>
- </div>
- <Toaster position="top-center" richColors />
- </div>
- );
- }
|