|
|
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 || {},
|
|
|
}
|
|
|
}
|