前言
本文主要实现tab切换并缓存对应页面,因为系统包含使用iframe嵌套的跨系统页面,而使用keep-alive缓存的iframe页面是不生效的,所以最终主要采用keep-alive缓存当前系统的页面及使用iframe的显示隐藏缓存跨系统页面
一、tab切换实现
<template>
<div class="tags-view">
<el-tabs v-model="activeTab" type="card" @tab-remove="handleClose" @tab-click="handleClick"
@contextmenu.prevent.native="openContextMenu($event)">
<el-tab-pane
v-for="(item, index) in visitedViews"
:key="item.randomVal"
:label="item.title"
:name="item.path"
:closable="index > 0 ? true : false"
lazy>
</el-tab-pane>
</el-tabs>
<ul
v-show="contextMenuVisible"
:style="{left: menuLeft+'px',top: menuTop+'px'}"
class="contextmenu">
<li @click="handleClose(openMenuTag.path)">关闭</li>
<li @click="closeLeftTabs">关闭左边</li>
<li @click="closeRightTabs">关闭右边</li>
<li @click="closeOtherTabs">关闭其他</li>
<li @click="closeAllTabs">关闭所有</li>
</ul>
</div>
</template>
<script>
import {mapMutations, mapState} from "vuex";
export default {
name: 'tagsView',
data() {
return {
// 当前激活的菜单
activeTab: '',
// 是否显示右键菜单
contextMenuVisible: false,
menuLeft: 0,
menuTop: 0,
// 右键菜单对应的当前tag
openMenuTag: {}
}
},
computed: {
...mapState({
visitedViews: state => state.tagsView.visitedViews,
})
},
watch: {
$route: {
handler (val) {
this.activeTab = val.path;
// 添加路由tab
this.ADD_VIEW({
...val
});
},
immediate: true
},
contextMenuVisible() {
if (this.contextMenuVisible) {
document.body.addEventListener("click", this.closeContextMenu);
} else {
document.body.removeEventListener("click", this.closeContextMenu);
}
}
},
methods: {
...mapMutations('tagsView', [
"ADD_VIEW",
"DEL_VIEW",
"DEL_LEFT_VIEW",
"DEL_RIGHT_VIEW",
"DEL_OTHER_VIEW",
"DEL_ALL_VIEW",
]),
// 添加路由
handleClick(tab) {
this.$router.push(this.visitedViews[tab.index].fullPath)
},
// 右键显示菜单
openContextMenu(e) {
if (e.target.id) {
// 获取当前tag对应的路由路径
const path = e.target.id.split("-")[1];
// 设置菜单位置
this.menuLeft = e.pageX;
this.menuTop = e.pageY + 10;
this.contextMenuVisible = true;
// 设置当前菜单对应的tag对象
const index = this.visitedViews.findIndex(item => {
return item.path === path;
});
this.openMenuTag = {...this.visitedViews[index], tagIndex: index};
}
},
// 移除当前tab
handleClose(routePath) {
// 移除当前tab
this.DEL_VIEW(routePath);
// 默认显示最后一个tab
this.moveToLastTag();
},
// 关闭左侧tabs
closeLeftTabs() {
// 关闭左侧tabs
this.DEL_LEFT_VIEW(this.openMenuTag.tagIndex);
// 默认显示最后一个tab
this.moveToLastTag();
},
// 关闭右侧tabs
closeRightTabs() {
// 关闭右侧tabs
this.DEL_RIGHT_VIEW(this.openMenuTag.tagIndex);
// 默认显示最后一个tab
this.moveToLastTag();
},
// 关闭其他tabs
closeOtherTabs() {
// 关闭其他tabs
this.DEL_OTHER_VIEW(this.openMenuTag.tagIndex);
// 默认显示最后一个tab
this.moveToLastTag();
},
// 关闭所有tabs
closeAllTabs() {
// 关闭所有tabs
this.DEL_ALL_VIEW();
// 默认显示最后一个tab
this.moveToLastTag();
},
// 关闭contextMenu
closeContextMenu() {
this.contextMenuVisible = false;
},
// 默认显示最后一个tab
moveToLastTag() {
// 获取最后一个tab对象
const lastTag = this.visitedViews[this.visitedViews.length - 1];
if (this.$route.path === lastTag.path) {
return;
}
this.$router.push(lastTag.fullPath);
},
}
}
</script>
二、vuex状态存储tab列表
const state = {
visitedViews: [
{
fullPath: '/',
path: '/',
title: '首页',
randomVal: Math.random()*10000
}
]
};
const mutations = {
// 添加视图
ADD_VIEW (state, view) {
const viewObj = Object.assign({}, view, {
title: view.meta.title || view.meta.childTitle || 'no-name',
randomVal: Math.random()*10000
});
const index = state.visitedViews.findIndex(v => {
// 此处因为我们路由层级默认是2级,2级以上的基本都是参数,例如:/system/test/:id
const v_path = v.path.split("/").splice(0,3).join("/");
const view_path = view.path.split("/").splice(0,3).join("/");
return v_path.indexOf(view_path) !== -1
});
if (index !== -1) { // 编辑
this._vm.$set(state.visitedViews,index,viewObj);
} else { // 新增
state.visitedViews.push(viewObj)
}
},
// 移除视图
DEL_VIEW (state, routePath) {
const index = state.visitedViews.findIndex(item => item.path === routePath);
state.visitedViews.splice(index, 1);
},
// 移除左侧视图
DEL_LEFT_VIEW (state, tabIndex) {
state.visitedViews = state.visitedViews.filter((item, index) => {
return index === 0 || index >= tabIndex
});
},
// 移除右侧视图
DEL_RIGHT_VIEW (state, tabIndex) {
state.visitedViews = state.visitedViews.filter((item, index) => {
return index === 0 || index <= tabIndex
});
},
// 移除右侧视图
DEL_OTHER_VIEW (state, tabIndex) {
state.visitedViews = state.visitedViews.filter((item, index) => {
return index === 0 || index === tabIndex
});
},
// 移除所有视图
DEL_ALL_VIEW (state) {
state.visitedViews = state.visitedViews.filter((item, index) => {
return index === 0
});
},
// 重置视图
RESETVIEWS (state) {
state.visitedViews = [
{
fullPath: '/',
path: '/',
title: '首页',
randomVal: Math.random()*10000
}
]
}
};
const actions = {
};
export default {
namespaced: true, // 开启命名空间
state,
mutations,
actions
}
三、缓存
<!-- 需要缓存的iframe页面 通过v-show控制显示隐藏显示条件根据自己业务逻辑进行修改-->
<template v-for="cView in cachedIframeViews">
<transition name="fade-transform" mode="out-in" :key="cView.rule_id">
<iframe
:src="cView.iframeUrl"
frameborder="0"
class="main-iframe"
v-show="curRouteMenu.rule_id === cView.rule_id">
</iframe>
</transition>
</template>
<!-- 不需要缓存的iframe页面 通过v-if控制 -->
<transition name="fade-transform" mode="out-in">
<iframe :src="currentUrl" frameborder="0" class="main-iframe" v-if="!curRouteMenu.isCache"></iframe>
</transition>
<!-- keep-alive 是否需要缓存通过include控制 cachedNames是需要缓存的组件name -->
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedNames">
<router-view></router-view>
</keep-alive>
</transition>