vue-element-admin解决三级目录的KeepAlive缓存问题(配置版)

vue-element-admin解决三级目录的KeepAlive缓存问题(配置版)

本文章只是列出三种解决方法具体怎么改。本文章比较粗略,建议改完后,看看详情版的,里面有更多细节,请前往详情版

一、解决方法

①添加<RouterViewKeepAlive>解决

本文章的该解决方法,参考该文章——见该文章

  • 首先,添加<RouterViewKeepAlive>组件
    新目录:src\layout\components\RouterViewKeepAlive\RouterViewKeepAlive.vue

    <!-- 父级路由组件,用于二级路由上, 该二级可以被keep-alive缓存 -->
    <!-- 由于该二级可以被keep-alive缓存,所以其三级的内容将保存 -->
    <!-- 注意:面包屑关闭后,不会从KeepAlive的include属性清除 -->
    <template>
      <div class="app-main">
        <router-view />
      </div>
    </template>
    <script>
    export default {
      name: 'RouterViewKeepAlive'
    }
    </script>
    <style lang="scss" scoped>
      .app-main {
        
      }
    </style>
    
  • 然后,在<AppMain>添加“cachedViews”计算属性上添加“RouterViewKeepAlive”
    目录:src\layout\components\AppMain.vue

    <script>
    export default {
      name: 'AppMain',
      computed: {
        cachedViews() {
          // return this.$store.state.tagsView.cachedViews
          // 加入RouterViewKeepAlive组件,总是缓存二级目录路由配置为“RouterViewKeepAlive”的
          return ['RouterViewKeepAlive', ...this.$store.state.tagsView.cachedViews]
        },
        key() {
          return this.$route.path
        }
      }
    }
    </script>
    
  • 最后,修改路由配置(以原项目Nested Routes路由配置nested.js为例)
    目录:src\router\modules\nested.js

    /** When your routing table is too long, you can split it into small modules **/
    
    import Layout from '@/layout'
    // 导入RouterViewKeepAlive
    import RouterViewKeepAlive from '@/layout/components/RouterViewKeepAlive/RouterViewKeepAlive.vue'
    
    const nestedRouter = {
      path: '/nested',
      component: Layout,
      redirect: '/nested/menu1/menu1-1',
      name: 'Nested',
      meta: {
        title: 'Nested Routes',
        icon: 'nested'
      },
      children: [
        {
          path: 'menu1',
          // component: () => import('@/views/nested/menu1/index'), // Parent router-view
          component: RouterViewKeepAlive, // 使用RouterViewKeepAlive作为二级组件
          // name: 'Menu1',
          name: 'RouterViewKeepAlive', // 名字改为“RouterViewKeepAlive”,虽然没必要,但为了维护性
          meta: { title: 'Menu 1' },
          redirect: '/nested/menu1/menu1-1',
          children: [
            {
              path: 'menu1-1',
              component: () => import('@/views/nested/menu1/menu1-1'),
              name: 'Menu1-1',
              meta: { title: 'Menu 1-1' }
            },
            {
              path: 'menu1-2',
              component: () => import('@/views/nested/menu1/menu1-2'),
              name: 'Menu1-2',
              redirect: '/nested/menu1/menu1-2/menu1-2-1',
              meta: { title: 'Menu 1-2' },
              children: [
                {
                  path: 'menu1-2-1',
                  component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
                  name: 'Menu1-2-1',
                  meta: { title: 'Menu 1-2-1' }
                },
                {
                  path: 'menu1-2-2',
                  component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
                  name: 'Menu1-2-2',
                  meta: { title: 'Menu 1-2-2' }
                }
              ]
            },
            {
              path: 'menu1-3',
              component: () => import('@/views/nested/menu1/menu1-3'),
              name: 'Menu1-3',
              meta: { title: 'Menu 1-3' }
            }
          ]
        },
        {
          path: 'menu2',
          name: 'Menu2',
          component: () => import('@/views/nested/menu2/index'),
          meta: { title: 'Menu 2' }
        }
      ]
    }
    
    export default nestedRouter
    
    

    注意:由于配置了“component: RouterViewKeepAlive”,使用了<RouterViewKeepAlive>作为二级组件,替换了’@/views/nested/menu1/index’的<Menu1>组件。

  • 结果
    可以看到“三级或以上的目录”被成功缓存了
    在这里插入图片描述

注意:这种实现方式存在弊端,就是永远关不掉(TagsView上的关闭),会一直占用内存
在这里插入图片描述

②转变Router配置解决

