vue-element-admin 超过两级嵌套路由无法缓存的解决办法
注意,此解决办法只适用于顶级路由为布局组件,中层路由只为嵌套、末级路由调用页面组件的路由结构PS: 如您无暇查看解决过程,可直接点击右侧目录中的 步骤一、步骤二 参考修改即可问题起因最近公司项目开发,为了套个好看的外壳加上一些基础的功能,选用了花裤衩大佬的 vue-element-admin 项目,移除了一些不需要的功能,最终剩下了后台布局、tab页签、登录页、mock等,基于此开始了愉快的项目开
注意,此解决办法只适用于顶级路由为布局组件,中层路由只为嵌套、末级路由调用页面组件的路由结构
PS: 如您无暇查看解决过程,可直接点击右侧目录中的 步骤一、步骤二 参考修改即可
问题起因
最近公司项目开发,为了套个好看的外壳加上一些基础的功能,选用了花裤衩大佬的 vue-element-admin 项目,移除了一些不需要的功能,最终剩下了后台布局、tab页签、登录页、mock等,基于此开始了愉快的项目开发。
按原型图要求,有如下页面结构:
- 平台管理(侧边栏目录)
- 厂商管理(侧边栏菜单)
- 厂商列表(菜单点击后跳转的首页)
- 新增厂商(点新增按钮跳转到此页面)
- 编辑厂商(点编辑按钮跳转到此页面)
之后脑子一抽,将路由表定义成了如下结构(后来细想之后,其实完全没有必要搞成三层结构 - -):
export const plantformRouter = {
path: '/plantform',
component: Layout,
name: 'Plantform',
redirect: 'noRedirect',
meta: {
title: "平台管理",
icon: 'plantform'
},
children: [
{
path: 'dept/merchant',
redirect: '/plantform/dept/merchant/index',
name: 'PlantformMerchant',
component: Blank,
meta: {
title: "厂商管理"
},
children: [{
path: "index",
name: "PlantformMerchantIndex",
hidden: true,
nodeType: "function",
component: () => import('@/views/plantform/dept/merchant/index'),
meta: {
title: "厂商管理",
breadcrumb: false,
activeMenu: '/plantform/dept/merchant'
}
}, {
path: "add",
name: "PlantformMerchantAdd",
hidden: true,
nodeType: "function",
component: () => import('@/views/plantform/dept/merchant/add'),
meta: {
title: "新增厂商",
breadcrumb: false,
activeMenu: '/plantform/dept/merchant'
}
}, {
path: "edit",
name: "PlantformMerchantEdit",
hidden: true,
nodeType: "function",
component: () => import('@/views/plantform/dept/merchant/edit'),
meta: {
title: "编辑厂商",
breadcrumb: false,
activeMenu: '/plantform/dept/merchant'
}
}]
}
]
}
这里还使用到了一个Blank空白容器组件,如下:
<template>
<router-view />
</template>
<script>
export default {
name: "Blank",
};
</script>
项目开发渐渐接近尾声,但是项目中所有的页面都无法缓存
由于之前自己也动手编写过tab页签的实现,虽说稀烂,但是依稀记得当时编写的tab页签是能够正常缓存的,当时所使用的技术点:
keep-alive + include
于是接下来开始分析问题现象 + 面向百度解决问题 - -
问题现象
-
首先,我们看下vue-element-admin在页签缓存上的表现:
- 如下图,我打开了两个tab页,可以看到,在拖拽select页面,有三个选中项:
- 此时,我删除拖拽select页面的两个选中项:
- 当我切换到“富文本编辑”页面后,再切换回拖拽select页面时,发现拖拽select页面保存了我刚刚的操作:
- 如下图,我打开了两个tab页,可以看到,在拖拽select页面,有三个选中项:
-
然后,我们看下我现有项目在页签缓存上的表现:
- 如下图,我打开了两个tab页,可以看到,在“新增厂商”页面,没有输入任何内容:
- 此时,我在“新增厂商”页面输入一些内容:
- 当我切换到“厂商列表”页面,然后再切换回“新增厂商”页面时,发现“新增厂商”页面丢失了我刚刚输入的内容:
至此,问题已基本确定,vue-element-admin并无任何问题,是由于现有项目上的一些稀奇古怪的操作导致了tab页签不缓存问题的发生,接下来,我们来解决这个问题
- 如下图,我打开了两个tab页,可以看到,在“新增厂商”页面,没有输入任何内容:
解决过程
首先,我从百度搜索找到了如下的解决方案:
element-admin嵌套路由页面不能缓存问题解决
vue element admin 三级路由缓存问题的解决办法
我并未采用如上方案处理,各位看官可以参考下如上方案,后来找到了如下博文:
vue多级菜单(路由)导致缓存(keep-alive)失效
参考如上几篇博文,已基本确定问题根源,其原因在于vue-router无法缓存超过两级的路由,上面的博文也给出了相应的解决方案,由于与项目中实际代码有点不一致,接下来我自己按照如上博文的思路对原有代码进行了相应的修改。
首先,vue-element-admin将路由表维护在前端项目中,该路由表遵从树状结构(为了渲染侧边栏菜单树),那么为了满足vue-router最大只支持2级路由缓存的要求,我们需要将权限过滤完成的路由进行扁平化处理,先梳理下原项目相关代码:
- vue-router钩子函数定义(src/permission.js):
// 只摘取核心代码
router.beforeEach(async (to, from, next) => {
// 拉取用户权限数据
const { permissions } = await store.dispatch('user/getInfo')
// 按角色生成对应的路由表
const accessRoutes = await store.dispatch('permission/generateRoutes', permissions)
// 动态添加可访问的路由
router.addRoutes(accessRoutes)
})
如上JS告诉了我们,在哪里生成的路由表,我们需要做的就是将 accessRoutes 做扁平化处理,拍成两级路由
- 按角色动态生成路由(src/store/modules/permission.js):
// 只摘取核心代码
const actions = {
generateRoutes({
commit
}, permissionKeys) {
return new Promise(resolve => {
// 按菜单key数组过滤权限数据(原项目是按角色过滤权限,这里之前稍微修改了下)
let accessedRoutes = filterAsyncRoutes(asyncRoutes, permissionKeys, false)
commit('SET_ROUTES', accessedRoutes)
// 返回可访问的动态路由,供vue-router beforeEach使用
resolve(accessedRoutes)
})
}
}
如上JS中,我们不需要修改原有的 filterAsyncRoutes 方法,该方法用于过滤出可访问的路由,我们需要做的是将该方法的返回值 accessedRoutes 拍平,然后将resolve方法返回的可访问动态路由替换为我们拍平后的路由数据
- 然后,我们看下原项目中路由缓存相关部分的代码:
<!-- src/layout/components/AppMain.vue -->
<template>
<section class="app-main">
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
</section>
</template>
<script>
export default {
name: 'AppMain',
computed: {
cachedViews() {
return this.$store.state.tagsView.cachedViews
},
key() {
return this.$route.path
}
}
}
</script>
<!-- src/store/modules/tagsView.js -->
<script>
const state = {
visitedViews: [],
cachedViews: []
}
const mutations = {
ADD_CACHED_VIEW: (state, view) => {
if (state.cachedViews.includes(view.name)) return
if (!view.meta.noCache) {
state.cachedViews.push(view.name)
}
}
}
</script>
如上代码中,AppMain组件使用了vuex中的cachedViews作为缓存条件,只有被cachedViews所包含的key对应的组件才会被缓存(这里有一个坑,稍后会说)
解决步骤一:
好了,到这里,我们就基本上知道该怎么修改了,接下来开始愉快的修改之路:
- 增加路由扁平化处理方法(src/store/modules/permission.js):
注意: 非完整JS文件,请勿直接拷贝覆盖原文件,请阅读后自行对该文件进行修改
注意: 父级路由不允许包含页面组件,所有页面组件都在末级路由中声明,顶级路由组件是Layout(PS: 您创建的其他布局组件也可以)
/**
* 生成扁平化机构路由(仅两级结构)
* @param {允许访问的路由Tree} accessRoutes
* 路由基本机构:
* {
* name: String,
* path: String,
* component: Component,
* redirect: String,
* children: [
* ]
* }
*/
function generateFlatRoutes(accessRoutes) {
let flatRoutes = [];
for (let item of accessRoutes) {
let childrenFflatRoutes = [];
if (item.children && item.children.length > 0) {
childrenFflatRoutes = castToFlatRoute(item.children, "");
}
// 一级路由是布局路由,需要处理的只是其子路由数据
flatRoutes.push({
name: item.name,
path: item.path,
component: item.component,
redirect: item.redirect,
children: childrenFflatRoutes
});
}
return flatRoutes;
}
/**
* 将子路由转换为扁平化路由数组(仅一级)
* @param {待转换的子路由数组} routes
* @param {父级路由路径} parentPath
*/
function castToFlatRoute(routes, parentPath, flatRoutes = []) {
for (let item of routes) {
if (item.children && item.children.length > 0) {
if (item.redirect && item.redirect !== 'noRedirect') {
flatRoutes.push({
name: item.name,
path: (parentPath + "/" + item.path).substring(1),
redirect: item.redirect,
meta: item.meta
});
}
castToFlatRoute(item.children, parentPath + "/" + item.path, flatRoutes);
} else {
flatRoutes.push({
name: item.name,
path: (parentPath + "/" + item.path).substring(1),
component: item.component,
meta: item.meta
});
}
}
return flatRoutes;
}
// 在原有的生成可访问路由方法中,增加路由扁平化处理,并返回扁平化处理后的结果,具体修改参看分割线部分:
const actions = {
generateRoutes({
commit
}, permissionKeys) {
return new Promise(resolve => {
let accessedRoutes = filterAsyncRoutes(asyncRoutes, permissionKeys, false)
// -------------------------- 分隔线 ----------------------------
let flatRoutes = generateFlatRoutes(accessedRoutes)
commit('SET_ROUTES', accessedRoutes)
resolve(flatRoutes)
// -------------------------- /分隔线 ----------------------------
})
}
}
- 为了方便后续维护,还是修改下vue-router钩子函数的变量名称吧(PS: 不改也行):
// 拉取用户权限数据
const { permissions } = await store.dispatch('user/getInfo')
// 这里把原来的 accessRoutes 修改成了 flatRoutes
const flatRoutes = await store.dispatch('permission/generateRoutes', permissions)
// dynamically add accessible routes
router.addRoutes(flatRoutes)
解决问题的看官,请直接跳到步骤二
到这里,我们已经完成了基本的改造,按道理来说路由缓存应该就生效了,然而实际情况并非如此,再次测试发现路由组件还是一如既往地重新渲染。
然后就开始了 百度找答案,比对原有项目相关代码,安装vue-devtools观察 之路,最终有如下发现:
- 现有项目表现:
- 原项目表现:
emmm,好像跟原项目的表现不太一样,为啥原项目的标签叫“Dashboard”,而现有项目的标签叫“Index”,到这里,问题原因大概也能猜出来了,路由缓存是基于name的,这个name指的不是路由配置中的name属性值,而是组件的name属性值(PS:这个搞的时间有点长,我一直以为是按路由的name属性匹配的,尝试多次之后才发现是按组件的name值匹配的)
参考博文: vue.js的keep-alive include无效
解决步骤二:
为所有相关页面的组件,添加name属性值,注意: name属性值必须与路由表所声明的name属性值保持一致。
现有项目路由组件修改示例(可以参看文章开头的路由表配置,这里的组件名称与路由表name属性一一对应):
<script>
export default {
name: "PlantformMerchantAdd"
}
</script>
<script>
export default {
name: "PlantformMerchantEdit"
}
</script>
<script>
export default {
name: "PlantformMerchantIndex"
}
</script>
上一张处理完成的结果图:
总结
本篇知识点:
- vue-router只会缓存1、2级路由,超出2级则不会缓存
- keep-alive 的 include属性会匹配对应组件的名称,匹配上的才会缓存
- vue-router的name属性是用于路由跳转,与keep-alive的include无任何关系
更多推荐
所有评论(0)