import React, { memo, useEffect, useMemo, useState } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { Menu, type MenuValue } from '@miragenflow/ui/menu'; import { MiragenFlowButton } from '@miragenflow/ui'; import router, { IRouter } from 'router'; import { useAppDispatch, useAppSelector } from 'modules/store'; import { selectGlobal, toggleMenu } from 'modules/global'; import MenuLogo from './MenuLogo'; import Style from './Menu.module.less'; const { SubMenu, MenuItem, HeadMenu } = Menu; interface IMenuProps { showLogo?: boolean; showOperation?: boolean; } const routePath = (item: IRouter, parentPath = '') => item.absolute ? item.path : `${parentPath}${item.path}` || '/'; const isVisible = (item: IRouter, scopes: string[], roles: string[]) => { if (!item.meta || item.meta.hidden) return false; if (item.meta.requiredScope && !roles.includes('super_admin') && !scopes.includes(item.meta.requiredScope)) return false; return true; }; const visibleChildren = (item: IRouter, scopes: string[], roles: string[]) => (item.children || []).filter((child) => isVisible(child, scopes, roles)); const hasVisibleChild = (item: IRouter, scopes: string[], roles: string[]) => visibleChildren(item, scopes, roles).length > 0; const renderMenuItems = (menu: IRouter[], scopes: string[], roles: string[], parentPath = ''): React.ReactNode[] => menu.map((item) => { const { children, meta } = item; if (!meta || !isVisible(item, scopes, roles)) return null; const currentPath = routePath(item, parentPath); if (children?.length) { const visible = visibleChildren(item, scopes, roles); if (!visible.length) return null; if (visible.length === 1) { const child = visible[0]; const childPath = routePath(child, currentPath); const ChildIcon = child.meta?.Icon || meta.Icon; return : undefined}>{child.meta?.title || meta.title}; } return : undefined}> {renderMenuItems(visible, scopes, roles, currentPath)} ; } return : undefined}>{meta.title}; }); const expandedFor = (menu: IRouter[], pathname: string, scopes: string[], roles: string[], parent = ''): string[] => menu.flatMap((item) => { if (!item.children?.length || !hasVisibleChild(item, scopes, roles)) return []; const visible = visibleChildren(item, scopes, roles); if (visible.length === 1) return []; const current = routePath(item, parent); const childPaths = visible.map((child) => routePath(child, current)); return childPaths.includes(pathname) ? [current] : expandedFor(visible, pathname, scopes, roles, current); }); export const HeaderMenu = memo(() => { const globalState = useAppSelector(selectGlobal); const location = useLocation(); const navigate = useNavigate(); const user = useAppSelector((state) => state.user.userInfo as { scopes?: string[]; roles?: string[] }); const [active, setActive] = useState(location.pathname); useEffect(() => setActive(location.pathname), [location.pathname]); return { setActive(value); navigate(String(value)); }}> {renderMenuItems(router, user.scopes || [], user.roles || [])} ; }); export default memo((props: IMenuProps) => { const location = useLocation(); const navigate = useNavigate(); const dispatch = useAppDispatch(); const globalState = useAppSelector(selectGlobal); const user = useAppSelector((state) => state.user.userInfo as { scopes?: string[]; roles?: string[] }); const scopes = user.scopes || []; const roles = user.roles || []; const requiredExpanded = useMemo(() => expandedFor(router, location.pathname, scopes, roles), [location.pathname, scopes.join(','), roles.join(',')]); const [expanded, setExpanded] = useState(requiredExpanded); useEffect(() => { setExpanded((current) => Array.from(new Set([...current, ...requiredExpanded]))); }, [requiredExpanded.join('|')]); const onNavigate = (value: MenuValue) => { navigate(String(value)); if (window.innerWidth < 1000) dispatch(toggleMenu(true)); }; if (globalState.collapsed) return null; return <> dispatch(toggleMenu(true))} /> 元境幻生管理台 {globalState.version} : undefined} logo={props.showLogo ? : undefined} >{renderMenuItems(router, scopes, roles)} ; });