import type { RouteKey } from '@elegant-router/types' import { useEventListener } from '@vueuse/core' import { defineStore } from 'pinia' import { computed, ref } from 'vue' import { SetupStoreId } from '@/enum' import { useRouterPush } from '@/hooks/common/router' import { router } from '@/router' import { useRouteStore } from '@/store/modules/route' import { localStg } from '@/utils/storage' import { useThemeStore } from '../theme' import { extractTabsByAllRoutes, filterTabsByIds, findTabByRouteName, getAllTabs, getDefaultHomeTab, getFixedTabIds, getTabByRoute, getTabIdByRoute, isTabInTabs, reorderFixedTabs, updateTabByI18nKey, updateTabsByI18nKey, } from './shared' export const useTabStore = defineStore(SetupStoreId.Tab, () => { const routeStore = useRouteStore() const themeStore = useThemeStore() const { routerPush } = useRouterPush(false) /** Tabs */ const tabs = ref([]) /** Get active tab */ const homeTab = ref() /** Init home tab */ function initHomeTab() { homeTab.value = getDefaultHomeTab(router, routeStore.routeHome) } /** Get all tabs */ const allTabs = computed(() => getAllTabs(tabs.value, homeTab.value)) /** Active tab id */ const activeTabId = ref('') /** * 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(router, storageTabs) 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) } } /** * Remove tab * * @param tabId Tab id */ async function removeTab(tabId: string) { const removeTabIndex = tabs.value.findIndex(tab => tab.id === tabId) if (removeTabIndex === -1) return const removedTabRouteKey = tabs.value[removeTabIndex].routeKey const isRemoveActiveTab = activeTabId.value === tabId // 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) } // reset route cache routeStore.resetRouteCache(removedTabRouteKey) } /** 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] // Identify tabs to be removed and collect their routeKeys if strategy is 'close' const tabsToRemove = tabs.value.filter(tab => !remainTabIds.includes(tab.id)) const routeKeysToReset: RouteKey[] = [] for (const tab of tabsToRemove) { routeKeysToReset.push(tab.routeKey) } 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) { update() } else { 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() } // After tabs are updated and route potentially switched, reset cache for removed tabs for (const routeKey of routeKeysToReset) { routeStore.resetRouteCache(routeKey) } } const { routerPushByKey } = useRouterPush() /** * Replace tab * * @param key Route key * @param options Router push options */ async function replaceTab(key: RouteKey, options?: App.Global.RouterPushOptions) { const oldTabId = activeTabId.value // push new route await routerPushByKey(key, options) // remove old tab (exclude fixed tab) if (!isTabRetain(oldTabId)) { await removeTab(oldTabId) } } /** * 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) } /** * Fix tab * * @param tabId */ function fixTab(tabId: string) { const tabIndex = tabs.value.findIndex(t => t.id === tabId) if (tabIndex === -1) return const tab = tabs.value[tabIndex] const fixedCount = getFixedTabIds(tabs.value).length tab.fixedIndex = fixedCount if (tabIndex !== fixedCount) { tabs.value.splice(tabIndex, 1) tabs.value.splice(fixedCount, 0, tab) } reorderFixedTabs(tabs.value) } /** * Unfix tab * * @param tabId */ function unfixTab(tabId: string) { const tabIndex = tabs.value.findIndex(t => t.id === tabId) if (tabIndex === -1) return const tab = tabs.value[tabIndex] tab.fixedIndex = undefined const fixedCount = getFixedTabIds(tabs.value).length if (tabIndex !== fixedCount) { tabs.value.splice(tabIndex, 1) tabs.value.splice(fixedCount, 0, tab) } reorderFixedTabs(tabs.value) } /** * Set new label of tab * * @default * @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 * @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, homeTab, initHomeTab, initTabStore, addTab, removeTab, removeActiveTab, removeTabByRouteName, replaceTab, clearTabs, clearLeftTabs, clearRightTabs, fixTab, unfixTab, switchRouteByTab, setTabLabel, resetTabLabel, isTabRetain, updateTabsByLocale, getTabIdByRoute, cacheTabs, } })