<template>
<div class="px-10px bg-white flex ">
<a-tabs v-model:activeKey="tabsMenuValue" @tabClick="tabClick" class="flex-1">
<a-tab-pane v-for="item in tabsMenuList" :key="item.path">
<template #tab>
<span>
<dashboard-outlined />
{
{
item.title }}
<close-outlined v-if="item.close" @click.stop="tabRemove(item)" class="ml-4px" />
</span>
</template>
</a-tab-pane>
</a-tabs>
<div style="width: 80px;" class="flex align-items-center justify-content-center ">
<a-dropdown>
<a-button type="primary" size="small">
{
{
$t("tabs.more") }}
<DownOutlined />
</a-button>
<template #overlay>
<a-menu>
<a-menu-item>
<a href="javascript:;" @click="closeCurrent">{
{
$t("tabs.closeCurrent") }}</a>
</a-menu-item>
<a-menu-item>
<a href="javascript:;" @click="closeOtherTab">{
{
$t("tabs.closeOther") }}</a>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</div>
</template>
<script lang='ts' setup>
import Sortable from "sortablejs";
import {
onMounted, ref, computed, watch } from 'vue'
import {
useRoute, useRouter } from 'vue-router';
import {
useTabsStore } from '@/store/useTabsStore'
import {
useKeepAliveStore } from '@/store/keepAlive'
import {
DashboardOutlined, CloseOutlined, DownOutlined } from '@ant-design/icons-vue'
const route = useRoute()
const router = useRouter()
const tabsMenuValue = ref(route.fullPath);
const tabStore = useTabsStore();
const keepAliveStore = useKeepAliveStore();
const tabsMenuList = computed(() => tabStore.tabsMenuList);
watch(
() => route.fullPath,
() => {
if (route.meta.isFull) return;
tabsMenuValue.value = route.fullPath;
const tabsParams = {
icon: route.meta.icon as string,
title: route.meta.title as string,
path: route.fullPath,
name: route.name as string,
close: !route.meta.isAffix
};
tabStore.addTabs(tabsParams);
route.meta.isKeepAlive && keepAliveStore.addKeepAliveName(route.name as string);
},
{
immediate: true }
);
onMounted(() => {
tabsDrop()
})
const tabClick = (path: string) => {
console.log(path)
router.push(path);
}
const tabRemove = (item: any) => {
console.log('item', item)
const {
path }: {
path: string } = item
const name = tabStore.tabsMenuList.filter(item => item.path == path)[0].name || "";
keepAliveStore.removeKeepAliveName(name);
tabStore.removeTabs(path as string, path == route.fullPath);
};
function closeCurrent() {
console.log(route)
if (route.meta.isAffix) return
tabStore.removeTabs(route.fullPath)
keepAliveStore.removeKeepAliveName(route.name as string)
}
function closeOtherTab() {
tabStore.closeMultipleTab(route.fullPath)
keepAliveStore.setKeepAliveName([route.name] as string[])
}
const tabsDrop = () => {
Sortable.create(document.querySelector(".ant-tabs-nav-list") as HTMLElement, {
draggable: ".ant-tabs-tab",
animation: 300,
});
};
</script>
<style lang="scss" scoped>
:deep(.ant-tabs-nav) {
margin-bottom: 0 !important;
}
</style>
import router from "@/router";
import {
defineStore } from 'pinia'
import {
TabsMenuProps, TabsState } from './interface/index'
export const useTabsStore = defineStore('useTabsStore', {
state: (): TabsState => ({
tabsMenuList: []
}),
actions: {
async addTabs(tabItem: TabsMenuProps) {
if (this.tabsMenuList.every(item => item.path !== tabItem.path)) {
this.tabsMenuList.push(tabItem);
}
},
async removeTabs(tabPath: string, isCurrent = true) {
const tabsMenuList = this.tabsMenuList;
if (isCurrent) {
tabsMenuList.forEach((item, index) => {
if (item.path !== tabPath) return;
const nextTab = tabsMenuList[index + 1] || tabsMenuList[index - 1];
if (!nextTab) return;
router.push(nextTab.path);
});
}
this.tabsMenuList = tabsMenuList.filter(item => item.path !== tabPath);
},
async closeMultipleTab(tabsMenuValue?: string) {
this.tabsMenuList = this.tabsMenuList.filter(item => {
return item.path === tabsMenuValue || !item.close;
});
},
async setTabs(tabsMenuList: TabsMenuProps[]) {
this.tabsMenuList = tabsMenuList;
},
async setTabsTitle(title: string) {
const nowFullPath = location.hash.substring(1);
this.tabsMenuList.forEach(item => {
if (item.path == nowFullPath) item.title = title;
});
}
},
})