fix: 为后端返回的菜单数据添加 id,避免直接使用 index 作为菜单树的 node id 导致异常

This commit is contained in:
guoquan 2025-06-07 16:49:55 +08:00
parent 7a9a301935
commit 1f085222b3
3 changed files with 121 additions and 49 deletions

View File

@ -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);
};

View File

@ -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,

View File

@ -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找当前节点信息