瀏覽代碼

perf: 优化router标题处理逻辑

BaiLuoYan 3 月之前
父節點
當前提交
4452af338f
共有 5 個文件被更改,包括 98 次插入116 次删除
  1. 2 3
      src/components/Topbar/useService.ts
  2. 6 6
      src/router/routes.tsx
  3. 56 56
      src/router/titles.ts
  4. 1 1
      src/router/types.ts
  5. 33 50
      src/utils/navUtils.ts

+ 2 - 3
src/components/Topbar/useService.ts

@@ -24,9 +24,8 @@ export function useService() {
 
     const getMenuItemLabel = useCallback(
         (item: NavMenuItem): string => {
-            return t(item.locale || `menus.${item.name}`, {
-                defaultValue: item.name,
-            });
+            // getNavMenuItems 中已经设置了 locale,item.locale 总是存在
+            return t(item.locale!, { defaultValue: item.name });
         },
         [t, i18n.language]
     );

+ 6 - 6
src/router/routes.tsx

@@ -36,12 +36,12 @@ const routes: AppRouteObject[] = [
                     {
                         name: 'test1',
                         path: '/test/test1',
-                        element: <div className='text-white'>test1</div>,
+                        element: <div className="text-white">test1</div>,
                     },
                     {
                         name: 'test2',
                         path: '/test/test2',
-                        element: <div className='text-white'>test2</div>,
+                        element: <div className="text-white">test2</div>,
                     },
                 ],
             },
@@ -50,26 +50,26 @@ const routes: AppRouteObject[] = [
     {
         name: 'to',
         path: '/to',
-        inMenu: false,
+        hideInMenu: true,
         element: <Redirect />,
     },
     {
         name: '403',
         path: '/403',
         element: <Forbidden />,
-        inMenu: false,
+        hideInMenu: true,
     },
     {
         name: '500',
         path: '/500',
         element: <ServerError />,
-        inMenu: false,
+        hideInMenu: true,
     },
     {
         name: '404',
         path: '*',
         element: <NotFound />,
-        inMenu: false,
+        hideInMenu: true,
     },
 ] as AppRouteObject[];
 

+ 56 - 56
src/router/titles.ts

@@ -1,78 +1,78 @@
 import i18next from 'i18next';
+import { matchPath } from 'react-router-dom';
 
 import routerConfig from './routes';
 
 import type { AppRouteObject } from './types';
 
