| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- import type { RouteRecordRaw } from 'vue-router';
- import type {
- AuthorityDataList,
- ComponentRecordType,
- GenerateMenuAndRoutesOptions,
- RouteMeta,
- RouteRecordStringComponent,
- } from '@vben-core/typings';
- import { mapTree } from '@vben-core/shared/utils';
- /**
- * 动态生成路由 - 后端方式
- */
- async function generateRoutesByBackend(
- options: GenerateMenuAndRoutesOptions,
- ): Promise<RouteRecordRaw[]> {
- const {
- fetchMenuListAsync,
- layoutMap = {},
- pageMap = {},
- routes: dynamicRoutes,
- } = options;
- try {
- const menuRoutes = await fetchMenuListAsync?.();
- // console.log('menuRoutes:', menuRoutes);
- // [{
- // "menu_id": 1,
- // "menu_name": "systemManage",
- // "menu_key": "_systemManage",
- // "component": "/layout",
- // "active_menu": null,
- // "parent_id": 0,
- // "order_num": 99,
- // "menu_type": 1,
- // "visible": 0,
- // "perms": null,
- // "icon": "ii-system",
- // "path": "/systemmanage",
- // "redirect": null,
- // "hidden_header": 0,
- // "remark": "系统管理",
- // "no_cache": 0,
- // "api_parameter": null,
- // "api_url": null
- // },]
- if (!menuRoutes) {
- return [];
- }
- const normalizePageMap: ComponentRecordType = {};
- for (const [key, value] of Object.entries(pageMap)) {
- normalizePageMap[normalizeViewPath(key)] = value;
- }
- // 转换接口权限数据
- const processMenuRoutes = processMenuData(
- menuRoutes as AuthorityDataList[],
- );
- const routes = convertRoutes(
- processMenuRoutes as unknown as RouteRecordStringComponent[],
- layoutMap,
- normalizePageMap,
- );
- // console.log('menuRoutes:', menuRoutes);
- // console.log('processMenuRoutes:', processMenuRoutes);
- // 合并动态路由和后端路由
- const finalRoutes = [...dynamicRoutes, ...routes];
- // console.log('finalRoutes:', finalRoutes);
- return finalRoutes;
- } catch (error) {
- console.error(error);
- return [];
- }
- }
- function convertRoutes(
- routes: RouteRecordStringComponent[],
- layoutMap: ComponentRecordType,
- pageMap: ComponentRecordType,
- ): RouteRecordRaw[] {
- return mapTree(routes, (node) => {
- const route = node as unknown as RouteRecordRaw;
- const { component, name } = node;
- if (!name) {
- console.error('route name is required', route);
- }
- // layout转换
- if (component && layoutMap[component]) {
- route.component = layoutMap[component];
- // 页面组件转换
- } else if (component) {
- const normalizePath = normalizeViewPath(component);
- route.component =
- pageMap[
- normalizePath.endsWith('.vue')
- ? normalizePath
- : `${normalizePath}.vue`
- ];
- }
- return route;
- });
- }
- function normalizeViewPath(path: string): string {
- // 去除相对路径前缀
- const normalizedPath = path.replace(/^(\.\/|\.\.\/)+/, '');
- // 确保路径以 '/' 开头
- const viewPath = normalizedPath.startsWith('/')
- ? normalizedPath
- : `/${normalizedPath}`;
- // 这里耦合了vben-admin的目录结构
- return viewPath.replace(/^\/views/, '');
- }
- /**
- * 菜单类型枚举
- */
- const MenuType = {
- /** 目录 */
- DIRECTORY: 1,
- /** 菜单 */
- MENU: 2,
- /** 按钮 */
- BUTTON: 3,
- /** API接口 */
- API: 4,
- } as const;
- /**
- * 处理接口返回的权限数据
- * 1.分离按钮和菜单权限;
- * 2.将按钮生成权限组,存入store中
- * 3.将数据库字段转为系统所用字段,并递归生成菜单树
- */
- function processMenuData(routes: AuthorityDataList[]) {
- // 使用语义化的枚举值过滤按钮类型
- // const buttonTempArr = routes.filter(
- // (item) => item.menu_type === MenuType.BUTTON,
- // );
- const menuTempArr = routes.filter((item) =>
- [MenuType.DIRECTORY, MenuType.MENU].includes(item.menu_type),
- );
- // const apiTempArr = routes.filter((item) => item.menu_type === MenuType.API);
- const menuTree = convertToTree(menuTempArr);
- // console.log('api list:', apiTempArr);
- // console.log('button list:', buttonTempArr);
- // console.log('menu list:', menuTempArr);
- // console.log('menu tree:', menuTree);
- return menuTree;
- }
- // 转换为菜单结构
- function convertToTree(routes: AuthorityDataList[]) {
- const tree: AuthorityDataList[] = [];
- const map = new Map();
- routes.forEach((route) => {
- map.set(route.menu_id, { ...route, children: [] });
- });
- // 根据parent_id 递归生成路由树
- routes.forEach((route) => {
- const node = map.get(route.menu_id);
- if (route.parent_id === 0) {
- // 顶级节点直接加入tree
- tree.push(node);
- } else {
- // 非顶级节点加入到父节点的children中
- const parent = map.get(route.parent_id);
- if (parent) {
- parent.children.push(node);
- }
- }
- });
- // 递归排序
- function sortTree(nodes: AuthorityDataList[]) {
- return nodes
- .sort((a, b) => a.order_num - b.order_num)
- .map((node) => {
- if (node.children && node.children.length > 0) {
- node.children = sortTree(node.children);
- }
- return node;
- });
- }
- const sortedTree = sortTree(tree);
- interface RouteItem {
- children?: RouteItem[];
- component?: string;
- meta: RouteMeta;
- parent?: string; // 当前菜单的直接父级路径
- parents?: string[]; // 当前菜单的所有父级路径
- name: string; // 菜单名称
- path: string; // 菜单路径
- }
- // 递归生成路由树
- function generateRoutes(nodes: AuthorityDataList[]): RouteItem[] {
- return nodes.map((node) => {
- const route: RouteItem = {
- path: node.path,
- name: node.menu_key,
- component: node.component,
- meta: {
- title: node.menu_name,
- icon: node.icon,
- hideInMenu: node.visible === 1,
- keepAlive: node.no_cache === 0,
- hideInBreadcrumb: node.hidden_header === 1,
- hideInTab: node.hidden_header === 1,
- // affixTab: node.affix_tab === 1,
- // affixTabOrder: node.affix_tab_order,
- // maxNumOfOpenTab: node.max_num_of_open_tab,
- // menuVisibleWithForbidden: node.menu_visible_with_forbidden === 1,
- // noBasicLayout: node.no_basic_layout === 1,
- // openInNewWindow: node.open_in_new_window === 1,
- order: node.order_num,
- query: node.api_parameter,
- // redirect: node.redirect,
- authority: node.perms ? node.perms.split(',') : [],
- // hideChildrenInMenu: node.visible === 1,
- },
- };
- // 处理直接父级路径
- const parent =
- node.parent_id === 0
- ? null
- : routes.find((n) => n.menu_id === node.parent_id)?.path || '';
- if (parent) route.parent = parent;
- // 递归处理子节点
- if (node.children?.length) {
- route.children = generateRoutes(node.children);
- }
- return route;
- });
- }
- const treeRoutes = generateRoutes(sortedTree);
- // 递归处理父级路径 parents
- function handleParentPaths(routes: RouteItem[]) {
- // 扁平化所有路由,包括子路由
- function flattenRoutes(items: RouteItem[]): RouteItem[] {
- const result: RouteItem[] = [];
- for (const item of items) {
- result.push(item);
- if (item.children && item.children.length > 0) {
- result.push(...flattenRoutes(item.children));
- }
- }
- return result;
- }
- // 标准化路径格式
- function normalizePath(path: string): string {
- return path.startsWith('/') ? path : `/${path}`;
- }
- // 获取所有父级路径
- function getAllParentPaths(
- route: RouteItem,
- allRoutes: RouteItem[],
- ): string[] {
- const parentPaths: string[] = [];
- const visitedPaths = new Set<string>();
- let currentPath = route.parent ? normalizePath(route.parent) : null;
- while (currentPath && !visitedPaths.has(currentPath)) {
- visitedPaths.add(currentPath);
- parentPaths.unshift(currentPath);
- const parentRoute = allRoutes.find(
- (r) => normalizePath(r.path) === currentPath,
- );
- currentPath = parentRoute?.parent
- ? normalizePath(parentRoute.parent)
- : null;
- }
- return parentPaths;
- }
- // 一次性扁平化所有路由
- const allFlattenedRoutes = flattenRoutes(routes);
- function processRoutes(routesToProcess: RouteItem[]): RouteItem[] {
- return routesToProcess.map((route) => {
- const newRoute = { ...route };
- if (newRoute.parent) {
- newRoute.parent = normalizePath(newRoute.parent);
- newRoute.parents = getAllParentPaths(newRoute, allFlattenedRoutes);
- }
- // 递归处理子路由,但使用同一个扁平化路由数组
- if (newRoute.children && newRoute.children.length > 0) {
- newRoute.children = processRoutes(newRoute.children);
- }
- return newRoute;
- });
- }
- return processRoutes(routes);
- }
- const handledTreeRoutes = handleParentPaths(treeRoutes);
- return handledTreeRoutes;
- }
- export { generateRoutesByBackend };
|