vue2实现Tabs标签的切换和删除
实现Tabs功能
- 点击菜单增加标签
- 点击切换标签
- 点击标签的× 关闭标签
- 点击关闭所有标签功能
- 点击关闭其他标签功能
- 缓存标签出现的页面- keepAlive
- 刷新页面缓存标签功能,关闭浏览器或跳转到登录页清除标签数组
- 点击刷新当前页面-provide和inject
- 超出长度出现左右滑动箭头
点击菜单增加标签
标签组件TabsContent.vue
<el-tabs
v-model="editableTabsValue"
type="card"
closable
@tab-click="clickTable"
@tab-remove="removeTable"
>
<el-tab-pane
:key="item.name + index"
v-for="(item, index) in editableTabs"
:label="item.title"
:name="'/' + item.name"
>
</el-tab-pane>
</el-tabs>
data() {
return {
editableTabsValue: this.$store.state.editableTabsValue,
editableTabs: this.$store.state.editableTabs,
};
},
左侧菜单组件,我的是leftnav.vue
methods: {
openMenuItem(chmenu) {
store.commit("ADD_TABS", chmenu);
//调用store里面的方法;chumenu里面的值包含如下
},
},
store.js里面的方法
ADD_TABS:(state,tab)=>{//增加标签页方法
//在editableTabs中查找此界面是否已打开,否进入if
if(state.editableTabs.findIndex(e=>e.name===tab.url)===-1){
var index = tab.url.lastIndexOf("\/"); //这个是去掉路径的第一个‘/’的,不需要可忽略
var pageName = tab.url.substring(index + 1, tab.url.length);
state.keepList.push(pageName)
state.editableTabs.push({//添加当前标签页进入editableTabs
title:tab.menuname,
name:tab.url,
close:true//使除了我的页面标签页外其他都可关闭
})
}
state.editableTabsValue=tab.path//添加标签页后默认打开
},
点击切换标签
标签组件TabsContent.vue
methods: {
// 点击切换标签
clickTable(tab) {
var name = JSON.stringify(tab.paneName).replace('"', "").replace('"', ""); //对tab参数处理,以获得当前点击的标签页的路由
var label = tab.label;
store.commit("CHANGE_TABS", name); //调用store.js中的切换方法切换标签页
router.push({
name: label,
});
}
}
CHANGE_TABS:(state,name)=>{//切换标签页方法
//将需打开标签页路由赋予editableTabsvalue
state.editableTabsValue=name
},
点击标签的× 关闭标签
// 移除标签
removeTable(targetName) {
// 动态缓存页面接口--这个是下一个功能的可忽略
var index = targetName.lastIndexOf("/");
var pageName = targetName.substring(index + 1, targetName.length);
this.keepList = this.keepList.filter((item) => item != pageName);
this.$store.state.keepList = this.keepList;
// 点单个标签后面的× 移除标签 ----这个方法可以看官网的 我的因为路径不匹配改了一下
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
var targetNameTwo = targetName.slice(1);
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetNameTwo) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
return activeName;
}
}
});
}
this.editableTabsValue = activeName;
var targetNameNew = targetName.slice(1); //这个也是地址的 不需要可忽略
this.editableTabs = tabs.filter((tab) => tab.name !== targetNameNew);
this.$store.state.editableTabs = this.editableTabs; //更新后的数据存到store
for (var i = 0; i < this.editableTabs.length; i++) {
if (this.editableTabsValue === this.editableTabs[i].name) {
var title = this.editableTabs[i].title;
router.push({ //跳转页面,我的是根据路由的name跳转,不是url
name: title,
});
return;
}
}
},
原因:点击切换路径匹配不到页面
如图,使用的是router.push(name),点击获取到的页面url直接拼接到地址栏,导致匹配不到地址,出现空白页
改为使用
<el-tabs
v-model="editableTabsValue"
type="card"
editable
@edit="handleTabsEdit"
@tab-click="clickTable" //点击切换标签页的方法
>
<el-tab-pane
:key="item.name + index"
v-for="(item, index) in editableTabs"
:label="item.title"
:name="item.name"
>
{{ item.content }}{{ index }}
</el-tab-pane>
</el-tabs>
clickTable(tab) {
var label = tab.label; //拿到路由名称
router.push({ //根据路由名称进行页面跳转
name: label,
});
},
缓存标签出现的页面- keepAlive
index.vue中
方法一:直接包裹组件,这样会缓存所有包裹的页面,不太符合情况
<el-main clss="index-main">
<keep-alive><router-view></router-view></keep-alive>
</el-main>
方法二:存在于keepList这个数组的页面才会被缓存,这里要注意的是数组内的值要与页面的name对应
<el-main clss="index-main">
<keep-alive :include="keepList"><router-view></router-view></keep-alive>
</el-main>
刷新页面缓存标签功能,关闭浏览器或跳转到登录页清除标签数组
标签数据缓存在store,刷新页面的时候先存储但session中,页面加载后再重新取出赋值
export default {
name: "App",
watch: {
$route: function () {
this.reload();
},
},
created() {
//在页面加载时读取sessionStorage里的状态信息
if (sessionStorage.getItem("store")) {
this.$store.replaceState(
Object.assign(
{},
this.$store.state,
JSON.parse(sessionStorage.getItem("store"))
)
);
}
//在页面刷新时将vuex里的信息保存到sessionStorage里
window.addEventListener("beforeunload", () => {
sessionStorage.setItem("store", JSON.stringify(this.$store.state));
});
},
methods: {
reload() {
//首页清空标签数组
if (this.$route.path == "/login") {
sessionStorage.setItem("store", {});
this.$store.state.editableTabs = [];
this.$store.state.keepList = [];
}
},
},
};
点击关闭所有、关闭其他标签、刷新页面功能
<div class="tabContent-right" v-if="this.editableTabs.length != 0">
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link"> 页面操作 </span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="c" icon="el-icon-refresh-right"
>刷新</el-dropdown-item
>
<el-dropdown-item command="a" icon="el-icon-folder-opened"
>关闭所有</el-dropdown-item
>
<el-dropdown-item command="b" icon="el-icon-folder"
>关闭其他</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
</div>
handleCommand(command) {
// 关闭所有标签
if (command == "a") {
this.editableTabs = [];
this.$store.state.editableTabs = this.editableTabs;
this.keepList = [];
this.$store.state.keepList = this.keepList;
this.$router.push({
path: "/personal/MyFile",
meta: {
keepMenu: true,
},
});
this.onlyOneStatus();
} else if (command == "b") {
// 移除其他,保留当前页
for (var j = 0; j < this.editableTabs.length; j++) {
if (this.editableTabsValue == "/" + this.editableTabs[j].name) {
// 动态缓存
var name = this.editableTabs[j].name;
var index = name.lastIndexOf("/");
var pageName = name.substring(index + 1, name.length);
this.keepList = [];
this.keepList.push(pageName);
this.$store.state.keepList = this.keepList;
// 移除
var oneTab = {};
oneTab.name = this.editableTabs[j].name;
oneTab.title = this.editableTabs[j].title;
this.editableTabs = [];
this.editableTabs.push(oneTab);
this.$store.state.editableTabs = this.editableTabs;
this.onlyOneStatus();
return;
}
}
} else {
// 刷新
this.refresh();
}
},
刷新页面功能的方法-provide和inject
在App.vue页面
<template>
<div id="app">
<router-view v-if="isPageAlive"></router-view>
</div>
</template>
export default {
name: "App",
provide() {
return {
refresh: this.refresh,
};
},
data() {
return {
isPageAlive: true,
};
},
methods: {
refresh() {
this.isPageAlive = false;
this.$nextTick(function () {
this.isPageAlive = true;
});
},
},
};
超出长度出现左右滑动箭头
标签组件TabsContent.vue
<div class="tabContentBox">
<div class="iconLeft" @click="arrowBack" v-if="showButton == true">
<i class="el-icon-caret-left"></i>
</div>
<div class="tag-style" ref="tagBox">
<div
class="scrollWrapper"
ref="scrollWrapper"
id="nav"
:style="{ marginLeft: tabScroll }"
>
<el-tabs
v-model="editableTabsValue"
type="card"
closable
@tab-click="clickTable"
@tab-remove="removeTable"
>
<el-tab-pane
:key="item.name + index"
v-for="(item, index) in editableTabs"
:label="item.title"
:name="'/' + item.name"
>
</el-tab-pane>
</el-tabs>
</div>
</div>
<div class="iconLeft" @click="arrowForward" v-if="showButton == true">
<i class="el-icon-caret-right"></i>
</div>
</div>
data() {
return {
tabScroll: "0px", // 移动的距离
showButton: false,
swiperScrollWidth: 0, // 盒子的宽度
swiperScrollContentWidth: 0, // 内容的宽度
};
},
// 标签向左切换
arrowBack() {
let tabBoxWidth = this.$refs.tagBox.clientWidth; //盒子宽度
let offsetLeft = Math.abs(this.$refs.scrollWrapper.offsetLeft); //移动距离
if (offsetLeft > tabBoxWidth) {
//移动距离大于父盒子宽度,向前移动一整个父盒子宽度
this.tabScroll = offsetLeft + tabBoxWidth + "px";
} else {
this.tabScroll = "0px"; // 否则移动到开始位置
}
},
// 标签向右切换
arrowForward() {
let tabBoxWidth = this.$refs.tagBox.clientWidth; //盒子宽度
let scrollWidth = this.$refs.scrollWrapper.scrollWidth; //内容宽度
// 必须要在循环的父级添加 定位样式, offsetLeft 获取元素相对带有定位父元素左边框的偏移
let offsetLeft = Math.abs(this.$refs.scrollWrapper.offsetLeft); //移动距离
let diffWidth = scrollWidth - tabBoxWidth; //计算内容宽度与盒子宽度的差值
if (diffWidth - offsetLeft > tabBoxWidth) {
//判断差值减去移动距离是否大于盒子宽度 大于则滚动已移动距离+盒子宽度
this.tabScroll = -(offsetLeft + tabBoxWidth) + "px";
} else {
this.tabScroll = -diffWidth + "px"; //小于则移动差值距离
}
},
//关闭所有标签后,左右箭头不消失的问题,不需要可忽略此方法
checkButtonStatus() {
if (!this.$refs.scrollWrapper) return;
// 盒子的宽度
let containerSize = this.$refs.tagBox.clientWidth;
// 内容的宽度
let navSize = this.$refs.scrollWrapper.scrollWidth;
if (containerSize > navSize || containerSize == navSize) {
this.showButton = false;
} else {
this.showButton = true;
}
},
//关闭其他标签后,左右箭头不消失的问题,不需要可忽略此方法
onlyOneStatus() {
if (this.editableTabs.length <= 1) {
this.showButton = false;
}
},
新建标签页组件-完整代码
<template>
<div class="tabBackground">
<div class="tabContentBox">
<div class="iconLeft" @click="arrowBack" v-if="showButton == true">
<i class="el-icon-caret-left"></i>
</div>
<div class="tag-style" ref="tagBox">
<div
class="scrollWrapper"
ref="scrollWrapper"
id="nav"
:style="{ marginLeft: tabScroll }"
>
<el-tabs
v-model="editableTabsValue"
type="card"
closable
@tab-click="clickTable"
@tab-remove="removeTable"
>
<el-tab-pane
:key="item.name + index"
v-for="(item, index) in editableTabs"
:label="item.title"
:name="'/' + item.name"
>
</el-tab-pane>
</el-tabs>
</div>
</div>
<div class="iconLeft" @click="arrowForward" v-if="showButton == true">
<i class="el-icon-caret-right"></i>
</div>
<div class="tabContent-right" v-if="this.editableTabs.length != 0">
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link"> 页面操作 </span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="c" icon="el-icon-refresh-right"
>刷新</el-dropdown-item
>
<el-dropdown-item command="a" icon="el-icon-folder-opened"
>关闭所有</el-dropdown-item
>
<el-dropdown-item command="b" icon="el-icon-folder"
>关闭其他</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</div>
</template>
<script>
import store from "../vuex/store";
import router from "../router";
export default {
inject: ["refresh"], //刷新用的
data() {
return {
editableTabsValue: this.$store.state.editableTabsValue,
editableTabs: this.$store.state.editableTabs,
keepList: this.$store.state.keepList,
tabScroll: "0px", // 移动的距离
showButton: false,
swiperScrollWidth: 0, // 盒子的宽度
swiperScrollContentWidth: 0, // 内容的宽度
};
},
watch: {
$route: function () {
this.setActiveTab();
this.checkButtonStatus();
window.addEventListener("resize", this.checkButtonStatus);
},
editableTabsGet: {
handler: function (oldVal, newVal) {
if (!this.$refs.scrollWrapper) return;
if (this.editableTabs.length <= 1) {
// 盒子的宽度
this.$refs.tagBox.clientWidth = "0px";
// 内容的宽度
this.$refs.scrollWrapper.scrollWidth = "0px";
this.showButton = false;
}
},
immediate: true,
deep: true,
},
},
mounted() {
this.setActiveTab();
window.addEventListener("resize", this.checkButtonStatus);
},
methods: {
// 设置活跃的tab
setActiveTab() {
this.editableTabsValue = this.$route.path;
},
// 移除标签
removeTable(targetName) {
// 动态缓存页面接口
var index = targetName.lastIndexOf("/");
var pageName = targetName.substring(index + 1, targetName.length);
this.keepList = this.keepList.filter((item) => item != pageName);
this.$store.state.keepList = this.keepList;
// 移除标签
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
var targetNameTwo = targetName.slice(1);
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetNameTwo) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
return activeName;
}
}
});
}
this.editableTabsValue = activeName;
var targetNameNew = targetName.slice(1);
this.editableTabs = tabs.filter((tab) => tab.name !== targetNameNew);
this.$store.state.editableTabs = this.editableTabs;
for (var i = 0; i < this.editableTabs.length; i++) {
if (this.editableTabsValue === this.editableTabs[i].name) {
var title = this.editableTabs[i].title;
router.push({
name: title,
});
return;
}
}
},
// 点击切换标签
clickTable(tab) {
var name = JSON.stringify(tab.paneName).replace('"', "").replace('"', ""); //对tab参数处理,以获得当前点击的标签页的路由
var label = tab.label;
store.commit("CHANGE_TABS", name); //调用切换方法切换标签页
router.push({
name: label,
});
},
handleCommand(command) {
// 关闭所有标签
if (command == "a") {
this.editableTabs = [];
this.$store.state.editableTabs = this.editableTabs;
this.keepList = [];
this.$store.state.keepList = this.keepList;
this.$router.push({
path: "/personal/MyFile",
meta: {
keepMenu: true,
},
});
this.onlyOneStatus();
} else if (command == "b") {
// 移除其他,保留当前页
for (var j = 0; j < this.editableTabs.length; j++) {
if (this.editableTabsValue == "/" + this.editableTabs[j].name) {
// 动态缓存
var name = this.editableTabs[j].name;
var index = name.lastIndexOf("/");
var pageName = name.substring(index + 1, name.length);
this.keepList = [];
this.keepList.push(pageName);
this.$store.state.keepList = this.keepList;
// 移除
var oneTab = {};
oneTab.name = this.editableTabs[j].name;
oneTab.title = this.editableTabs[j].title;
this.editableTabs = [];
this.editableTabs.push(oneTab);
this.$store.state.editableTabs = this.editableTabs;
this.onlyOneStatus();
return;
}
}
} else {
// 刷新
this.refresh();
}
},
// 标签向左切换
arrowBack() {
let tabBoxWidth = this.$refs.tagBox.clientWidth; //盒子宽度
let offsetLeft = Math.abs(this.$refs.scrollWrapper.offsetLeft); //移动距离
if (offsetLeft > tabBoxWidth) {
//移动距离大于父盒子宽度,向前移动一整个父盒子宽度
this.tabScroll = offsetLeft + tabBoxWidth + "px";
} else {
this.tabScroll = "0px"; // 否则移动到开始位置
}
},
// 标签向右切换
arrowForward() {
let tabBoxWidth = this.$refs.tagBox.clientWidth; //盒子宽度
let scrollWidth = this.$refs.scrollWrapper.scrollWidth; //内容宽度
// 必须要在循环的父级添加 定位样式, offsetLeft 获取元素相对带有定位父元素左边框的偏移
let offsetLeft = Math.abs(this.$refs.scrollWrapper.offsetLeft); //移动距离
let diffWidth = scrollWidth - tabBoxWidth; //计算内容宽度与盒子宽度的差值
if (diffWidth - offsetLeft > tabBoxWidth) {
//判断差值减去移动距离是否大于盒子宽度 大于则滚动已移动距离+盒子宽度
this.tabScroll = -(offsetLeft + tabBoxWidth) + "px";
} else {
this.tabScroll = -diffWidth + "px"; //小于则移动差值距离
}
},
checkButtonStatus() {
if (!this.$refs.scrollWrapper) return;
// 盒子的宽度
let containerSize = this.$refs.tagBox.clientWidth;
// 内容的宽度
let navSize = this.$refs.scrollWrapper.scrollWidth;
if (containerSize > navSize || containerSize == navSize) {
this.showButton = false;
} else {
this.showButton = true;
}
},
onlyOneStatus() {
if (this.editableTabs.length <= 1) {
this.showButton = false;
}
},
},
};
</script>
<style scoped>
.tabContentBox {
display: flex;
justify-content: space-between;
border-bottom: 1px solid rgb(215, 215, 220);
height: 40px;
background-color: #fff;
}
.tabBackground {
height: 50px;
background-color: #eff0f2;
}
.tabContent-right {
width: 110px;
height: 40px;
line-height: 40px;
position: relative;
}
.iconLeft {
width: 3%;
text-align: center;
height: 40px;
line-height: 40px;
font-size: 16px;
margin: 0 5px;
background-color: #fff;
}
.tag-style {
display: flex;
overflow: hidden;
pointer-events: all;
cursor: pointer;
position: relative;
}
.scrollWrapper {
display: flex;
overflow-x: auto;
transition: all 500ms linear;
}
.scrollWrapper::-webkit-scrollbar {
height: 0;
}
</style>