-// 扁平化路由配置
-const flattenRoutes = (routes: AppRouteObject[], parentNames: string[] = []): AppRouteObject[] => {
-    return routes.reduce((acc: AppRouteObject[], route: AppRouteObject) => {
-        // 添加当前路由,并添加 locale 字段
-        if (route.name && !route.index) {
-            const currentNames = [...parentNames, route.name];
-            acc.push({
-                ...route,
-                locale: `menus.${currentNames.join('.')}`,
-            });
-        } else {
-            acc.push(route);
+/**
+ * 扁平化路由配置,提取所有有 name 的路由
+ */
+function getAllRoutes(
+    routes: AppRouteObject[],
+    parentNames: string[] = []
+): Array<{
+    path: string;
+    locale: string;
+}> {
+    const result: Array<{ path: string; locale: string }> = [];
+
+    for (const route of routes) {
+        if (route.name && route.path && !route.index) {
+            const locale = route.locale || `menus.${[...parentNames, route.name].join('.')}`;
+            result.push({ path: route.path, locale });
         }
 
-        // 如果有子路由,递归处理
         if (route.children) {
-            acc.push(
-                ...flattenRoutes(
-                    route.children,
-                    route.name ? [...parentNames, route.name] : parentNames
-                )
-            );
+            const currentNames = route.name ? [...parentNames, route.name] : parentNames;
+            result.push(...getAllRoutes(route.children, currentNames));
         }
+    }
 
-        return acc;
-    }, []);
-};
+    return result;
+}
 
-const flatRoutes = flattenRoutes(routerConfig);
-console.log('flatRoutes:', flatRoutes);
+let allRoutesCache: Array<{ path: string; locale: string }> | null = null;
+function getAllRoutesCache(): Array<{ path: string; locale: string }> {
+    if (!allRoutesCache) {
+        allRoutesCache = getAllRoutes(routerConfig);
+    }
+    return allRoutesCache;
+}
 
 /**
- * 获取路由路径对应的标题
+ * 根据路径获取 locale key
  * @param pathname 路由路径
- * @returns 路由标题
+ * @returns locale key
  */
-export const getTitleByPath = (pathname: string): string => {
-    console.log('getTitleByPath ~ pathname:', pathname);
-
-    // 先尝试精确匹配
-    let route = flatRoutes.find((route) => route.path === pathname);
-
-    // 处理带参路由
-    if (!route) {
-        route = flatRoutes.find((route) => {
-            if (!route.path) return false;
+export const getLocaleByPath = (pathname: string): string => {
+    const allRoutes = getAllRoutesCache();
+    // 按路径长度降序排序,优先匹配更具体的路由
+    const matchedRoute = allRoutes
+        .filter((route) => {
             if (route.path === '*') return true;
-            // 将路由路径转换为正则表达式
-            const routePattern = route.path
-                .replace(/\/:([^/?]+)\?/g, '(?:/[^/]+)?') // 处理可选参数
-                .replace(/\/:([^/?]+)/g, '(?:/[^/]+)'); // 处理必选参数
+            return matchPath({ path: route.path, end: false }, pathname) !== null;
+        })
+        .sort((a, b) => {
+            if (a.path === '*') return 1;
+            if (b.path === '*') return -1;
+            return b.path.length - a.path.length;
+        })[0];
 
-            try {
-                return new RegExp(`^${routePattern}$`).test(pathname);
-            } catch {
-                return false;
-            }
-        });
-    }
-
-    if (route) {
-        if (route.locale) {
-            return i18next.t(route.locale, { defaultValue: '' });
-        }
-        return '';
+    if (matchedRoute) {
+        return matchedRoute.locale;
     }
 
     // 如果找不到对应的路由,将路径转换为 i18n key
-    const i18nKey = `menus.${pathname.slice(1).replace(/\//g, '.')}`;
-    console.log(' getTitleByPath ~ i18nKey:', i18nKey);
-    return i18next.t(i18nKey, { defaultValue: '' });
+    return `menus.${pathname.slice(1).replace(/\//g, '.')}`;
+};
+
+/**
+ * 获取路由路径对应的标题
+ * @param pathname 路由路径
+ * @returns 路由标题
+ */
+export const getTitleByPath = (pathname: string): string => {
+    const locale = getLocaleByPath(pathname);
+    return i18next.t(locale, { defaultValue: '' });
 };

+ 1 - 1
src/router/types.ts

@@ -6,6 +6,6 @@ import type { RouteObject } from 'react-router-dom';
 export type AppRouteObject = RouteObject & {
     name: string;
     locale?: string;
-    inMenu?: boolean; // 是否在导航菜单中显示,默认为 true
+    hideInMenu?: boolean; // 是否在导航菜单中显示,默认为 false
     children?: AppRouteObject[];
 };

+ 33 - 50
src/utils/navUtils.ts

@@ -1,4 +1,5 @@
 import routerConfig from '@/router/routes';
+import { getLocaleByPath } from '@/router/titles';
 
 import type { AppRouteObject } from '@/router/types';
 
@@ -8,9 +9,38 @@ export interface NavMenuItem {
     locale?: string;
 }
 
+/**
+ * 处理单个路由项,如果符合条件则添加到菜单项数组
+ * @param route 路由对象
+ * @param items 菜单项数组
+ */
+function processRouteItem(route: AppRouteObject, items: NavMenuItem[]): void {
+    // 跳过 index 路由
+    if (route.index) {
+        return;
+    }
+
+    // 检查是否应该在菜单中显示
+    if (route.hideInMenu === true) {
+        return;
+    }
+
+    // 只有有 name 和 path 的路由才添加到菜单
+    if (route.name && route.path) {
+        const currentPath = route.path.startsWith('/') ? route.path : `/${route.path}`;
+        const locale = getLocaleByPath(currentPath);
+
+        items.push({
+            name: route.name,
+            path: currentPath,
+            locale,
+        });
+    }
+}
+
 /**
  * 从路由配置中提取菜单项
- * 只包含 inMenu !== false 的路由,且只提取顶层路由(不包含子路由)
+ * 只包含 hideInMenu !== true 的路由,且只提取顶层路由(不包含子路由)
  */
 function extractMenuItems(routes: AppRouteObject[]): NavMenuItem[] {
     const items: NavMenuItem[] = [];
@@ -19,61 +49,14 @@ function extractMenuItems(routes: AppRouteObject[]): NavMenuItem[] {
         // 如果路由有 children,只处理第一层 children,不递归处理更深层的子路由
         if (route.children) {
             for (const childRoute of route.children) {
-                // 跳过 index 路由
-                if (childRoute.index) {
-                    continue;
-                }
-
-                // 检查是否应该在菜单中显示
-                if (childRoute.inMenu === false) {
-                    continue;
-                }
-
-                // 只有有 name 和 path 的路由才添加到菜单
-                // 不处理子路由的 children,子路由不应该出现在顶层菜单
-                if (childRoute.name && childRoute.path) {
-                    const currentPath = childRoute.path.startsWith('/')
-                        ? childRoute.path
-                        : `/${childRoute.path}`;
-
-                    // 构建 locale key
-                    const locale = `menus.${childRoute.name}`;
-
-                    items.push({
-                        name: childRoute.name,
-                        path: currentPath,
-                        locale,
-                    });
-                }
+                processRouteItem(childRoute, items);
             }
             // 处理完 children 后继续下一个路由,不处理当前路由本身
             continue;
         }
 
         // 如果没有 children,处理当前路由
-        // 跳过 index 路由
-        if (route.index) {
-            continue;
-        }
-
-        // 检查是否应该在菜单中显示
-        if (route.inMenu === false) {
-            continue;
-        }
-
-        // 只有有 name 和 path 的路由才添加到菜单
-        if (route.name && route.path) {
-            const currentPath = route.path.startsWith('/') ? route.path : `/${route.path}`;
-
-            // 构建 locale key
-            const locale = `menus.${route.name}`;
-
-            items.push({
-                name: route.name,
-                path: currentPath,
-                locale,
-            });
-        }
+        processRouteItem(route, items);
     }
 
     return items;