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

#上一篇文章讲述了三级路由缓存的一个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 也就可以获取任意某个路由的完整面包屑导航信息了。

相关推荐
计算机学姐8 小时前
基于SSM的社区外来务工人员管理系统【2026最新】
java·vue.js·java-ee·tomcat·maven·intellij-idea·mybatis
零Suger8 小时前
React Router v7数据模式使用指南
javascript·笔记·react.js
顾安r8 小时前
12.17 脚本网页 创意导航
java·linux·前端·游戏·html
小明记账簿8 小时前
CSS mix-blend-mode 实现元素融合效果
前端·css
_Kayo_8 小时前
React 动态显示icon
前端·react.js·react
Q_Q5110082858 小时前
小程序springBoot新农村综合风貌旅游展示平台
vue.js·spring boot·后端
free-elcmacom8 小时前
Python实战项目<3>赛制分数分析
开发语言·前端·python·数据分析
Mr.Jessy13 小时前
JavaScript高级:构造函数与原型
开发语言·前端·javascript·学习·ecmascript
白兰地空瓶15 小时前
🚀你以为你在写 React?其实你在“搭一套前端操作系统”
前端·react.js