|
|
|
|
import {defineStore} from "pinia";
|
|
|
|
|
import type { RouteLocationNormalized, RouteLocationRaw, Router } from "vue-router";
|
|
|
|
|
import {getRawRoute} from '@/utils/index'
|
|
|
|
|
import { unref, toRaw } from "vue";
|
|
|
|
|
import { useGo, useRedo } from "@/hooks/web/usePage";
|
|
|
|
|
import { PageEnum } from "@/types/pageEnum";
|
|
|
|
|
export interface MultipleTabState {
|
|
|
|
|
cacheTabList: Set<string>;
|
|
|
|
|
tabList: RouteLocationNormalized[];
|
|
|
|
|
lastDragEndIndex: number;
|
|
|
|
|
}
|
|
|
|
|
function handleGotoPage(router: Router) {
|
|
|
|
|
const go = useGo(router);
|
|
|
|
|
go(unref(router.currentRoute).path, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getToTarget = (tabItem: RouteLocationNormalized) => {
|
|
|
|
|
const { params, path, query } = tabItem;
|
|
|
|
|
return {
|
|
|
|
|
params: params || {},
|
|
|
|
|
path,
|
|
|
|
|
query: query || {},
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
export const useMultipleTabStore = defineStore({
|
|
|
|
|
id: 'app-multi-tab',
|
|
|
|
|
state: () : MultipleTabState => ({
|
|
|
|
|
// Tabs that need to be cached
|
|
|
|
|
cacheTabList: new Set(),
|
|
|
|
|
// multiple tab list
|
|
|
|
|
tabList: [],
|
|
|
|
|
// Index of the last moved tab
|
|
|
|
|
lastDragEndIndex: 0,
|
|
|
|
|
}),
|
|
|
|
|
getters: {
|
|
|
|
|
getTabList(): RouteLocationNormalized[] {
|
|
|
|
|
return this.tabList;
|
|
|
|
|
},
|
|
|
|
|
getCachedTabList(): string[] {
|
|
|
|
|
return Array.from(this.cacheTabList);
|
|
|
|
|
},
|
|
|
|
|
getLastDragEndIndex(): number {
|
|
|
|
|
return this.lastDragEndIndex;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
actions: {
|
|
|
|
|
/**
|
|
|
|
|
* Update the cache according to the currently opened tabs
|
|
|
|
|
*/
|
|
|
|
|
async updateCacheTab() {
|
|
|
|
|
const cacheMap: Set<string> = new Set();
|
|
|
|
|
|
|
|
|
|
for (const tab of this.tabList) {
|
|
|
|
|
const item = getRawRoute(tab);
|
|
|
|
|
// Ignore the cache
|
|
|
|
|
const needCache = !item.meta?.ignoreKeepAlive;
|
|
|
|
|
if (!needCache) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const name = item.name as string;
|
|
|
|
|
cacheMap.add(name);
|
|
|
|
|
}
|
|
|
|
|
this.cacheTabList = cacheMap;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Refresh tabs
|
|
|
|
|
*/
|
|
|
|
|
async refreshPage(router: Router) {
|
|
|
|
|
const { currentRoute } = router;
|
|
|
|
|
const route = unref(currentRoute);
|
|
|
|
|
const name = route.name;
|
|
|
|
|
|
|
|
|
|
const findTab = this.getCachedTabList.find((item) => item === name);
|
|
|
|
|
if (findTab) {
|
|
|
|
|
this.cacheTabList.delete(findTab);
|
|
|
|
|
}
|
|
|
|
|
const redo = useRedo(router);
|
|
|
|
|
await redo();
|
|
|
|
|
},
|
|
|
|
|
clearCacheTabs(): void {
|
|
|
|
|
this.cacheTabList = new Set();
|
|
|
|
|
},
|
|
|
|
|
resetState(): void {
|
|
|
|
|
this.tabList = [];
|
|
|
|
|
this.clearCacheTabs();
|
|
|
|
|
},
|
|
|
|
|
goToPage(router: Router) {
|
|
|
|
|
const go = useGo(router);
|
|
|
|
|
const len = this.tabList.length;
|
|
|
|
|
const { path } = unref(router.currentRoute);
|
|
|
|
|
|
|
|
|
|
let toPath: any;
|
|
|
|
|
|
|
|
|
|
if (len > 0) {
|
|
|
|
|
const page = this.tabList[len - 1];
|
|
|
|
|
const p = page.fullPath || page.path;
|
|
|
|
|
if (p) {
|
|
|
|
|
toPath = p;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Jump to the current page and report an error
|
|
|
|
|
path !== toPath && go(toPath , true);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async addTab(route: RouteLocationNormalized) {
|
|
|
|
|
|
|
|
|
|
// if (
|
|
|
|
|
// routesFilterPrefix.some((prefix) => {
|
|
|
|
|
// return route.path.startsWith(prefix);
|
|
|
|
|
// })
|
|
|
|
|
// ) {
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
const { path, name, fullPath, params, query, meta } = getRawRoute(route);
|
|
|
|
|
// 404 The page does not need to add a tab
|
|
|
|
|
if (path === PageEnum.ERROR_PAGE || path === PageEnum.BASE_LOGIN || !name || name === 'error') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let updateIndex = -1;
|
|
|
|
|
// Existing pages, do not add tabs repeatedly
|
|
|
|
|
const tabHasExits = this.tabList.some((tab, index) => {
|
|
|
|
|
updateIndex = index;
|
|
|
|
|
return (tab.fullPath || tab.path) === (fullPath || path);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// If the tab already exists, perform the update operation
|
|
|
|
|
if (tabHasExits) {
|
|
|
|
|
const curTab = toRaw(this.tabList)[updateIndex];
|
|
|
|
|
if (!curTab) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
curTab.params = params || curTab.params;
|
|
|
|
|
curTab.query = query || curTab.query;
|
|
|
|
|
curTab.fullPath = fullPath || curTab.fullPath;
|
|
|
|
|
this.tabList.splice(updateIndex, 1, curTab);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Add tab
|
|
|
|
|
// 获取动态路由打开数,超过 0 即代表需要控制打开数
|
|
|
|
|
const dynamicLevel = meta?.dynamicLevel ?? -1;
|
|
|
|
|
if (dynamicLevel > 0) {
|
|
|
|
|
// 如果动态路由层级大于 0 了,那么就要限制该路由的打开数限制了
|
|
|
|
|
// 首先获取到真实的路由,使用配置方式减少计算开销.
|
|
|
|
|
// const realName: string = path.match(/(\S*)\//)![1];
|
|
|
|
|
const realPath = meta?.realPath ?? '';
|
|
|
|
|
// 获取到已经打开的动态路由数, 判断是否大于某一个值
|
|
|
|
|
if (this.tabList.filter((e) => e.meta?.realPath ?? '' === realPath).length >= dynamicLevel) {
|
|
|
|
|
// 关闭第一个
|
|
|
|
|
const index = this.tabList.findIndex((item) => item.meta.realPath === realPath);
|
|
|
|
|
index !== -1 && this.tabList.splice(index, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.tabList.push(route);
|
|
|
|
|
}
|
|
|
|
|
this.updateCacheTab();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async closeTab(tab: RouteLocationNormalized, router: Router) {
|
|
|
|
|
const close = (route: RouteLocationNormalized) => {
|
|
|
|
|
const { fullPath, meta: { affix } = {} } = route;
|
|
|
|
|
if (affix) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const index = this.tabList.findIndex((item) => item.fullPath === fullPath);
|
|
|
|
|
index !== -1 && this.tabList.splice(index, 1);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const { currentRoute, replace } = router;
|
|
|
|
|
|
|
|
|
|
const { path } = unref(currentRoute);
|
|
|
|
|
if (path !== tab.path) {
|
|
|
|
|
// Closed is not the activation tab
|
|
|
|
|
close(tab);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Closed is activated atb
|
|
|
|
|
let toTarget: RouteLocationRaw = {};
|
|
|
|
|
|
|
|
|
|
const index = this.tabList.findIndex((item) => item.path === path);
|
|
|
|
|
|
|
|
|
|
// If the current is the leftmost tab
|
|
|
|
|
if (index === 0) {
|
|
|
|
|
// There is only one tab, then jump to the homepage, otherwise jump to the right tab
|
|
|
|
|
if (this.tabList.length === 1) {
|
|
|
|
|
//const userStore = useUserStore();
|
|
|
|
|
// toTarget = userStore.getUserInfo.homePath || PageEnum.BASE_HOME;
|
|
|
|
|
toTarget = PageEnum.BASE_HOME;
|
|
|
|
|
} else {
|
|
|
|
|
// Jump to the right tab
|
|
|
|
|
const page = this.tabList[index + 1];
|
|
|
|
|
toTarget = getToTarget(page);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Close the current tab
|
|
|
|
|
const page = this.tabList[index - 1];
|
|
|
|
|
toTarget = getToTarget(page);
|
|
|
|
|
}
|
|
|
|
|
close(currentRoute.value);
|
|
|
|
|
await replace(toTarget);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Close according to key
|
|
|
|
|
async closeTabByKey(key: string, router: Router) {
|
|
|
|
|
const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === key);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
await this.closeTab(this.tabList[index], router);
|
|
|
|
|
const { currentRoute, replace } = router;
|
|
|
|
|
// 检查当前路由是否存在于tabList中
|
|
|
|
|
const isActivated = this.tabList.findIndex((item) => {
|
|
|
|
|
return item.fullPath === currentRoute.value.fullPath;
|
|
|
|
|
});
|
|
|
|
|
// 如果当前路由不存在于TabList中,尝试切换到其它路由
|
|
|
|
|
if (isActivated === -1) {
|
|
|
|
|
let pageIndex;
|
|
|
|
|
if (index > 0) {
|
|
|
|
|
pageIndex = index - 1;
|
|
|
|
|
} else if (index < this.tabList.length - 1) {
|
|
|
|
|
pageIndex = index + 1;
|
|
|
|
|
} else {
|
|
|
|
|
pageIndex = -1;
|
|
|
|
|
}
|
|
|
|
|
if (pageIndex >= 0) {
|
|
|
|
|
const page = this.tabList[index - 1];
|
|
|
|
|
const toTarget = getToTarget(page);
|
|
|
|
|
await replace(toTarget);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Sort the tabs
|
|
|
|
|
async sortTabs(oldIndex: number, newIndex: number) {
|
|
|
|
|
const currentTab = this.tabList[oldIndex];
|
|
|
|
|
this.tabList.splice(oldIndex, 1);
|
|
|
|
|
this.tabList.splice(newIndex, 0, currentTab);
|
|
|
|
|
this.lastDragEndIndex = this.lastDragEndIndex + 1;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Close the tab on the left and jump
|
|
|
|
|
async closeLeftTabs(route: RouteLocationNormalized, router: Router) {
|
|
|
|
|
const index = this.tabList.findIndex((item) => item.path === route.path);
|
|
|
|
|
|
|
|
|
|
if (index > 0) {
|
|
|
|
|
const leftTabs = this.tabList.slice(0, index);
|
|
|
|
|
const pathList: string[] = [];
|
|
|
|
|
for (const item of leftTabs) {
|
|
|
|
|
const affix = item?.meta?.affix ?? false;
|
|
|
|
|
if (!affix) {
|
|
|
|
|
pathList.push(item.fullPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.bulkCloseTabs(pathList);
|
|
|
|
|
}
|
|
|
|
|
this.updateCacheTab();
|
|
|
|
|
handleGotoPage(router);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Close the tab on the right and jump
|
|
|
|
|
async closeRightTabs(route: RouteLocationNormalized, router: Router) {
|
|
|
|
|
const { replace } = router;
|
|
|
|
|
const index = this.tabList.findIndex((item) => item.fullPath === route.fullPath);
|
|
|
|
|
|
|
|
|
|
if (index >= 0 && index < this.tabList.length - 1) {
|
|
|
|
|
const rightTabs = this.tabList.slice(index + 1, this.tabList.length);
|
|
|
|
|
|
|
|
|
|
const pathList: string[] = [];
|
|
|
|
|
for (const item of rightTabs) {
|
|
|
|
|
const affix = item?.meta?.affix ?? false;
|
|
|
|
|
if (!affix) {
|
|
|
|
|
pathList.push(item.fullPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.bulkCloseTabs(pathList);
|
|
|
|
|
}
|
|
|
|
|
this.updateCacheTab();
|
|
|
|
|
await replace(route);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async closeAllTab(router: Router) {
|
|
|
|
|
this.tabList = this.tabList.filter((item) => item?.meta?.affix ?? false);
|
|
|
|
|
this.clearCacheTabs();
|
|
|
|
|
this.goToPage(router);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Close other tabs
|
|
|
|
|
*/
|
|
|
|
|
async closeOtherTabs(route: RouteLocationNormalized, router: Router) {
|
|
|
|
|
const { replace } = router;
|
|
|
|
|
const closePathList = this.tabList.map((item) => item.fullPath);
|
|
|
|
|
|
|
|
|
|
const pathList: string[] = [];
|
|
|
|
|
|
|
|
|
|
for (const path of closePathList) {
|
|
|
|
|
if (path !== route.fullPath) {
|
|
|
|
|
const closeItem = this.tabList.find((item) => item.path === path);
|
|
|
|
|
if (!closeItem) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const affix = closeItem?.meta?.affix ?? false;
|
|
|
|
|
if (!affix) {
|
|
|
|
|
pathList.push(closeItem.fullPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.bulkCloseTabs(pathList);
|
|
|
|
|
this.updateCacheTab();
|
|
|
|
|
replace(route);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Close tabs in bulk
|
|
|
|
|
*/
|
|
|
|
|
async bulkCloseTabs(pathList: string[]) {
|
|
|
|
|
this.tabList = this.tabList.filter((item) => !pathList.includes(item.fullPath));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set tab's title
|
|
|
|
|
*/
|
|
|
|
|
async setTabTitle(title: string, route: RouteLocationNormalized) {
|
|
|
|
|
const findTab = this.getTabList.find((item) => item === route);
|
|
|
|
|
if (findTab) {
|
|
|
|
|
findTab.meta.title = title;
|
|
|
|
|
await this.updateCacheTab();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* replace tab's path
|
|
|
|
|
* **/
|
|
|
|
|
async updateTabPath(fullPath: string, route: RouteLocationNormalized) {
|
|
|
|
|
const findTab = this.getTabList.find((item) => item === route);
|
|
|
|
|
if (findTab) {
|
|
|
|
|
findTab.fullPath = fullPath;
|
|
|
|
|
findTab.path = fullPath;
|
|
|
|
|
await this.updateCacheTab();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|