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.
xy-frontend/src/components/LayoutTabs.vue

235 lines
6.3 KiB
Vue

2 years ago
<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: #169e8c;
}
}
.el-tabs--border-card {
height: 30px;
flex: 1;
margin-right: 12px;
border: 1px solid #dcdfe6;
box-shadow: none;
}
.el-tabs__item {
height: 30px;
line-height: 30px;
font-size: 12px;
}
.el-tabs__content {
display: none;
}
}
</style>