#上一篇文章讲述了三级路由缓存的一个ParentView这个方案,它是可以实现三级路由缓存的问题,但是经过测试反馈,发现有一个严重的bug,就是打开的三级路由页面有几个,新打开的三级路由页面的onMounted或者onBeforeMount 就会触发n+1次 导致接口重复被调用#
这就使我不得不再次研究,如何更优雅且没有bug的来实现Vue三级及以上路由无法缓存的问题
重新思考
既然二级路由可以实现缓存,那么可不可以把三级以及三级以上路由,转化为二级路由呢
有的同学说路由本来就是三级的,转为二级菜单那里就不可以使用了,那么有没有一个方法,既不影响页面菜单那里的UI展示效果,又能把三级路由转为二级路由呢
代码实现
这里需要注意,路由配置还是保持多级嵌套的形式,而这个配置并非最终注册使用的路由,仅仅是提供侧边栏导航菜单使用,同时再生成一份用于动态注册路由的数据,图例如果没看明白的话,可以看下面两组数据。
一、处理后端返回的菜单数据(原始数据还是原始数据的处理方式)
javascript
import { RouteRecordRaw } from "vue-router";
import { defineStore } from "pinia";
import { constantRoutes } from "@/router";
import { store } from "@/store";
const modules = import.meta.glob("../../views/**/**.vue");
const Layout = () => import("@/layout/index.vue");
const Other = () => import("@/layout/other.vue");
const filterAsyncRoutes = (routes: [],basepath:string) => {
const asyncRoutes: RouteRecordRaw[] = [];
routes.sort((item1, item2) => {
return item1.sort - item2.sort
}).forEach((route) => {
const tmpRoute = {
// path: basepath+'/'+route.menuUrl,
// component: basepath + '/' + route.menuUrl,
// name: route.menuName,
// meta: { title: route.menuName, icon: route.icon, show: false, keepAlive: true },
path: basepath + '/' + route.resourceUrl,
component: basepath + '/' + route.resourceUrl,
name: route.resourceName,
meta: { title: route.resourceName, type: route.resourceType, icon: route.icon, show: route.isShow, keepAlive: true, sort: route.sort },
children: route.childrenList.length > 0 ? route.childrenList : []
}; // ES6扩展运算符复制新对象
//解析路径(旧的逻辑)
if (tmpRoute.component?.toString() == "Layout") {
tmpRoute.component = Layout;
} else {
const component = modules[`../../views${tmpRoute.component}.vue`];
if (component) {
tmpRoute.component = component;
} else {
if(tmpRoute.children.length > 0){
tmpRoute.component = Other
}else {
tmpRoute.component = modules[`../../views/error-page/404.vue`];
}
}
}
if (tmpRoute.children && tmpRoute.children.length > 0) {
tmpRoute.children = filterAsyncRoutes(tmpRoute.children.sort((item1, item2) => {
return item1.sort - item2.sort
// if (item1.sort > item2.sort) {
// return 1;
// } else {
// return -1;
// }
}), basepath + '/' + route.resourceUrl); // basepath + '/' + route.menuUrl
}
asyncRoutes.push(tmpRoute);
});
return asyncRoutes;
};
// setup
export const usePermissionStore = defineStore("permission", () => {
//当前模块的id
const menuid = ref<string>()
//当前路径
const currpath = ref<string>()
// state
const routes = ref<RouteRecordRaw[]>([]);
// actions
function setRoutes(newRoutes: RouteRecordRaw[]) {
routes.value = constantRoutes.concat(newRoutes);
}
function setMenuid(id: string) {
menuid.value = id
}
function setCurrPath(path: string) {
currpath.value = path;
}
/**
* 生成动态路由
*
* @param childrenList 用户的菜单集合
* @returns
*/
function generateRoutes(childrenList: []) {
//构成菜单树形结构
//1.第一级 默认为 头部
//2.第二级 为功能菜单
//拼转成前端数据
const asyncRoutes: RouteRecordRaw[] = [];
childrenList.sort((item1, item2) => {
// if (item1.sort > item2.sort) {
// return 1;
// } else {
// return -1;
// }
return item1.sort - item2.sort
}).map((model) => {
//模块
const tmp = {
path: model.url,
component: Layout,
redirect: model.url,
name: model.moduleName,
meta: { title: model.moduleName, id: model.id, icon: model.icon, show: true, keepAlive: true },
// children: model.accessibleMenuList.length > 0 ? model.accessibleMenuList : []
children: model.childrenList.length > 0 ? model.childrenList : []
}
if (tmp.children && tmp.children.length > 0) {
tmp.children = filterAsyncRoutes(tmp.children, model.url);
}
asyncRoutes.push(tmp);
});
//设置当前模块
// console.log("当前路径的数据:", asyncRoutes)
return new Promise<RouteRecordRaw[]>((resolve, reject) => {
setRoutes(asyncRoutes);
resolve(asyncRoutes);
});
}
return { routes, menuid, currpath, setRoutes, generateRoutes, setMenuid, setCurrPath };
});
// 非setup
export function usePermissionStoreHook() {
return usePermissionStore(store);
}
二、在注册动态路(router.addRoute)由之前处理路由数据
bash
/**
* 将三级路由扁平化为二级路由
* @param {Array} routes - 原始的三级路由树
* @returns {Array} - 扁平化后的路由数组
*/
function flattenThreeLevelRoutes(routes: any[]) {
const result = [];
for (const level1Route of routes) {
// 保留一级路由的基本信息
const flatLevel1Route = {
...level1Route,
children: []
};
if (level1Route.children && level1Route.children.length > 0) {
for (const level2Route of level1Route.children) {
// 处理二级路由的children(三级路由)
if (level2Route.children && level2Route.children.length > 0) {
// 将三级路由提升为二级路由
for (const level3Route of level2Route.children) {
// console.log('三级路由',level3Route)
flatLevel1Route.children.push({
...level3Route
});
}
} else {
// 二级路由没有children,直接保留
flatLevel1Route.children.push(level2Route);
}
}
}
result.push(flatLevel1Route);
}
return result;
}
const { accessibleModuleList } = await userStore.getInfo();
//下传前端的菜单权限等数据
const accessRoutes = await permissionStore.generateRoutes(accessibleModuleList);
console.log('原始路由数据', accessRoutes);
// 关键步骤:将三级路由扁平化为二级
const flatRoutes = flattenThreeLevelRoutes(accessRoutes);
console.log('扁平化后的路由数据', flatRoutes);
flatRoutes.forEach((route) => {
router.addRoute(route);
});
通过上面的关键步骤,我的项目已经可以实现三级路由缓存的效果了
三、面包屑导航问题处理(我的项目中去掉了面包屑导航这个鸡肋的功能)
原有的面包屑导航是通过 $route.matched 可以获取到嵌套路由每一层级的信息,而当路由被处理成两级后,也就无法通过 $route.matched 进行显示了,所以在处理路由数据的同时,也需要处理面包屑导航的信息。大致最终会处理成这样:
javascript
{
path: '/users',
meta: {
title: '用户管理'
},
children: [
{
path: 'clients/list',
meta: {
title: '客户列表',
breadCrumb: [
{ path: '/users', title: '用户管理' },
{ path: 'clients', title: '客户管理' },
{ path: 'list', title: '客户列表' }
]
}
},
{
path: 'clients/detail',
meta: {
title: '客户详情',
breadCrumb: [
{ path: '/users', title: '用户管理' },
{ path: 'clients', title: '客户管理' },
{ path: 'detail', title: '客户详情' }
]
}
}
]
}
这样一来,通过 $route.meta.breadcrumb 也就可以获取任意某个路由的完整面包屑导航信息了。