import React, { useState, useCallback, useMemo, useEffect, } from "react"; import { Layout, Menu, Breadcrumb, Tabs, Button, Dropdown, Space, Typography } from "antd"; import { ReloadOutlined, CloseOutlined, CloseCircleOutlined, DownOutlined, MenuFoldOutlined, MenuUnfoldOutlined, } from "@ant-design/icons"; import menuItems, { findMenuPath, getPageLabel, getOpenKeys, getSelectedKeys, } from "./menuConfig"; const { Header, Sider, Content } = Layout; const { Text } = Typography; const TABS_KEY = "sim_layout_tabs"; const ACTIVE_KEY = "sim_layout_active"; /* ---- 路径规范化:统一补全 /safetyEval 前缀,避免同一路由生成多个标签 ---- */ const normalizePath = (path) => { if (!path) return path; return path.startsWith("/safetyEval") ? path : `/safetyEval${path}`; }; /* ---- sessionStorage 持久化 ---- */ function loadState() { try { const rawTabs = JSON.parse(sessionStorage.getItem(TABS_KEY) || "[]"); const tabs = rawTabs.map((t) => ({ ...t, key: normalizePath(t.key) })); // 路由去重:相同路由只保留一个标签 const seen = new Set(); const deduped = tabs.filter((t) => { if (seen.has(t.key)) return false; seen.add(t.key); return true; }); const activeKey = normalizePath(sessionStorage.getItem(ACTIVE_KEY) || ""); return { tabs: deduped, activeKey }; } catch { return { tabs: [], activeKey: "" }; } } function saveState(tabs, activeKey) { sessionStorage.setItem(TABS_KEY, JSON.stringify(tabs)); sessionStorage.setItem(ACTIVE_KEY, activeKey || ""); } export default function SimulatedLayout({ children, history }) { const [collapsed, setCollapsed] = useState(false); const [state, setState] = useState(loadState); const [currentPath, setCurrentPath] = useState(() => normalizePath(window.location.pathname)); // 监听路由变化,更新 currentPath useEffect(() => { const unlisten = history?.listen?.((location) => { setCurrentPath(normalizePath(location.pathname || window.location.pathname)); }); return () => unlisten?.(); }, [history]); // 路由变化时自动将当前路径加入标签 useEffect(() => { if (currentPath && !currentPath.endsWith("/container/")) { addTab(currentPath); } }, [currentPath]); /* ---- 标签操作 ---- */ const addTab = useCallback((path) => { setState((prev) => { if (prev.tabs.find((t) => t.key === path)) { // 已存在则仅更新 activeKey if (prev.activeKey !== path) { saveState(prev.tabs, path); return { ...prev, activeKey: path }; } return prev; } const newTabs = [...prev.tabs, { key: path, label: getPageLabel(path) }]; saveState(newTabs, path); return { tabs: newTabs, activeKey: path }; }); }, []); const navigateTo = useCallback((path) => { const normalized = normalizePath(path); if (normalized !== currentPath) { history.replace(normalized.replace(/^\/safetyEval/, "")); setCurrentPath(normalized); } }, [currentPath, history]); const handleCloseTab = useCallback((targetKey) => { setState((prev) => { const idx = prev.tabs.findIndex((t) => t.key === targetKey); const newTabs = prev.tabs.filter((t) => t.key !== targetKey); if (newTabs.length === 0) { saveState([], ""); return { tabs: [], activeKey: "" }; } let newActive = prev.activeKey; if (targetKey === prev.activeKey) { newActive = newTabs[Math.max(0, idx - 1)]?.key || newTabs[0]?.key; navigateTo(newActive); } saveState(newTabs, newActive); return { tabs: newTabs, activeKey: newActive }; }); }, [navigateTo]); const handleCloseOthers = useCallback((targetKey) => { setState((prev) => { const target = prev.tabs.find((t) => t.key === targetKey); const newTabs = target ? [target] : []; saveState(newTabs, targetKey); if (targetKey !== currentPath) navigateTo(targetKey); return { tabs: newTabs, activeKey: targetKey }; }); }, [currentPath, navigateTo]); const handleCloseRight = useCallback((targetKey) => { setState((prev) => { const idx = prev.tabs.findIndex((t) => t.key === targetKey); if (idx === -1) return prev; const newTabs = prev.tabs.slice(0, idx + 1); saveState(newTabs, targetKey); return { tabs: newTabs, activeKey: targetKey }; }); }, []); const handleCloseAll = useCallback(() => { saveState([], ""); setState({ tabs: [], activeKey: "" }); }, []); const handleRefresh = useCallback(() => { window.location.reload(); }, []); /* ---- 菜单事件 ---- */ const handleMenuClick = useCallback( ({ key }) => { navigateTo(key); }, [navigateTo], ); const handleTabChange = useCallback( (key) => { navigateTo(key); }, [navigateTo], ); const handleTabEdit = useCallback( (targetKey, action) => { if (action === "remove") handleCloseTab(targetKey); }, [handleCloseTab], ); /* ---- 面包屑 ---- */ const breadcrumbItems = useMemo(() => { const path = findMenuPath(currentPath); return path.map((item, idx) => ({ title: idx === path.length - 1 ? ( item.label ) : ( handleMenuClick({ key: item.key })}>{item.label} ), key: item.key, })); }, [currentPath, handleMenuClick]); /* ---- 菜单选中/展开 ---- */ const selectedKeys = useMemo(() => getSelectedKeys(currentPath), [currentPath]); const defaultOpenKeys = useMemo(() => getOpenKeys(currentPath), [currentPath]); /* ---- 标签页右键菜单 ---- */ const buildContextMenu = useCallback( (tabKey) => { const { tabs } = state; const isOnlyOne = tabs.length <= 1; const idx = tabs.findIndex((t) => t.key === tabKey); const hasRight = idx >= 0 && idx < tabs.length - 1; return { items: [ { key: "refresh", icon: , label: "刷新当前标签", }, { key: "close", icon: , label: "关闭当前标签", disabled: isOnlyOne, }, { key: "close-others", icon: , label: "关闭其他标签", disabled: isOnlyOne, }, { key: "close-right", icon: , label: "关闭右侧标签", disabled: !hasRight, }, { type: "divider" }, { key: "close-all", icon: , label: "关闭所有标签", disabled: tabs.length === 0, }, ], onClick: ({ key }) => { switch (key) { case "refresh": handleRefresh(); break; case "close": handleCloseTab(tabKey); break; case "close-others": handleCloseOthers(tabKey); break; case "close-right": handleCloseRight(tabKey); break; case "close-all": handleCloseAll(); break; } }, }; }, [state, handleRefresh, handleCloseTab, handleCloseOthers, handleCloseRight, handleCloseAll], ); /* ---- 标签 label 渲染(带右键菜单) ---- */ const renderTabLabel = useCallback( (tab) => ( {tab.label} ), [buildContextMenu], ); const { tabs, activeKey } = state; const siderWidth = collapsed ? 64 : 220; return ( {/* ---- 侧边栏 ---- */} {collapsed ? "证照" : "证照管理系统"} {/* ---- 右侧主体 ---- */} {/* ---- Header + 面包屑 + 操作按钮 ---- */} : } onClick={() => setCollapsed(!collapsed)} /> } onClick={handleRefresh} > 刷新 } onClick={handleCloseAll} disabled={tabs.length === 0} > 关闭所有 模拟布局·Dev {/* ---- 标签栏 ---- */} {tabs.length > 0 ? ( ({ key: tab.key, label: renderTabLabel(tab), closable: true, }))} tabBarExtraContent={ , label: "刷新当前标签", }, { key: "close-others", icon: , label: "关闭其他标签", disabled: tabs.length <= 1, }, { key: "close-all", icon: , label: "关闭所有标签", disabled: tabs.length === 0, }, ], onClick: ({ key }) => { switch (key) { case "refresh": handleRefresh(); break; case "close-others": handleCloseOthers(activeKey); break; case "close-all": handleCloseAll(); break; } }, }} placement="bottomRight" > } /> } /> ) : ( 暂无打开的标签页,请从左侧菜单选择页面 )} {/* ---- 内容区 ---- */} {children} ); }