现在方法一,存在“关闭面包屑却不会关闭该缓存,会一直占用内存”的弊端,那怎么解决呢?
我们可以换一种思路——“注册路由时,将三级或以上的路由配置转换为一级和二级的那样”,如下图:
在这里插入图片描述
由于vue-element-admin项目在左侧菜单栏等地方用到了@/store的permission.js的“routes”。所以,现在的思路是“只改变Router的挂载,其他保持不改”,步骤如下。

  • 首先,对permission.js,添加flattenRoutes方法和修改generateRoutes
    目录:src\store\modules\permission.js

    // ...
    /**
     * 将单个路由,假如有三级或三级目录,则转为二级目录格式
     * @param {Object} router 要处理的路由
     * @returns {Object} 处理后的路由
     */
    function flattenRouter(router) {
      // 创建一个新的对象来存储转换后的路由
      const newRouter = {
        ...router,
        children: []
      }
      const routerChildren = router.children
      // 从根路由开始扁平化
      if (routerChildren && routerChildren.length > 0) {
        flatten('', routerChildren)
      }
      /**
       * 递归函数来遍历和扁平化路由
       * @param {String} parentPath 父路由路径
       * @param {Array} routes 路由
       */
      function flatten(parentPath, routes) {
        routes.forEach(route => {
          const { path, children } = route
          // 构建完整的路径
          const fullPath = `${parentPath}${path.startsWith('/') ? path.slice(1) : path}`
          // 如果当前路由有子路由,则递归处理
          if (children && children.length > 0) {
            flatten(`${fullPath}/`, children)
          } else {
            // 否则,将当前路由添加到新的children数组中
            newRouter.children.push({
              ...route,
              path: fullPath
            })
          }
        })
      }
      return newRouter
    }
    
    /**
     * 处理路由,将三级或三级以上目录的转为二级目录格式
     * @param {Array} routes routes
     * @param {Array} 处理后的路由
     */
    export function flattenRoutes(routes) {
      const res = []
      routes.forEach(route => {
        const newRouter = flattenRouter(route)
        res.push(newRouter)
      })
      return res
    }
    
    // ...
    const actions = {
      generateRoutes({ commit }, roles) {
        return new Promise(resolve => {
          let accessedRoutes
          if (roles.includes('admin')) {
            accessedRoutes = asyncRoutes || []
          } else {
            accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
          }
          // 处理路由,将三级或三级以上目录的转为二级目录格式
          const flattenAccessedRoutes = flattenRoutes(accessedRoutes)
          // store存的是accessedRoutes,用于左侧边导航栏,多级目录(包含三级或以上)
          commit('SET_ROUTES', accessedRoutes)
          // resolve(accessedRoutes)
          // Promise的resolve出去的是flattenAccessedRoutes,用于路由,多级目录(已转换为二级目录格式,不包含三级或以上)
          resolve(flattenAccessedRoutes)
        })
      }
    }
    // ...
    

    完整代码如下:

    import { asyncRoutes, constantRoutes } from '@/router'
    
    /**
     * Use meta.role to determine if the current user has permission
     * @param roles
     * @param route
     */
    function hasPermission(roles, route) {
      if (route.meta && route.meta.roles) {
        return roles.some(role => route.meta.roles.includes(role))
      } else {
        return true
      }
    }
    
    /**
     * Filter asynchronous routing tables by recursion
     * @param routes asyncRoutes
     * @param roles
     */
    export function filterAsyncRoutes(routes, roles) {
      const res = []
    
      routes.forEach(route => {
        const tmp = { ...route }
        if (hasPermission(roles, tmp)) {
          if (tmp.children) {
            tmp.children = filterAsyncRoutes(tmp.children, roles)
          }
          res.push(tmp)
        }
      })
    
      return res
    }
    
    /**
     * 将单个路由,假如有三级或三级目录,则转为二级目录格式
     * @param {Object} router 要处理的路由
     * @returns {Object} 处理后的路由
     */
    function flattenRouter(router) {
      // 创建一个新的对象来存储转换后的路由
      const newRouter = {
        ...router,
        children: []
      }
    
      const routerChildren = router.children
      // 从根路由开始扁平化
      if (routerChildren && routerChildren.length > 0) {
        flatten('', routerChildren)
      }
    
      /**
       * 递归函数来遍历和扁平化路由
       * @param {String} parentPath 父路由路径
       * @param {Array} routes 路由
       */
      function flatten(parentPath, routes) {
        routes.forEach(route => {
          const { path, children } = route
          // 构建完整的路径
          const fullPath = `${parentPath}${path.startsWith('/') ? path.slice(1) : path}`
          // 如果当前路由有子路由,则递归处理
          if (children && children.length > 0) {
            flatten(`${fullPath}/`, children)
          } else {
            // 否则,将当前路由添加到新的children数组中
            newRouter.children.push({
              ...route,
              path: fullPath
            })
          }
        })
      }
    
      return newRouter
    }
    
    /**
     * 处理路由,将三级或三级以上目录的转为二级目录格式
     * @param {Array} routes routes
     * @param {Array} 处理后的路由
     */
    export function flattenRoutes(routes) {
      const res = []
      routes.forEach(route => {
        const newRouter = flattenRouter(route)
        res.push(newRouter)
      })
      return res
    }
    
    const state = {
      routes: [],
      addRoutes: []
    }
    
    const mutations = {
      SET_ROUTES: (state, routes) => {
        state.addRoutes = routes
        state.routes = constantRoutes.concat(routes)
      }
    }
    
    const actions = {
      generateRoutes({ commit }, roles) {
        return new Promise(resolve => {
          let accessedRoutes
          if (roles.includes('admin')) {
            accessedRoutes = asyncRoutes || []
          } else {
            accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
          }
          // 处理路由,将三级或三级以上目录的转为二级目录格式
          const flattenAccessedRoutes = flattenRoutes(accessedRoutes)
          // store存的是accessedRoutes,用于左侧边导航栏,多级目录(包含三级或以上)
          commit('SET_ROUTES', accessedRoutes)
          // resolve(accessedRoutes)
          // Promise的resolve出去的是flattenAccessedRoutes,用于路由,多级目录(已转换为二级目录,不包含三级或以上)
          resolve(flattenAccessedRoutes)
        })
      }
    }
    
    export default {
      namespaced: true,
      state,
      mutations,
      actions
    }
    
  • 测试,见详情版

  • 结果
    可以看到“三级或以上的目录”被成功缓存了
    在这里插入图片描述

