From 4c1ea155b09bb0852d251ebe32d2c4df5937fec9 Mon Sep 17 00:00:00 2001 From: yujiangping Date: Wed, 24 Dec 2025 13:41:12 +0800 Subject: [PATCH] fix:breadcrumbs --- web/src/components/Header/index.tsx | 27 +++----- web/src/hooks/useNavigationBreadcrumbs.ts | 84 ++++++++++++++++------- 2 files changed, 69 insertions(+), 42 deletions(-) diff --git a/web/src/components/Header/index.tsx b/web/src/components/Header/index.tsx index 4f30c104..9aeeab6b 100644 --- a/web/src/components/Header/index.tsx +++ b/web/src/components/Header/index.tsx @@ -1,9 +1,9 @@ -import { type FC, useCallback, useRef } from 'react'; +import { type FC, useRef } from 'react'; import { Layout, Dropdown, Space, Breadcrumb } from 'antd'; import type { MenuProps, BreadcrumbProps } from 'antd'; import { UserOutlined, LogoutOutlined, SettingOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; -import { useParams, useNavigate } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { useUser } from '@/store/user'; import { useMenu } from '@/store/menu'; import styles from './index.module.css' @@ -13,8 +13,7 @@ const { Header } = Layout; const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { const { t } = useTranslation(); - const params = useParams(); - const navigate = useNavigate(); + const location = useLocation(); const settingModalRef = useRef(null) const userInfoModalRef = useRef(null) @@ -55,7 +54,7 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { key: '1', label: (<>
{user.username}
-
{user.email}
+
{user.email}
), }, { @@ -90,7 +89,7 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { onClick: handleLogout, }, ]; - const formatBreadcrumbNames = useCallback(() => { + const formatBreadcrumbNames = () => { return breadcrumbs.map((menu, index) => { const item: any = { title: menu.i18nKey ? t(menu.i18nKey) : menu.label, @@ -109,23 +108,13 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { }; item.href = '#'; } else if (menu.path && menu.path !== '#') { - // 对于三级面包屑的二级菜单,如果路径包含动态参数,替换为当前参数值 - if (breadcrumbs.length === 3 && index === 1 && menu.path.includes(':id') && params.id) { - const dynamicPath = menu.path.replace(':id', params.id); - item.onClick = (e: React.MouseEvent) => { - e.preventDefault(); - navigate(dynamicPath); - }; - item.href = '#'; - } else { - // 只有当 path 不是 '#' 时才设置 path - item.path = menu.path; - } + // 只有当 path 不是 '#' 时才设置 path + item.path = menu.path; } return item; }); - }, [breadcrumbs, params.id, t, navigate]) + } return (
diff --git a/web/src/hooks/useNavigationBreadcrumbs.ts b/web/src/hooks/useNavigationBreadcrumbs.ts index 67af853e..c314399b 100644 --- a/web/src/hooks/useNavigationBreadcrumbs.ts +++ b/web/src/hooks/useNavigationBreadcrumbs.ts @@ -11,47 +11,85 @@ export const useNavigationBreadcrumbs = (source: 'space' | 'manage' = 'manage') const menus = allMenus[source] || []; // 查找匹配的菜单项并构建keyPath - const findMenuKeyPath = (menuList: any[]): string[] | null => { - const checkDynamicMatch = (pattern: string, path: string) => { - const pathPattern = pattern.replace(/:[\w-]+/g, '[^/]+'); - const regex = new RegExp(`^${pathPattern}$`); - return regex.test(path); - }; + const findMenuKeyPath = (menuList: any[], parentKeys: string[] = []): string[] | null => { + let bestMatch: { path: string; parentId?: string; score: number } | null = null; for (const menu of menuList) { + // 检查子菜单 if (menu.subs && menu.subs.length > 0) { + const menuPath = menu.path ? (menu.path[0] !== '/' ? '/' + menu.path : menu.path) : ''; for (const sub of menu.subs) { - // 检查三级菜单 - if (sub.subs && sub.subs.length > 0) { - for (const subSub of sub.subs) { - if (subSub.path) { - const subSubPath = subSub.path[0] !== '/' ? '/' + subSub.path : subSub.path; - if (subSubPath === currentPath || (subSubPath.includes(':') && checkDynamicMatch(subSubPath, currentPath))) { - return [subSub.path, `${sub.id}`, `${menu.id}`]; - } - } - } - } - - // 检查二级菜单 if (sub.path) { const subPath = sub.path[0] !== '/' ? '/' + sub.path : sub.path; - if (subPath === currentPath || (subPath.includes(':') && checkDynamicMatch(subPath, currentPath))) { + + // 精确匹配优先 + if (subPath === currentPath) { return [sub.path, `${menu.id}`]; } + console.log('menuPath', menuPath) + // 动态路由匹配 + if (subPath.includes(':')) { + // 检查是否在父菜单下 + if (menuPath && currentPath.startsWith(menuPath + '/')) { + const relativePath = currentPath.replace(menuPath, ''); + const pathSegments = subPath.split('/'); + const relativeSegments = relativePath.split('/'); + if (pathSegments.length === relativeSegments.length) { + const pathPattern = subPath.replace(/:[\w-]+/g, '[^/]+').replace(/\[[\w-]+\]/g, '[^/]+'); + const regex = new RegExp(`^${pathPattern}$`); + if (regex.test(relativePath)) { + return [sub.path, `${menu.id}`]; + } + } + } + // 直接匹配子菜单路径 + const pathSegments = subPath.split('/'); + const currentSegments = currentPath.split('/'); + if (pathSegments.length === currentSegments.length) { + const pathPattern = subPath.replace(/:[\w-]+/g, '[^/]+').replace(/\[[\w-]+\]/g, '[^/]+'); + const regex = new RegExp(`^${pathPattern}$`); + if (regex.test(currentPath)) { + return [sub.path, `${menu.id}`]; + } + } + } } } } - // 检查一级菜单 + // 检查主菜单 if (menu.path) { const menuPath = menu.path[0] !== '/' ? '/' + menu.path : menu.path; - if (menuPath === currentPath || (menuPath.includes(':') && checkDynamicMatch(menuPath, currentPath))) { - return [menu.path]; + // 精确匹配优先 + if (menuPath === currentPath) { + return [menu.path, ...parentKeys].reverse(); + } + // 动态路由匹配 + if (menuPath.includes(':')) { + const pathSegments = menuPath.split('/'); + const currentSegments = currentPath.split('/'); + if (pathSegments.length === currentSegments.length) { + const pathPattern = menuPath.replace(/:[\w-]+/g, '[^/]+').replace(/\[[\w-]+\]/g, '[^/]+'); + const regex = new RegExp(`^${pathPattern}$`); + if (regex.test(currentPath)) { + const score = menuPath.split('/').length; + if (!bestMatch || score > bestMatch.score) { + bestMatch = { path: menu.path, score }; + } + } + } + } else if (currentPath.startsWith(menuPath + '/')) { + const score = menuPath.split('/').length; + if (!bestMatch || score > bestMatch.score) { + bestMatch = { path: menu.path, score }; + } } } } + if (bestMatch) { + return bestMatch.parentId ? [bestMatch.path, bestMatch.parentId] : [bestMatch.path]; + } return null; };