最近写我自己的后台开发框架,要弄一个多页面标签功能,之前有试过vue-element-admin的多页面,以为很完美,就按它的思路重新写了一个,但发现还是有问题的。
vue-element-admin它用的是在keep-alive组件上使用include属性,绑定$store.state.tagsView.cachedViews,当点击菜单时,往$store.state.tagsView.cachedViews添加页面的name值,在标签卡上点击关闭后就从$store.state.tagsView.cachedViews里面把缓存的name值删除掉,这样听似乎没什么问题。但它无法很好的支持无限级别的子菜单的缓存。
目前vue-element-admin官方预览地址的菜单结构大多是一级菜单分类,下面是二级子菜单。如下图所示,它只能缓存二级子菜单,三级子菜单它缓存不了。为什么会出现这个情况呢。因为嵌套router-view的问题。
按vue-element-admin的路由结构,它的一级菜单,其实对应的是一个layout组件,layout里面有个router-view(称它为一级router-view)它有用keep-alive包裹着,用来放二级菜单对应的页面,所以对于二级菜单来说,它都是用同一个router-view。如果我需要创建三级菜单的话,那就需要在二级菜单目录里创建一个包含router-view(称它为二级router-view)的index.vue文件,用来放三级菜单对应的页面,那么你就会发现这个三级菜单的页面怎么也缓存不了。
因为只有一级router-view被keep-alive包裹起着缓存作用,下面的router-view它不缓存。当然我们也可以在二级的router-view也包一个keep-alive,也用include属性,但你会发现也用不了,因为还要匹配name值,就是说二级router-view的文件也得写上name值,写上name值后你发现还是用不了,因为include数组里面没有这个二级router-view的name值,所以你还得在tabsView里的addView里面做手脚,把路由所匹配到的所有路由的name值都添加到cachedViews里,然后还要在关闭时再进行处理。天啊。我想想都头痛,理论是应该是可以实现的,但会增加了很多前端代码量。
请注意!下面的方法也是有Bug的,请重点看下面的BUT开始部分
还好keep-alive还有另一个属性exclude,我马上就有思路了,而且非常简洁,默认全部页面进行缓存,所有的router-view都包一层keep-alive,只有在点击标签卡上的关闭按钮时,往$store.state.sys.excludeViews添加关闭页面的name值,下次打开后再从excludeViews里面把页面的name值删除掉就行了,非常地简单易懂,不过最底层的页面,仍然需要写上跟路由定义时完全匹配的name值。这一步我仍然想不到有什么办法可以省略掉。
为方便代码,我写了一个组件aliveRouterView组件,并合局注册,这个组件用来代替router-view组件,如下面代码所示,$store.state.sys.config.PAGE_TABS这个值是是否开户多页面标签功能参数
<template>
<keep-alive :exclude="exclude">
<router-view />
</keep-alive>
</template>
<script>
export default {
computed: {
exclude() {
if (this.$store.state.sys.config.PAGE_TABS) {
return this.$store.state.sys.excludeViews;
} else {
return /.*/;
}
}
}
};
</script>
多页面标签组件viewTabs.vue,如下面代码所示
<template>
<div class="__common-layout-tabView">
<el-scrollbar>
<div class="__tabs">
<div
class="__tab-item"
:class="{ '__is-active':item.name==$route.name }"
v-for="item in viewRouters"
:key="item.path"
@click="onClick(item)"
>
{
{item.meta.title}}
<span
class="el-icon-close"
@click.stop="onClose(item)"
:style="viewRouters.length<=1?'width:0;':''"
></span>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script>
export default {
data() {
return {
viewRouters: []
};
},
watch: {
$route: {
handler(v) {
if (!this.viewRouters.some(item => item.name == v.name)) {
this.viewRouters.push(v);
}
},
immediate: true
}
},
methods: {
onClick(data) {
if (this.$route.fullPath != data.fullPath) {
this.$router.push(data.fullPath);
}
},
onClose(data) {
let index = this.viewRouters.indexOf(data);
if (index >= 0) {
this.viewRouters.splice(index, 1);
if (data.name == this.$route.name) {
this.$router.push(this.viewRouter