注意事项:
①与方法一“添加<RouterViewKeepAlive>解决”的区别:
方法一是“使用了<RouterViewKeepAlive>作为二级组件,替换了’@/views/nested/menu1/index’的<Menu1>组件。”;
而方法二是“扁平化路由”,就如上方测试改nested.js那样,一些“component”的配置是没意义的,所以注释掉了
这种实现方式同样存在弊端,就是Breadcrumb 面包屑多级关系不见了。因为,由于vue-element-admin项目Breadcrumb 面包屑是通过$route来实现的,而我们恰好改的就是路由配置。区别如下:
在这里插入图片描述
在这里插入图片描述
③上面,添加flattenRoutes方法只是对“accessedRoutes”做了处理,还未对“constantRoutes”处理,比如同时对“constantRoutes”处理。处理代码如下:
目录:src\router\index.js

// ...
/* 处理路由 */
import { flattenRoutes } from '@/store/modules/permission'

// ...
const createRouter = () => new Router({
  // mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }),
  // routes: constantRoutes
  // 处理路由,将三级或三级以上目录的转为二级目录格式
  routes: flattenRoutes(constantRoutes)
})
// ...

③直接移除include解决

此方法官方文档此次提到“前往@/layout/components/AppMain.vue文件下,移除include相关代码即可。当然直接使用 keep-alive 也是有弊端的,他并不能动态的删除缓存,你最多只能帮它设置一个最大缓存实例的个数 limit。

更改如下:
目录:src\layout\components\AppMain.vue

<template>
  <section class="app-main">
    <transition name="fade-transform" mode="out-in">
      <!-- 移除include -->
      <!-- <keep-alive :include="cachedViews"> -->
      <keep-alive>
        <router-view :key="key" />
      </keep-alive>
    </transition>
  </section>
</template>
// ...

如果,想要设置最大缓存个数,比如设置最大10个。只需将“<keep-alive>”改为“<keep-alive :max=“10”>”

注意事项:
①该方法的弊端:官方文档说的也很清楚了——“他并不能动态的删除缓存,只能帮它设置一个最大缓存实例的个数”。
②与方法一的对比:没使用了<RouterViewKeepAlive>作为二级组件,替换了’@/views/nested/menu1/index’的<Menu1>组件;该方法的“他并不能动态的删除缓存”的范围比方法一的范围大,该方法所有的目录都不能动态删除缓存,而方法一是三级或以上的目录不能移除。
③与方法二的对比:没“扁平化路由”无方法二的弊端——Breadcrumb 面包屑多级关系不见了

二、总结

vue-element-admin解决三级目录的KeepAlive缓存问题:
①添加<RouterViewKeepAlive>解决
弊端:永远关不掉(TagsView上的关闭),会一直占用内存
(可以给keep-alive设置一个最大缓存实例的个数,但不一定满足项目需求;如果该项目三级或以上的目录不多,就几个,那还能接受内存的占用)

②转变Router配置解决
弊端:Breadcrumb 面包屑多级关系不见了
(如果真实项目,无需“Breadcrumb 面包屑”同时最多三级(见方法二的“注意事项”),还可以接受。)

