import { useEffect, useRef, useState } from "react"; import { useNavigate, useLocation } from "react-router-dom"; import { PanelLeftOpen, PanelLeftClose } from "lucide-react"; import { getStoredAuth } from "@/core/auth"; import { isWujieEnv } from "@/core/embed/wujie"; import { defaultExpandedIds, menuItemContainsPath, menuItemMatchesPath, type MenuItem, } from "@/core/page-layout/sidebar-menu"; import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu"; import './page-sdebar.css' type PageSidebarDarkProps = { /** 是否收起(true 时只渲染左下角浮动入口,sidebar 主体隐藏) */ collapsed: boolean; onToggleCollapsed: () => void; /** 全局 Header 隐藏时,侧边栏从视口顶部开始占满高度。 */ headerHidden?: boolean; }; export function PageSidebarDark({ collapsed, onToggleCollapsed, headerHidden = false, }: PageSidebarDarkProps) { const isAdmin = getStoredAuth()?.system_role === "admin"; const visibleMenuItems = useSidebarMenuItems(!!isAdmin); const inWujieEnv = isWujieEnv(); // defaultExpandedIds 是共享的 ReadonlySet,复制一份给 useState(避免外部突变) const [expandedIds, setExpandedIds] = useState>(() => new Set(defaultExpandedIds)); const navigate = useNavigate(); const { pathname } = useLocation(); // pathname 变化时取消任何未触发的 hover 关闭计时(cleanup 在卸载时也跑) useEffect(() => { return () => { if (hoverCloseTimerRef.current !== null) { window.clearTimeout(hoverCloseTimerRef.current); } }; }, []); const handleNavHoverEnter = (id: string) => { if (hoverCloseTimerRef.current !== null) { window.clearTimeout(hoverCloseTimerRef.current); hoverCloseTimerRef.current = null; } setHoveredTopId(id); }; const scheduleNavPanelClose = () => { if (hoverCloseTimerRef.current !== null) { window.clearTimeout(hoverCloseTimerRef.current); } hoverCloseTimerRef.current = window.setTimeout(() => { setHoveredTopId(null); hoverCloseTimerRef.current = null; }, 180); }; const hasActiveChild = (item: MenuItem): boolean => menuItemContainsPath(item, pathname); // lazy init:仅在首次 render 计算一次 active id;之后由下面的 useEffect 跟随 pathname 同步 const [activeTopId, setActiveTopId] = useState( () => visibleMenuItems.find(hasActiveChild)?.id ?? visibleMenuItems[0]?.id ?? "qa-center", ); const [hoveredTopId, setHoveredTopId] = useState(null); const hoverCloseTimerRef = useRef(null); useEffect(() => { const matchedTopItem = visibleMenuItems.find(hasActiveChild); if (matchedTopItem) { setActiveTopId(matchedTopItem.id); } // 不在导航时强制关闭 flyout:flyout 绝对定位在 group 盒子之外,强制关闭后鼠标 // 仍停在 flyout 区域却收不到 group 的 mouseenter,导致连续点击第二个任务时面板 // 消失且不回来。开合只交给 hover(mouseleave 有 180ms 宽限),即可连续点选。 }, [pathname, visibleMenuItems]); const toggleExpanded = (id: string) => { setExpandedIds((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); }; const findFirstRoutePath = (items?: MenuItem[]): string | null => { if (!items?.length) { return null; } for (const child of items) { if (child.path) { return child.path; } const nestedPath = findFirstRoutePath(child.children); if (nestedPath) { return nestedPath; } } return null; }; const handleTopItemClick = (item: MenuItem) => { setActiveTopId(item.id); if (item.children?.length) { const firstPath = findFirstRoutePath(item.children); if (firstPath) { navigate(firstPath); } return; } if (item.path) { navigate(item.path); } }; const handleSecondItemClick = (item: MenuItem) => { const path = item.path ?? findFirstRoutePath(item.children); if (path) { navigate(path); } }; const handleMenuItemClick = (item: MenuItem) => { if (item.children?.length) { toggleExpanded(item.id); return; } if (item.path) { navigate(item.path); } }; const activeTopItem = visibleMenuItems.find((item) => item.id === activeTopId) ?? visibleMenuItems.find(hasActiveChild) ?? visibleMenuItems[0]; const sidebarMenuItemsToRender = inWujieEnv ? visibleMenuItems : activeTopItem?.children ?? []; const isSidebarItemActive = (item: MenuItem) => inWujieEnv ? item.id === activeTopId : item.path || item.aliasPaths?.length ? menuItemMatchesPath(item, pathname) : menuItemContainsPath(item, pathname); const handleSidebarItemClick = (item: MenuItem) => { if (inWujieEnv) { handleTopItemClick(item); return; } handleSecondItemClick(item); }; const getSidebarLabelClassName = (label: string) => { if (!inWujieEnv && Array.from(label.trim()).length === 4) { return "item-nav__label item-nav__label--four-char"; } return "item-nav__label"; }; const renderFlyoutMenu = (items: MenuItem[], level = 0) => (
{items.map((item) => { const hasChildren = !!item.children?.length; const isExpanded = expandedIds.has(item.id); const isSelected = item.path || item.aliasPaths?.length ? menuItemMatchesPath(item, pathname) : hasActiveChild(item); return (
{hasChildren && isExpanded ? renderFlyoutMenu(item.children ?? [], level + 1) : null}
); })}
); // 收起时主侧栏不占宽度,保留左下角入口以便随时恢复。 if (collapsed) { return ( ); } return ( ); }