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"; /* ---- sessionStorage 持久化 ---- */ function loadState() { try { const tabs = JSON.parse(sessionStorage.getItem(TABS_KEY) || "[]"); const activeKey = sessionStorage.getItem(ACTIVE_KEY) || ""; return { tabs, 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 }) { const [collapsed, setCollapsed] = useState(false); const [state, setState] = useState(loadState); const currentPath = window.location.pathname; // 页面加载时自动将当前路径加入标签 useEffect(() => { if (currentPath && !currentPath.endsWith("/container/")) { addTab(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) => { if (path !== currentPath) { window.location.href = path; } }, [currentPath]); 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} ); }