一般的后台管理系统功能都比较繁多,存在有多级菜单的需求,但是在这种项目里往往keep-alive的表现却非常不稳定,有时候某个页面可以缓存,但是点几下就发现缓存丢了;有时候不知道怎么回事又死活不缓存了。
造成这个问题的原因是: 多级路由组件嵌套。
具体分析: 假如一个后台管理系统,有一个main.vue是所有页面的框架, 里面有这样一段代码
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
然后建立了一个父级菜单,页面是ChildView.vue, 里面的代码如下
<router-view></router-view>
假设这个父菜单里有两个子菜单,分别为 A和B, 然后A设置为缓存(keepAlive=true), B不缓存(keepAlive=false);
当点击A菜单的时候,系统缓存了ChildView.vue组件。
当点击B菜单的时候,由于B设置的不缓存,所以导致ChildView.vue组件被销毁
这就是keep-alive为什么会失效的根本原因。
还有更复杂的,比如同一个菜单里的子菜单可以缓存,但是点击另外一个父菜单或者父菜单下的子菜单却不缓存, 究其原理和上面的分析是一样的原因,就是多级菜单,多个共用组件导致的keepAlive缓存失效, keepAlive根本没考虑到页面缓存的复杂性。
以下几种表现也是这个问题造成的原因之一:
- activated和deactivated不触发
- 从A页面进入B页面发现的时候发现A页面的接口又会被重复触发调用
分析问题:
既然是多个router-view嵌套并且共用的情况下造成的,那么如果只存在一个router-view,也就是只需要main.vue作为框架内所有页面的容器,就不会有这个问题。
实际上是不是多级菜单对于项目或者业务上来讲一点都不影响,只是界面显示上需要,让用户能更快点击到自己需要的功能页面而已。 既然这样的话,显示的菜单保留多级的,实际的router弄成一级, 将显示菜单和业务router分离开。
解决问题:
首先将配置好的多级router用vuex缓存起来,用作展示的菜单。
然后将router转换一下,转换成一级菜单,用addRoutes异步添加到router里面。
局部示例代码:
const formatRouter = (routes, newRoutes = []) => {
routes.map(item => {
if (item.children && item.children.length > 0) formatRouter(item.children, newRoutes);
newRoutes.push(item);
})
return newRoutes;
}
let flatRoutes = formatRouter(routes);
router.addRoutes(flatRoutes);
然后面包屑导航要调整一下,大部分逻辑都是从route.matched里面获取的,但是现在router全是一级的,我们要从展示的菜单数据里面拿面包屑导航数据。
示例代码:
/**
* 自定义查找字段, 根据最后一级某个字段查找完整树(整个父类)
* @param {*} val 要查找对比的值
* @param {*} data 要查找的数据
* @param {*} fKey 要查找对比的字段
*/
const recursiveTreeByLastLevel = (val, data, fKey = 'value') => {
let rData = [];
for (let i = 0, len = data.length; i < len; i++) {
rData.push(data[i]);
if (data[i].children && data[i].children.length > 0) {
rData = rData.concat(recursiveTreeByLastLevel(val, data[i].children, fKey));
if (rData.some(item => item[fKey] === val)) return rData;
}
if (data[i][fKey] === val) return rData;
rData = [];
}
return rData;
}
router.afterEach((to, from, next) => {
var routerList = recursiveTreeByLastLevel(to.name, store.state.sidebarMenu, 'name')
store.commit('setCrumbList', routerList) // 通过vuex缓存
})
搞完,收工!