utils/autoRouteHelper.ts
// src/utils/autoRouteHelper.ts
import { lazy } from "react";
import withLoading from "@/components/router/withLoading";
/** 自动生成某个文件夹下的子路由 */
interface RouteItem {
path: string;
element?: any;
children?: RouteItem[];
handle?: {
title: string;
};
}
/**
* 生成嵌套路由树(保留 children 树结构,排除 components)
* @param globModules 模块对象
* @param baseDir 模块路径
* @param titleMap 路由标题映射表
* @returns 返回树形结构路由数据
*/
export function generateNestedRoutes(
globModules: Record<string, () => Promise<any>>,
baseDir: string,
titleMap: Record<string, string> = {}
): RouteItem[] {
const root: Record<string, any> = {};
for (const [fullPath, loader] of Object.entries(globModules)) {
if (fullPath.includes("/components/")) continue;
const reg = new RegExp(`${baseDir}/(.*)/index\\.tsx$`, "i");
const match = fullPath.match(reg);
if (!match) continue;
const subPath = match[1].replace(/\\/g, "/"); // windows 兼容
const segments = subPath.split("/"); // ["pageb", "list"]
let current = root;
// let fullSegmentPath = "";
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
// fullSegmentPath += (i === 0 ? "" : "/") + segment;
if (!current[segment]) {
current[segment] = {
path: segment,
childrenMap: {}
};
}
// 最后一层,挂 element 和 title
if (i === segments.length - 1) {
current[segment].element = withLoading(lazy(loader));
current[segment].handle = {
title: titleMap[subPath.toLowerCase()] || segment
};
}
current = current[segment].childrenMap;
}
}
// 将 childrenMap 转为 children[]
function convertToTree(obj: Record<string, any>): RouteItem[] {
return Object.values(obj).map(({ childrenMap, ...rest }) => {
const node: RouteItem = { ...rest };
const children = convertToTree(childrenMap);
if (children.length > 0) node.children = children;
return node;
});
}
return convertToTree(root);
}
/** 合并所有模块的默认导出,适合用于路由模块化(eager 模式) */
export function mergeModuleRoutes(
modules: Record<string, any>
): any[] {
const routes: any[] = [];
Object.values(modules).forEach((mod: any) => {
if (Array.isArray(mod.default)) {
routes.push(...mod.default);
} else if (mod.default) {
routes.push(mod.default);
}
});
return routes;
}
react路由使用
目录\router\modules\supplier.ts
import.meta.glob('@/pages/supplier/**/index.tsx'); 自动获取pages/supplier目录下的所有页面传递给generateNestedRoutes返回路由不限制层级,可以一二级也可以一二三四级别等等
\router\modules
新增路由文件 如supplier.ts
import { lazy } from "react";
import withLoading from "@/components/router/withLoading";
import { generateNestedRoutes } from "@/utils/routerHelper";
const personalPageModules = import.meta.glob('@/pages/supplier/**/index.tsx');
const titleMap: Record<string, string> = {
list: "供应商列表",
// 你可以继续添加其他路径对应的标题
};
/** 供应商管理 */
const supplier: Array<any> = [
{
path: "supplier",
element: withLoading(lazy(() => import("@/pages/supplier/index"))),
handle: {
title: "供应商管理",
},
children:[
...generateNestedRoutes(personalPageModules,"supplier",titleMap)
]
},
];
console.log(supplier,"供应商管理")
export default supplier;
router/index.ts目录中
const modules = import.meta.glob('./modules/*.ts', { eager: true });
...mergeModuleRoutes(modules),
自动获取modules目录下的所有路由文件
vue路由中使用
routerHelper.ts
// src/utils/autoRouteHelper.ts
// src/utils/autoRouteHelper.ts
/**
* 自动生成某个模块文件夹下的子路由(用于 Vue Router)
*/
interface TitleMap {
[key: string]: string;
}
interface AutoRouteOptions {
/** 模块目录,如 'views/merchantmanage' */
baseDir: string;
/** 路由标题映射表 */
titleMap?: TitleMap;
/** 排除目录数组,如 ['components', 'common'] */
excludeDirs?: string[];
}
/**
* 自动生成某个目录下的子路由,支持多级目录,支持排除子目录
* @param globModules import.meta.glob 的结果
* @param options 配置项
*/
export function generateChildrenRoutes(
globModules: Record<string, () => Promise<any>>,
options: AutoRouteOptions
) {
const { baseDir, titleMap = {}, excludeDirs = [] } = options;
return Object.entries(globModules)
.map(([fullPath, loader]) => {
// 1. 判断是否命中排除目录
const shouldExclude = excludeDirs.some((dir) =>
fullPath.includes(`${baseDir}/${dir}/`)
);
if (shouldExclude) return null;
// 2. 提取 baseDir 后的子路径部分
const match = fullPath.match(new RegExp(`${baseDir}/(.+)\\.vue$`));
if (!match) return null;
const subPath = match[1]; // 例:user/detail/index 或 home
const segments = subPath.split('/');
// 构建路由 path,忽略 index
let routePath = segments
.map((seg) => (seg === 'index' ? '' : seg))
.filter(Boolean)
.join('/');
if (!routePath) routePath = '';
const routeName = routePath.replace(/\//g, '-');
return {
path: routePath,
name: routeName,
component: loader,
meta: {
title: titleMap[routePath.toLowerCase()] || segments.at(-1) || routePath
}
};
})
.filter(Boolean) as any[];
}
/** 合并所有模块的默认导出,适合用于路由模块化(eager 模式) */
export function mergeModuleRoutes(
modules: Record<string, any>
): any[] {
const routes: any[] = [];
Object.values(modules).forEach((mod: any) => {
if (Array.isArray(mod.default)) {
routes.push(...mod.default);
} else if (mod.default) {
routes.push(mod.default);
}
});
return routes;
}
/使用实例
const modules = import.meta.glob('@/views/merchantmanage/**/*.vue');
const routes = generateChildrenRoutes(modules, {
baseDir: 'views/merchantmanage',
excludeDirs: ['components', 'fragments', 'common'],
titleMap: {
user: '用户管理',
'user/detail': '用户详情'
}
});