80 changed files with 1546 additions and 1497 deletions
@ -1,17 +1,13 @@ |
|||||
<template> |
<template> |
||||
<span :class="$attrs.class"> |
<span :class="$attrs.class"> |
||||
<Icon :icon="icon" /> |
<g-icon :icon="icon" /> |
||||
</span> |
</span> |
||||
</template> |
</template> |
||||
<script lang="ts"> |
<script lang="ts"> |
||||
import { defineComponent } from 'vue'; |
import { defineComponent } from 'vue'; |
||||
import { Icon } from '/@/components/Icon'; |
|
||||
import { propTypes } from '/@/utils/propTypes'; |
|
||||
|
|
||||
export default defineComponent({ |
export default defineComponent({ |
||||
components: { Icon }, |
|
||||
props: { |
props: { |
||||
icon: propTypes.string, |
icon: String, |
||||
}, |
}, |
||||
}); |
}); |
||||
</script> |
</script> |
||||
|
|||||
@ -1,13 +1,15 @@ |
|||||
|
import type { EChartsOption } from 'echarts'; |
||||
|
import type { Ref } from 'vue'; |
||||
|
|
||||
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
||||
import { tryOnUnmounted } from '@vueuse/core'; |
import { tryOnUnmounted } from '@vueuse/core'; |
||||
import { unref, Ref, nextTick, watch, computed, ref } from 'vue'; |
import { unref, nextTick, watch, computed, ref } from 'vue'; |
||||
import type { EChartsOption } from 'echarts'; |
|
||||
import { useDebounce } from '/@/hooks/core/useDebounce'; |
import { useDebounce } from '/@/hooks/core/useDebounce'; |
||||
import { useEventListener } from '/@/hooks/event/useEventListener'; |
import { useEventListener } from '/@/hooks/event/useEventListener'; |
||||
import { useBreakpoint } from '/@/hooks/event/useBreakpoint'; |
import { useBreakpoint } from '/@/hooks/event/useBreakpoint'; |
||||
|
|
||||
import echarts from '/@/plugins/echarts'; |
import echarts from './echarts'; |
||||
import { useRootSetting } from '../setting/useRootSetting'; |
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
||||
|
|
||||
export function useECharts( |
export function useECharts( |
||||
elRef: Ref<HTMLDivElement>, |
elRef: Ref<HTMLDivElement>, |
||||
@ -1,28 +1,85 @@ |
|||||
import { tabStore } from '/@/store/modules/tab'; |
import type { RouteLocationNormalized, Router } from 'vue-router'; |
||||
import { appStore } from '/@/store/modules/app'; |
|
||||
import type { RouteLocationNormalized } from 'vue-router'; |
|
||||
|
|
||||
export function useTabs() { |
import { useRouter } from 'vue-router'; |
||||
function canIUseFn(): boolean { |
import { unref } from 'vue'; |
||||
const { multiTabsSetting: { show } = {} } = appStore.getProjectConfig; |
|
||||
|
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) { |
if (!show) { |
||||
throw new Error('The multi-tab page is currently not open, please open it in the settings!'); |
throw new Error('The multi-tab page is currently not open, please open it in the settings!'); |
||||
} |
} |
||||
return !!show; |
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 { |
return { |
||||
refreshPage: async () => { |
refreshPage: () => handleTabAction(TableActionEnum.REFRESH), |
||||
if (canIUseFn()) { |
closeAll: () => handleTabAction(TableActionEnum.CLOSE_ALL), |
||||
await tabStore.commitRedoPage(); |
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
|
// github repo url
|
||||
export const GITHUB_URL = 'https://github.com/anncwb/vue-vben-admin'; |
export const GITHUB_URL = 'https://github.com/anncwb/vue-vben-admin'; |
||||
|
|
||||
// vue-vben-admin-next-doc
|
// vue-vben-admin-next-doc
|
||||
export const DOC_URL = 'https://vvbin.cn/doc-next/'; |
export const DOC_URL = 'https://vvbin.cn/doc-next/'; |
||||
|
|
||||
// site url
|
// site url
|
||||
export const SITE_URL = 'https://vvbin.cn/next/'; |
export const SITE_URL = 'https://vvbin.cn/next/'; |
||||
|
|||||
@ -1,16 +1,9 @@ |
|||||
import type { App } from 'vue'; |
import type { App } from 'vue'; |
||||
import { createStore } from 'vuex'; |
import { createPinia } from 'pinia'; |
||||
import { config } from 'vuex-module-decorators'; |
const store = createPinia(); |
||||
import { isDevMode } from '/@/utils/env'; |
|
||||
|
|
||||
config.rawError = true; |
|
||||
|
|
||||
const store = createStore({ |
|
||||
strict: isDevMode(), |
|
||||
}); |
|
||||
|
|
||||
export function setupStore(app: App<Element>) { |
export function setupStore(app: App<Element>) { |
||||
app.use(store); |
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 { defineStore } from 'pinia'; |
||||
import store from '/@/store'; |
|
||||
|
|
||||
import { LOCK_INFO_KEY } from '/@/enums/cacheEnum'; |
import { LOCK_INFO_KEY } from '/@/enums/cacheEnum'; |
||||
|
|
||||
import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper'; |
|
||||
import { Persistent } from '/@/utils/cache/persistent'; |
import { Persistent } from '/@/utils/cache/persistent'; |
||||
|
import { useUserStore } from './user'; |
||||
|
|
||||
import { userStore } from './user'; |
interface LockState { |
||||
|
lockInfo: Nullable<LockInfo>; |
||||
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; |
|
||||
} |
|
||||
|
|
||||
/** |
export const useLockStore = defineStore({ |
||||
* @description: unlock page |
id: 'app-lock', |
||||
*/ |
state: (): LockState => ({ |
||||
@Action |
lockInfo: Persistent.getLocal(LOCK_INFO_KEY), |
||||
public async unLockAction({ password }: { password: string }) { |
}), |
||||
const tryLogin = async () => { |
getters: { |
||||
try { |
getLockInfo() { |
||||
const username = userStore.getUserInfoState.username; |
return this.lockInfo; |
||||
const res = await userStore.login({ username, password, goHome: false, mode: 'none' }); |
}, |
||||
if (res) { |
}, |
||||
this.resetLockInfo(); |
actions: { |
||||
} |
setLockInfo(info: LockInfo) { |
||||
return res; |
this.lockInfo = Object.assign({}, this.lockInfo, info); |
||||
} catch (error) { |
Persistent.setLocal(LOCK_INFO_KEY, this.lockInfo); |
||||
return false; |
}, |
||||
|
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; |
||||
} |
} |
||||
}; |
const tryLogin = async () => { |
||||
|
try { |
||||
if (this.getLockInfo?.pwd === password) { |
const username = userStore.getUserInfo?.username; |
||||
this.resetLockInfo(); |
const res = await userStore.login({ |
||||
return true; |
username, |
||||
} |
password: password!, |
||||
return await tryLogin(); |
goHome: false, |
||||
} |
mode: 'none', |
||||
} |
}); |
||||
export const lockStore = getModule<Lock>(Lock); |
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