news 2026/6/24 0:05:54

vue3 三级路由无法缓存的终终终终终终极解决方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
vue3 三级路由无法缓存的终终终终终终极解决方案

#上一篇文章讲述了三级路由缓存的一个ParentView这个方案,它是可以实现三级路由缓存的问题,但是经过测试反馈,发现有一个严重的bug,就是打开的三级路由页面有几个,新打开的三级路由页面的onMounted或者onBeforeMount 就会触发n+1次 导致接口重复被调用#

这就使我不得不再次研究,如何更优雅且没有bug的来实现Vue三级及以上路由无法缓存的问题

重新思考

既然二级路由可以实现缓存,那么可不可以把三级以及三级以上路由,转化为二级路由呢

有的同学说路由本来就是三级的,转为二级菜单那里就不可以使用了,那么有没有一个方法,既不影响页面菜单那里的UI展示效果,又能把三级路由转为二级路由呢

代码实现

这里需要注意,路由配置还是保持多级嵌套的形式,而这个配置并非最终注册使用的路由,仅仅是提供侧边栏导航菜单使用,同时再生成一份用于动态注册路由的数据,图例如果没看明白的话,可以看下面两组数据。

一、处理后端返回的菜单数据(原始数据还是原始数据的处理方式)

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)由之前处理路由数据

/** * 将三级路由扁平化为二级路由 * @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进行显示了,所以在处理路由数据的同时,也需要处理面包屑导航的信息。大致最终会处理成这样:

{ 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也就可以获取任意某个路由的完整面包屑导航信息了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 23:20:23

大火的 ChatBI,是如何实现灵活的自然语言数据分析?

在数字化转型的浪潮中&#xff0c;随着数据已成为企业宝贵的资产&#xff0c;数据分析的需求日益增长。传统 BI&#xff08;商业智能&#xff09;工具虽能提供强大的数据可视化能力&#xff0c;但需要依赖 IT 代码开发、集中式报表制作的模式&#xff0c;已难以满足现代企业快速…

作者头像 李华
网站建设 2026/6/23 18:45:19

33、文本编辑器nvi与Elvis功能解析

文本编辑器nvi与Elvis功能解析 nvi编辑器特点及使用 nvi是一款简洁的文本编辑器,具备一些独特的功能和特点。 1. 搜索与滚动功能 增量搜索 :在nvi中使用 :set searchincr 命令可以启用增量搜索功能。当你输入搜索内容时,光标会在文件中移动,始终定位在匹配文本的第一…

作者头像 李华
网站建设 2026/6/23 20:28:50

35、文本编辑器Elvis与Vile:特性、功能与操作全解析

文本编辑器Elvis与Vile:特性、功能与操作全解析 Elvis编辑器介绍 Elvis是一款功能丰富的文本编辑器,其格式大多直观易懂,详细文档可在Elvis在线文档中找到。 语法颜色特性 颜色关联原因 :Elvis将字体和颜色与文件语法的不同部分关联起来,是为了能像屏幕显示那样打印文…

作者头像 李华
网站建设 2026/6/22 22:45:18

36、Vile编辑器:功能、初始化与多窗口编辑全解析

Vile编辑器:功能、初始化与多窗口编辑全解析 1. Vile基本命令与选项 Vile是一款功能强大的编辑器,它有一些基本的命令和选项。当输入 -? 时,Vile会打印简短的使用摘要然后退出。使用 @cmdfile 选项,Vile会将指定的文件作为启动文件运行,并绕过任何正常的启动文件(…

作者头像 李华
网站建设 2026/6/23 14:32:48

Subfinder字幕搜索工具:一键解决多平台字幕下载难题

还在为找不到合适的字幕而烦恼吗&#xff1f;Subfinder作为一款强大的字幕搜索工具&#xff0c;能够帮你轻松解决多平台字幕下载的困扰。无论你是追剧达人还是电影爱好者&#xff0c;这款工具都能为你的观影体验加分不少&#xff01; 【免费下载链接】subfinder 字幕查找器 项…

作者头像 李华
网站建设 2026/6/23 11:11:26

Instinct开源智能编辑模型终极指南:重新定义代码编写体验

Instinct开源智能编辑模型终极指南&#xff1a;重新定义代码编写体验 【免费下载链接】instinct 项目地址: https://ai.gitcode.com/hf_mirrors/continuedev/instinct 你是否曾在深夜编程时&#xff0c;面对复杂的代码重构任务感到力不从心&#xff1f;当需要修改一个函…

作者头像 李华