Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
214 lines
5.7 KiB
TypeScript
214 lines
5.7 KiB
TypeScript
import type { Router, RouteRecordRaw } from 'vue-router';
|
||
|
||
import type {
|
||
AppRouteRecordRaw,
|
||
ExRouteRecordRaw,
|
||
MenuRecordRaw,
|
||
RouteMeta,
|
||
RouteRecordStringComponent,
|
||
} from '@vben-core/typings';
|
||
|
||
import {
|
||
filterTree,
|
||
isHttpUrl,
|
||
mapTree,
|
||
sortTree,
|
||
} from '@vben-core/shared/utils';
|
||
|
||
/**
|
||
* 根据 routes 生成菜单列表
|
||
* @param routes - 路由配置列表
|
||
* @param router - Vue Router 实例
|
||
* @returns 生成的菜单列表
|
||
*/
|
||
function generateMenus(
|
||
routes: RouteRecordRaw[],
|
||
router: Router,
|
||
): MenuRecordRaw[] {
|
||
// 将路由列表转换为一个以 name 为键的对象映射
|
||
const finalRoutesMap: { [key: string]: string } = Object.fromEntries(
|
||
router.getRoutes().map(({ name, path }) => [name, path]),
|
||
);
|
||
|
||
let menus = mapTree<ExRouteRecordRaw, MenuRecordRaw>(routes, (route) => {
|
||
// 获取最终的路由路径
|
||
const path = finalRoutesMap[route.name as string] ?? route.path ?? '';
|
||
|
||
const {
|
||
meta = {} as RouteMeta,
|
||
name: routeName,
|
||
redirect,
|
||
children = [],
|
||
} = route;
|
||
const {
|
||
activeIcon,
|
||
badge,
|
||
badgeType,
|
||
badgeVariants,
|
||
hideChildrenInMenu = false,
|
||
icon,
|
||
link,
|
||
order,
|
||
title = '',
|
||
} = meta;
|
||
|
||
// 确保菜单名称不为空
|
||
const name = (title || routeName || '') as string;
|
||
|
||
// 处理子菜单
|
||
const resultChildren = hideChildrenInMenu
|
||
? []
|
||
: ((children as MenuRecordRaw[]) ?? []);
|
||
|
||
// 设置子菜单的父子关系
|
||
if (resultChildren.length > 0) {
|
||
resultChildren.forEach((child) => {
|
||
child.parents = [...(route.parents ?? []), path];
|
||
child.parent = path;
|
||
});
|
||
}
|
||
|
||
// 确定最终路径
|
||
const resultPath = hideChildrenInMenu ? redirect || path : link || path;
|
||
|
||
return {
|
||
activeIcon,
|
||
badge,
|
||
badgeType,
|
||
badgeVariants,
|
||
icon,
|
||
name,
|
||
order,
|
||
parent: route.parent,
|
||
parents: route.parents,
|
||
path: resultPath,
|
||
show: !meta.hideInMenu,
|
||
children: resultChildren,
|
||
};
|
||
});
|
||
|
||
// 对菜单进行排序,避免order=0时被替换成999的问题
|
||
menus = sortTree(menus, (a, b) => (a?.order ?? 999) - (b?.order ?? 999));
|
||
|
||
// 过滤掉隐藏的菜单项
|
||
return filterTree(menus, (menu) => !!menu.show);
|
||
}
|
||
|
||
/**
|
||
* 转换后端菜单数据为路由数据
|
||
* @param menuList 后端菜单数据
|
||
* @param parent 父级菜单
|
||
* @param nameSet 用于跟踪已使用的 name,防止重复
|
||
* @returns 路由数据
|
||
*/
|
||
function convertServerMenuToRouteRecordStringComponent(
|
||
menuList: AppRouteRecordRaw[],
|
||
parent = '',
|
||
nameSet: Set<string> = new Set(),
|
||
): RouteRecordStringComponent[] {
|
||
const menus: RouteRecordStringComponent[] = [];
|
||
menuList.forEach((menu) => {
|
||
// 处理外链菜单(顶级或子级)
|
||
if (isHttpUrl(menu.path)) {
|
||
// add by 芋艿:如果有 ?_iframe 参数,则作为内嵌页面处理
|
||
// 如果有 _iframe 参数,则使用 iframeSrc;如果没有,则使用 link
|
||
const url = new URL(menu.path);
|
||
let link: string | undefined;
|
||
let iframeSrc: string | undefined;
|
||
if (url.searchParams.has('_iframe')) {
|
||
url.searchParams.delete('_iframe');
|
||
iframeSrc = url.toString();
|
||
} else {
|
||
link = menu.path;
|
||
}
|
||
|
||
const urlMenu: RouteRecordStringComponent = {
|
||
component: 'IFrameView',
|
||
meta: {
|
||
hideInMenu: !menu.visible,
|
||
icon: menu.icon,
|
||
iframeSrc,
|
||
link,
|
||
order: menu.sort,
|
||
title: menu.name,
|
||
},
|
||
name: menu.name,
|
||
path: `${menu.id}`,
|
||
};
|
||
menus.push(urlMenu);
|
||
return;
|
||
} else if (menu.children && menu.parentId === 0) {
|
||
menu.component = 'BasicLayout';
|
||
} else if (!menu.children) {
|
||
menu.component = menu.component as string;
|
||
}
|
||
if (menu.component === 'Layout') {
|
||
menu.component = 'BasicLayout';
|
||
}
|
||
|
||
if (menu.children && menu.parentId !== 0) {
|
||
menu.component = '';
|
||
}
|
||
|
||
// path
|
||
if (parent) {
|
||
menu.path = `${parent}/${menu.path}`;
|
||
}
|
||
|
||
if (!menu.path.startsWith('/')) {
|
||
menu.path = `/${menu.path}`;
|
||
}
|
||
|
||
// add by 芋艿:防止 name 重复,只有在 name 重复时,才自动添加 id
|
||
let finalName = menu.componentName || menu.name;
|
||
if (nameSet.has(finalName)) {
|
||
finalName = menu.name + menu.id;
|
||
console.error(`menu name duplicate: ${menu.name}, id: ${menu.id}`, menu);
|
||
}
|
||
nameSet.add(finalName);
|
||
|
||
// add by 芋艿:处理 menu.component 中的 query 参数
|
||
// https://doc.vben.pro/guide/essentials/route.html#query
|
||
let query: Record<string, string> | undefined;
|
||
// add by 芋艿:防止 component 为 null 时,调用 indexOf 报错;关联
|
||
if (!menu.component) {
|
||
menu.component = '';
|
||
}
|
||
const queryIndex = menu.component.indexOf('?');
|
||
if (queryIndex !== -1) {
|
||
// 提取 query 字符串并解析为对象
|
||
const queryString = menu.component.slice(queryIndex + 1);
|
||
query = Object.fromEntries(new URLSearchParams(queryString).entries());
|
||
// 移除 component 中的 query 部分
|
||
menu.component = menu.component.slice(0, queryIndex);
|
||
}
|
||
|
||
const buildMenu: RouteRecordStringComponent = {
|
||
component: menu.component,
|
||
meta: {
|
||
hideInMenu: !menu.visible,
|
||
icon: menu.icon,
|
||
keepAlive: menu.keepAlive,
|
||
order: menu.sort,
|
||
title: menu.name,
|
||
...(query && { query }),
|
||
},
|
||
name: finalName,
|
||
path: menu.path,
|
||
};
|
||
|
||
if (menu.children && menu.children.length > 0) {
|
||
buildMenu.children = convertServerMenuToRouteRecordStringComponent(
|
||
menu.children,
|
||
menu.path,
|
||
nameSet,
|
||
);
|
||
}
|
||
|
||
menus.push(buildMenu);
|
||
});
|
||
return menus;
|
||
}
|
||
|
||
export { convertServerMenuToRouteRecordStringComponent, generateMenus };
|