③直接移除include解决
弊端:他并不能动态的删除缓存,只能帮它设置一个最大缓存实例的个数
(如果真实项目,可以接受“不能动态的删除缓存”和“设置最大缓存实例的个数”的弊端,那该方法是最简单的解决方法。)

这里强调一下:由于上述方法是对原本vue-element-admin项目构建上的修复,一旦按照文章修复了,一定要记得项目的可维护性,不然,下一个接手该项目的码农将会很疑惑。比如,在真实项目的“README.md”上添加修改的文字描述和路由配置注意事项,同时在src\router\index.js的路由配置上注释好。

最终,似乎都没有十全十美的解决方案,每一种方案总是存在一些“舍去”。就vue-element-admin的作者在文档提过“如果没有标签导航栏需求的用户,建议移除此功能”

网上也有更多的解决方法,比如:

如果想了解更多关于vue-element-admin项目<keep-alive>不缓存的原因,也欢迎看看个人的另一篇文章
如果大家有其他更完美的解决方案或者本文章方法的不足之处,欢迎在评论区讨论!

三、参考文献

vue-element-admin 中,可以通过配置路由 meta 中的 keepAlive 属性来进行缓存。但是对于三级路由缓存,需要将他们作为二级路由的子路由进行配置。 具体配置方式如下: 1. 在 router/index.js 中,将三级路由定义为二级路由的子路由,例如: ```javascript { path: '/system', component: Layout, redirect: '/system/menu', name: 'System', meta: { title: '系统管理', icon: 'example' }, children: [ { path: 'menu', name: 'Menu', component: () => import('@/views/system/menu'), meta: { title: '菜单管理', icon: 'table', keepAlive: true } }, { path: 'role', name: 'Role', component: () => import('@/views/system/role'), meta: { title: '角色管理', icon: 'tree', keepAlive: true } }, { path: 'user', name: 'User', component: () => import('@/views/system/user'), meta: { title: '用户管理', icon: 'user', keepAlive: true } }, { path: ':id', // 三级路由 name: 'EditUser', component: () => import('@/views/system/edit-user'), meta: { title: '编辑用户', icon: 'user', hidden: true } // 隐藏该路由 }, ] }, ``` 2. 在 src/layout/components/MultiTab下的index.vue中,监听路由变化,根据当前路由中的 fullPath 进行判断,如果是三级路由,则将 fullPath 的前缀作为 parentPath 保存下来,用于找到他的二级父路由,从而正确进行缓存。 ```javascript computed: { // 通过 fullPath 匹配出二级父路由名称 parentPath() { const { fullPath } = this.$route; const matched = this.$route.matched; if (matched && matched.length > 2) { const parentPath = `/${matched[1].path}`; return fullPath.replace(parentPath, '').split('/')[1]; } return ''; }, // 是否需要缓存 needKeepAlive() { const { meta } = this.$route; if (meta && (typeof meta.keepAlive !== 'undefined')) { return meta.keepAlive; } return false; } }, watch: { '$route': function(newVal, oldVal) { // 监听路由变化,判断是否需要缓存 if (this.needKeepAlive) { const fullPath = newVal.fullPath; if (fullPath !== oldVal.fullPath) { const parentPath = this.parentPath; if (parentPath) { this.cacheList.push(`${parentPath}/${fullPath}`); } else { this.cacheList.push(fullPath); } } this.$forceUpdate(); } } } ``` 3. 在 src/layout/components/MultiTab下的tab-list.vue中,根据传入的 cacheList 进行渲染路由缓存。 ```javascript <keep-alive> <router-view v-if="$route.meta.keepAlive" :key="$route.path" /> </keep-alive> <component v-else :is="currentRoute.component" :key="currentRoute.path" /> ... computed: { // 用于 keep-alive 的路由列表 keepAliaveList() { if (this.cacheList.length) { const keepAliveList = this.cacheList.map(item => { const matched = item.split('/'); if (matched.length > 2) { // 当前路由是三级路由,需要找到他的二级父路由 const parentPath = `/${matched[1]}`; const parentRoute = this.getRouteObjByName(parentPath); if (parentRoute) { return { ...this.getRouteObjByName(item), parent: parentRoute }; } } else { // 当前路由是二级路由,直接返回 return this.getRouteObjByName(item); } }).filter(item => item); return keepAliveList; } return []; } }, methods: { // 根据路由名称获取路由对象 getRouteObjByName(name) { const routes = this.$router.options.routes; const obj = routes.find(route => route.path === name); return obj; }, // 关闭所有缓存的标签页 closeAllTags() { this.cacheList = []; this.$store.dispatch('multiTab/resetTabs'); }, ... } ``` 通过这些配置,即可实现 vue-element-admin三级路由缓存功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值