You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 lines
6.7 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="layout-tabs">
<el-tabs
type="border-card"
v-model="curTabKey"
closable
@tab-click="clickTab"
@tab-remove="removeTab"
>
<el-tab-pane
v-for="item in tabs"
:label="item.title"
:name="item.tabKey"
:key="item.tabKey"
>
<template slot="label"
>{{ item.title }}
<i
v-if="curTabKey === item.tabKey"
class="el-icon-refresh"
@click="refreshTab(item)"
></i
></template>
</el-tab-pane>
</el-tabs>
<div class="close-tabs" @click="closeOtherTabs">关闭其他</div>
</div>
</template>
<script>
import { mapMutations, mapActions } from "vuex";
import EventBus from "@/utils/event-bus";
export default {
name: "LayoutTabs",
props: {
// 【根据项目修改】tab页面在路由的第几层或者说第几层的 router-view 组件(当前项目为第二层)
tabRouteViewDepth: {
type: Number,
default: 2,
},
// tab页面的key值从route对象中取一个key值对应一个tab页面
// 默认为matchRoute.path值
getTabKey: {
type: Function,
default: function (routeMatch /* , route */) {
return routeMatch.path;
},
},
// tab页签的标题默认从路由meta.title中获取
tabTitleKey: {
type: String,
default: "title",
},
},
data() {
return {
tabs: [],
curTabKey: "",
};
},
methods: {
...mapActions("cache", ["addCache", "removeCache", "removeCacheEntry"]),
...mapMutations(["setIsRenderTab"]),
// 切换tab
changeCurTab() {
// 当前路由信息
const { path, query, params, hash, matched } = this.$route;
// tab标签页路由信息meta、componentName
const routeMatch = matched[this.tabRouteViewDepth - 1];
const meta = routeMatch.meta;
const componentName = routeMatch.components?.default?.name;
// 获取tab标签页信息tabKey标签页key值title-标签页标题tab-存在的标签页
const tabKey = this.getTabKey(routeMatch, this.$route);
const title = String(meta[this.tabTitleKey] || "");
const tab = this.tabs.find((tab) => tab.tabKey === tabKey);
if (!tabKey) {
// tabKey默认为路由的name值
console.warn(
`LayoutTabs组件${path} 路由没有匹配的tab标签页如有需要请配置tab标签页的key值`
);
return;
}
// 同一个路由但是新旧路径不同时需要清除路由缓存。例如route.path配置为 '/detail/:id'时路径会不同
// 这里判断 props.tabRouteViewDepth === matched.length 必须是跟tab同级路由否则会影响多级路由缓存
if (
tab &&
tab.path !== path &&
this.tabRouteViewDepth === matched.length
) {
this.removeCacheEntry(componentName || "");
tab.title = "";
}
const newTab = {
tabKey,
title: tab?.title || title,
path,
params,
query,
hash,
componentName,
};
tab ? Object.assign(tab, newTab) : this.tabs.push(newTab);
this.curTabKey = tabKey;
},
// 点击tab
clickTab(pane) {
if (!pane.index) return;
const tab = this.tabs[Number(pane.index)];
if (tab.path !== this.$route.path) {
this.gotoTab(tab);
}
},
// 移除tab
async removeTab(tabKey) {
// 剩下一个时不能删
if (this.tabs.length === 1) return;
const index = this.tabs.findIndex((tab) => tab.tabKey === tabKey);
if (index < -1) return;
const tab = this.tabs[index];
this.tabs.splice(index, 1);
// 如果删除的是当前tab则切换到最后一个tab
if (tab.tabKey === this.curTabKey) {
const lastTab = this.tabs[this.tabs.length - 1];
lastTab && this.gotoTab(lastTab);
}
this.removeCache(tab.componentName || "");
},
// 跳转tab页面
async gotoTab(tab) {
await this.$router.push({
path: tab.path,
query: tab.query,
hash: tab.hash,
});
},
// 关闭非当前页的所有tab页签
closeOtherTabs() {
this.tabs
.filter((tab) => tab.tabKey !== this.curTabKey)
.forEach((tab) => {
this.removeCache(tab.componentName || "");
});
this.tabs = this.tabs.filter((tab) => tab.tabKey === this.curTabKey);
},
// 刷新当前tab页面
async refreshTab(tab) {
this.setIsRenderTab(false);
await this.removeCacheEntry(tab.componentName);
this.setIsRenderTab(true);
},
// 关闭tab页面默认关闭当前页
async closeLayoutTab(tabKey = this.curTabKey) {
const index = this.tabs.findIndex((tab) => tab.tabKey === tabKey);
if (index > -1) {
this.removeCache(this.tabs[index].componentName);
this.tabs.splice(index, 1);
}
},
// 设置当前tab的标题
setCurTabTitle(title) {
const curTab = this.tabs.find((tab) => tab.tabKey === this.curTabKey);
if (curTab) {
curTab.title = title;
}
},
},
watch: {
"$route.path": {
handler() {
this.changeCurTab();
},
immediate: true,
},
},
created() {
// 对外提供的事件关闭弹窗设置tab标题
EventBus.$on("LayoutTabs:closeTab", (tabKey) => {
this.closeLayoutTab(tabKey);
});
EventBus.$on("LayoutTabs:setTabTitle", (title) => {
this.setCurTabTitle(title);
});
},
};
</script>
<style lang="less">
.layout-tabs {
position: relative;
height: 32px;
line-height: 32px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f7fa;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12), 0 0 6px 0 rgba(0, 0, 0, 0.04);
//background-color: #fcc;
.close-tabs {
padding-right: 12px;
cursor: pointer;
color: #999;
height: 32px;
line-height: 32px;
font-size: 12px;
&:hover {
color: #337ab7;
}
}
.el-tabs--border-card {
height: 30px;
flex: 1;
margin-right: 12px;
border: 1px solid #dcdfe6;
box-shadow: none;
}
.el-icon-refresh {
margin: 0px 8px 0px 4px;
}
.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active {
color: #fff;
background-color: #337ab7;
}
.el-tabs--border-card
> .el-tabs__header
.el-tabs__item:not(.is-disabled):hover {
color: #fff;
background-color: #337ab7;
}
.el-tabs__item .el-icon-close {
transition: none;
}
.el-tabs__item {
height: 30px;
line-height: 30px;
font-size: 12px;
padding: 0 10px !important;
}
.el-tabs__content {
display: none;
}
}
</style>