Commit e12c588c0abb8d639babd7f4b62abec111187302
1 parent
37320160
refactor(route): refactoring the routing multi-layer model close #215
Showing
21 changed files
with
275 additions
and
345 deletions
.eslintrc.js
@@ -38,15 +38,15 @@ module.exports = { | @@ -38,15 +38,15 @@ module.exports = { | ||
38 | '@typescript-eslint/no-unused-vars': [ | 38 | '@typescript-eslint/no-unused-vars': [ |
39 | 'error', | 39 | 'error', |
40 | { | 40 | { |
41 | - argsIgnorePattern: '^h$', | ||
42 | - varsIgnorePattern: '^h$', | 41 | + argsIgnorePattern: '^_', |
42 | + varsIgnorePattern: '^_', | ||
43 | }, | 43 | }, |
44 | ], | 44 | ], |
45 | 'no-unused-vars': [ | 45 | 'no-unused-vars': [ |
46 | 'error', | 46 | 'error', |
47 | { | 47 | { |
48 | - argsIgnorePattern: '^h$', | ||
49 | - varsIgnorePattern: '^h$', | 48 | + argsIgnorePattern: '^_', |
49 | + varsIgnorePattern: '^_', | ||
50 | }, | 50 | }, |
51 | ], | 51 | ], |
52 | 'space-before-function-paren': 'off', | 52 | 'space-before-function-paren': 'off', |
.vscode/settings.json
@@ -8,7 +8,6 @@ | @@ -8,7 +8,6 @@ | ||
8 | "explorer.openEditors.visible": 0, | 8 | "explorer.openEditors.visible": 0, |
9 | "editor.tabSize": 2, | 9 | "editor.tabSize": 2, |
10 | "editor.renderControlCharacters": true, | 10 | "editor.renderControlCharacters": true, |
11 | - "window.zoomLevel": -1, | ||
12 | "editor.minimap.renderCharacters": false, | 11 | "editor.minimap.renderCharacters": false, |
13 | "editor.minimap.maxColumn": 300, | 12 | "editor.minimap.maxColumn": 300, |
14 | "editor.minimap.showSlider": "always", | 13 | "editor.minimap.showSlider": "always", |
CHANGELOG.zh_CN.md
build/vite/plugin/hmr.ts
0 → 100644
1 | +import type { Plugin } from 'vite'; | ||
2 | + | ||
3 | +/** | ||
4 | + * TODO | ||
5 | + * Temporarily solve the Vite circular dependency problem, and wait for a better solution to fix it later. I don't know what problems this writing will bring. | ||
6 | + * @returns | ||
7 | + */ | ||
8 | + | ||
9 | +export function configHmrPlugin(): Plugin { | ||
10 | + return { | ||
11 | + name: 'singleHMR', | ||
12 | + handleHotUpdate({ modules, file }) { | ||
13 | + if (file.match(/xml$/)) return []; | ||
14 | + modules.forEach((m) => { | ||
15 | + m.importedModules = new Set(); | ||
16 | + m.importers = new Set(); | ||
17 | + }); | ||
18 | + return modules; | ||
19 | + }, | ||
20 | + }; | ||
21 | +} |
build/vite/plugin/index.ts
@@ -17,6 +17,7 @@ import { configThemePlugin } from './theme'; | @@ -17,6 +17,7 @@ import { configThemePlugin } from './theme'; | ||
17 | import { configImageminPlugin } from './imagemin'; | 17 | import { configImageminPlugin } from './imagemin'; |
18 | import { configWindiCssPlugin } from './windicss'; | 18 | import { configWindiCssPlugin } from './windicss'; |
19 | import { configSvgIconsPlugin } from './svgSprite'; | 19 | import { configSvgIconsPlugin } from './svgSprite'; |
20 | +import { configHmrPlugin } from './hmr'; | ||
20 | 21 | ||
21 | export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { | 22 | export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { |
22 | const { VITE_USE_IMAGEMIN, VITE_USE_MOCK, VITE_LEGACY, VITE_BUILD_COMPRESS } = viteEnv; | 23 | const { VITE_USE_IMAGEMIN, VITE_USE_MOCK, VITE_LEGACY, VITE_BUILD_COMPRESS } = viteEnv; |
@@ -28,6 +29,9 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { | @@ -28,6 +29,9 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { | ||
28 | vueJsx(), | 29 | vueJsx(), |
29 | ]; | 30 | ]; |
30 | 31 | ||
32 | + // TODO | ||
33 | + !isBuild && vitePlugins.push(configHmrPlugin()); | ||
34 | + | ||
31 | // @vitejs/plugin-legacy | 35 | // @vitejs/plugin-legacy |
32 | VITE_LEGACY && isBuild && vitePlugins.push(legacy()); | 36 | VITE_LEGACY && isBuild && vitePlugins.push(legacy()); |
33 | 37 |
src/layouts/default/header/components/Breadcrumb.vue
@@ -33,6 +33,7 @@ | @@ -33,6 +33,7 @@ | ||
33 | import { useGo } from '/@/hooks/web/usePage'; | 33 | import { useGo } from '/@/hooks/web/usePage'; |
34 | import { isString } from '/@/utils/is'; | 34 | import { isString } from '/@/utils/is'; |
35 | import { useI18n } from '/@/hooks/web/useI18n'; | 35 | import { useI18n } from '/@/hooks/web/useI18n'; |
36 | + import { getMenus } from '/@/router/menus'; | ||
36 | 37 | ||
37 | export default defineComponent({ | 38 | export default defineComponent({ |
38 | name: 'LayoutBreadcrumb', | 39 | name: 'LayoutBreadcrumb', |
@@ -47,7 +48,7 @@ | @@ -47,7 +48,7 @@ | ||
47 | const { getShowBreadCrumbIcon } = useRootSetting(); | 48 | const { getShowBreadCrumbIcon } = useRootSetting(); |
48 | 49 | ||
49 | const { t } = useI18n(); | 50 | const { t } = useI18n(); |
50 | - watchEffect(() => { | 51 | + watchEffect(async () => { |
51 | if (currentRoute.value.name === REDIRECT_NAME) return; | 52 | if (currentRoute.value.name === REDIRECT_NAME) return; |
52 | 53 | ||
53 | const matched = currentRoute.value?.matched; | 54 | const matched = currentRoute.value?.matched; |
src/layouts/page/ParentView.vue deleted
100644 → 0
1 | -<!-- | ||
2 | - * @Description: The reason is that tsx will report warnings under multi-level nesting. | ||
3 | ---> | ||
4 | -<template> | ||
5 | - <div> | ||
6 | - <RouterView> | ||
7 | - <template #default="{ Component, route }"> | ||
8 | - <transition | ||
9 | - :name=" | ||
10 | - getTransitionName({ | ||
11 | - route, | ||
12 | - openCache: openCache, | ||
13 | - enableTransition: getEnableTransition, | ||
14 | - cacheTabs: getCaches, | ||
15 | - def: getBasicTransition, | ||
16 | - }) | ||
17 | - " | ||
18 | - mode="out-in" | ||
19 | - appear | ||
20 | - > | ||
21 | - <keep-alive v-if="openCache" :include="getCaches"> | ||
22 | - <component :is="Component" v-bind="getKey(Component, route)" /> | ||
23 | - </keep-alive> | ||
24 | - <component v-else :is="Component" v-bind="getKey(Component, route)" /> | ||
25 | - </transition> | ||
26 | - </template> | ||
27 | - </RouterView> | ||
28 | - </div> | ||
29 | -</template> | ||
30 | -<script lang="ts"> | ||
31 | - import { computed, defineComponent, unref } from 'vue'; | ||
32 | - | ||
33 | - import { useRootSetting } from '/@/hooks/setting/useRootSetting'; | ||
34 | - import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; | ||
35 | - | ||
36 | - import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; | ||
37 | - import { useCache, getKey } from './useCache'; | ||
38 | - import { getTransitionName } from './transition'; | ||
39 | - | ||
40 | - export default defineComponent({ | ||
41 | - parentView: true, | ||
42 | - setup() { | ||
43 | - const { getCaches } = useCache(false); | ||
44 | - | ||
45 | - const { getShowMultipleTab } = useMultipleTabSetting(); | ||
46 | - | ||
47 | - const { getOpenKeepAlive } = useRootSetting(); | ||
48 | - | ||
49 | - const { getBasicTransition, getEnableTransition } = useTransitionSetting(); | ||
50 | - | ||
51 | - const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab)); | ||
52 | - | ||
53 | - return { | ||
54 | - getCaches, | ||
55 | - getBasicTransition, | ||
56 | - openCache, | ||
57 | - getEnableTransition, | ||
58 | - getTransitionName, | ||
59 | - getKey, | ||
60 | - }; | ||
61 | - }, | ||
62 | - }); | ||
63 | -</script> |
src/layouts/page/index.vue
@@ -16,9 +16,9 @@ | @@ -16,9 +16,9 @@ | ||
16 | appear | 16 | appear |
17 | > | 17 | > |
18 | <keep-alive v-if="openCache" :include="getCaches"> | 18 | <keep-alive v-if="openCache" :include="getCaches"> |
19 | - <component :is="Component" v-bind="getKey(Component, route)" /> | 19 | + <component :is="Component" :key="route.fullPath" /> |
20 | </keep-alive> | 20 | </keep-alive> |
21 | - <component v-else :is="Component" v-bind="getKey(Component, route)" /> | 21 | + <component v-else :is="Component" :key="route.fullPath" /> |
22 | </transition> | 22 | </transition> |
23 | </template> | 23 | </template> |
24 | </RouterView> | 24 | </RouterView> |
@@ -34,15 +34,15 @@ | @@ -34,15 +34,15 @@ | ||
34 | import { useRootSetting } from '/@/hooks/setting/useRootSetting'; | 34 | import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
35 | 35 | ||
36 | import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; | 36 | import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; |
37 | - import { useCache, getKey } from './useCache'; | ||
38 | import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; | 37 | import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; |
39 | import { getTransitionName } from './transition'; | 38 | import { getTransitionName } from './transition'; |
40 | 39 | ||
40 | + import { useStore } from 'vuex'; | ||
41 | + | ||
41 | export default defineComponent({ | 42 | export default defineComponent({ |
42 | name: 'PageLayout', | 43 | name: 'PageLayout', |
43 | components: { FrameLayout }, | 44 | components: { FrameLayout }, |
44 | setup() { | 45 | setup() { |
45 | - const { getCaches } = useCache(true); | ||
46 | const { getShowMultipleTab } = useMultipleTabSetting(); | 46 | const { getShowMultipleTab } = useMultipleTabSetting(); |
47 | 47 | ||
48 | const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting(); | 48 | const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting(); |
@@ -51,6 +51,17 @@ | @@ -51,6 +51,17 @@ | ||
51 | 51 | ||
52 | const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab)); | 52 | const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab)); |
53 | 53 | ||
54 | + const { getters } = useStore(); | ||
55 | + | ||
56 | + const getCaches = computed((): string[] => { | ||
57 | + if (!unref(getOpenKeepAlive)) { | ||
58 | + return []; | ||
59 | + } | ||
60 | + // TODO The useStore is used here mainly to solve the problem of circular dependency hot update | ||
61 | + const cacheTabs = getters['app-tab/getCachedTabsState']; | ||
62 | + return cacheTabs; | ||
63 | + }); | ||
64 | + | ||
54 | return { | 65 | return { |
55 | getTransitionName, | 66 | getTransitionName, |
56 | openCache, | 67 | openCache, |
@@ -58,7 +69,6 @@ | @@ -58,7 +69,6 @@ | ||
58 | getBasicTransition, | 69 | getBasicTransition, |
59 | getCaches, | 70 | getCaches, |
60 | getCanEmbedIFramePage, | 71 | getCanEmbedIFramePage, |
61 | - getKey, | ||
62 | }; | 72 | }; |
63 | }, | 73 | }, |
64 | }); | 74 | }); |
src/layouts/page/useCache.ts deleted
100644 → 0
1 | -import type { FunctionalComponent } from 'vue'; | ||
2 | -import type { RouteLocation } from 'vue-router'; | ||
3 | -import { computed, ref, unref, getCurrentInstance } from 'vue'; | ||
4 | -import { useRootSetting } from '/@/hooks/setting/useRootSetting'; | ||
5 | - | ||
6 | -import { useRouter } from 'vue-router'; | ||
7 | -import { useStore } from 'vuex'; | ||
8 | - | ||
9 | -const ParentLayoutName = 'ParentLayout'; | ||
10 | - | ||
11 | -const PAGE_LAYOUT_KEY = '__PAGE_LAYOUT__'; | ||
12 | - | ||
13 | -export function getKey(component: FunctionalComponent & { type: Indexable }, route: RouteLocation) { | ||
14 | - return !!component?.type.parentView ? {} : { key: route.fullPath }; | ||
15 | -} | ||
16 | - | ||
17 | -export function useCache(isPage: boolean) { | ||
18 | - const { getters } = useStore(); | ||
19 | - | ||
20 | - const name = ref(''); | ||
21 | - const { currentRoute } = useRouter(); | ||
22 | - const instance = getCurrentInstance(); | ||
23 | - const routeName = instance?.type.name; | ||
24 | - if (routeName && ![ParentLayoutName].includes(routeName)) { | ||
25 | - name.value = routeName; | ||
26 | - } else { | ||
27 | - const matched = currentRoute.value?.matched; | ||
28 | - if (!matched) { | ||
29 | - return; | ||
30 | - } | ||
31 | - const len = matched.length; | ||
32 | - if (len < 2) return; | ||
33 | - name.value = matched[len - 2].name as string; | ||
34 | - } | ||
35 | - | ||
36 | - const { getOpenKeepAlive } = useRootSetting(); | ||
37 | - | ||
38 | - const getCaches = computed((): string[] => { | ||
39 | - if (!unref(getOpenKeepAlive)) { | ||
40 | - return []; | ||
41 | - } | ||
42 | - const cached = getters['app-tab/getCachedMapState']; | ||
43 | - | ||
44 | - if (isPage) { | ||
45 | - // page Layout | ||
46 | - return cached.get(PAGE_LAYOUT_KEY) || []; | ||
47 | - } | ||
48 | - const cacheSet = new Set<string>(); | ||
49 | - cacheSet.add(unref(name)); | ||
50 | - | ||
51 | - const list = cached.get(unref(name)); | ||
52 | - | ||
53 | - if (!list) { | ||
54 | - return Array.from(cacheSet); | ||
55 | - } | ||
56 | - list.forEach((item) => { | ||
57 | - cacheSet.add(item); | ||
58 | - }); | ||
59 | - | ||
60 | - return Array.from(cacheSet); | ||
61 | - }); | ||
62 | - return { getCaches }; | ||
63 | -} |
src/logics/mitt/tabChange.ts
@@ -4,7 +4,7 @@ | @@ -4,7 +4,7 @@ | ||
4 | 4 | ||
5 | import Mitt from '/@/utils/mitt'; | 5 | import Mitt from '/@/utils/mitt'; |
6 | import type { RouteLocationNormalized } from 'vue-router'; | 6 | import type { RouteLocationNormalized } from 'vue-router'; |
7 | -import { getRoute } from '/@/router/helper/routeHelper'; | 7 | +import { getRawRoute } from '/@/utils'; |
8 | 8 | ||
9 | const mitt = new Mitt(); | 9 | const mitt = new Mitt(); |
10 | 10 | ||
@@ -13,7 +13,7 @@ const key = Symbol(); | @@ -13,7 +13,7 @@ const key = Symbol(); | ||
13 | let lastChangeTab: RouteLocationNormalized; | 13 | let lastChangeTab: RouteLocationNormalized; |
14 | 14 | ||
15 | export function setLastChangeTab(lastChangeRoute: RouteLocationNormalized) { | 15 | export function setLastChangeTab(lastChangeRoute: RouteLocationNormalized) { |
16 | - const r = getRoute(lastChangeRoute); | 16 | + const r = getRawRoute(lastChangeRoute); |
17 | mitt.emit(key, r); | 17 | mitt.emit(key, r); |
18 | lastChangeTab = r; | 18 | lastChangeTab = r; |
19 | } | 19 | } |
src/router/constant.ts
1 | -import type { AppRouteRecordRaw } from '/@/router/types'; | ||
2 | -import ParentLayout from '/@/layouts/page/ParentView.vue'; | ||
3 | -import { t } from '/@/hooks/web/useI18n'; | ||
4 | - | ||
5 | export const REDIRECT_NAME = 'Redirect'; | 1 | export const REDIRECT_NAME = 'Redirect'; |
6 | 2 | ||
3 | +export const PARENT_LAYOUT_NAME = 'ParentLayout'; | ||
4 | + | ||
7 | export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exception.vue'); | 5 | export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exception.vue'); |
8 | 6 | ||
9 | /** | 7 | /** |
@@ -12,78 +10,23 @@ export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exceptio | @@ -12,78 +10,23 @@ export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exceptio | ||
12 | export const LAYOUT = () => import('/@/layouts/default/index.vue'); | 10 | export const LAYOUT = () => import('/@/layouts/default/index.vue'); |
13 | 11 | ||
14 | /** | 12 | /** |
15 | - * @description: page-layout | 13 | + * @description: parent-layout |
16 | */ | 14 | */ |
17 | -export const getParentLayout = (name: string) => { | 15 | +export const getParentLayout = (_name?: string) => { |
18 | return () => | 16 | return () => |
19 | new Promise((resolve) => { | 17 | new Promise((resolve) => { |
20 | resolve({ | 18 | resolve({ |
21 | - ...ParentLayout, | ||
22 | - name, | 19 | + name: PARENT_LAYOUT_NAME, |
23 | }); | 20 | }); |
24 | }); | 21 | }); |
25 | }; | 22 | }; |
26 | 23 | ||
27 | -// 404 on a page | ||
28 | -export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = { | ||
29 | - path: '/:path(.*)*', | ||
30 | - name: 'ErrorPage', | ||
31 | - component: LAYOUT, | ||
32 | - meta: { | ||
33 | - title: 'ErrorPage', | ||
34 | - hideBreadcrumb: true, | ||
35 | - }, | ||
36 | - children: [ | ||
37 | - { | ||
38 | - path: '/:path(.*)*', | ||
39 | - name: 'ErrorPage', | ||
40 | - component: EXCEPTION_COMPONENT, | ||
41 | - meta: { | ||
42 | - title: 'ErrorPage', | ||
43 | - hideBreadcrumb: true, | ||
44 | - }, | ||
45 | - }, | ||
46 | - ], | ||
47 | -}; | ||
48 | - | ||
49 | -export const REDIRECT_ROUTE: AppRouteRecordRaw = { | ||
50 | - path: '/redirect', | ||
51 | - name: REDIRECT_NAME, | ||
52 | - component: LAYOUT, | ||
53 | - meta: { | ||
54 | - title: REDIRECT_NAME, | ||
55 | - hideBreadcrumb: true, | ||
56 | - }, | ||
57 | - children: [ | ||
58 | - { | ||
59 | - path: '/redirect/:path(.*)', | ||
60 | - name: REDIRECT_NAME, | ||
61 | - component: () => import('/@/views/sys/redirect/index.vue'), | ||
62 | - meta: { | ||
63 | - title: REDIRECT_NAME, | ||
64 | - hideBreadcrumb: true, | ||
65 | - }, | ||
66 | - }, | ||
67 | - ], | ||
68 | -}; | ||
69 | - | ||
70 | -export const ERROR_LOG_ROUTE: AppRouteRecordRaw = { | ||
71 | - path: '/error-log', | ||
72 | - name: 'errorLog', | ||
73 | - component: LAYOUT, | ||
74 | - meta: { | ||
75 | - title: 'ErrorLog', | ||
76 | - hideBreadcrumb: true, | ||
77 | - }, | ||
78 | - children: [ | ||
79 | - { | ||
80 | - path: 'list', | ||
81 | - name: 'errorLogList', | ||
82 | - component: () => import('/@/views/sys/error-log/index.vue'), | ||
83 | - meta: { | ||
84 | - title: t('routes.basic.errorLogList'), | ||
85 | - hideBreadcrumb: true, | ||
86 | - }, | ||
87 | - }, | ||
88 | - ], | ||
89 | -}; | 24 | +// export const getParentLayout = (name: string) => { |
25 | +// return () => | ||
26 | +// new Promise((resolve) => { | ||
27 | +// resolve({ | ||
28 | +// ...ParentLayout, | ||
29 | +// name, | ||
30 | +// }); | ||
31 | +// }); | ||
32 | +// }; |
src/router/guard/permissionGuard.ts
@@ -5,7 +5,7 @@ import { permissionStore } from '/@/store/modules/permission'; | @@ -5,7 +5,7 @@ import { permissionStore } from '/@/store/modules/permission'; | ||
5 | import { PageEnum } from '/@/enums/pageEnum'; | 5 | import { PageEnum } from '/@/enums/pageEnum'; |
6 | import { userStore } from '/@/store/modules/user'; | 6 | import { userStore } from '/@/store/modules/user'; |
7 | 7 | ||
8 | -import { PAGE_NOT_FOUND_ROUTE } from '/@/router/constant'; | 8 | +import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'; |
9 | 9 | ||
10 | const LOGIN_PATH = PageEnum.BASE_LOGIN; | 10 | const LOGIN_PATH = PageEnum.BASE_LOGIN; |
11 | 11 |
src/router/helper/menuHelper.ts
1 | import { AppRouteModule } from '/@/router/types'; | 1 | import { AppRouteModule } from '/@/router/types'; |
2 | import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'; | 2 | import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'; |
3 | 3 | ||
4 | -import { findPath, forEach, treeMap } from '/@/utils/helper/treeHelper'; | 4 | +import { findPath, treeMap } from '/@/utils/helper/treeHelper'; |
5 | import { cloneDeep } from 'lodash-es'; | 5 | import { cloneDeep } from 'lodash-es'; |
6 | import { isUrl } from '/@/utils/is'; | 6 | import { isUrl } from '/@/utils/is'; |
7 | 7 | ||
8 | -export function getAllParentPath(treeData: any[], path: string) { | 8 | +export function getAllParentPath<T = Recordable>(treeData: T[], path: string) { |
9 | const menuList = findPath(treeData, (n) => n.path === path) as Menu[]; | 9 | const menuList = findPath(treeData, (n) => n.path === path) as Menu[]; |
10 | return (menuList || []).map((item) => item.path); | 10 | return (menuList || []).map((item) => item.path); |
11 | } | 11 | } |
12 | 12 | ||
13 | -// 拼接父级路径 | ||
14 | -function joinParentPath(list: any, node: any) { | ||
15 | - let allPaths = getAllParentPath(list, node.path); | ||
16 | - | ||
17 | - allPaths = allPaths.slice(0, allPaths.length - 1); | ||
18 | - let parentPath = ''; | ||
19 | - if (Array.isArray(allPaths) && allPaths.length >= 2) { | ||
20 | - parentPath = allPaths[allPaths.length - 1]; | ||
21 | - } else { | ||
22 | - allPaths.forEach((p) => { | ||
23 | - parentPath += /^\//.test(p) ? p : `/${p}`; | ||
24 | - }); | 13 | +function joinParentPath(menus: Menu[], parentPath = '') { |
14 | + for (let index = 0; index < menus.length; index++) { | ||
15 | + const menu = menus[index]; | ||
16 | + const p = menu.path.startsWith('/') ? menu.path : `/${menu.path}`; | ||
17 | + const parent = isUrl(menu.path) ? menu.path : `${parentPath}${p}`; | ||
18 | + menus[index].path = parent; | ||
19 | + if (menu?.children?.length) { | ||
20 | + joinParentPath(menu.children, parent); | ||
21 | + } | ||
25 | } | 22 | } |
26 | - node.path = `${/^\//.test(node.path) ? node.path : `${parentPath}/${node.path}`}`.replace( | ||
27 | - /\/\//g, | ||
28 | - '/' | ||
29 | - ); | ||
30 | - return node; | ||
31 | } | 23 | } |
32 | 24 | ||
33 | -// 解析菜单模块 | 25 | +// Parsing the menu module |
34 | export function transformMenuModule(menuModule: MenuModule): Menu { | 26 | export function transformMenuModule(menuModule: MenuModule): Menu { |
35 | const { menu } = menuModule; | 27 | const { menu } = menuModule; |
36 | 28 | ||
37 | const menuList = [menu]; | 29 | const menuList = [menu]; |
38 | - forEach(menuList, (m) => { | ||
39 | - !isUrl(m.path) && joinParentPath(menuList, m); | ||
40 | - }); | ||
41 | 30 | ||
31 | + joinParentPath(menuList); | ||
42 | return menuList[0]; | 32 | return menuList[0]; |
43 | } | 33 | } |
44 | 34 | ||
@@ -54,17 +44,16 @@ export function transformRouteToMenu(routeModList: AppRouteModule[]) { | @@ -54,17 +44,16 @@ export function transformRouteToMenu(routeModList: AppRouteModule[]) { | ||
54 | routeList.push(item); | 44 | routeList.push(item); |
55 | } | 45 | } |
56 | }); | 46 | }); |
57 | - return treeMap(routeList, { | 47 | + const list = treeMap(routeList, { |
58 | conversion: (node: AppRouteRecordRaw) => { | 48 | conversion: (node: AppRouteRecordRaw) => { |
59 | - const { meta: { title, icon, hideMenu = false } = {} } = node; | ||
60 | - | ||
61 | - !isUrl(node.path) && joinParentPath(routeList, node); | 49 | + const { meta: { title, hideMenu = false } = {} } = node; |
62 | return { | 50 | return { |
51 | + ...(node.meta || {}), | ||
63 | name: title, | 52 | name: title, |
64 | - icon, | ||
65 | - path: node.path, | ||
66 | hideMenu, | 53 | hideMenu, |
67 | }; | 54 | }; |
68 | }, | 55 | }, |
69 | }); | 56 | }); |
57 | + joinParentPath(list); | ||
58 | + return list; | ||
70 | } | 59 | } |
src/router/helper/routeHelper.ts
1 | import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types'; | 1 | import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types'; |
2 | -import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'; | 2 | +import type { Router, RouteRecordNormalized } from 'vue-router'; |
3 | 3 | ||
4 | import { getParentLayout, LAYOUT } from '/@/router/constant'; | 4 | import { getParentLayout, LAYOUT } from '/@/router/constant'; |
5 | import { cloneDeep } from 'lodash-es'; | 5 | import { cloneDeep } from 'lodash-es'; |
6 | import { warn } from '/@/utils/log'; | 6 | import { warn } from '/@/utils/log'; |
7 | +import { createRouter, createWebHashHistory } from 'vue-router'; | ||
7 | 8 | ||
8 | export type LayoutMapKey = 'LAYOUT'; | 9 | export type LayoutMapKey = 'LAYOUT'; |
9 | 10 | ||
10 | const LayoutMap = new Map<LayoutMapKey, () => Promise<typeof import('*.vue')>>(); | 11 | const LayoutMap = new Map<LayoutMapKey, () => Promise<typeof import('*.vue')>>(); |
11 | 12 | ||
12 | -let dynamicViewsModules: Record< | ||
13 | - string, | ||
14 | - () => Promise<{ | ||
15 | - [key: string]: any; | ||
16 | - }> | ||
17 | ->; | 13 | +let dynamicViewsModules: Record<string, () => Promise<Recordable>>; |
18 | 14 | ||
19 | -// 动态引入 | 15 | +// Dynamic introduction |
20 | function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { | 16 | function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { |
21 | dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}'); | 17 | dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}'); |
22 | if (!routes) return; | 18 | if (!routes) return; |
@@ -26,19 +22,14 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { | @@ -26,19 +22,14 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { | ||
26 | if (component) { | 22 | if (component) { |
27 | item.component = dynamicImport(dynamicViewsModules, component as string); | 23 | item.component = dynamicImport(dynamicViewsModules, component as string); |
28 | } else if (name) { | 24 | } else if (name) { |
29 | - item.component = getParentLayout(name); | 25 | + item.component = getParentLayout(); |
30 | } | 26 | } |
31 | children && asyncImportRoute(children); | 27 | children && asyncImportRoute(children); |
32 | }); | 28 | }); |
33 | } | 29 | } |
34 | 30 | ||
35 | function dynamicImport( | 31 | function dynamicImport( |
36 | - dynamicViewsModules: Record< | ||
37 | - string, | ||
38 | - () => Promise<{ | ||
39 | - [key: string]: any; | ||
40 | - }> | ||
41 | - >, | 32 | + dynamicViewsModules: Record<string, () => Promise<Recordable>>, |
42 | component: string | 33 | component: string |
43 | ) { | 34 | ) { |
44 | const keys = Object.keys(dynamicViewsModules); | 35 | const keys = Object.keys(dynamicViewsModules); |
@@ -84,18 +75,69 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul | @@ -84,18 +75,69 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul | ||
84 | return (routeList as unknown) as T[]; | 75 | return (routeList as unknown) as T[]; |
85 | } | 76 | } |
86 | 77 | ||
87 | -// Return to the new routing structure, not affected by the original example | ||
88 | -export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized { | ||
89 | - if (!route) return route; | ||
90 | - const { matched, ...opt } = route; | ||
91 | - return { | ||
92 | - ...opt, | ||
93 | - matched: (matched | ||
94 | - ? matched.map((item) => ({ | ||
95 | - meta: item.meta, | ||
96 | - name: item.name, | ||
97 | - path: item.path, | ||
98 | - })) | ||
99 | - : undefined) as RouteRecordNormalized[], | ||
100 | - }; | 78 | +/** |
79 | + * Convert multi-level routing to level 2 routing | ||
80 | + */ | ||
81 | +export function flatRoutes(routeModules: AppRouteModule[]) { | ||
82 | + for (let index = 0; index < routeModules.length; index++) { | ||
83 | + const routeModule = routeModules[index]; | ||
84 | + if (!isMultipleRoute(routeModule)) { | ||
85 | + continue; | ||
86 | + } | ||
87 | + promoteRouteLevel(routeModule); | ||
88 | + } | ||
89 | +} | ||
90 | + | ||
91 | +// Routing level upgrade | ||
92 | +function promoteRouteLevel(routeModule: AppRouteModule) { | ||
93 | + // Use vue-router to splice menus | ||
94 | + let router: Router | null = createRouter({ | ||
95 | + routes: [routeModule as any], | ||
96 | + history: createWebHashHistory(), | ||
97 | + }); | ||
98 | + | ||
99 | + const routes = router.getRoutes(); | ||
100 | + const children = cloneDeep(routeModule.children); | ||
101 | + addToChildren(routes, children || [], routeModule); | ||
102 | + router = null; | ||
103 | + | ||
104 | + routeModule.children = routeModule.children?.filter((item) => !item.children?.length); | ||
105 | +} | ||
106 | + | ||
107 | +// Add all sub-routes to the secondary route | ||
108 | +function addToChildren( | ||
109 | + routes: RouteRecordNormalized[], | ||
110 | + children: AppRouteRecordRaw[], | ||
111 | + routeModule: AppRouteModule | ||
112 | +) { | ||
113 | + for (let index = 0; index < children.length; index++) { | ||
114 | + const child = children[index]; | ||
115 | + const route = routes.find((item) => item.name === child.name); | ||
116 | + if (route) { | ||
117 | + routeModule.children = routeModule.children || []; | ||
118 | + routeModule.children?.push(route as any); | ||
119 | + if (child.children?.length) { | ||
120 | + addToChildren(routes, child.children, routeModule); | ||
121 | + } | ||
122 | + } | ||
123 | + } | ||
124 | +} | ||
125 | + | ||
126 | +// Determine whether the level exceeds 2 levels | ||
127 | +function isMultipleRoute(routeModule: AppRouteModule) { | ||
128 | + if (!routeModule || !Reflect.has(routeModule, 'children') || !routeModule.children?.length) { | ||
129 | + return false; | ||
130 | + } | ||
131 | + | ||
132 | + const children = routeModule.children; | ||
133 | + | ||
134 | + let flag = false; | ||
135 | + for (let index = 0; index < children.length; index++) { | ||
136 | + const child = children[index]; | ||
137 | + if (child.children?.length) { | ||
138 | + flag = true; | ||
139 | + break; | ||
140 | + } | ||
141 | + } | ||
142 | + return flag; | ||
101 | } | 143 | } |
src/router/menus/index.ts
@@ -5,6 +5,7 @@ import { appStore } from '/@/store/modules/app'; | @@ -5,6 +5,7 @@ import { appStore } from '/@/store/modules/app'; | ||
5 | import { permissionStore } from '/@/store/modules/permission'; | 5 | import { permissionStore } from '/@/store/modules/permission'; |
6 | import { transformMenuModule, getAllParentPath } from '/@/router/helper/menuHelper'; | 6 | import { transformMenuModule, getAllParentPath } from '/@/router/helper/menuHelper'; |
7 | import { filter } from '/@/utils/helper/treeHelper'; | 7 | import { filter } from '/@/utils/helper/treeHelper'; |
8 | +import { isUrl } from '/@/utils/is'; | ||
8 | import router from '/@/router'; | 9 | import router from '/@/router'; |
9 | import { PermissionModeEnum } from '/@/enums/appEnum'; | 10 | import { PermissionModeEnum } from '/@/enums/appEnum'; |
10 | import { pathToRegexp } from 'path-to-regexp'; | 11 | import { pathToRegexp } from 'path-to-regexp'; |
@@ -19,8 +20,6 @@ Object.keys(modules).forEach((key) => { | @@ -19,8 +20,6 @@ Object.keys(modules).forEach((key) => { | ||
19 | menuModules.push(...modList); | 20 | menuModules.push(...modList); |
20 | }); | 21 | }); |
21 | 22 | ||
22 | -const reg = /(((https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; | ||
23 | - | ||
24 | // =========================== | 23 | // =========================== |
25 | // ==========Helper=========== | 24 | // ==========Helper=========== |
26 | // =========================== | 25 | // =========================== |
@@ -40,18 +39,15 @@ const staticMenus: Menu[] = []; | @@ -40,18 +39,15 @@ const staticMenus: Menu[] = []; | ||
40 | })(); | 39 | })(); |
41 | 40 | ||
42 | async function getAsyncMenus() { | 41 | async function getAsyncMenus() { |
43 | - // 前端角色控制菜单 直接取菜单文件 | ||
44 | return !isBackMode() ? staticMenus : permissionStore.getBackMenuListState; | 42 | return !isBackMode() ? staticMenus : permissionStore.getBackMenuListState; |
45 | } | 43 | } |
46 | 44 | ||
47 | -// 获取菜单 树级 | ||
48 | export const getMenus = async (): Promise<Menu[]> => { | 45 | export const getMenus = async (): Promise<Menu[]> => { |
49 | const menus = await getAsyncMenus(); | 46 | const menus = await getAsyncMenus(); |
50 | const routes = router.getRoutes(); | 47 | const routes = router.getRoutes(); |
51 | return !isBackMode() ? filter(menus, basicFilter(routes)) : menus; | 48 | return !isBackMode() ? filter(menus, basicFilter(routes)) : menus; |
52 | }; | 49 | }; |
53 | 50 | ||
54 | -// 获取当前路径的顶级路径 | ||
55 | export async function getCurrentParentPath(currentPath: string) { | 51 | export async function getCurrentParentPath(currentPath: string) { |
56 | const menus = await getAsyncMenus(); | 52 | const menus = await getAsyncMenus(); |
57 | 53 | ||
@@ -60,7 +56,7 @@ export async function getCurrentParentPath(currentPath: string) { | @@ -60,7 +56,7 @@ export async function getCurrentParentPath(currentPath: string) { | ||
60 | return allParentPath?.[0]; | 56 | return allParentPath?.[0]; |
61 | } | 57 | } |
62 | 58 | ||
63 | -// 获取1级菜单,删除children | 59 | +// Get the level 1 menu, delete children |
64 | export async function getShallowMenus(): Promise<Menu[]> { | 60 | export async function getShallowMenus(): Promise<Menu[]> { |
65 | const menus = await getAsyncMenus(); | 61 | const menus = await getAsyncMenus(); |
66 | const routes = router.getRoutes(); | 62 | const routes = router.getRoutes(); |
@@ -68,7 +64,7 @@ export async function getShallowMenus(): Promise<Menu[]> { | @@ -68,7 +64,7 @@ export async function getShallowMenus(): Promise<Menu[]> { | ||
68 | return !isBackMode() ? shallowMenuList.filter(basicFilter(routes)) : shallowMenuList; | 64 | return !isBackMode() ? shallowMenuList.filter(basicFilter(routes)) : shallowMenuList; |
69 | } | 65 | } |
70 | 66 | ||
71 | -// 获取菜单的children | 67 | +// Get the children of the menu |
72 | export async function getChildrenMenus(parentPath: string) { | 68 | export async function getChildrenMenus(parentPath: string) { |
73 | const menus = await getAsyncMenus(); | 69 | const menus = await getAsyncMenus(); |
74 | const parent = menus.find((item) => item.path === parentPath); | 70 | const parent = menus.find((item) => item.path === parentPath); |
@@ -78,14 +74,10 @@ export async function getChildrenMenus(parentPath: string) { | @@ -78,14 +74,10 @@ export async function getChildrenMenus(parentPath: string) { | ||
78 | return !isBackMode() ? filter(parent.children, basicFilter(routes)) : parent.children; | 74 | return !isBackMode() ? filter(parent.children, basicFilter(routes)) : parent.children; |
79 | } | 75 | } |
80 | 76 | ||
81 | -// 通用过滤方法 | ||
82 | function basicFilter(routes: RouteRecordNormalized[]) { | 77 | function basicFilter(routes: RouteRecordNormalized[]) { |
83 | return (menu: Menu) => { | 78 | return (menu: Menu) => { |
84 | const matchRoute = routes.find((route) => { | 79 | const matchRoute = routes.find((route) => { |
85 | - const match = route.path.match(reg)?.[0]; | ||
86 | - if (match && match === menu.path) { | ||
87 | - return true; | ||
88 | - } | 80 | + if (isUrl(menu.path)) return true; |
89 | 81 | ||
90 | if (route.meta?.carryParam) { | 82 | if (route.meta?.carryParam) { |
91 | return pathToRegexp(route.path).test(menu.path); | 83 | return pathToRegexp(route.path).test(menu.path); |
src/router/routes/basic.ts
0 → 100644
1 | +import type { AppRouteRecordRaw } from '/@/router/types'; | ||
2 | +import { t } from '/@/hooks/web/useI18n'; | ||
3 | +import { REDIRECT_NAME, LAYOUT, EXCEPTION_COMPONENT } from '/@/router/constant'; | ||
4 | + | ||
5 | +// 404 on a page | ||
6 | +export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = { | ||
7 | + path: '/:path(.*)*', | ||
8 | + name: 'ErrorPage', | ||
9 | + component: LAYOUT, | ||
10 | + meta: { | ||
11 | + title: 'ErrorPage', | ||
12 | + hideBreadcrumb: true, | ||
13 | + }, | ||
14 | + children: [ | ||
15 | + { | ||
16 | + path: '/:path(.*)*', | ||
17 | + name: 'ErrorPage', | ||
18 | + component: EXCEPTION_COMPONENT, | ||
19 | + meta: { | ||
20 | + title: 'ErrorPage', | ||
21 | + hideBreadcrumb: true, | ||
22 | + }, | ||
23 | + }, | ||
24 | + ], | ||
25 | +}; | ||
26 | + | ||
27 | +export const REDIRECT_ROUTE: AppRouteRecordRaw = { | ||
28 | + path: '/redirect', | ||
29 | + name: REDIRECT_NAME, | ||
30 | + component: LAYOUT, | ||
31 | + meta: { | ||
32 | + title: REDIRECT_NAME, | ||
33 | + hideBreadcrumb: true, | ||
34 | + }, | ||
35 | + children: [ | ||
36 | + { | ||
37 | + path: '/redirect/:path(.*)', | ||
38 | + name: REDIRECT_NAME, | ||
39 | + component: () => import('/@/views/sys/redirect/index.vue'), | ||
40 | + meta: { | ||
41 | + title: REDIRECT_NAME, | ||
42 | + hideBreadcrumb: true, | ||
43 | + }, | ||
44 | + }, | ||
45 | + ], | ||
46 | +}; | ||
47 | + | ||
48 | +export const ERROR_LOG_ROUTE: AppRouteRecordRaw = { | ||
49 | + path: '/error-log', | ||
50 | + name: 'errorLog', | ||
51 | + component: LAYOUT, | ||
52 | + meta: { | ||
53 | + title: 'ErrorLog', | ||
54 | + hideBreadcrumb: true, | ||
55 | + }, | ||
56 | + children: [ | ||
57 | + { | ||
58 | + path: 'list', | ||
59 | + name: 'errorLogList', | ||
60 | + component: () => import('/@/views/sys/error-log/index.vue'), | ||
61 | + meta: { | ||
62 | + title: t('routes.basic.errorLogList'), | ||
63 | + hideBreadcrumb: true, | ||
64 | + }, | ||
65 | + }, | ||
66 | + ], | ||
67 | +}; |
src/router/routes/index.ts
1 | import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types'; | 1 | import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types'; |
2 | 2 | ||
3 | -import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '../constant'; | 3 | +import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic'; |
4 | 4 | ||
5 | import { mainOutRoutes } from './mainOut'; | 5 | import { mainOutRoutes } from './mainOut'; |
6 | import { PageEnum } from '/@/enums/pageEnum'; | 6 | import { PageEnum } from '/@/enums/pageEnum'; |
7 | import { t } from '/@/hooks/web/useI18n'; | 7 | import { t } from '/@/hooks/web/useI18n'; |
8 | +import { flatRoutes } from '/@/router/helper/routeHelper'; | ||
8 | 9 | ||
9 | const modules = import.meta.globEager('./modules/**/*.ts'); | 10 | const modules = import.meta.globEager('./modules/**/*.ts'); |
10 | 11 | ||
@@ -16,6 +17,9 @@ Object.keys(modules).forEach((key) => { | @@ -16,6 +17,9 @@ Object.keys(modules).forEach((key) => { | ||
16 | routeModuleList.push(...modList); | 17 | routeModuleList.push(...modList); |
17 | }); | 18 | }); |
18 | 19 | ||
20 | +// Multi-level routing conversion | ||
21 | +flatRoutes(routeModuleList); | ||
22 | + | ||
19 | export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList]; | 23 | export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList]; |
20 | 24 | ||
21 | export const RootRoute: AppRouteRecordRaw = { | 25 | export const RootRoute: AppRouteRecordRaw = { |
src/store/modules/permission.ts
@@ -14,12 +14,12 @@ import { filter } from '/@/utils/helper/treeHelper'; | @@ -14,12 +14,12 @@ import { filter } from '/@/utils/helper/treeHelper'; | ||
14 | import { toRaw } from 'vue'; | 14 | import { toRaw } from 'vue'; |
15 | import { getMenuListById } from '/@/api/sys/menu'; | 15 | import { getMenuListById } from '/@/api/sys/menu'; |
16 | 16 | ||
17 | -import { transformObjToRoute } from '/@/router/helper/routeHelper'; | 17 | +import { transformObjToRoute, flatRoutes } from '/@/router/helper/routeHelper'; |
18 | import { transformRouteToMenu } from '/@/router/helper/menuHelper'; | 18 | import { transformRouteToMenu } from '/@/router/helper/menuHelper'; |
19 | 19 | ||
20 | import { useMessage } from '/@/hooks/web/useMessage'; | 20 | import { useMessage } from '/@/hooks/web/useMessage'; |
21 | import { useI18n } from '/@/hooks/web/useI18n'; | 21 | import { useI18n } from '/@/hooks/web/useI18n'; |
22 | -import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '/@/router/constant'; | 22 | +import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'; |
23 | 23 | ||
24 | const { createMessage } = useMessage(); | 24 | const { createMessage } = useMessage(); |
25 | const NAME = 'app-permission'; | 25 | const NAME = 'app-permission'; |
@@ -113,11 +113,12 @@ class Permission extends VuexModule { | @@ -113,11 +113,12 @@ class Permission extends VuexModule { | ||
113 | 113 | ||
114 | // Dynamically introduce components | 114 | // Dynamically introduce components |
115 | routeList = transformObjToRoute(routeList); | 115 | routeList = transformObjToRoute(routeList); |
116 | + | ||
116 | // Background routing to menu structure | 117 | // Background routing to menu structure |
117 | const backMenuList = transformRouteToMenu(routeList); | 118 | const backMenuList = transformRouteToMenu(routeList); |
118 | - | ||
119 | this.commitBackMenuListState(backMenuList); | 119 | this.commitBackMenuListState(backMenuList); |
120 | 120 | ||
121 | + flatRoutes(routeList); | ||
121 | routes = [PAGE_NOT_FOUND_ROUTE, ...routeList]; | 122 | routes = [PAGE_NOT_FOUND_ROUTE, ...routeList]; |
122 | } | 123 | } |
123 | routes.push(ERROR_LOG_ROUTE); | 124 | routes.push(ERROR_LOG_ROUTE); |
src/store/modules/tab.ts
@@ -8,8 +8,8 @@ import { PageEnum } from '/@/enums/pageEnum'; | @@ -8,8 +8,8 @@ import { PageEnum } from '/@/enums/pageEnum'; | ||
8 | 8 | ||
9 | import store from '/@/store'; | 9 | import store from '/@/store'; |
10 | import router from '/@/router'; | 10 | import router from '/@/router'; |
11 | -import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/constant'; | ||
12 | -import { getRoute } from '/@/router/helper/routeHelper'; | 11 | +import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic'; |
12 | +import { getRawRoute } from '/@/utils'; | ||
13 | 13 | ||
14 | import { useGo, useRedo } from '/@/hooks/web/usePage'; | 14 | import { useGo, useRedo } from '/@/hooks/web/usePage'; |
15 | import { cloneDeep } from 'lodash-es'; | 15 | import { cloneDeep } from 'lodash-es'; |
@@ -18,8 +18,6 @@ const NAME = 'app-tab'; | @@ -18,8 +18,6 @@ const NAME = 'app-tab'; | ||
18 | 18 | ||
19 | hotModuleUnregisterModule(NAME); | 19 | hotModuleUnregisterModule(NAME); |
20 | 20 | ||
21 | -export const PAGE_LAYOUT_KEY = '__PAGE_LAYOUT__'; | ||
22 | - | ||
23 | function isGotoPage() { | 21 | function isGotoPage() { |
24 | const go = useGo(); | 22 | const go = useGo(); |
25 | go(unref(router.currentRoute).path, true); | 23 | go(unref(router.currentRoute).path, true); |
@@ -27,7 +25,7 @@ function isGotoPage() { | @@ -27,7 +25,7 @@ function isGotoPage() { | ||
27 | 25 | ||
28 | @Module({ namespaced: true, name: NAME, dynamic: true, store }) | 26 | @Module({ namespaced: true, name: NAME, dynamic: true, store }) |
29 | class Tab extends VuexModule { | 27 | class Tab extends VuexModule { |
30 | - cachedMapState = new Map<string, string[]>(); | 28 | + cachedTabsState: Set<string> = new Set(); |
31 | 29 | ||
32 | // tab list | 30 | // tab list |
33 | tabsState: RouteLocationNormalized[] = []; | 31 | tabsState: RouteLocationNormalized[] = []; |
@@ -43,8 +41,8 @@ class Tab extends VuexModule { | @@ -43,8 +41,8 @@ class Tab extends VuexModule { | ||
43 | return this.tabsState.find((item) => item.path === route.path)!; | 41 | return this.tabsState.find((item) => item.path === route.path)!; |
44 | } | 42 | } |
45 | 43 | ||
46 | - get getCachedMapState(): Map<string, string[]> { | ||
47 | - return this.cachedMapState; | 44 | + get getCachedTabsState(): string[] { |
45 | + return Array.from(this.cachedTabsState); | ||
48 | } | 46 | } |
49 | 47 | ||
50 | get getLastDragEndIndexState(): number { | 48 | get getLastDragEndIndexState(): number { |
@@ -53,7 +51,7 @@ class Tab extends VuexModule { | @@ -53,7 +51,7 @@ class Tab extends VuexModule { | ||
53 | 51 | ||
54 | @Mutation | 52 | @Mutation |
55 | commitClearCache(): void { | 53 | commitClearCache(): void { |
56 | - this.cachedMapState = new Map(); | 54 | + this.cachedTabsState = new Set(); |
57 | } | 55 | } |
58 | 56 | ||
59 | @Mutation | 57 | @Mutation |
@@ -77,46 +75,16 @@ class Tab extends VuexModule { | @@ -77,46 +75,16 @@ class Tab extends VuexModule { | ||
77 | 75 | ||
78 | @Mutation | 76 | @Mutation |
79 | commitCachedMapState(): void { | 77 | commitCachedMapState(): void { |
80 | - const cacheMap = new Map<string, string[]>(); | 78 | + const cacheMap: Set<string> = new Set(); |
81 | 79 | ||
82 | - const pageCacheSet = new Set<string>(); | ||
83 | this.tabsState.forEach((tab) => { | 80 | this.tabsState.forEach((tab) => { |
84 | - const item = getRoute(tab); | 81 | + const item = getRawRoute(tab); |
85 | const needCache = !item.meta?.ignoreKeepAlive; | 82 | const needCache = !item.meta?.ignoreKeepAlive; |
86 | if (!needCache) return; | 83 | if (!needCache) return; |
87 | - | ||
88 | - if (item.meta?.affix) { | ||
89 | - const name = item.name as string; | ||
90 | - pageCacheSet.add(name); | ||
91 | - } else if (item?.matched && needCache) { | ||
92 | - const matched = item?.matched; | ||
93 | - if (!matched) return; | ||
94 | - const len = matched.length; | ||
95 | - | ||
96 | - if (len < 2) return; | ||
97 | - | ||
98 | - for (let i = 0; i < matched.length; i++) { | ||
99 | - const key = matched[i].name as string; | ||
100 | - | ||
101 | - if (i < 2) { | ||
102 | - pageCacheSet.add(key); | ||
103 | - } | ||
104 | - if (i < len - 1) { | ||
105 | - const { meta, name } = matched[i + 1]; | ||
106 | - if (meta && (meta.affix || needCache)) { | ||
107 | - const mapList = cacheMap.get(key) || []; | ||
108 | - if (!mapList.includes(name as string)) { | ||
109 | - mapList.push(name as string); | ||
110 | - } | ||
111 | - cacheMap.set(key, mapList); | ||
112 | - } | ||
113 | - } | ||
114 | - } | ||
115 | - } | 84 | + const name = item.name as string; |
85 | + cacheMap.add(name); | ||
116 | }); | 86 | }); |
117 | - | ||
118 | - cacheMap.set(PAGE_LAYOUT_KEY, Array.from(pageCacheSet)); | ||
119 | - this.cachedMapState = cacheMap; | 87 | + this.cachedTabsState = cacheMap; |
120 | } | 88 | } |
121 | 89 | ||
122 | @Mutation | 90 | @Mutation |
@@ -162,7 +130,7 @@ class Tab extends VuexModule { | @@ -162,7 +130,7 @@ class Tab extends VuexModule { | ||
162 | @Mutation | 130 | @Mutation |
163 | commitResetState(): void { | 131 | commitResetState(): void { |
164 | this.tabsState = []; | 132 | this.tabsState = []; |
165 | - this.cachedMapState = new Map(); | 133 | + this.cachedTabsState = new Set(); |
166 | } | 134 | } |
167 | 135 | ||
168 | @Mutation | 136 | @Mutation |
@@ -190,7 +158,7 @@ class Tab extends VuexModule { | @@ -190,7 +158,7 @@ class Tab extends VuexModule { | ||
190 | ) { | 158 | ) { |
191 | return; | 159 | return; |
192 | } | 160 | } |
193 | - this.commitTabRoutesState(getRoute(route)); | 161 | + this.commitTabRoutesState(getRawRoute(route)); |
194 | 162 | ||
195 | this.commitCachedMapState(); | 163 | this.commitCachedMapState(); |
196 | } | 164 | } |
@@ -198,17 +166,12 @@ class Tab extends VuexModule { | @@ -198,17 +166,12 @@ class Tab extends VuexModule { | ||
198 | @Mutation | 166 | @Mutation |
199 | async commitRedoPage() { | 167 | async commitRedoPage() { |
200 | const route = router.currentRoute.value; | 168 | const route = router.currentRoute.value; |
201 | - for (const [key, value] of this.cachedMapState) { | ||
202 | - const index = value.findIndex((item) => item === (route.name as string)); | ||
203 | - if (index === -1) { | ||
204 | - continue; | ||
205 | - } | ||
206 | - if (value.length === 1) { | ||
207 | - this.cachedMapState.delete(key); | ||
208 | - continue; | ||
209 | - } | ||
210 | - value.splice(index, 1); | ||
211 | - this.cachedMapState.set(key, value); | 169 | + const name = route.name; |
170 | + | ||
171 | + const findVal = Array.from(this.cachedTabsState).find((item) => item === name); | ||
172 | + if (findVal) { | ||
173 | + this.cachedTabsState.delete(findVal); | ||
174 | + // this.cachedTabsState.splice(index, 1); | ||
212 | } | 175 | } |
213 | const redo = useRedo(); | 176 | const redo = useRedo(); |
214 | await redo(); | 177 | await redo(); |
src/utils/index.ts
1 | -export const timestamp = () => +Date.now(); | 1 | +import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'; |
2 | import { unref } from 'vue'; | 2 | import { unref } from 'vue'; |
3 | import { isObject } from '/@/utils/is'; | 3 | import { isObject } from '/@/utils/is'; |
4 | -export const clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n)); | 4 | + |
5 | export const noop = () => {}; | 5 | export const noop = () => {}; |
6 | -export const now = () => Date.now(); | ||
7 | 6 | ||
8 | /** | 7 | /** |
9 | * @description: Set ui mount node | 8 | * @description: Set ui mount node |
@@ -91,3 +90,18 @@ export function setTitle(title: string, appTitle?: string) { | @@ -91,3 +90,18 @@ export function setTitle(title: string, appTitle?: string) { | ||
91 | setDocumentTitle(_title); | 90 | setDocumentTitle(_title); |
92 | } | 91 | } |
93 | } | 92 | } |
93 | + | ||
94 | +export function getRawRoute(route: RouteLocationNormalized): RouteLocationNormalized { | ||
95 | + if (!route) return route; | ||
96 | + const { matched, ...opt } = route; | ||
97 | + return { | ||
98 | + ...opt, | ||
99 | + matched: (matched | ||
100 | + ? matched.map((item) => ({ | ||
101 | + meta: item.meta, | ||
102 | + name: item.name, | ||
103 | + path: item.path, | ||
104 | + })) | ||
105 | + : undefined) as RouteRecordNormalized[], | ||
106 | + }; | ||
107 | +} |
vite.config.ts
@@ -43,7 +43,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { | @@ -43,7 +43,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { | ||
43 | }, | 43 | }, |
44 | 44 | ||
45 | build: { | 45 | build: { |
46 | - minify: 'esbuild', | 46 | + // minify: 'esbuild', |
47 | outDir: OUTPUT_DIR, | 47 | outDir: OUTPUT_DIR, |
48 | polyfillDynamicImport: VITE_LEGACY, | 48 | polyfillDynamicImport: VITE_LEGACY, |
49 | terserOptions: { | 49 | terserOptions: { |