You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

431 lines
11 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { defineStore } from 'pinia'
import type { RouteLocationNormalized, RouteLocationRaw, Router } from 'vue-router/auto'
import { PAGE_NOT_FOUND_NAME, PageEnum, REDIRECT_NAME, TabActionEnum } from '~/constants'
import { getRawRoute } from '~/utils'
export const useMultipleTabStore = defineStore('MULTIPLE_TAB', () => {
const initState: DefineMultipleTabOptions = {
// Tabs that need to be cached
cacheTabList: new Set(),
// multiple tab list
tabList: [],
// Index of the last moved tab
lastDragEndIndex: 0,
}
const state = $ref<DefineMultipleTabOptions>(Object.assign({}, initState))
function getCachedTabList() {
return Array.from(state.cacheTabList)
}
/**
* Update the cache according to the currently opened tabs
*/
function updateCacheTab() {
const cacheMap: Set<string> = new Set()
for (const tab of state.tabList) {
const item = getRawRoute(tab)
// Ignore cache
const needCache = !item.meta?.ignoreKeepAlive
if (!needCache) {
continue
}
const name = item.name as string
cacheMap.add(name)
}
state.cacheTabList = cacheMap
}
/**
* Refresh tabs
*/
async function refreshPage(router: Router) {
const { currentRoute } = router
const route = unref(currentRoute)
const name = route.name
const findTab = getCachedTabList().find(item => item === name)
if (findTab) {
state.cacheTabList.delete(findTab)
}
}
/**
* Clear cached tabs
*/
function clearCacheTabs() {
state.cacheTabList = new Set()
}
function resetState() {
state.tabList = []
clearCacheTabs()
}
/**
* Go to page
*/
function goToPage(router: Router) {
const go = useGo(router)
const len = state.tabList.length
const { path } = unref(router.currentRoute)
let toPath: PageEnum | string = PageEnum.BASE_HOME
if (len > 0) {
const page = state.tabList[len - 1]
const p = page.fullPath || page.path
if (p) {
toPath = p
}
}
// Jump
path !== toPath && go(toPath, true)
}
async function checkTab(route: RouteLocationNormalized) {
const { path, name, meta } = getRawRoute(route)
// filter
if (
[PageEnum.ERROR_PAGE, PageEnum.BASE_LOGIN, PageEnum.BASE_REGISTER, PageEnum.BASE_LOCK].includes(
path as PageEnum,
)
|| meta?.hideInTab
|| !name
|| [
REDIRECT_NAME,
PAGE_NOT_FOUND_NAME,
].includes(name as string)
) {
return
}
await addTab(route)
}
async function addTab(route: RouteLocationNormalized) {
const { path, fullPath, params, query } = getRawRoute(route)
let updateIndex = -1
// Existing pages, do not add tabs repeatedly
const tabHasExist = state.tabList.some((tab, index) => {
updateIndex = index
return (tab.fullPath || tab.path) === (fullPath || path)
})
// If the tab already exists, perform the update operation
if (tabHasExist) {
// Refresh
const curTab = toRaw(state.tabList)[updateIndex]
if (!curTab) {
return
}
curTab.params = params || curTab.params
curTab.query = query || curTab.query
curTab.fullPath = fullPath || curTab.fullPath
state.tabList.splice(updateIndex, 1, curTab)
}
else {
// Add tab
// TODO 动态路由?
state.tabList.push(route)
}
updateCacheTab()
}
async function closeTab(tab: RouteLocationNormalized, router: Router) {
const close = (route: RouteLocationNormalized) => {
const { fullPath, meta: { affix } = {} } = route
if (affix) {
return
}
const index = state.tabList.findIndex(item => item.fullPath === fullPath)
index !== -1 && state.tabList.splice(index, 1)
}
const { currentRoute, replace } = router
const { path } = unref(currentRoute)
if (path !== tab.path) {
close(tab)
return
}
// Closed is activated tab
let toTarget: RouteLocationRaw = {}
const index = state.tabList.findIndex(item => item.path === path)
// 左边
if (index === 0) {
// There is only one tab, then jump to the homepage, otherwise jump to the right tab
if (state.tabList.length === 1) {
toTarget = PageEnum.BASE_HOME
}
else {
// Jump to the right tab
const page = state.tabList[index + 1]
toTarget = getToTarget(page)
}
}
else {
// Close the current tab
const page = state.tabList[index - 1]
toTarget = getToTarget(page)
}
close(currentRoute.value)
await replace(toTarget)
}
// Close according to key
async function closeTabByKey(key: string, router: Router) {
const index = state.tabList.findIndex(item => (item.fullPath || item.path) === key)
if (index !== -1) {
await closeTab(state.tabList[index], router)
const { currentRoute, replace } = router
// 检查当前路由是否存在于tabList中
const isActivated = state.tabList.findIndex((item) => {
return item.fullPath === currentRoute.value.fullPath
})
// 如果当前路由不存在于TabList中尝试切换到其它路由
if (isActivated === -1) {
let pageIndex
if (index > 0) {
pageIndex = index - 1
}
else if (index < state.tabList.length - 1) {
pageIndex = index + 1
}
else {
pageIndex = -1
}
if (pageIndex >= 0) {
const page = state.tabList[index - 1]
const toTarget = getToTarget(page)
await replace(toTarget)
}
}
}
}
// Sort the tabs
async function sortTabs(oldIndex: number, newIndex: number) {
const currentTab = state.tabList[oldIndex]
state.tabList.splice(oldIndex, 1)
state.tabList.splice(newIndex, 0, currentTab)
state.lastDragEndIndex = state.lastDragEndIndex + 1
}
// Close the tab on the right and jump
async function closeLeftTabs(route: RouteLocationNormalized, router: Router) {
const index = state.tabList.findIndex(item => item.path === route.path)
if (index > 0) {
const leftTabs = state.tabList.slice(0, index)
const pathList: string[] = []
for (const item of leftTabs) {
const affix = item?.meta?.affix ?? false
if (!affix) {
pathList.push(item.fullPath)
}
}
bulkCloseTabs(pathList)
}
updateCacheTab()
handleGotoPage(router, route)
}
// Close the tab on the left and jump
async function closeRightTabs(route: RouteLocationNormalized, router: Router) {
const index = state.tabList.findIndex(item => item.fullPath === route.fullPath)
if (index >= 0 && index < state.tabList.length - 1) {
const rightTabs = state.tabList.slice(index + 1, state.tabList.length)
const pathList: string[] = []
for (const item of rightTabs) {
const affix = item?.meta?.affix ?? false
if (!affix) {
pathList.push(item.fullPath)
}
}
bulkCloseTabs(pathList)
}
updateCacheTab()
handleGotoPage(router, route)
}
async function closeAllTabs(router: Router) {
state.tabList = state.tabList.filter(item => item?.meta?.affix ?? false)
clearCacheTabs()
goToPage(router)
}
/**
* Close other tabs
*/
async function closeOtherTabs(route: RouteLocationNormalized, router: Router) {
const closePathList = state.tabList.map(item => item.fullPath)
const pathList: string[] = []
for (const path of closePathList) {
if (path !== route.fullPath) {
const closeItem = state.tabList.find(item => item.path === path)
if (!closeItem) {
continue
}
const affix = closeItem?.meta?.affix ?? false
if (!affix) {
pathList.push(closeItem.fullPath)
}
}
}
bulkCloseTabs(pathList)
updateCacheTab()
handleGotoPage(router, route)
}
/**
* Close tabs in bulk
*/
function bulkCloseTabs(pathList: string[]) {
state.tabList = state.tabList.filter(item => !pathList.includes(item.fullPath))
}
/**
* Set tab's title
*/
async function setTabTitle(title: string, route: RouteLocationNormalized) {
const findTab = state.tabList.find(item => item === route)
if (findTab) {
findTab.meta.title = title
updateCacheTab()
}
}
/**
* replace tab's path
*/
async function updateTabPath(fullPath: string, route: RouteLocationNormalized) {
const findTab = state.tabList.find(item => item === route)
if (findTab) {
findTab.fullPath = fullPath
findTab.path = fullPath
updateCacheTab()
}
}
function getTabActions(tabItem: RouteLocationNormalized) {
if (!tabItem) {
return
}
const { meta } = tabItem
const { currentRoute } = useRouter()
const { path } = unref(currentRoute)
const isCurItem = tabItem ? tabItem.path === path : false
const index = state.tabList.findIndex(tab => tab.path === tabItem.path)
// Refresh button
const refreshDisabled = !isCurItem
// Close left
const closeLeftDisabled = index === 0
const disabled = state.tabList.length === 1
// Close right
const closeRightDisabled = index === state.tabList.length - 1 && state.lastDragEndIndex >= 0
return [
{
label: 'layout.multipleTab.reload',
key: TabActionEnum.REFRESH_PAGE,
icon: 'ant-design:reload-outlined',
disabled: refreshDisabled,
},
{
label: 'layout.multipleTab.close',
key: TabActionEnum.CLOSE_CURRENT,
icon: 'ant-design:close-outlined',
disabled: !!meta?.affix || disabled,
},
{
type: 'divider',
key: 'divider1',
},
{
icon: 'ant-design:vertical-right-outlined',
key: TabActionEnum.CLOSE_LEFT,
label: 'layout.multipleTab.closeLeft',
disabled: closeLeftDisabled,
},
{
icon: 'ant-design:vertical-left-outlined',
key: TabActionEnum.CLOSE_RIGHT,
label: 'layout.multipleTab.closeRight',
disabled: closeRightDisabled,
},
{
type: 'divider',
key: 'divider2',
},
{
icon: 'ant-design:holder-outlined',
key: TabActionEnum.CLOSE_OTHER,
label: 'layout.multipleTab.closeOther',
disabled,
},
{
label: 'layout.multipleTab.closeAll',
key: TabActionEnum.CLOSE_ALL,
icon: 'ant-design:line-outlined',
disabled,
},
]
}
return {
...toRefs(state),
getCachedTabList,
updateCacheTab,
refreshPage,
clearCacheTabs,
resetState,
goToPage,
checkTab,
addTab,
closeTab,
closeTabByKey,
sortTabs,
closeLeftTabs,
closeRightTabs,
closeAllTabs,
closeOtherTabs,
bulkCloseTabs,
setTabTitle,
updateTabPath,
getTabActions,
}
}, {
persist: {
paths: ['tabList'],
},
})
function handleGotoPage(router: Router, route?: RouteLocationNormalized) {
const currentPath = unref(router.currentRoute).path
// check if current route in tabList
const isExist = useMultipleTabStore().tabList.find(item => item.path === currentPath)
// if not in tabList, jump to target page or homepage
if (!isExist) {
const go = useGo(router)
const targetPath = route?.path || PageEnum.BASE_HOME
go(targetPath, true)
}
}
function getToTarget(tabItem: RouteLocationNormalized) {
const { params, path, query } = tabItem
return {
params: params || {},
path,
query: query || {},
}
}