Files
cn-rdms-web/src/store/modules/tab/index.ts

321 lines
7.7 KiB
TypeScript
Raw Normal View History

2026-03-26 20:18:20 +08:00
import { computed, ref } from 'vue';
import { useEventListener } from '@vueuse/core';
import { defineStore } from 'pinia';
import type { RouteKey } from '@elegant-router/types';
import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router';
import { localStg } from '@/utils/storage';
import { getGlobalRouter } from '@/router/instance';
2026-03-26 20:18:20 +08:00
import { SetupStoreId } from '@/enum';
import { useThemeStore } from '../theme';
import {
extractTabsByAllRoutes,
filterTabsByIds,
findTabByRouteName,
getAllTabs,
getDefaultHomeTab,
getFixedTabIds,
getTabByRoute,
getTabIdByRoute,
isTabInTabs,
updateTabByI18nKey,
updateTabsByI18nKey
} from './shared';
export const useTabStore = defineStore(SetupStoreId.Tab, () => {
const routeStore = useRouteStore();
const themeStore = useThemeStore();
const { routerPush } = useRouterPush(false);
/** Tabs */
const tabs = ref<App.Global.Tab[]>([]);
/** Get active tab */
const homeTab = ref<App.Global.Tab>();
/** Init home tab */
function initHomeTab() {
homeTab.value = getDefaultHomeTab(getGlobalRouter(), routeStore.routeHome);
2026-03-26 20:18:20 +08:00
}
/** Get all tabs */
const allTabs = computed(() => getAllTabs(tabs.value, homeTab.value));
/** Active tab id */
const activeTabId = ref<string>('');
/**
* Set active tab id
*
* @param id Tab id
*/
function setActiveTabId(id: string) {
activeTabId.value = id;
}
/**
* Init tab store
*
* @param currentRoute Current route
*/
function initTabStore(currentRoute: App.Global.TabRoute) {
const storageTabs = localStg.get('globalTabs');
if (themeStore.tab.cache && storageTabs) {
const extractedTabs = extractTabsByAllRoutes(getGlobalRouter(), storageTabs);
2026-03-26 20:18:20 +08:00
tabs.value = updateTabsByI18nKey(extractedTabs);
}
addTab(currentRoute);
}
/**
* Add tab
*
* @param route Tab route
* @param active Whether to activate the added tab
*/
function addTab(route: App.Global.TabRoute, active = true) {
const tab = getTabByRoute(route);
const isHomeTab = tab.id === homeTab.value?.id;
if (!isHomeTab && !isTabInTabs(tab.id, tabs.value)) {
tabs.value.push(tab);
}
if (active) {
setActiveTabId(tab.id);
}
}
async function resetRemovedTabCaches(removedTabs: App.Global.Tab[], remainingTabs: App.Global.Tab[]) {
const routeKeysToReset = Array.from(
new Set(
removedTabs
.filter(tab => !remainingTabs.some(remainingTab => remainingTab.routeKey === tab.routeKey))
.map(tab => tab.routeKey)
)
);
2026-03-27 14:03:42 +08:00
await Promise.all(routeKeysToReset.map(routeKey => routeStore.resetRouteCache(routeKey)));
2026-03-26 20:18:20 +08:00
}
/**
* Remove tab when remove active tab, switch to next tab or home tab
*
* @param tabId Tab id
*/
async function removeTab(tabId: string) {
const removeTabIndex = tabs.value.findIndex(tab => tab.id === tabId);
if (removeTabIndex === -1) return;
const isRemoveActiveTab = activeTabId.value === tabId;
const removedTab = tabs.value[removeTabIndex];
// if remove the last tab, then switch to the second last tab
const nextTab = tabs.value[removeTabIndex + 1] || tabs.value[removeTabIndex - 1] || homeTab.value;
// remove tab
tabs.value.splice(removeTabIndex, 1);
// if current tab is removed, then switch to next tab
if (isRemoveActiveTab && nextTab) {
await switchRouteByTab(nextTab);
}
if (removedTab) {
await resetRemovedTabCaches([removedTab], tabs.value);
}
}
/** remove active tab */
async function removeActiveTab() {
await removeTab(activeTabId.value);
}
/**
* remove tab by route name
*
* @param routeName route name
*/
async function removeTabByRouteName(routeName: RouteKey) {
const tab = findTabByRouteName(routeName, tabs.value);
if (!tab) return;
await removeTab(tab.id);
}
/**
* Clear tabs
*
* @param excludes Exclude tab ids
*/
async function clearTabs(excludes: string[] = []) {
const remainTabIds = [...getFixedTabIds(tabs.value), ...excludes];
const tabsToRemove = tabs.value.filter(tab => !remainTabIds.includes(tab.id));
const removedTabsIds = tabsToRemove.map(tab => tab.id);
// If no tabs are actually being removed based on excludes and fixed tabs, exit
if (removedTabsIds.length === 0) {
return;
}
const isRemoveActiveTab = removedTabsIds.includes(activeTabId.value);
// filterTabsByIds returns tabs NOT in removedTabsIds, so these are the tabs that will remain
const updatedTabs = filterTabsByIds(removedTabsIds, tabs.value);
function update() {
tabs.value = updatedTabs;
}
if (isRemoveActiveTab) {
const activeTabCandidate = updatedTabs[updatedTabs.length - 1] || homeTab.value;
if (activeTabCandidate) {
// Ensure there's a tab to switch to
await switchRouteByTab(activeTabCandidate);
}
}
// Update the tabs array regardless of switch success or if a candidate was found
update();
await resetRemovedTabCaches(tabsToRemove, updatedTabs);
}
/**
* Switch route by tab
*
* @param tab
*/
async function switchRouteByTab(tab: App.Global.Tab) {
const fail = await routerPush(tab.fullPath);
if (!fail) {
setActiveTabId(tab.id);
}
}
/**
* Clear left tabs
*
* @param tabId
*/
async function clearLeftTabs(tabId: string) {
const tabIds = tabs.value.map(tab => tab.id);
const index = tabIds.indexOf(tabId);
if (index === -1) return;
const excludes = tabIds.slice(index);
await clearTabs(excludes);
}
/**
* Clear right tabs
*
* @param tabId
*/
async function clearRightTabs(tabId: string) {
const isHomeTab = tabId === homeTab.value?.id;
if (isHomeTab) {
clearTabs();
return;
}
const tabIds = tabs.value.map(tab => tab.id);
const index = tabIds.indexOf(tabId);
if (index === -1) return;
const excludes = tabIds.slice(0, index + 1);
await clearTabs(excludes);
}
/**
* Set new label of tab
*
* @default activeTabId
* @param label New tab label
* @param tabId Tab id
*/
function setTabLabel(label: string, tabId?: string) {
const id = tabId || activeTabId.value;
const tab = tabs.value.find(item => item.id === id);
if (!tab) return;
tab.oldLabel = tab.label;
tab.newLabel = label;
}
/**
* Reset tab label
*
* @default activeTabId
* @param tabId Tab id
*/
function resetTabLabel(tabId?: string) {
const id = tabId || activeTabId.value;
const tab = tabs.value.find(item => item.id === id);
if (!tab) return;
tab.newLabel = undefined;
}
/**
* Is tab retain
*
* @param tabId
*/
function isTabRetain(tabId: string) {
if (tabId === homeTab.value?.id) return true;
const fixedTabIds = getFixedTabIds(tabs.value);
return fixedTabIds.includes(tabId);
}
/** Update tabs by locale */
function updateTabsByLocale() {
tabs.value = updateTabsByI18nKey(tabs.value);
if (homeTab.value) {
homeTab.value = updateTabByI18nKey(homeTab.value);
}
}
/** Cache tabs */
function cacheTabs() {
if (!themeStore.tab.cache) return;
localStg.set('globalTabs', tabs.value);
}
// cache tabs when page is closed or refreshed
useEventListener(window, 'beforeunload', () => {
cacheTabs();
});
return {
/** All tabs */
tabs: allTabs,
activeTabId,
initHomeTab,
initTabStore,
addTab,
removeTab,
removeActiveTab,
removeTabByRouteName,
clearTabs,
clearLeftTabs,
clearRightTabs,
switchRouteByTab,
setTabLabel,
resetTabLabel,
isTabRetain,
updateTabsByLocale,
getTabIdByRoute,
cacheTabs
};
});