80 changed files with 1546 additions and 1497 deletions
@ -1,17 +1,13 @@ |
|||
<template> |
|||
<span :class="$attrs.class"> |
|||
<Icon :icon="icon" /> |
|||
<g-icon :icon="icon" /> |
|||
</span> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
import { Icon } from '/@/components/Icon'; |
|||
import { propTypes } from '/@/utils/propTypes'; |
|||
|
|||
export default defineComponent({ |
|||
components: { Icon }, |
|||
props: { |
|||
icon: propTypes.string, |
|||
icon: String, |
|||
}, |
|||
}); |
|||
</script> |
|||
|
|||
@ -1,13 +1,15 @@ |
|||
import type { EChartsOption } from 'echarts'; |
|||
import type { Ref } from 'vue'; |
|||
|
|||
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
|||
import { tryOnUnmounted } from '@vueuse/core'; |
|||
import { unref, Ref, nextTick, watch, computed, ref } from 'vue'; |
|||
import type { EChartsOption } from 'echarts'; |
|||
import { unref, nextTick, watch, computed, ref } from 'vue'; |
|||
import { useDebounce } from '/@/hooks/core/useDebounce'; |
|||
import { useEventListener } from '/@/hooks/event/useEventListener'; |
|||
import { useBreakpoint } from '/@/hooks/event/useBreakpoint'; |
|||
|
|||
import echarts from '/@/plugins/echarts'; |
|||
import { useRootSetting } from '../setting/useRootSetting'; |
|||
import echarts from './echarts'; |
|||
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
|||
|
|||
export function useECharts( |
|||
elRef: Ref<HTMLDivElement>, |
|||
@ -1,28 +1,85 @@ |
|||
import { tabStore } from '/@/store/modules/tab'; |
|||
import { appStore } from '/@/store/modules/app'; |
|||
import type { RouteLocationNormalized } from 'vue-router'; |
|||
import type { RouteLocationNormalized, Router } from 'vue-router'; |
|||
|
|||
export function useTabs() { |
|||
function canIUseFn(): boolean { |
|||
const { multiTabsSetting: { show } = {} } = appStore.getProjectConfig; |
|||
import { useRouter } from 'vue-router'; |
|||
import { unref } from 'vue'; |
|||
|
|||
import { useMultipleTabStore } from '/@/store/modules/multipleTab'; |
|||
import { useAppStore } from '/@/store/modules/app'; |
|||
|
|||
enum TableActionEnum { |
|||
REFRESH, |
|||
CLOSE_ALL, |
|||
CLOSE_LEFT, |
|||
CLOSE_RIGHT, |
|||
CLOSE_OTHER, |
|||
CLOSE_CURRENT, |
|||
CLOSE, |
|||
} |
|||
|
|||
export function useTabs(_router: Router) { |
|||
const appStore = useAppStore(); |
|||
|
|||
function canIUseTabs(): boolean { |
|||
const { show } = appStore.getMultiTabsSetting; |
|||
if (!show) { |
|||
throw new Error('The multi-tab page is currently not open, please open it in the settings!'); |
|||
} |
|||
return !!show; |
|||
} |
|||
|
|||
const tabStore = useMultipleTabStore(); |
|||
const router = _router || useRouter(); |
|||
|
|||
const { currentRoute } = router; |
|||
|
|||
function getCurrentTab() { |
|||
const route = unref(currentRoute); |
|||
return tabStore.getTabList.find((item) => item.path === route.path)!; |
|||
} |
|||
|
|||
async function handleTabAction(action: TableActionEnum, tab?: RouteLocationNormalized) { |
|||
const canIUse = canIUseTabs; |
|||
if (!canIUse) { |
|||
return; |
|||
} |
|||
const currentTab = getCurrentTab(); |
|||
switch (action) { |
|||
case TableActionEnum.REFRESH: |
|||
await tabStore.refreshPage(router); |
|||
break; |
|||
|
|||
case TableActionEnum.CLOSE_ALL: |
|||
await tabStore.closeAllTab(router); |
|||
break; |
|||
|
|||
case TableActionEnum.CLOSE_LEFT: |
|||
await tabStore.closeLeftTabs(currentTab, router); |
|||
break; |
|||
|
|||
case TableActionEnum.CLOSE_RIGHT: |
|||
await tabStore.closeRightTabs(currentTab, router); |
|||
break; |
|||
|
|||
case TableActionEnum.CLOSE_OTHER: |
|||
await tabStore.closeOtherTabs(currentTab, router); |
|||
break; |
|||
|
|||
case TableActionEnum.CLOSE_CURRENT: |
|||
case TableActionEnum.CLOSE: |
|||
await tabStore.closeTab(tab || currentTab, router); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return { |
|||
refreshPage: async () => { |
|||
if (canIUseFn()) { |
|||
await tabStore.commitRedoPage(); |
|||
} |
|||
refreshPage: () => handleTabAction(TableActionEnum.REFRESH), |
|||
closeAll: () => handleTabAction(TableActionEnum.CLOSE_ALL), |
|||
closeLeft: () => handleTabAction(TableActionEnum.CLOSE_LEFT), |
|||
closeRight: () => handleTabAction(TableActionEnum.CLOSE_RIGHT), |
|||
closeOther: () => handleTabAction(TableActionEnum.CLOSE_OTHER), |
|||
closeCurrent: () => handleTabAction(TableActionEnum.CLOSE_CURRENT), |
|||
close: (tab?: RouteLocationNormalized) => { |
|||
handleTabAction(TableActionEnum.CLOSE, tab); |
|||
}, |
|||
closeAll: () => canIUseFn() && tabStore.closeAllTabAction(), |
|||
closeLeft: () => canIUseFn() && tabStore.closeLeftTabAction(tabStore.getCurrentTab), |
|||
closeRight: () => canIUseFn() && tabStore.closeRightTabAction(tabStore.getCurrentTab), |
|||
closeOther: () => canIUseFn() && tabStore.closeOtherTabAction(tabStore.getCurrentTab), |
|||
closeCurrent: () => canIUseFn() && tabStore.closeTabAction(tabStore.getCurrentTab), |
|||
close: (tab?: RouteLocationNormalized) => |
|||
canIUseFn() && tabStore.closeTabAction(tab || tabStore.getCurrentTab), |
|||
}; |
|||
} |
|||
|
|||
@ -1,21 +0,0 @@ |
|||
<template> |
|||
<TabContent :type="TabContentEnum.EXTRA_TYPE" :tabItem="$route" /> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
|
|||
import { TabContentEnum } from '../types'; |
|||
|
|||
import TabContent from './TabContent.vue'; |
|||
export default defineComponent({ |
|||
name: 'QuickButton', |
|||
components: { |
|||
TabContent, |
|||
}, |
|||
setup() { |
|||
return { |
|||
TabContentEnum, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -1,6 +1,8 @@ |
|||
// github repo url
|
|||
export const GITHUB_URL = 'https://github.com/anncwb/vue-vben-admin'; |
|||
|
|||
// vue-vben-admin-next-doc
|
|||
export const DOC_URL = 'https://vvbin.cn/doc-next/'; |
|||
|
|||
// site url
|
|||
export const SITE_URL = 'https://vvbin.cn/next/'; |
|||
|
|||
@ -1,16 +1,9 @@ |
|||
import type { App } from 'vue'; |
|||
import { createStore } from 'vuex'; |
|||
import { config } from 'vuex-module-decorators'; |
|||
import { isDevMode } from '/@/utils/env'; |
|||
|
|||
config.rawError = true; |
|||
|
|||
const store = createStore({ |
|||
strict: isDevMode(), |
|||
}); |
|||
import { createPinia } from 'pinia'; |
|||
const store = createPinia(); |
|||
|
|||
export function setupStore(app: App<Element>) { |
|||
app.use(store); |
|||
} |
|||
|
|||
export default store; |
|||
export { store }; |
|||
|
|||
@ -1,81 +0,0 @@ |
|||
import store from '/@/store'; |
|||
import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper'; |
|||
import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-decorators'; |
|||
|
|||
import { formatToDateTime } from '/@/utils/dateUtil'; |
|||
import { ErrorTypeEnum } from '/@/enums/exceptionEnum'; |
|||
import projectSetting from '/@/settings/projectSetting'; |
|||
|
|||
export interface ErrorInfo { |
|||
type: ErrorTypeEnum; |
|||
file: string; |
|||
name?: string; |
|||
message: string; |
|||
stack?: string; |
|||
detail: string; |
|||
url: string; |
|||
time?: string; |
|||
} |
|||
|
|||
export interface ErrorState { |
|||
errorInfoState: ErrorInfo[] | null; |
|||
errorListCountState: number; |
|||
} |
|||
|
|||
const NAME = 'app-error'; |
|||
hotModuleUnregisterModule(NAME); |
|||
@Module({ dynamic: true, namespaced: true, store, name: NAME }) |
|||
class Error extends VuexModule implements ErrorState { |
|||
// error log list
|
|||
errorInfoState: ErrorInfo[] = []; |
|||
|
|||
// error log count
|
|||
errorListCountState = 0; |
|||
|
|||
get getErrorInfoState() { |
|||
return this.errorInfoState; |
|||
} |
|||
|
|||
get getErrorListCountState() { |
|||
return this.errorListCountState; |
|||
} |
|||
|
|||
@Mutation |
|||
commitErrorInfoState(info: ErrorInfo): void { |
|||
const item = { |
|||
...info, |
|||
time: formatToDateTime(new Date()), |
|||
}; |
|||
this.errorInfoState = [item, ...this.errorInfoState]; |
|||
this.errorListCountState += 1; |
|||
} |
|||
|
|||
@Mutation |
|||
commitErrorListCountState(count: number): void { |
|||
this.errorListCountState = count; |
|||
} |
|||
|
|||
@Action |
|||
setupErrorHandle(error: any) { |
|||
const { useErrorHandle } = projectSetting; |
|||
if (!useErrorHandle) return; |
|||
|
|||
const errInfo: Partial<ErrorInfo> = { |
|||
message: error.message, |
|||
type: ErrorTypeEnum.AJAX, |
|||
}; |
|||
if (error.response) { |
|||
const { |
|||
config: { url = '', data: params = '', method = 'get', headers = {} } = {}, |
|||
data = {}, |
|||
} = error.response; |
|||
errInfo.url = url; |
|||
errInfo.name = 'Ajax Error!'; |
|||
errInfo.file = '-'; |
|||
errInfo.stack = JSON.stringify(data); |
|||
errInfo.detail = JSON.stringify({ params, method, headers }); |
|||
} |
|||
this.commitErrorInfoState(errInfo as ErrorInfo); |
|||
} |
|||
} |
|||
export const errorStore = getModule<Error>(Error); |
|||
@ -0,0 +1,77 @@ |
|||
import type { ErrorLogInfo } from '/#/store'; |
|||
|
|||
import { defineStore } from 'pinia'; |
|||
import { store } from '/@/store'; |
|||
|
|||
import { formatToDateTime } from '/@/utils/dateUtil'; |
|||
import projectSetting from '/@/settings/projectSetting'; |
|||
|
|||
import { ErrorTypeEnum } from '/@/enums/exceptionEnum'; |
|||
|
|||
export interface ErrorLogState { |
|||
errorLogInfoList: Nullable<ErrorLogInfo[]>; |
|||
errorLogListCount: number; |
|||
} |
|||
|
|||
export const useErrorLogStore = defineStore({ |
|||
id: 'app-error-log', |
|||
state: (): ErrorLogState => ({ |
|||
errorLogInfoList: null, |
|||
errorLogListCount: 0, |
|||
}), |
|||
getters: { |
|||
getErrorLogInfoList() { |
|||
return this.errorLogInfoList || []; |
|||
}, |
|||
getErrorLogListCount() { |
|||
return this.errorLogListCount; |
|||
}, |
|||
}, |
|||
actions: { |
|||
addErrorLogInfo(info: ErrorLogInfo) { |
|||
const item = { |
|||
...info, |
|||
time: formatToDateTime(new Date()), |
|||
}; |
|||
this.errorLogInfoList = [item, ...(this.errorLogInfoList || [])]; |
|||
this.errorLogListCount += 1; |
|||
}, |
|||
|
|||
setErrorLogListCount(count: number): void { |
|||
this.errorLogListCount = count; |
|||
}, |
|||
|
|||
/** |
|||
* Triggered after ajax request error |
|||
* @param error |
|||
* @returns |
|||
*/ |
|||
addAjaxErrorInfo(error) { |
|||
const { useErrorHandle } = projectSetting; |
|||
if (!useErrorHandle) { |
|||
return; |
|||
} |
|||
const errInfo: Partial<ErrorLogInfo> = { |
|||
message: error.message, |
|||
type: ErrorTypeEnum.AJAX, |
|||
}; |
|||
if (error.response) { |
|||
const { |
|||
config: { url = '', data: params = '', method = 'get', headers = {} } = {}, |
|||
data = {}, |
|||
} = error.response; |
|||
errInfo.url = url; |
|||
errInfo.name = 'Ajax Error!'; |
|||
errInfo.file = '-'; |
|||
errInfo.stack = JSON.stringify(data); |
|||
errInfo.detail = JSON.stringify({ params, method, headers }); |
|||
} |
|||
this.addErrorLogInfo(errInfo as ErrorLogInfo); |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
// Need to be used outside the setup
|
|||
export function useErrorLogStoreWithOut() { |
|||
return useErrorLogStore(store); |
|||
} |
|||
@ -1,61 +1,59 @@ |
|||
import type { LockInfo } from '/@/store/types'; |
|||
import type { LockInfo } from '/#/store'; |
|||
|
|||
import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-decorators'; |
|||
import store from '/@/store'; |
|||
import { defineStore } from 'pinia'; |
|||
|
|||
import { LOCK_INFO_KEY } from '/@/enums/cacheEnum'; |
|||
|
|||
import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper'; |
|||
import { Persistent } from '/@/utils/cache/persistent'; |
|||
import { useUserStore } from './user'; |
|||
|
|||
import { userStore } from './user'; |
|||
|
|||
const NAME = 'app-lock'; |
|||
hotModuleUnregisterModule(NAME); |
|||
@Module({ dynamic: true, namespaced: true, store, name: NAME }) |
|||
class Lock extends VuexModule { |
|||
// lock info
|
|||
private lockInfoState: LockInfo | null = Persistent.getLocal(LOCK_INFO_KEY); |
|||
|
|||
get getLockInfo(): LockInfo { |
|||
return this.lockInfoState || ({} as LockInfo); |
|||
} |
|||
|
|||
@Mutation |
|||
commitLockInfoState(info: LockInfo): void { |
|||
this.lockInfoState = Object.assign({}, this.lockInfoState, info); |
|||
Persistent.setLocal(LOCK_INFO_KEY, this.lockInfoState); |
|||
} |
|||
|
|||
@Mutation |
|||
resetLockInfo(): void { |
|||
Persistent.removeLocal(LOCK_INFO_KEY); |
|||
this.lockInfoState = null; |
|||
} |
|||
interface LockState { |
|||
lockInfo: Nullable<LockInfo>; |
|||
} |
|||
|
|||
/** |
|||
* @description: unlock page |
|||
*/ |
|||
@Action |
|||
public async unLockAction({ password }: { password: string }) { |
|||
const tryLogin = async () => { |
|||
try { |
|||
const username = userStore.getUserInfoState.username; |
|||
const res = await userStore.login({ username, password, goHome: false, mode: 'none' }); |
|||
if (res) { |
|||
this.resetLockInfo(); |
|||
} |
|||
return res; |
|||
} catch (error) { |
|||
return false; |
|||
export const useLockStore = defineStore({ |
|||
id: 'app-lock', |
|||
state: (): LockState => ({ |
|||
lockInfo: Persistent.getLocal(LOCK_INFO_KEY), |
|||
}), |
|||
getters: { |
|||
getLockInfo() { |
|||
return this.lockInfo; |
|||
}, |
|||
}, |
|||
actions: { |
|||
setLockInfo(info: LockInfo) { |
|||
this.lockInfo = Object.assign({}, this.lockInfo, info); |
|||
Persistent.setLocal(LOCK_INFO_KEY, this.lockInfo); |
|||
}, |
|||
resetLockInfo() { |
|||
Persistent.removeLocal(LOCK_INFO_KEY); |
|||
this.lockInfo = null; |
|||
}, |
|||
// Unlock
|
|||
async unLock(password?: string) { |
|||
const userStore = useUserStore(); |
|||
if (this.lockInfo?.pwd === password) { |
|||
this.resetLockInfo(); |
|||
return true; |
|||
} |
|||
}; |
|||
|
|||
if (this.getLockInfo?.pwd === password) { |
|||
this.resetLockInfo(); |
|||
return true; |
|||
} |
|||
return await tryLogin(); |
|||
} |
|||
} |
|||
export const lockStore = getModule<Lock>(Lock); |
|||
const tryLogin = async () => { |
|||
try { |
|||
const username = userStore.getUserInfo?.username; |
|||
const res = await userStore.login({ |
|||
username, |
|||
password: password!, |
|||
goHome: false, |
|||
mode: 'none', |
|||
}); |
|||
if (res) { |
|||
this.resetLockInfo(); |
|||
} |
|||
return res; |
|||
} catch (error) { |
|||
return false; |
|||
} |
|||
}; |
|||
return await tryLogin(); |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
@ -0,0 +1,288 @@ |
|||
import type { RouteLocationNormalized, RouteLocationRaw, Router } from 'vue-router'; |
|||
|
|||
import { toRaw, unref } from 'vue'; |
|||
import { defineStore } from 'pinia'; |
|||
import { store } from '/@/store'; |
|||
|
|||
import { useGo, useRedo } from '/@/hooks/web/usePage'; |
|||
|
|||
import { PageEnum } from '/@/enums/pageEnum'; |
|||
import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic'; |
|||
import { getRawRoute } from '/@/utils'; |
|||
|
|||
export interface MultipleTabState { |
|||
cacheTabList: Set<string>; |
|||
tabList: RouteLocationNormalized[]; |
|||
lastDragEndIndex: number; |
|||
} |
|||
|
|||
function handleGotoPage(router: Router) { |
|||
const go = useGo(router); |
|||
go(unref(router.currentRoute).path, true); |
|||
} |
|||
|
|||
export const useMultipleTabStore = defineStore({ |
|||
id: 'app-multiple-tab', |
|||
state: (): MultipleTabState => ({ |
|||
// Tabs that need to be cached
|
|||
cacheTabList: new Set(), |
|||
// multiple tab list
|
|||
tabList: [], |
|||
// Index of the last moved tab
|
|||
lastDragEndIndex: 0, |
|||
}), |
|||
getters: { |
|||
getTabList() { |
|||
return this.tabList; |
|||
}, |
|||
getCachedTabList(): string[] { |
|||
return Array.from(this.cacheTabList); |
|||
}, |
|||
getLastDragEndIndex(): number { |
|||
return this.lastDragEndIndex; |
|||
}, |
|||
}, |
|||
actions: { |
|||
/** |
|||
* Update the cache according to the currently opened tabs |
|||
*/ |
|||
async updateCacheTab() { |
|||
const cacheMap: Set<string> = new Set(); |
|||
|
|||
for (const tab of this.tabList) { |
|||
const item = getRawRoute(tab); |
|||
// Ignore the cache
|
|||
const needCache = !item.meta?.ignoreKeepAlive; |
|||
if (!needCache) { |
|||
return; |
|||
} |
|||
const name = item.name as string; |
|||
cacheMap.add(name); |
|||
} |
|||
this.cacheTabList = cacheMap; |
|||
}, |
|||
|
|||
/** |
|||
* Refresh tabs |
|||
*/ |
|||
async refreshPage(router: Router) { |
|||
const { currentRoute } = router; |
|||
const route = unref(currentRoute); |
|||
const name = route.name; |
|||
|
|||
const findTab = this.getCachedTabList.find((item) => item === name); |
|||
if (findTab) { |
|||
this.cacheTabList.delete(findTab); |
|||
} |
|||
const redo = useRedo(router); |
|||
await redo(); |
|||
}, |
|||
clearCacheTabs(): void { |
|||
this.cacheTabList = new Set(); |
|||
}, |
|||
resetState(): void { |
|||
this.tabList = []; |
|||
this.clearCacheTabs(); |
|||
}, |
|||
goToPage(router: Router) { |
|||
const go = useGo(router); |
|||
const len = this.tabList.length; |
|||
const { path } = unref(router.currentRoute); |
|||
|
|||
let toPath: PageEnum | string = PageEnum.BASE_HOME; |
|||
|
|||
if (len > 0) { |
|||
const page = this.tabList[len - 1]; |
|||
const p = page.fullPath || page.path; |
|||
if (p) { |
|||
toPath = p; |
|||
} |
|||
} |
|||
// Jump to the current page and report an error
|
|||
path !== toPath && go(toPath as PageEnum, true); |
|||
}, |
|||
|
|||
async addTab(route: RouteLocationNormalized) { |
|||
const { path, name, fullPath, params, query } = getRawRoute(route); |
|||
// 404 The page does not need to add a tab
|
|||
if ( |
|||
path === PageEnum.ERROR_PAGE || |
|||
!name || |
|||
[REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string) |
|||
) { |
|||
return; |
|||
} |
|||
|
|||
let updateIndex = -1; |
|||
// Existing pages, do not add tabs repeatedly
|
|||
const tabHasExits = this.tabList.some((tab, index) => { |
|||
updateIndex = index; |
|||
return (tab.fullPath || tab.path) === (fullPath || path); |
|||
}); |
|||
|
|||
// If the tab already exists, perform the update operation
|
|||
if (tabHasExits) { |
|||
const curTab = toRaw(this.tabList)[updateIndex]; |
|||
if (!curTab) { |
|||
return; |
|||
} |
|||
curTab.params = params || curTab.params; |
|||
curTab.query = query || curTab.query; |
|||
curTab.fullPath = fullPath || curTab.fullPath; |
|||
this.tabList.splice(updateIndex, 1, curTab); |
|||
return; |
|||
} |
|||
// Add tab
|
|||
this.tabList.push(route); |
|||
this.updateCacheTab(); |
|||
}, |
|||
|
|||
async closeTab(tab: RouteLocationNormalized, router: Router) { |
|||
const getToTarget = (tabItem: RouteLocationNormalized) => { |
|||
const { params, path, query } = tabItem; |
|||
return { |
|||
params: params || {}, |
|||
path, |
|||
query: query || {}, |
|||
}; |
|||
}; |
|||
|
|||
const close = (route: RouteLocationNormalized) => { |
|||
const { fullPath, meta: { affix } = {} } = route; |
|||
if (affix) { |
|||
return; |
|||
} |
|||
const index = this.tabList.findIndex((item) => item.fullPath === fullPath); |
|||
index !== -1 && this.tabList.splice(index, 1); |
|||
}; |
|||
|
|||
const { currentRoute, replace } = router; |
|||
|
|||
const { path } = unref(currentRoute); |
|||
if (path !== tab.path) { |
|||
// Closed is not the activation tab
|
|||
close(tab); |
|||
return; |
|||
} |
|||
|
|||
// Closed is activated atb
|
|||
let toTarget: RouteLocationRaw = {}; |
|||
|
|||
const index = this.tabList.findIndex((item) => item.path === path); |
|||
|
|||
// If the current is the leftmost tab
|
|||
if (index === 0) { |
|||
// There is only one tab, then jump to the homepage, otherwise jump to the right tab
|
|||
if (this.tabList.length === 1) { |
|||
toTarget = PageEnum.BASE_HOME; |
|||
} else { |
|||
// Jump to the right tab
|
|||
const page = this.tabList[index + 1]; |
|||
toTarget = getToTarget(page); |
|||
} |
|||
} else { |
|||
// Close the current tab
|
|||
const page = this.tabList[index - 1]; |
|||
toTarget = getToTarget(page); |
|||
} |
|||
close(currentRoute.value); |
|||
replace(toTarget); |
|||
}, |
|||
|
|||
// Close according to key
|
|||
async closeTabByKey(key: string, router: Router) { |
|||
const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === key); |
|||
index !== -1 && this.closeTab(this.tabList[index], router); |
|||
}, |
|||
|
|||
// Sort the tabs
|
|||
async sortTabs(oldIndex: number, newIndex: number) { |
|||
const currentTab = this.tabList[oldIndex]; |
|||
this.tabList.splice(oldIndex, 1); |
|||
this.tabList.splice(newIndex, 0, currentTab); |
|||
this.lastDragEndIndex = this.lastDragEndIndex + 1; |
|||
}, |
|||
|
|||
// Close the tab on the right and jump
|
|||
async closeLeftTabs(route: RouteLocationNormalized, router: Router) { |
|||
const index = this.tabList.findIndex((item) => item.path === route.path); |
|||
|
|||
if (index > 0) { |
|||
const leftTabs = this.tabList.slice(0, index); |
|||
const pathList: string[] = []; |
|||
for (const item of leftTabs) { |
|||
const affix = item?.meta?.affix ?? false; |
|||
if (!affix) { |
|||
pathList.push(item.fullPath); |
|||
} |
|||
} |
|||
this.bulkCloseTabs(pathList); |
|||
} |
|||
this.updateCacheTab(); |
|||
handleGotoPage(router); |
|||
}, |
|||
|
|||
// Close the tab on the left and jump
|
|||
async closeRightTabs(route: RouteLocationNormalized, router: Router) { |
|||
const index = this.tabList.findIndex((item) => item.fullPath === route.fullPath); |
|||
|
|||
if (index >= 0 && index < this.tabList.length - 1) { |
|||
const rightTabs = this.tabList.slice(index + 1, this.tabList.length); |
|||
|
|||
const pathList: string[] = []; |
|||
for (const item of rightTabs) { |
|||
const affix = item?.meta?.affix ?? false; |
|||
if (!affix) { |
|||
pathList.push(item.fullPath); |
|||
} |
|||
} |
|||
this.bulkCloseTabs(pathList); |
|||
} |
|||
this.updateCacheTab(); |
|||
handleGotoPage(router); |
|||
}, |
|||
|
|||
async closeAllTab(router: Router) { |
|||
this.tabList = this.tabList.filter((item) => item?.meta?.affix ?? false); |
|||
this.clearCacheTabs(); |
|||
this.goToPage(router); |
|||
}, |
|||
|
|||
/** |
|||
* Close other tabs |
|||
*/ |
|||
async closeOtherTabs(route: RouteLocationNormalized, router: Router) { |
|||
const closePathList = this.tabList.map((item) => item.fullPath); |
|||
|
|||
const pathList: string[] = []; |
|||
|
|||
for (const path of closePathList) { |
|||
if (path !== route.fullPath) { |
|||
const closeItem = this.tabList.find((item) => item.path === path); |
|||
if (!closeItem) { |
|||
return; |
|||
} |
|||
const affix = closeItem?.meta?.affix ?? false; |
|||
if (!affix) { |
|||
pathList.push(closeItem.fullPath); |
|||
} |
|||
} |
|||
} |
|||
this.bulkCloseTabs(pathList); |
|||
this.updateCacheTab(); |
|||
handleGotoPage(router); |
|||
}, |
|||
|
|||
/** |
|||
* Close tabs in bulk |
|||
*/ |
|||
async bulkCloseTabs(pathList: string[]) { |
|||
this.tabList = this.tabList.filter((item) => !pathList.includes(item.fullPath)); |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
// Need to be used outside the setup
|
|||
export function useMultipleTabWithOutStore() { |
|||
return useMultipleTabStore(store); |
|||
} |
|||
@ -1,294 +0,0 @@ |
|||
import type { RouteLocationNormalized, RouteLocationRaw } from 'vue-router'; |
|||
|
|||
import { toRaw, unref } from 'vue'; |
|||
import { Action, Module, Mutation, VuexModule, getModule } from 'vuex-module-decorators'; |
|||
import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper'; |
|||
|
|||
import { PageEnum } from '/@/enums/pageEnum'; |
|||
|
|||
import store from '/@/store'; |
|||
import router from '/@/router'; |
|||
import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic'; |
|||
import { getRawRoute } from '/@/utils'; |
|||
|
|||
import { useGo, useRedo } from '/@/hooks/web/usePage'; |
|||
import { cloneDeep } from 'lodash-es'; |
|||
|
|||
const NAME = 'app-tab'; |
|||
|
|||
hotModuleUnregisterModule(NAME); |
|||
|
|||
function isGotoPage() { |
|||
const go = useGo(); |
|||
go(unref(router.currentRoute).path, true); |
|||
} |
|||
|
|||
@Module({ namespaced: true, name: NAME, dynamic: true, store }) |
|||
class Tab extends VuexModule { |
|||
cachedTabsState: Set<string> = new Set(); |
|||
|
|||
// tab list
|
|||
tabsState: RouteLocationNormalized[] = []; |
|||
|
|||
lastDragEndIndexState = 0; |
|||
|
|||
get getTabsState() { |
|||
return this.tabsState; |
|||
} |
|||
|
|||
get getCurrentTab(): RouteLocationNormalized { |
|||
const route = unref(router.currentRoute); |
|||
return this.tabsState.find((item) => item.path === route.path)!; |
|||
} |
|||
|
|||
get getCachedTabsState(): string[] { |
|||
return Array.from(this.cachedTabsState); |
|||
} |
|||
|
|||
get getLastDragEndIndexState(): number { |
|||
return this.lastDragEndIndexState; |
|||
} |
|||
|
|||
@Mutation |
|||
commitClearCache(): void { |
|||
this.cachedTabsState = new Set(); |
|||
} |
|||
|
|||
@Mutation |
|||
goToPage() { |
|||
const go = useGo(); |
|||
const len = this.tabsState.length; |
|||
const { path } = unref(router.currentRoute); |
|||
|
|||
let toPath: PageEnum | string = PageEnum.BASE_HOME; |
|||
|
|||
if (len > 0) { |
|||
const page = this.tabsState[len - 1]; |
|||
const p = page.fullPath || page.path; |
|||
if (p) { |
|||
toPath = p; |
|||
} |
|||
} |
|||
// Jump to the current page and report an error
|
|||
path !== toPath && go(toPath as PageEnum, true); |
|||
} |
|||
|
|||
@Mutation |
|||
commitCachedMapState(): void { |
|||
const cacheMap: Set<string> = new Set(); |
|||
|
|||
this.tabsState.forEach((tab) => { |
|||
const item = getRawRoute(tab); |
|||
const needCache = !item.meta?.ignoreKeepAlive; |
|||
if (!needCache) return; |
|||
const name = item.name as string; |
|||
cacheMap.add(name); |
|||
}); |
|||
this.cachedTabsState = cacheMap; |
|||
} |
|||
|
|||
@Mutation |
|||
commitTabRoutesState(route: RouteLocationNormalized) { |
|||
const { path, fullPath, params, query } = route; |
|||
|
|||
let updateIndex = -1; |
|||
// Existing pages, do not add tabs repeatedly
|
|||
const hasTab = this.tabsState.some((tab, index) => { |
|||
updateIndex = index; |
|||
return (tab.fullPath || tab.path) === (fullPath || path); |
|||
}); |
|||
if (hasTab) { |
|||
const curTab = toRaw(this.tabsState)[updateIndex]; |
|||
if (!curTab) return; |
|||
curTab.params = params || curTab.params; |
|||
curTab.query = query || curTab.query; |
|||
curTab.fullPath = fullPath || curTab.fullPath; |
|||
this.tabsState.splice(updateIndex, 1, curTab); |
|||
return; |
|||
} |
|||
this.tabsState = cloneDeep([...this.tabsState, route]); |
|||
} |
|||
|
|||
/** |
|||
* @description: close tab |
|||
*/ |
|||
@Mutation |
|||
commitCloseTab(route: RouteLocationNormalized): void { |
|||
const { fullPath, meta: { affix } = {} } = route; |
|||
if (affix) return; |
|||
const index = this.tabsState.findIndex((item) => item.fullPath === fullPath); |
|||
index !== -1 && this.tabsState.splice(index, 1); |
|||
} |
|||
|
|||
@Mutation |
|||
commitCloseAllTab(): void { |
|||
this.tabsState = this.tabsState.filter((item) => { |
|||
return item.meta && item.meta.affix; |
|||
}); |
|||
} |
|||
|
|||
@Mutation |
|||
commitResetState(): void { |
|||
this.tabsState = []; |
|||
this.cachedTabsState = new Set(); |
|||
} |
|||
|
|||
@Mutation |
|||
commitSortTabs({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }): void { |
|||
const currentTab = this.tabsState[oldIndex]; |
|||
|
|||
this.tabsState.splice(oldIndex, 1); |
|||
this.tabsState.splice(newIndex, 0, currentTab); |
|||
this.lastDragEndIndexState = this.lastDragEndIndexState + 1; |
|||
} |
|||
|
|||
@Mutation |
|||
closeMultipleTab({ pathList }: { pathList: string[] }): void { |
|||
this.tabsState = toRaw(this.tabsState).filter((item) => !pathList.includes(item.fullPath)); |
|||
} |
|||
|
|||
@Action |
|||
addTabAction(route: RouteLocationNormalized) { |
|||
const { path, name } = route; |
|||
// 404 The page does not need to add a tab
|
|||
if ( |
|||
path === PageEnum.ERROR_PAGE || |
|||
!name || |
|||
[REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string) |
|||
) { |
|||
return; |
|||
} |
|||
this.commitTabRoutesState(getRawRoute(route)); |
|||
|
|||
this.commitCachedMapState(); |
|||
} |
|||
|
|||
@Mutation |
|||
async commitRedoPage() { |
|||
const route = router.currentRoute.value; |
|||
const name = route.name; |
|||
|
|||
const findVal = Array.from(this.cachedTabsState).find((item) => item === name); |
|||
if (findVal) { |
|||
this.cachedTabsState.delete(findVal); |
|||
// this.cachedTabsState.splice(index, 1);
|
|||
} |
|||
const redo = useRedo(); |
|||
await redo(); |
|||
} |
|||
|
|||
@Action |
|||
closeAllTabAction() { |
|||
this.commitCloseAllTab(); |
|||
this.commitClearCache(); |
|||
this.goToPage(); |
|||
} |
|||
|
|||
@Action |
|||
closeTabAction(tab: RouteLocationNormalized) { |
|||
function getObj(tabItem: RouteLocationNormalized) { |
|||
const { params, path, query } = tabItem; |
|||
return { |
|||
params: params || {}, |
|||
path, |
|||
query: query || {}, |
|||
}; |
|||
} |
|||
const { currentRoute, replace } = router; |
|||
|
|||
const { path } = unref(currentRoute); |
|||
if (path !== tab.path) { |
|||
// Closed is not the activation tab
|
|||
this.commitCloseTab(tab); |
|||
return; |
|||
} |
|||
|
|||
// Closed is activated atb
|
|||
let toObj: RouteLocationRaw = {}; |
|||
|
|||
const index = this.getTabsState.findIndex((item) => item.path === path); |
|||
|
|||
// If the current is the leftmost tab
|
|||
if (index === 0) { |
|||
// There is only one tab, then jump to the homepage, otherwise jump to the right tab
|
|||
if (this.getTabsState.length === 1) { |
|||
toObj = PageEnum.BASE_HOME; |
|||
} else { |
|||
// Jump to the right tab
|
|||
const page = this.getTabsState[index + 1]; |
|||
toObj = getObj(page); |
|||
} |
|||
} else { |
|||
// Close the current tab
|
|||
const page = this.getTabsState[index - 1]; |
|||
toObj = getObj(page); |
|||
} |
|||
this.commitCloseTab(currentRoute.value); |
|||
replace(toObj); |
|||
} |
|||
|
|||
@Action |
|||
closeTabByKeyAction(key: string) { |
|||
const index = this.tabsState.findIndex((item) => (item.fullPath || item.path) === key); |
|||
index !== -1 && this.closeTabAction(this.tabsState[index]); |
|||
} |
|||
|
|||
@Action |
|||
closeLeftTabAction(route: RouteLocationNormalized): void { |
|||
const index = this.tabsState.findIndex((item) => item.path === route.path); |
|||
|
|||
if (index > 0) { |
|||
const leftTabs = this.tabsState.slice(0, index); |
|||
const pathList: string[] = []; |
|||
for (const item of leftTabs) { |
|||
const affix = item.meta ? item.meta.affix : false; |
|||
if (!affix) { |
|||
pathList.push(item.fullPath); |
|||
} |
|||
} |
|||
this.closeMultipleTab({ pathList }); |
|||
} |
|||
this.commitCachedMapState(); |
|||
isGotoPage(); |
|||
} |
|||
|
|||
@Action |
|||
closeRightTabAction(route: RouteLocationNormalized): void { |
|||
const index = this.tabsState.findIndex((item) => item.fullPath === route.fullPath); |
|||
|
|||
if (index >= 0 && index < this.tabsState.length - 1) { |
|||
const rightTabs = this.tabsState.slice(index + 1, this.tabsState.length); |
|||
|
|||
const pathList: string[] = []; |
|||
for (const item of rightTabs) { |
|||
const affix = item.meta ? item.meta.affix : false; |
|||
if (!affix) { |
|||
pathList.push(item.fullPath); |
|||
} |
|||
} |
|||
this.closeMultipleTab({ pathList }); |
|||
} |
|||
this.commitCachedMapState(); |
|||
isGotoPage(); |
|||
} |
|||
|
|||
@Action |
|||
closeOtherTabAction(route: RouteLocationNormalized): void { |
|||
const closePathList = this.tabsState.map((item) => item.fullPath); |
|||
const pathList: string[] = []; |
|||
closePathList.forEach((path) => { |
|||
if (path !== route.fullPath) { |
|||
const closeItem = this.tabsState.find((item) => item.path === path); |
|||
if (!closeItem) return; |
|||
const affix = closeItem.meta ? closeItem.meta.affix : false; |
|||
if (!affix) { |
|||
pathList.push(closeItem.fullPath); |
|||
} |
|||
} |
|||
}); |
|||
this.closeMultipleTab({ pathList }); |
|||
this.commitCachedMapState(); |
|||
isGotoPage(); |
|||
} |
|||
} |
|||
export const tabStore = getModule<Tab>(Tab); |
|||
@ -1,24 +0,0 @@ |
|||
import { MenuModeEnum, MenuTypeEnum } from '../enums/menuEnum'; |
|||
|
|||
export interface LockInfo { |
|||
pwd: string | undefined; |
|||
isLock: boolean; |
|||
} |
|||
|
|||
export interface UserInfo { |
|||
// 用户id
|
|||
userId: string | number; |
|||
// 用户名
|
|||
username: string; |
|||
// 真实名字
|
|||
realName: string; |
|||
// 介绍
|
|||
desc?: string; |
|||
} |
|||
|
|||
export interface BeforeMiniState { |
|||
menuCollapsed?: boolean; |
|||
menuSplit?: boolean; |
|||
menuMode?: MenuModeEnum; |
|||
menuType?: MenuTypeEnum; |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
import { ErrorTypeEnum } from '/@/enums/exceptionEnum'; |
|||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; |
|||
|
|||
// Lock screen information
|
|||
export interface LockInfo { |
|||
// Password required
|
|||
pwd?: string | undefined; |
|||
// Is it locked?
|
|||
isLock?: boolean; |
|||
} |
|||
|
|||
// Error-log information
|
|||
export interface ErrorLogInfo { |
|||
// Type of error
|
|||
type: ErrorTypeEnum; |
|||
// Error file
|
|||
file: string; |
|||
// Error name
|
|||
name?: string; |
|||
// Error message
|
|||
message: string; |
|||
// Error stack
|
|||
stack?: string; |
|||
// Error detail
|
|||
detail: string; |
|||
// Error url
|
|||
url: string; |
|||
// Error time
|
|||
time?: string; |
|||
} |
|||
|
|||
export interface UserInfo { |
|||
userId: string | number; |
|||
username: string; |
|||
realName: string; |
|||
desc?: string; |
|||
} |
|||
|
|||
export interface BeforeMiniState { |
|||
menuCollapsed?: boolean; |
|||
menuSplit?: boolean; |
|||
menuMode?: MenuModeEnum; |
|||
menuType?: MenuTypeEnum; |
|||
} |
|||
Loading…
Reference in new issue