Vue keep-alive 多级路由缓存方案

基于vue-element-admin框架 的多级路由缓存

效果图

1. keep-alive 路由缓存原理

keep-alive根据路由名称缓存 对应页组件 name 属性必须和 include 数组中一样

cachedViews 数组由store维护


  
    
  

复制代码

2. 路由缓存方案

  • 在vue-element-admin中跟路由是src/layout/component/AppMain文件,只有一级路由,多级路由是新建一个空的路由文件来实现的。
  • 本方案使用一个同一个文件实现多级路由,不用再另外写空路由文件。
原理
  • keep-alive必须是通过组件名字来匹配的,想用一个组件文件来复用的问题在于如何动态改变组件的名字。
  • 组件是在router中配置的,在这里给组件改名还是比较麻烦的,尝试了各种方法终于实现了动态改变组件名字的效果 (没错就是用的重命名大法~~)。
  • 子路由文件demo:

    import EmptyRoute from '@/layout/EmptyRoute'
    export default {
      path: '/lab',
      component: load('layout/index'),
      redirect: 'noRedirect',
      name: 'layout',
      alwaysShow: true,
      meta: {
        title: '实验室',
      },
      children: [
        {
          path: 'todo-list',
          component: load('views/lab/todo/list/index'),
          name: 'lab-todo-list',
          meta: {
            title: '待办事项'
          }
        },
        {
          path: 'todo-list-detail/:id',
          component: load('views/lab/todo/list/detail'),
          name: 'lab-todo-list-detail',
          hidden: true,
          meta: {
            title: '查看待办事项',
            activeMenu: '/lab/todo-list',
          }
        },
        {
          path: 'common',
          name: 'common',
          redirect: 'noRedirect',//这个别忘了加
          component: { ...EmptyRoute, name: 'common' },//子路由
          alwaysShow: true,
          meta: {
            title: '通用要求'
          },
          children: [
            {
              path: 'fairness',
              component: load('views/lab/common/fairness/index'),
              name: 'lab-common-fairness',
              meta: {
                title: '公正性',
              }
            },
            {
              path: 'privacy',
              name: 'privacy',
              redirect: 'noRedirect',
              component: { ...EmptyRoute, name: 'privacy' },//子路由
              alwaysShow: true,
              meta: {
                title: '保密性'
              },
              children: [
                {
                  path: 'agreement',
                  component: load('views/lab/common/privacy/agreement/index'),
                  name: 'lab-common-privacy-agreement',
                  meta: {
                    title: '保密协议',
                  }
                }
              ]
            }
          ]
        }
      ]
    }
    复制代码
  • 路由文件EmptyRoute.vue

    
    
    复制代码
  • store中的 tagsView.js 改造

    • 一切基于visitedViews: 根据数组中各级route的matched 数组来设置各级别路由应该缓存的路由名字,由cached对象保存,核心方法:setMatched,matched对象使用路由的名字作为key值

Vue keep-alive 多级路由缓存方案_第1张图片

  • 代码
 /* eslint-disable no-shadow */
const state = {
  isRefresh: false,//是否是刷新的
  cached: {},
  visitedViews: [],
}
const mutations = {}
function filterView(view) {
  if (!view) return view
  const {
    fullPath,
    name,
    path,
    meta,
    params,
    query,
    matched
  } = view
  return {
    fullPath,
    name,
    path,
    meta,
    params,
    query,
    matched: matched ? matched.map(i => ({
      meta: i.meta,
      name: i.name,
      path: i.path,
    })) : undefined
  }
}
const actions = {
  retsetState({ state }) {
    state.visitedViews = []
    state.cached = {}
  },
  setMatched({ dispatch, state }) {
    const obj = {}
    state.visitedViews.forEach(view => {
      if (view.meta.affix && view.meta.matchedKey) {
        let arr = obj[view.meta.matchedKey] || []
        if (!arr.includes(view.name)) {
          arr.push(view.name)
        }
        obj[view.meta.matchedKey] = arr
      } else if (view.matched && !view.meta.noCache) {
        const matched = view.matched
        const len = matched.length
        if (len < 2) return
        for (let idx = 0; idx < matched.length; idx++) {
          const key = matched[idx].name;
          if (idx < len - 1) {
            const vnext = matched[idx + 1];
            const { meta, name } = vnext
            if (meta && (meta.affix || !meta.noCache)) {
              let arr = obj[key] || []
              if (!arr.includes(name)) {
                arr.push(name)
              }
              obj[key] = arr
            }
          }
        }
      }
    })
    state.cached = obj
  },
  addView({ dispatch, state }, view) {
    try {
      if (state.visitedViews.some(v => v.path === view.path) && state.isRefresh===false) return
      state.isRefresh = false
      view = filterView(view)
      const idx = state.visitedViews.findIndex(v => v.name === view.name)
      if (idx > -1) {
        state.visitedViews.splice(idx, 1, { ...view, title: view.meta.title || '' })
      } else {
        state.visitedViews.push(
          { ...view, title: view.meta.title || '' }
        )
      }
      dispatch('setMatched')
    } catch (error) {
      console.log('addView', error);
    }
  },
  delView({ dispatch, state }, view) {
    return new Promise(resolve => {
      const idx = state.visitedViews.findIndex(i => i.path === view.path)
      if (idx > -1) {
        state.visitedViews.splice(idx, 1)
      }
      dispatch('setMatched')
      resolve({ visitedViews: state.visitedViews })
    })
  },
  refreshView({ dispatch, state }, view) {
    return new Promise(resolve => {
      let name = view.name
      let key = 'layout'
      if (view.matched) {
        const len = view.matched.length
        key = view.matched[len - 2].name
      }
      state.cached[key] = state.cached[key].filter(i => i !== name)
      state.isRefresh = true
      resolve()
    })
  },
  delOthersViews({ dispatch, state }, view) {
    return new Promise(resolve => {
      let arr = state.visitedViews.filter(i => i.meta.affix)
      if (view && !view.meta.affix) {
        arr.push(view)
      }
      state.visitedViews = arr
      dispatch('setMatched')
      resolve({ visitedViews: arr })
    })
  },
}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}
  • layout中TagsView组件方法改造:调用actions方法变化
initTags() {
  this.affixTags = this.filterAffixTags(this.routes)
  for (const tag of this.affixTags) {
    // Must have tag name
    if (tag.name) {
      this.$store.dispatch('tagsView/addView', tag)
    }
  }
}
addTags() {
  const route = this.getActiveRoute(this.$route)
  const { name } = route
  if (name) {
    this.$store.dispatch('tagsView/addView', route)
  }
  return false
},
refreshSelectedTag(view) {
  this.$store.dispatch('tagsView/refreshView', view).then(() => {
    const { fullPath } = view
    this.$nextTick(() => {
      this.$router.replace({
        path: '/redirect' + fullPath
      })
    })
  })
},
closeSelectedTag(view) {
  if (view.meta && view.meta.affix) return
  this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
    if (this.isActive(view)) {
      this.toLastView(visitedViews, view)
    }
  })
},
closeOthersTags() {
  this.$router.push(this.selectedTag)
  this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
    this.moveToCurrentTag()
  })
},
closeAllTags(view) {
  this.$store.dispatch('tagsView/delOthersViews').then(({ visitedViews }) => {
    this.toLastView(visitedViews, view)
  })
},
  • layout/index 中的改造

    把 AppMain 标签更换
    
    样式当然照搬过来

经过以上改造,就可以愉快的写多级路由了!!!

你可能感兴趣的