news 2026/6/10 16:59:22

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/1 1:06:19

揭秘Docker Buildx构建上下文:5个你必须知道的性能优化技巧

第一章&#xff1a;揭秘Docker Buildx构建上下文的本质Docker Buildx 是 Docker 官方提供的一个 CLI 插件&#xff0c;扩展了原生 docker build 命令的功能&#xff0c;支持多平台构建、并行执行以及更灵活的输出选项。其核心机制之一是“构建上下文&#xff08;Build Context&…

作者头像 李华
网站建设 2026/6/10 1:04:43

34、Elvis编辑器的高级特性与功能详解

Elvis编辑器的高级特性与功能详解 1. GUI界面按钮与对话框 Elvis编辑器的GUI界面有许多实用的按钮和功能。例如,第三行代码创建了名为“Quit”的按钮,用于退出程序。而第四行代码会改变其行为:如果 !modified 条件为真,按钮正常工作;若为假,按钮会忽略鼠标点击,且显…

作者头像 李华
网站建设 2026/6/9 12:31:25

紧急规避安全风险:立即检查这3项Azure CLI量子作业权限设置

第一章&#xff1a;紧急规避安全风险&#xff1a;立即检查这3项Azure CLI量子作业权限设置 在使用 Azure CLI 管理量子计算作业时&#xff0c;权限配置不当可能导致未授权访问、数据泄露或资源滥用。为确保生产环境安全&#xff0c;必须立即审查以下三项关键权限设置。 验证量…

作者头像 李华
网站建设 2026/6/10 13:42:26

IDM激活脚本终极教程:轻松实现永久免费使用

IDM激活脚本终极教程&#xff1a;轻松实现永久免费使用 【免费下载链接】IDM-Activation-Script IDM Activation & Trail Reset Script 项目地址: https://gitcode.com/gh_mirrors/id/IDM-Activation-Script 还在为IDM试用到期而烦恼吗&#xff1f;IDM激活脚本为你提…

作者头像 李华
网站建设 2026/6/10 15:26:34

SeedVR2:3步让模糊视频秒变4K超清!免费AI视频增强工具体验指南

SeedVR2&#xff1a;3步让模糊视频秒变4K超清&#xff01;免费AI视频增强工具体验指南 【免费下载链接】SeedVR-7B 项目地址: https://ai.gitcode.com/hf_mirrors/ByteDance-Seed/SeedVR-7B 你是否也曾为那些模糊不清的珍贵视频感到遗憾&#xff1f;毕业旅行、家庭聚会…

作者头像 李华
网站建设 2026/6/9 22:32:44

晶台光耦KL3H4:光耦技术在智能电表中的革新应用

智能电表作为智能电网的核心设备&#xff0c;对信号传输的可靠性和抗干扰能力要求极高。传统电表多采用继电器或机械隔离方式&#xff0c;存在易受电磁干扰、寿命短、体积大等缺陷。而光耦技术通过光信号实现电-光-电的转换&#xff0c;有效解决了这些问题&#xff0c;成为智能…

作者头像 李华