|
@@ -1,8 +1,10 @@
|
|
|
import type { RouteRecordRaw } from 'vue-router';
|
|
import type { RouteRecordRaw } from 'vue-router';
|
|
|
|
|
|
|
|
import type {
|
|
import type {
|
|
|
|
|
+ AuthorityDataList,
|
|
|
ComponentRecordType,
|
|
ComponentRecordType,
|
|
|
GenerateMenuAndRoutesOptions,
|
|
GenerateMenuAndRoutesOptions,
|
|
|
|
|
+ RouteMeta,
|
|
|
RouteRecordStringComponent,
|
|
RouteRecordStringComponent,
|
|
|
} from '@vben-core/typings';
|
|
} from '@vben-core/typings';
|
|
|
|
|
|
|
@@ -14,10 +16,36 @@ import { mapTree } from '@vben-core/shared/utils';
|
|
|
async function generateRoutesByBackend(
|
|
async function generateRoutesByBackend(
|
|
|
options: GenerateMenuAndRoutesOptions,
|
|
options: GenerateMenuAndRoutesOptions,
|
|
|
): Promise<RouteRecordRaw[]> {
|
|
): Promise<RouteRecordRaw[]> {
|
|
|
- const { fetchMenuListAsync, layoutMap = {}, pageMap = {} } = options;
|
|
|
|
|
|
|
+ const {
|
|
|
|
|
+ fetchMenuListAsync,
|
|
|
|
|
+ layoutMap = {},
|
|
|
|
|
+ pageMap = {},
|
|
|
|
|
+ routes: dynamicRoutes,
|
|
|
|
|
+ } = options;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const menuRoutes = await fetchMenuListAsync?.();
|
|
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) {
|
|
if (!menuRoutes) {
|
|
|
return [];
|
|
return [];
|
|
|
}
|
|
}
|
|
@@ -28,9 +56,21 @@ async function generateRoutesByBackend(
|
|
|
normalizePageMap[normalizeViewPath(key)] = value;
|
|
normalizePageMap[normalizeViewPath(key)] = value;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const routes = convertRoutes(menuRoutes, layoutMap, normalizePageMap);
|
|
|
|
|
|
|
+ // 转换接口权限数据
|
|
|
|
|
+ const processMenuRoutes = processMenuData(
|
|
|
|
|
+ menuRoutes as AuthorityDataList[],
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- return routes;
|
|
|
|
|
|
|
+ const routes = convertRoutes(
|
|
|
|
|
+ processMenuRoutes as unknown as RouteRecordStringComponent[],
|
|
|
|
|
+ layoutMap,
|
|
|
|
|
+ normalizePageMap,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 合并动态路由和后端路由
|
|
|
|
|
+ const finalRoutes = [...dynamicRoutes, ...routes];
|
|
|
|
|
+
|
|
|
|
|
+ return finalRoutes;
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error(error);
|
|
console.error(error);
|
|
|
return [];
|
|
return [];
|
|
@@ -80,4 +120,212 @@ function normalizeViewPath(path: string): string {
|
|
|
// 这里耦合了vben-admin的目录结构
|
|
// 这里耦合了vben-admin的目录结构
|
|
|
return viewPath.replace(/^\/views/, '');
|
|
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 };
|
|
export { generateRoutesByBackend };
|