chore: init read-star monorepo
This commit is contained in:
396
apps/admin/src/store/modules/tab/index.ts
Normal file
396
apps/admin/src/store/modules/tab/index.ts
Normal file
@ -0,0 +1,396 @@
|
||||
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<App.Global.Tab[]>([])
|
||||
|
||||
/** Get active tab */
|
||||
const homeTab = ref<App.Global.Tab>()
|
||||
|
||||
/** 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<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(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,
|
||||
}
|
||||
})
|
||||
263
apps/admin/src/store/modules/tab/shared.ts
Normal file
263
apps/admin/src/store/modules/tab/shared.ts
Normal file
@ -0,0 +1,263 @@
|
||||
import type { LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types'
|
||||
import type { Router } from 'vue-router'
|
||||
import { $t } from '@/locales'
|
||||
import { getRoutePath } from '@/router/elegant/transform'
|
||||
|
||||
/**
|
||||
* Get all tabs
|
||||
*
|
||||
* @param tabs Tabs
|
||||
* @param homeTab Home tab
|
||||
*/
|
||||
export function getAllTabs(tabs: App.Global.Tab[], homeTab?: App.Global.Tab) {
|
||||
if (!homeTab) {
|
||||
return []
|
||||
}
|
||||
|
||||
const filterHomeTabs = tabs.filter(tab => tab.id !== homeTab.id)
|
||||
|
||||
const fixedTabs = filterHomeTabs.filter(isFixedTab).sort((a, b) => a.fixedIndex! - b.fixedIndex!)
|
||||
|
||||
const remainTabs = filterHomeTabs.filter(tab => !isFixedTab(tab))
|
||||
|
||||
const allTabs = [homeTab, ...fixedTabs, ...remainTabs]
|
||||
|
||||
return updateTabsLabel(allTabs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Is fixed tab
|
||||
*
|
||||
* @param tab
|
||||
*/
|
||||
function isFixedTab(tab: App.Global.Tab) {
|
||||
return tab.fixedIndex !== undefined && tab.fixedIndex !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tab id by route
|
||||
*
|
||||
* @param route
|
||||
*/
|
||||
export function getTabIdByRoute(route: App.Global.TabRoute) {
|
||||
const { path, query = {}, meta } = route
|
||||
|
||||
let id = path
|
||||
|
||||
if (meta.multiTab) {
|
||||
const queryKeys = Object.keys(query).sort()
|
||||
const qs = queryKeys.map(key => `${key}=${query[key]}`).join('&')
|
||||
|
||||
id = `${path}?${qs}`
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tab by route
|
||||
*
|
||||
* @param route
|
||||
*/
|
||||
export function getTabByRoute(route: App.Global.TabRoute) {
|
||||
const { name, path, fullPath = path, meta } = route
|
||||
|
||||
const { title, i18nKey, fixedIndexInTab } = meta
|
||||
|
||||
// Get icon and localIcon from getRouteIcons function
|
||||
const { icon, localIcon } = getRouteIcons(route)
|
||||
|
||||
const label = i18nKey ? $t(i18nKey) : title
|
||||
|
||||
const tab: App.Global.Tab = {
|
||||
id: getTabIdByRoute(route),
|
||||
label,
|
||||
routeKey: name as LastLevelRouteKey,
|
||||
routePath: path as RouteMap[LastLevelRouteKey],
|
||||
fullPath,
|
||||
fixedIndex: fixedIndexInTab,
|
||||
icon,
|
||||
localIcon,
|
||||
i18nKey,
|
||||
}
|
||||
|
||||
return tab
|
||||
}
|
||||
|
||||
/**
|
||||
* The vue router will automatically merge the meta of all matched items, and the icons here may be affected by other
|
||||
* matching items, so they need to be processed separately
|
||||
*
|
||||
* @param route
|
||||
*/
|
||||
export function getRouteIcons(route: App.Global.TabRoute) {
|
||||
// Set default value for icon at the beginning
|
||||
let icon: string = route?.meta?.icon || import.meta.env.VITE_MENU_ICON
|
||||
let localIcon: string | undefined = route?.meta?.localIcon
|
||||
|
||||
// Route.matched only appears when there are multiple matches,so check if route.matched exists
|
||||
if (route.matched) {
|
||||
// Find the meta of the current route from matched
|
||||
const currentRoute = route.matched.find(r => r.name === route.name)
|
||||
// If icon exists in currentRoute.meta, it will overwrite the default value
|
||||
icon = currentRoute?.meta?.icon || icon
|
||||
localIcon = currentRoute?.meta?.localIcon
|
||||
}
|
||||
|
||||
return { icon, localIcon }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default home tab
|
||||
*
|
||||
* @param router
|
||||
* @param homeRouteName routeHome in useRouteStore
|
||||
*/
|
||||
export function getDefaultHomeTab(router: Router, homeRouteName: LastLevelRouteKey) {
|
||||
const homeRoutePath = getRoutePath(homeRouteName)
|
||||
const i18nLabel = $t(`route.${homeRouteName}`)
|
||||
|
||||
let homeTab: App.Global.Tab = {
|
||||
id: getRoutePath(homeRouteName),
|
||||
label: i18nLabel || homeRouteName,
|
||||
routeKey: homeRouteName,
|
||||
routePath: homeRoutePath,
|
||||
fullPath: homeRoutePath,
|
||||
}
|
||||
|
||||
const routes = router.getRoutes()
|
||||
const homeRoute = routes.find(route => route.name === homeRouteName)
|
||||
if (homeRoute) {
|
||||
homeTab = getTabByRoute(homeRoute)
|
||||
}
|
||||
|
||||
return homeTab
|
||||
}
|
||||
|
||||
/**
|
||||
* Is tab in tabs
|
||||
*
|
||||
* @param tab
|
||||
* @param tabs
|
||||
*/
|
||||
export function isTabInTabs(tabId: string, tabs: App.Global.Tab[]) {
|
||||
return tabs.some(tab => tab.id === tabId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter tabs by id
|
||||
*
|
||||
* @param tabId
|
||||
* @param tabs
|
||||
*/
|
||||
export function filterTabsById(tabId: string, tabs: App.Global.Tab[]) {
|
||||
return tabs.filter(tab => tab.id !== tabId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter tabs by ids
|
||||
*
|
||||
* @param tabIds
|
||||
* @param tabs
|
||||
*/
|
||||
export function filterTabsByIds(tabIds: string[], tabs: App.Global.Tab[]) {
|
||||
return tabs.filter(tab => !tabIds.includes(tab.id))
|
||||
}
|
||||
|
||||
/**
|
||||
* extract tabs by all routes
|
||||
*
|
||||
* @param router
|
||||
* @param tabs
|
||||
*/
|
||||
export function extractTabsByAllRoutes(router: Router, tabs: App.Global.Tab[]) {
|
||||
const routes = router.getRoutes()
|
||||
|
||||
const routeNames = routes.map(route => route.name)
|
||||
|
||||
return tabs.filter(tab => routeNames.includes(tab.routeKey))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fixed tabs
|
||||
*
|
||||
* @param tabs
|
||||
*/
|
||||
export function getFixedTabs(tabs: App.Global.Tab[]) {
|
||||
return tabs.filter(isFixedTab)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fixed tab ids
|
||||
*
|
||||
* @param tabs
|
||||
*/
|
||||
export function getFixedTabIds(tabs: App.Global.Tab[]) {
|
||||
const fixedTabs = getFixedTabs(tabs)
|
||||
|
||||
return fixedTabs.map(tab => tab.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder fixed tabs fixedIndex
|
||||
*
|
||||
* @param tabs
|
||||
*/
|
||||
export function reorderFixedTabs(tabs: App.Global.Tab[]) {
|
||||
const fixedTabs = getFixedTabs(tabs)
|
||||
fixedTabs.forEach((t, i) => {
|
||||
t.fixedIndex = i
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tabs label
|
||||
*
|
||||
* @param tabs
|
||||
*/
|
||||
function updateTabsLabel(tabs: App.Global.Tab[]) {
|
||||
const updated = tabs.map(tab => ({
|
||||
...tab,
|
||||
label: tab.newLabel || tab.oldLabel || tab.label,
|
||||
}))
|
||||
|
||||
return updated
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tab by i18n key
|
||||
*
|
||||
* @param tab
|
||||
*/
|
||||
export function updateTabByI18nKey(tab: App.Global.Tab) {
|
||||
const { i18nKey, label } = tab
|
||||
|
||||
return {
|
||||
...tab,
|
||||
label: i18nKey ? $t(i18nKey) : label,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tabs by i18n key
|
||||
*
|
||||
* @param tabs
|
||||
*/
|
||||
export function updateTabsByI18nKey(tabs: App.Global.Tab[]) {
|
||||
return tabs.map(tab => updateTabByI18nKey(tab))
|
||||
}
|
||||
|
||||
/**
|
||||
* find tab by route name
|
||||
*
|
||||
* @param name
|
||||
* @param tabs
|
||||
*/
|
||||
export function findTabByRouteName(name: RouteKey, tabs: App.Global.Tab[]) {
|
||||
const routePath = getRoutePath(name)
|
||||
|
||||
const tabId = routePath
|
||||
const multiTabId = `${routePath}?`
|
||||
|
||||
return tabs.find(tab => tab.id === tabId || tab.id.startsWith(multiTabId))
|
||||
}
|
||||
Reference in New Issue
Block a user