Files
schoolNews/schoolNewsWeb/src/utils/route-generator.ts

418 lines
11 KiB
TypeScript
Raw Normal View History

2025-10-07 13:31:06 +08:00
/**
* @description
* @author yslg
* @since 2025-10-07
*/
import type { RouteRecordRaw } from 'vue-router';
import type { SysMenu } from '@/types';
import { MenuType } from '@/types/enums';
2025-10-08 14:11:54 +08:00
import { routes } from '@/router';
2025-10-07 13:31:06 +08:00
/**
*
*/
const LAYOUT_MAP: Record<string, () => Promise<any>> = {
2025-10-08 14:11:54 +08:00
// 基础布局(旧版,带侧边栏)
2025-10-07 13:31:06 +08:00
'BasicLayout': () => import('@/layouts/BasicLayout.vue'),
2025-10-08 14:11:54 +08:00
// 导航布局(新版,顶部导航+动态侧边栏)
'NavigationLayout': () => import('@/layouts/NavigationLayout.vue'),
2025-10-07 13:31:06 +08:00
// 空白布局
'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
2025-10-08 14:11:54 +08:00
* @param isTopLevel
2025-10-07 13:31:06 +08:00
* @returns
*/
2025-10-08 14:11:54 +08:00
function generateRouteFromMenu(menu: SysMenu, isTopLevel = true): RouteRecordRaw | null {
// 跳过按钮类型
2025-10-07 13:31:06 +08:00
if (menu.type === MenuType.BUTTON) {
return null;
}
2025-10-08 14:11:54 +08:00
// 跳过静态路由(已经在 router 中定义,不需要再次添加)
if (menu.component === '__STATIC_ROUTE__') {
return null;
}
2025-10-07 13:31:06 +08:00
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,
}
};
2025-10-08 14:11:54 +08:00
// 如果有子菜单,使用布局组件
if (menu.children && menu.children.length > 0) {
// 如果是顶层的NAVIGATION类型菜单使用NavigationLayout
if (isTopLevel && menu.type === MenuType.NAVIGATION) {
route.component = getComponent(menu.component || 'NavigationLayout');
} else if (menu.type === MenuType.SIDEBAR) {
// SIDEBAR类型的菜单使用BlankLayout避免嵌套布局
// BlankLayout 只是一个纯容器,不会添加额外的导航栏或面包屑
route.component = getComponent(menu.component || 'BlankLayout');
2025-10-07 13:31:06 +08:00
} else {
2025-10-08 14:11:54 +08:00
// 其他情况使用BasicLayout
route.component = getComponent(menu.component || 'BasicLayout');
2025-10-07 13:31:06 +08:00
}
2025-10-08 14:11:54 +08:00
} else {
// 没有子菜单,使用具体的页面组件
if (menu.component) {
route.component = getComponent(menu.component);
} else {
// 如果没有指定组件使用BlankLayout作为默认
route.component = getComponent('BlankLayout');
2025-10-07 13:31:06 +08:00
}
}
2025-10-08 14:11:54 +08:00
// 处理子菜单
if (menu.children && menu.children.length > 0) {
route.children = [];
menu.children.forEach(child => {
const childRoute = generateRouteFromMenu(child, false);
if (childRoute) {
route.children!.push(childRoute);
}
});
// 如果没有设置重定向自动重定向到第一个有URL的子菜单
if (!route.redirect && route.children.length > 0) {
const firstChildWithUrl = findFirstMenuWithUrl(menu.children);
if (firstChildWithUrl?.url) {
route.redirect = firstChildWithUrl.url;
}
}
}
2025-10-07 13:31:06 +08:00
return route;
}
2025-10-08 14:11:54 +08:00
/**
* URL的菜单
* @param menus
* @returns URL的菜单
*/
function findFirstMenuWithUrl(menus: SysMenu[]): SysMenu | null {
for (const menu of menus) {
if (menu.type !== MenuType.BUTTON) {
if (menu.url) {
return menu;
}
if (menu.children && menu.children.length > 0) {
const found = findFirstMenuWithUrl(menu.children);
if (found) return found;
}
}
}
return null;
}
2025-10-07 13:31:06 +08:00
/**
*
* @param componentName /
* @returns
*/
function getComponent(componentName: string) {
// 检查是否是布局组件
if (LAYOUT_MAP[componentName]) {
return LAYOUT_MAP[componentName];
}
// 处理页面组件路径
let componentPath = componentName;
// 如果不是以@/开头的完整路径,则添加@/views/前缀
if (!componentPath.startsWith('@/')) {
2025-10-08 14:11:54 +08:00
// 确保路径以/开头
2025-10-07 13:31:06 +08:00
if (!componentPath.startsWith('/')) {
componentPath = '/' + componentPath;
}
2025-10-08 14:11:54 +08:00
// 添加@/views前缀
2025-10-07 13:31:06 +08:00
componentPath = '@/views' + componentPath;
}
2025-10-08 14:11:54 +08:00
// 将@/别名转换为相对路径因为Vite动态导入可能无法正确解析别名
if (componentPath.startsWith('@/')) {
componentPath = componentPath.replace('@/', '../');
}
2025-10-07 13:31:06 +08:00
// 如果没有.vue扩展名添加它
if (!componentPath.endsWith('.vue')) {
componentPath += '.vue';
}
// 动态导入组件
2025-10-08 14:11:54 +08:00
return () => {
try {
// 使用动态导入Vite 会自动处理路径解析
return import(/* @vite-ignore */ componentPath);
} catch (error) {
console.warn(`组件加载失败: ${componentPath}`, error);
// 返回404组件
return import('@/views/error/404.vue').catch(() =>
Promise.resolve({
template: `<div class="component-error">
<h3></h3>
<p>无法加载组件: ${componentPath}</p>
<p>错误: ${error instanceof Error ? error.message : String(error)}</p>
</div>`,
style: `
.component-error {
padding: 20px;
text-align: center;
color: #f56565;
background: #fed7d7;
border-radius: 4px;
}
`
})
);
}
};
}
/**
*
* @param routes
* @returns
*/
function convertRoutesToMenus(routes: RouteRecordRaw[]): SysMenu[] {
const menus: SysMenu[] = [];
routes.forEach(route => {
// 处理有子路由的情况(现在静态路由都有布局组件)
if (route.children && route.children.length > 0) {
route.children.forEach(child => {
// 只处理有 meta.menuType 的子路由
if (child.meta?.menuType !== undefined) {
const menu: SysMenu = {
menuID: child.name as string || child.path.replace(/\//g, '-'),
parentID: '0',
name: child.meta.title as string || child.name as string,
url: route.path, // 使用父路由的路径
type: child.meta.menuType as MenuType,
orderNum: (child.meta.orderNum as number) || -1,
// 标记为静态路由,避免重复生成路由
component: '__STATIC_ROUTE__', // 特殊标记
};
menus.push(menu);
}
});
}
// 处理没有子路由的情况(兼容性保留)
else if (route.meta?.menuType !== undefined) {
const menu: SysMenu = {
menuID: route.name as string || route.path.replace(/\//g, '-'),
parentID: '0',
name: route.meta.title as string || route.name as string,
url: route.path,
type: route.meta.menuType as MenuType,
orderNum: (route.meta.orderNum as number) || -1,
// 标记为静态路由,避免重复生成路由
component: '__STATIC_ROUTE__', // 特殊标记
};
menus.push(menu);
}
2025-10-07 13:31:06 +08:00
});
2025-10-08 14:11:54 +08:00
return menus;
2025-10-07 13:31:06 +08:00
}
/**
*
* @param menus
* @returns
*/
2025-10-08 14:11:54 +08:00
export function buildMenuTree(menus: SysMenu[]): SysMenu[] {
// 将静态路由转换为菜单项
const staticMenus = convertRoutesToMenus(routes);
// 合并动态菜单和静态菜单
const allMenus = [...staticMenus, ...menus];
if (allMenus.length === 0) {
2025-10-07 13:31:06 +08:00
return [];
}
const menuMap = new Map<string, SysMenu>();
const rootMenus: SysMenu[] = [];
// 创建菜单映射
2025-10-08 14:11:54 +08:00
allMenus.forEach(menu => {
2025-10-07 13:31:06 +08:00
if (menu.menuID) {
menuMap.set(menu.menuID, { ...menu, children: [] });
}
});
// 构建树结构
2025-10-08 14:11:54 +08:00
allMenus.forEach(menu => {
2025-10-07 13:31:06 +08:00
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;
}
2025-10-08 14:11:54 +08:00
/**
* 访URL
* @param menus
* @returns 访URL null
*/
export function getFirstAccessibleMenuUrl(menus: SysMenu[]): string | null {
if (!menus || menus.length === 0) {
return null;
}
return "/home";
}