/** * @description 动态路由生成器 * @author yslg * @since 2025-10-07 */ import type { RouteRecordRaw } from 'vue-router'; import type { SysMenu } from '@/types'; import { MenuType } from '@/types/enums'; /** * 布局组件映射 */ const LAYOUT_MAP: Record Promise> = { // 基础布局 'BasicLayout': () => import('@/layouts/BasicLayout.vue'), // 空白布局 'BlankLayout': () => import('@/layouts/BlankLayout.vue'), // 页面布局 'PageLayout': () => import('@/layouts/PageLayout.vue'), }; /** * 根据菜单生成路由配置 * @param menus 用户菜单列表 * @returns Vue Router路由配置数组 */ export function generateRoutes(menus: SysMenu[]): RouteRecordRaw[] { if (!menus || menus.length === 0) { return []; } const routes: RouteRecordRaw[] = []; // 构建菜单树 const menuTree = buildMenuTree(menus); // 生成路由 menuTree.forEach(menu => { const route = generateRouteFromMenu(menu); if (route) { routes.push(route); } }); return routes; } /** * 根据单个菜单生成路由 * @param menu 菜单对象 * @returns 路由配置 */ function generateRouteFromMenu(menu: SysMenu): RouteRecordRaw | null { // 只处理目录和菜单类型,忽略按钮类型 if (menu.type === MenuType.BUTTON) { return null; } const route: any = { path: menu.url || `/${menu.menuID}`, name: menu.menuID, meta: { title: menu.name, icon: menu.icon, menuId: menu.menuID, parentId: menu.parentID, orderNum: menu.orderNum, type: menu.type, hideInMenu: false, requiresAuth: true, } }; // 根据菜单类型处理组件 if (menu.type === MenuType.DIRECTORY) { // 目录类型 - 使用布局组件 route.component = getComponent(menu.component || 'BasicLayout'); // 处理子菜单 if (menu.children && menu.children.length > 0) { route.children = []; menu.children.forEach(child => { const childRoute = generateRouteFromMenu(child); if (childRoute) { route.children!.push(childRoute); } }); } else { // 如果是目录但没有子菜单,设置重定向 route.redirect = route.path + '/index'; } } else if (menu.type === MenuType.MENU) { // 菜单类型 - 使用页面组件 if (!menu.component) { console.warn(`菜单 ${menu.name} 缺少component字段`); return null; } route.component = getComponent(menu.component); } return route; } /** * 根据组件名称获取组件 * @param componentName 组件名称/路径 * @returns 组件异步加载函数 */ function getComponent(componentName: string) { // 检查是否是布局组件 if (LAYOUT_MAP[componentName]) { return LAYOUT_MAP[componentName]; } // 处理页面组件路径 let componentPath = componentName; // 如果不是以@/开头的完整路径,则添加@/views/前缀 if (!componentPath.startsWith('@/')) { // 如果不是以/开头,添加/ if (!componentPath.startsWith('/')) { componentPath = '/' + componentPath; } componentPath = '@/views' + componentPath; } // 如果没有.vue扩展名,添加它 if (!componentPath.endsWith('.vue')) { componentPath += '.vue'; } // 动态导入组件 return () => import(/* @vite-ignore */ componentPath).catch((error) => { console.warn(`组件加载失败: ${componentPath}`, error); // 返回404组件或空组件 return import('@/views/error/404.vue').catch(() => Promise.resolve({ template: `

组件加载失败

无法加载组件: ${componentPath}

错误: ${error.message}

`, style: ` .component-error { padding: 20px; text-align: center; color: #f56565; background: #fed7d7; border-radius: 4px; } ` }) ); }); } /** * 构建菜单树结构 * @param menus 菜单列表 * @returns 菜单树 */ function buildMenuTree(menus: SysMenu[]): SysMenu[] { if (!menus || menus.length === 0) { return []; } const menuMap = new Map(); const rootMenus: SysMenu[] = []; // 创建菜单映射 menus.forEach(menu => { if (menu.menuID) { menuMap.set(menu.menuID, { ...menu, children: [] }); } }); // 构建树结构 menus.forEach(menu => { const menuNode = menuMap.get(menu.menuID!); if (!menuNode) return; if (!menu.parentID || menu.parentID === '0') { // 根菜单 rootMenus.push(menuNode); } else { // 子菜单 const parent = menuMap.get(menu.parentID); if (parent) { parent.children = parent.children || []; parent.children.push(menuNode); } } }); // 按orderNum排序 const sortMenus = (menus: SysMenu[]): SysMenu[] => { return menus .sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0)) .map(menu => ({ ...menu, children: menu.children ? sortMenus(menu.children) : [] })); }; return sortMenus(rootMenus); } /** * 根据权限过滤菜单 * @param menus 菜单列表 * @param permissions 用户权限列表 * @returns 过滤后的菜单列表 */ export function filterMenusByPermissions( menus: SysMenu[], permissions: string[] ): SysMenu[] { if (!menus || menus.length === 0) { return []; } return menus .filter(() => { // 如果菜单没有设置权限要求,则默认显示 // 这里可以根据实际业务需求调整权限检查逻辑 return true; // 暂时返回true,后续可以根据菜单的权限字段进行过滤 }) .map(menu => { if (menu.children && menu.children.length > 0) { return { ...menu, children: filterMenusByPermissions(menu.children, permissions) }; } return menu; }); } /** * 查找路由路径对应的菜单 * @param menus 菜单树 * @param path 路由路径 * @returns 匹配的菜单 */ export function findMenuByPath(menus: SysMenu[], path: string): SysMenu | null { for (const menu of menus) { if (menu.url === path) { return menu; } if (menu.children && menu.children.length > 0) { const found = findMenuByPath(menu.children, path); if (found) { return found; } } } return null; } /** * 获取菜单路径数组(面包屑导航用) * @param menus 菜单树 * @param targetMenuId 目标菜单ID * @returns 菜单路径数组 */ export function getMenuPath(menus: SysMenu[], targetMenuId: string): SysMenu[] { const path: SysMenu[] = []; function findPath(menuList: SysMenu[]): boolean { for (const menu of menuList) { path.push(menu); if (menu.menuID === targetMenuId) { return true; } if (menu.children && menu.children.length > 0) { if (findPath(menu.children)) { return true; } } path.pop(); } return false; } findPath(menus); return path; }