fix: 为后端返回的菜单数据添加 id,避免直接使用 index 作为菜单树的 node id 导致异常
This commit is contained in:
parent
7a9a301935
commit
1f085222b3
@ -1,4 +1,5 @@
|
||||
import { http } from "@/utils/http";
|
||||
import { RouteRecordRaw } from "vue-router";
|
||||
|
||||
export type CaptchaDTO = {
|
||||
/** 验证码的base64图片 */
|
||||
@ -97,12 +98,67 @@ export const getLoginUserInfo = () => {
|
||||
return http.request<ResponseData<TokenDTO>>("get", "/getLoginUserInfo");
|
||||
};
|
||||
|
||||
type Result = {
|
||||
success: boolean;
|
||||
data: Array<any>;
|
||||
export interface RouteMeta {
|
||||
id: string;
|
||||
title: string;
|
||||
icon?: string;
|
||||
showLink?: boolean;
|
||||
showParent?: boolean;
|
||||
auths?: string[];
|
||||
rank?: number;
|
||||
frameSrc?: string;
|
||||
isFrameSrcInternal?: boolean;
|
||||
}
|
||||
|
||||
export type RouteItem = RouteRecordRaw & {
|
||||
name?: string;
|
||||
path: string;
|
||||
meta: RouteMeta;
|
||||
children?: RouteItem[];
|
||||
};
|
||||
|
||||
/** 获取动态菜单 */
|
||||
export const getAsyncRoutes = () => {
|
||||
return http.request<Result>("get", "/getRouters");
|
||||
type AsyncRoutesResponse = {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: RouteItem[];
|
||||
};
|
||||
|
||||
/**
|
||||
* 为后端返回的路由添加唯一id,后面我们在构建菜单树的层级结构时需要用到
|
||||
* 这里我们假设 name + path 是唯一的,若日后有 name + path 不唯一的情况,
|
||||
* 则需要修改此处的逻辑
|
||||
*/
|
||||
const addUniqueId = (routes: RouteItem[]): RouteItem[] => {
|
||||
return routes.map(route => {
|
||||
const id = `${route.name || ""}${route.path}`;
|
||||
|
||||
if (route.children && route.children.length > 0) {
|
||||
route.children = addUniqueId(route.children);
|
||||
}
|
||||
|
||||
return {
|
||||
...route,
|
||||
meta: {
|
||||
...route.meta,
|
||||
id
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
function withId(result: AsyncRoutesResponse) {
|
||||
if (result.data) {
|
||||
result.data = addUniqueId(result.data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动态菜单
|
||||
* TODO:对于开发环境下此处可以对路由数据做一些校验,比如说 name 是否重复,name+path 是否重复等等
|
||||
*/
|
||||
export const getAsyncRoutes = async () => {
|
||||
const result = await http.request<AsyncRoutesResponse>("get", "/getRouters");
|
||||
return withId(result);
|
||||
};
|
||||
|
||||
@ -27,7 +27,7 @@ const IFrame = () => import("@/layout/frameView.vue");
|
||||
const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}");
|
||||
|
||||
// 动态路由
|
||||
import { getAsyncRoutes } from "@/api/common/login";
|
||||
import { getAsyncRoutes, RouteItem } from "@/api/common/login";
|
||||
import { TokenDTO } from "@/api/common/login";
|
||||
|
||||
function handRank(routeInfo: any) {
|
||||
@ -152,34 +152,32 @@ function addPathMatch() {
|
||||
}
|
||||
|
||||
/** 处理动态路由(后端返回的路由) */
|
||||
function handleAsyncRoutes(routeList) {
|
||||
if (routeList.length === 0) {
|
||||
usePermissionStoreHook().handleWholeMenus(routeList);
|
||||
} else {
|
||||
formatFlatteningRoutes(addAsyncRoutes(routeList)).map(
|
||||
(v: RouteRecordRaw) => {
|
||||
// 防止重复添加路由
|
||||
if (
|
||||
router.options.routes[0].children.findIndex(
|
||||
value => value.path === v.path
|
||||
) !== -1
|
||||
) {
|
||||
return;
|
||||
} else {
|
||||
// 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转
|
||||
router.options.routes[0].children.push(v);
|
||||
// 最终路由进行升序
|
||||
ascending(router.options.routes[0].children);
|
||||
if (!router.hasRoute(v?.name)) router.addRoute(v);
|
||||
const flattenRouters: any = router
|
||||
.getRoutes()
|
||||
.find(n => n.path === "/");
|
||||
router.addRoute(flattenRouters);
|
||||
}
|
||||
function handleAsyncRoutes(routeList: RouteItem[]) {
|
||||
if (routeList.length) {
|
||||
const normalizedRoutes = normalizeBackendRoutes(routeList);
|
||||
const flattenedRoutes = formatFlatteningRoutes(normalizedRoutes);
|
||||
|
||||
flattenedRoutes.forEach((v: RouteRecordRaw) => {
|
||||
// 防止重复添加路由
|
||||
if (
|
||||
router.options.routes[0].children.findIndex(
|
||||
value => value.path === v.path
|
||||
) !== -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
usePermissionStoreHook().handleWholeMenus(routeList);
|
||||
|
||||
// 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转
|
||||
router.options.routes[0].children.push(v);
|
||||
// 最终路由进行升序
|
||||
ascending(router.options.routes[0].children);
|
||||
if (!router.hasRoute(v?.name)) router.addRoute(v);
|
||||
const flattenRouters: any = router.getRoutes().find(n => n.path === "/");
|
||||
router.addRoute(flattenRouters);
|
||||
});
|
||||
}
|
||||
|
||||
usePermissionStoreHook().handleWholeMenus(routeList);
|
||||
addPathMatch();
|
||||
}
|
||||
|
||||
@ -218,7 +216,7 @@ function initRouter() {
|
||||
* @param routesList 传入路由
|
||||
* @returns 返回处理后的一维路由
|
||||
*/
|
||||
function formatFlatteningRoutes(routesList: RouteRecordRaw[]) {
|
||||
function formatFlatteningRoutes(routesList: RouteItem[]) {
|
||||
if (routesList.length === 0) return routesList;
|
||||
let hierarchyList = buildHierarchyTree(routesList);
|
||||
for (let i = 0; i < hierarchyList.length; i++) {
|
||||
@ -293,12 +291,15 @@ function handleAliveRoute({ name }: ToRouteType, mode?: string) {
|
||||
}
|
||||
|
||||
/** 过滤后端传来的动态路由 重新生成规范路由 */
|
||||
function addAsyncRoutes(arrRoutes: Array<RouteRecordRaw>) {
|
||||
if (!arrRoutes || !arrRoutes.length) return;
|
||||
function normalizeBackendRoutes(backendRoutes: Array<RouteItem>) {
|
||||
if (!backendRoutes || !backendRoutes.length) return;
|
||||
|
||||
const modulesRoutesKeys = Object.keys(modulesRoutes);
|
||||
arrRoutes.forEach((v: RouteRecordRaw) => {
|
||||
// 将backstage属性加入meta,标识此路由为后端返回路由
|
||||
|
||||
backendRoutes.forEach(v => {
|
||||
// 标识此路由为后端返回路由
|
||||
v.meta.backstage = true;
|
||||
|
||||
// 父级的redirect属性取值:如果子级存在且父级的redirect属性不存在,默认取第一个子级的path;如果子级存在且父级的redirect属性存在,取存在的redirect属性,会覆盖默认值
|
||||
if (v?.children && v.children.length && !v.redirect)
|
||||
v.redirect = v.children[0].path;
|
||||
@ -316,14 +317,15 @@ function addAsyncRoutes(arrRoutes: Array<RouteRecordRaw>) {
|
||||
v.component = modulesRoutes[modulesRoutesKeys[index]];
|
||||
}
|
||||
if (v?.children && v.children.length) {
|
||||
addAsyncRoutes(v.children);
|
||||
normalizeBackendRoutes(v.children);
|
||||
}
|
||||
});
|
||||
return arrRoutes;
|
||||
|
||||
return backendRoutes;
|
||||
}
|
||||
|
||||
/** 获取路由历史模式 https://next.router.vuejs.org/zh/guide/essentials/history-mode.html */
|
||||
function getHistoryMode(routerHistory): RouterHistory {
|
||||
function getHistoryMode(routerHistory: string): RouterHistory {
|
||||
// len为1 代表只有历史模式 为2 代表历史模式中存在base参数 https://next.router.vuejs.org/zh/api/#%E5%8F%82%E6%95%B0-1
|
||||
const historyMode = routerHistory.split(",");
|
||||
const leftMode = historyMode[0];
|
||||
@ -379,7 +381,7 @@ export {
|
||||
addPathMatch,
|
||||
isOneOfArray,
|
||||
getHistoryMode,
|
||||
addAsyncRoutes,
|
||||
normalizeBackendRoutes,
|
||||
getParentPaths,
|
||||
findRouteByPath,
|
||||
handleAliveRoute,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { RouteItem } from "@/api/common/login";
|
||||
|
||||
/**
|
||||
* @description 提取菜单树中的每一项uniqueId
|
||||
* @param tree 树
|
||||
@ -47,29 +49,41 @@ export const deleteChildren = (tree: any[], pathList = []): any => {
|
||||
return tree;
|
||||
};
|
||||
|
||||
// 定义扩展属性类型
|
||||
export interface HierarchyNodeExtra {
|
||||
id?: string;
|
||||
parentId?: string | null;
|
||||
pathList?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建层级关系
|
||||
* @param tree 树
|
||||
* @param pathList 每一项的id组成的数组
|
||||
* @returns 创建层级关系后的树
|
||||
*/
|
||||
export const buildHierarchyTree = (tree: any[], pathList = []): any => {
|
||||
export function buildHierarchyTree<T extends RouteItem & HierarchyNodeExtra>(
|
||||
tree: T[],
|
||||
pathList: string[] = []
|
||||
): T[] {
|
||||
if (!Array.isArray(tree)) {
|
||||
console.warn("tree must be an array");
|
||||
return [];
|
||||
}
|
||||
if (!tree || tree.length === 0) return [];
|
||||
for (const [key, node] of tree.entries()) {
|
||||
node.id = key;
|
||||
node.parentId = pathList.length ? pathList[pathList.length - 1] : null;
|
||||
|
||||
for (const node of tree) {
|
||||
node.id = node.meta.id;
|
||||
node.parentId = pathList.at(-1) ?? null;
|
||||
node.pathList = [...pathList, node.id];
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
if (hasChildren) {
|
||||
|
||||
if (node?.children?.length) {
|
||||
buildHierarchyTree(node.children, node.pathList);
|
||||
}
|
||||
}
|
||||
|
||||
return tree;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 广度优先遍历,根据唯一uniqueId找当前节点信息
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user