318 lines
8.8 KiB
Plaintext
318 lines
8.8 KiB
Plaintext
<template>
|
|
<div class="tags-view" :class="{ 'dark-tags-view': settingStore.darkTheme }">
|
|
<a-tabs
|
|
v-model:activeKey="activeKey"
|
|
hide-add
|
|
type="editable-card"
|
|
@change="goPage"
|
|
@edit="onEdit"
|
|
size="small"
|
|
:tabBarGutter="6"
|
|
:tabBarStyle="{
|
|
margin: 0,
|
|
}"
|
|
>
|
|
<a-tab-pane
|
|
v-for="item in tabsList"
|
|
:key="item.key"
|
|
:tab="item.title"
|
|
:closable="!item.meta?.affix"
|
|
/>
|
|
<template #rightExtra>
|
|
<div class="tabs-close">
|
|
<a-dropdown placement="bottomLeft">
|
|
<div class="tabs-close-btn">
|
|
<DownOutlined />
|
|
</div>
|
|
<template #overlay>
|
|
<a-menu>
|
|
<a-menu-item @click="fullScreen">
|
|
<a-space>
|
|
<ExpandOutlined v-if="!getPageFullScreen" />
|
|
<CompressOutlined v-else />
|
|
<span>{{ getPageFullScreen ? '退出全屏' : '内容全屏' }}</span>
|
|
</a-space>
|
|
</a-menu-item>
|
|
<a-menu-item @click="reloadPage">
|
|
<a-space>
|
|
<SyncOutlined />
|
|
<span>刷新当前</span>
|
|
</a-space>
|
|
</a-menu-item>
|
|
<a-menu-item :disabled="currentRoute.meta?.affix" @click="closeCurrentTabItem">
|
|
<a-space>
|
|
<MinusOutlined />
|
|
<span>关闭当前</span>
|
|
</a-space>
|
|
</a-menu-item>
|
|
<a-menu-item @click="closeOther">
|
|
<a-space>
|
|
<SwapOutlined />
|
|
<span>关闭其他</span>
|
|
</a-space>
|
|
</a-menu-item>
|
|
<a-menu-item @click="closeLeft">
|
|
<a-space>
|
|
<DoubleLeftOutlined />
|
|
<span>关闭左侧</span>
|
|
</a-space>
|
|
</a-menu-item>
|
|
<a-menu-item @click="closeRight">
|
|
<a-space>
|
|
<DoubleRightOutlined />
|
|
<span>关闭右侧</span>
|
|
</a-space>
|
|
</a-menu-item>
|
|
<a-menu-item @click="closeAll">
|
|
<a-space>
|
|
<CloseOutlined />
|
|
<span>关闭全部</span>
|
|
</a-space>
|
|
</a-menu-item>
|
|
</a-menu>
|
|
</template>
|
|
</a-dropdown>
|
|
</div>
|
|
</template>
|
|
</a-tabs>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, ref, watch, inject } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { storage } from '@/utils/Storage';
|
|
import { RouteItem } from '@/store/modules/tabsView';
|
|
import { TABS_ROUTES,FIRST_ROUTE } from '@/store/mutation-types';
|
|
import { useGo, useRedo } from '@/hooks/web/usePage';
|
|
import { PageEnum } from '@/enums/pageEnum';
|
|
import { useTabsViewStore } from '@/store/modules/tabsView';
|
|
import {
|
|
DownOutlined,
|
|
SyncOutlined,
|
|
CloseOutlined,
|
|
MinusOutlined,
|
|
SwapOutlined,
|
|
DoubleLeftOutlined,
|
|
DoubleRightOutlined,
|
|
ExpandOutlined,
|
|
CompressOutlined,
|
|
} from '@ant-design/icons-vue';
|
|
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
|
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
|
|
|
|
// 获取简易的路由对象
|
|
const getSimpleRoute = (route): RouteItem => {
|
|
const { fullPath, hash, meta, name, params, path, query } = route;
|
|
return { fullPath, hash, meta, name, params, path, query };
|
|
};
|
|
|
|
const emit = defineEmits(['pageFullScreen']);
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const go = useGo();
|
|
const currentTabRoute = ref(null);
|
|
const refreshCurrent = ref(true);
|
|
const currentRoute = ref(route);
|
|
const activeKey = ref(route.name);
|
|
const tabsViewStore = useTabsViewStore();
|
|
const settingStore = useProjectSettingStore();
|
|
const asyncRouteStore = useAsyncRouteStore();
|
|
|
|
let routes: RouteItem[] = [];
|
|
try {
|
|
const routesStr = storage.get(TABS_ROUTES) as string | null | undefined;
|
|
routes = routesStr ? JSON.parse(routesStr) : [getSimpleRoute(route)];
|
|
} catch (e) {
|
|
routes = [getSimpleRoute(route)];
|
|
}
|
|
// 初始化标签页
|
|
tabsViewStore.initTabs(routes);
|
|
|
|
const getPageFullScreen = inject('isPageFullScreen');
|
|
|
|
//当前路由对象 或者 右键选择tab路由对象
|
|
const getCurrentTabRoute = computed(() =>
|
|
currentTabRoute.value ? currentTabRoute.value : route,
|
|
);
|
|
|
|
// 移除缓存组件名称
|
|
const delKeepAliveCompName = () => {
|
|
if (route.meta.keepAlive) {
|
|
const name = router.currentRoute.value.matched.find((item) => item.name == route.name)
|
|
?.components?.default.name;
|
|
if (name) {
|
|
asyncRouteStore.keepAliveComponents = asyncRouteStore.keepAliveComponents.filter(
|
|
(item) => item != name,
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
// 标签页列表
|
|
const tabsList: any = computed(() => {
|
|
return tabsViewStore.tabsList.map((item) => {
|
|
return {
|
|
...item,
|
|
title: item.meta?.title,
|
|
key: item.fullPath,
|
|
closable: !item.meta?.affix,
|
|
};
|
|
});
|
|
});
|
|
|
|
const whiteList: string[] = [
|
|
PageEnum.BASE_LOGIN_NAME,
|
|
PageEnum.REDIRECT_NAME,
|
|
PageEnum.ERROR_PAGE_NAME,
|
|
];
|
|
|
|
watch(
|
|
() => route.fullPath,
|
|
(to) => {
|
|
// 如果您用的路由模式是 hash 请去掉,|| route.hash 判断条件
|
|
if (whiteList.includes(route.name as string) || route.hash || route.meta.tagView === false)
|
|
return;
|
|
activeKey.value = to;
|
|
currentTabRoute.value = null;
|
|
currentRoute.value = route;
|
|
refreshCurrent.value = false;
|
|
tabsViewStore.addTabs(getSimpleRoute(route));
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
|
|
//内容页全屏
|
|
function fullScreen() {
|
|
emit('pageFullScreen');
|
|
}
|
|
|
|
// 在页面关闭或刷新之前,保存数据
|
|
window.addEventListener('beforeunload', () => {
|
|
storage.set(TABS_ROUTES, JSON.stringify(tabsList.value));
|
|
});
|
|
|
|
//删除tab
|
|
function closeTabItem(fullPath) {
|
|
const routeInfo = ref(tabsList.value.find((item) => item.fullPath == fullPath));
|
|
removeTab(routeInfo);
|
|
}
|
|
|
|
//删除当前 tab
|
|
function closeCurrentTabItem() {
|
|
removeTab(currentRoute);
|
|
}
|
|
|
|
//tab 编辑
|
|
const onEdit = (targetKey: string | MouseEvent) => {
|
|
closeTabItem(targetKey);
|
|
};
|
|
|
|
function goPage(fullPath) {
|
|
if (fullPath === route.fullPath) return;
|
|
activeKey.value = fullPath;
|
|
go(fullPath, true);
|
|
}
|
|
|
|
// 刷新页面
|
|
async function reloadPage() {
|
|
delKeepAliveCompName();
|
|
const redo = useRedo(router);
|
|
await redo();
|
|
}
|
|
|
|
// 关闭当前页面
|
|
function removeTab(route) {
|
|
tabsViewStore.closeCurrentTab(route);
|
|
// 如果关闭的是当前页
|
|
if (activeKey.value === route.value.fullPath) {
|
|
const current = tabsList.value[Math.max(0, tabsList.value.length - 1)];
|
|
activeKey.value = current.fullPath;
|
|
router.push(current);
|
|
}
|
|
}
|
|
|
|
// 关闭左侧
|
|
function closeLeft() {
|
|
tabsViewStore.closeLeftTabs(getCurrentTabRoute, activeKey.value);
|
|
router.replace(route.fullPath);
|
|
}
|
|
|
|
// 关闭右侧
|
|
function closeRight() {
|
|
tabsViewStore.closeRightTabs(getCurrentTabRoute, activeKey.value);
|
|
router.replace(route.fullPath);
|
|
}
|
|
|
|
// 关闭其他
|
|
function closeOther() {
|
|
tabsViewStore.closeOtherTabs(getCurrentTabRoute, activeKey.value);
|
|
router.replace(route.fullPath);
|
|
}
|
|
|
|
// 关闭全部
|
|
function closeAll() {
|
|
tabsViewStore.closeAllTabs();
|
|
router.replace(storage.get(FIRST_ROUTE));
|
|
// router.replace(PageEnum.BASE_HOME_REDIRECT);
|
|
}
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.tags-view {
|
|
:deep(
|
|
.ant-tabs.ant-tabs-card .ant-tabs-nav .ant-tabs-nav-wrap .ant-tabs-nav-list .ant-tabs-tab
|
|
) {
|
|
height: 32px;
|
|
line-height: 32px;
|
|
border-radius: 3px;
|
|
margin-right: 6px;
|
|
cursor: pointer;
|
|
position: relative;
|
|
border: none;
|
|
}
|
|
:deep(.ant-tabs .ant-tabs-nav .ant-tabs-nav-more) {
|
|
padding: 0 16px;
|
|
}
|
|
|
|
:deep(.ant-tabs .ant-tabs-nav .ant-tabs-tab-remove) {
|
|
margin-left: 0;
|
|
}
|
|
|
|
:deep(.ant-tabs-bar) {
|
|
margin-bottom: 0px;
|
|
}
|
|
.tabs-close {
|
|
min-width: 32px;
|
|
width: 32px;
|
|
height: 32px;
|
|
line-height: 32px;
|
|
text-align: center;
|
|
border-radius: 2px;
|
|
cursor: pointer;
|
|
//margin-right: 10px;
|
|
|
|
&-btn {
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
}
|
|
.dark-tags-view {
|
|
:deep(
|
|
.ant-tabs.ant-tabs-card .ant-tabs-nav .ant-tabs-nav-wrap .ant-tabs-nav-list .ant-tabs-tab
|
|
) {
|
|
background: #151515;
|
|
}
|
|
:deep(.ant-tabs-top > .ant-tabs-nav::before) {
|
|
border: none;
|
|
}
|
|
.tabs-close {
|
|
background: #151515;
|
|
}
|
|
}
|
|
</style>
|