diff --git a/.eslintrc.js b/.eslintrc.js index 23f997c5..8a1472aa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -38,15 +38,15 @@ module.exports = { '@typescript-eslint/no-unused-vars': [ 'error', { - argsIgnorePattern: '^h$', - varsIgnorePattern: '^h$', + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', }, ], 'no-unused-vars': [ 'error', { - argsIgnorePattern: '^h$', - varsIgnorePattern: '^h$', + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', }, ], 'space-before-function-paren': 'off', diff --git a/.vscode/settings.json b/.vscode/settings.json index a51df87b..f8ccecb0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,6 @@ "explorer.openEditors.visible": 0, "editor.tabSize": 2, "editor.renderControlCharacters": true, - "window.zoomLevel": -1, "editor.minimap.renderCharacters": false, "editor.minimap.maxColumn": 300, "editor.minimap.showSlider": "always", diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md index be23fc6e..9092b4f6 100644 --- a/CHANGELOG.zh_CN.md +++ b/CHANGELOG.zh_CN.md @@ -1,3 +1,9 @@ +## Wip + +### ✨ Refactor + +- 重构路由多层模式,解决嵌套 keepalive 执行多次问题 + ## 2.1.0 (2021-03-15) ### ✨ Features diff --git a/build/vite/plugin/hmr.ts b/build/vite/plugin/hmr.ts new file mode 100644 index 00000000..cb84e264 --- /dev/null +++ b/build/vite/plugin/hmr.ts @@ -0,0 +1,21 @@ +import type { Plugin } from 'vite'; + +/** + * TODO + * 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. + * @returns + */ + +export function configHmrPlugin(): Plugin { + return { + name: 'singleHMR', + handleHotUpdate({ modules, file }) { + if (file.match(/xml$/)) return []; + modules.forEach((m) => { + m.importedModules = new Set(); + m.importers = new Set(); + }); + return modules; + }, + }; +} diff --git a/build/vite/plugin/index.ts b/build/vite/plugin/index.ts index cf0ce0d6..d34cf049 100644 --- a/build/vite/plugin/index.ts +++ b/build/vite/plugin/index.ts @@ -17,6 +17,7 @@ import { configThemePlugin } from './theme'; import { configImageminPlugin } from './imagemin'; import { configWindiCssPlugin } from './windicss'; import { configSvgIconsPlugin } from './svgSprite'; +import { configHmrPlugin } from './hmr'; export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { const { VITE_USE_IMAGEMIN, VITE_USE_MOCK, VITE_LEGACY, VITE_BUILD_COMPRESS } = viteEnv; @@ -28,6 +29,9 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { vueJsx(), ]; + // TODO + !isBuild && vitePlugins.push(configHmrPlugin()); + // @vitejs/plugin-legacy VITE_LEGACY && isBuild && vitePlugins.push(legacy()); diff --git a/src/layouts/default/header/components/Breadcrumb.vue b/src/layouts/default/header/components/Breadcrumb.vue index 6ec6480e..d413400b 100644 --- a/src/layouts/default/header/components/Breadcrumb.vue +++ b/src/layouts/default/header/components/Breadcrumb.vue @@ -33,6 +33,7 @@ import { useGo } from '/@/hooks/web/usePage'; import { isString } from '/@/utils/is'; import { useI18n } from '/@/hooks/web/useI18n'; + import { getMenus } from '/@/router/menus'; export default defineComponent({ name: 'LayoutBreadcrumb', @@ -47,7 +48,7 @@ const { getShowBreadCrumbIcon } = useRootSetting(); const { t } = useI18n(); - watchEffect(() => { + watchEffect(async () => { if (currentRoute.value.name === REDIRECT_NAME) return; const matched = currentRoute.value?.matched; diff --git a/src/layouts/page/ParentView.vue b/src/layouts/page/ParentView.vue deleted file mode 100644 index 246beeaa..00000000 --- a/src/layouts/page/ParentView.vue +++ /dev/null @@ -1,63 +0,0 @@ - - - diff --git a/src/layouts/page/index.vue b/src/layouts/page/index.vue index f8a03ea0..096d84da 100644 --- a/src/layouts/page/index.vue +++ b/src/layouts/page/index.vue @@ -16,9 +16,9 @@ appear > - + - + @@ -34,15 +34,15 @@ import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; - import { useCache, getKey } from './useCache'; import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; import { getTransitionName } from './transition'; + import { useStore } from 'vuex'; + export default defineComponent({ name: 'PageLayout', components: { FrameLayout }, setup() { - const { getCaches } = useCache(true); const { getShowMultipleTab } = useMultipleTabSetting(); const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting(); @@ -51,6 +51,17 @@ const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab)); + const { getters } = useStore(); + + const getCaches = computed((): string[] => { + if (!unref(getOpenKeepAlive)) { + return []; + } + // TODO The useStore is used here mainly to solve the problem of circular dependency hot update + const cacheTabs = getters['app-tab/getCachedTabsState']; + return cacheTabs; + }); + return { getTransitionName, openCache, @@ -58,7 +69,6 @@ getBasicTransition, getCaches, getCanEmbedIFramePage, - getKey, }; }, }); diff --git a/src/layouts/page/useCache.ts b/src/layouts/page/useCache.ts deleted file mode 100644 index 8cf0b796..00000000 --- a/src/layouts/page/useCache.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { FunctionalComponent } from 'vue'; -import type { RouteLocation } from 'vue-router'; -import { computed, ref, unref, getCurrentInstance } from 'vue'; -import { useRootSetting } from '/@/hooks/setting/useRootSetting'; - -import { useRouter } from 'vue-router'; -import { useStore } from 'vuex'; - -const ParentLayoutName = 'ParentLayout'; - -const PAGE_LAYOUT_KEY = '__PAGE_LAYOUT__'; - -export function getKey(component: FunctionalComponent & { type: Indexable }, route: RouteLocation) { - return !!component?.type.parentView ? {} : { key: route.fullPath }; -} - -export function useCache(isPage: boolean) { - const { getters } = useStore(); - - const name = ref(''); - const { currentRoute } = useRouter(); - const instance = getCurrentInstance(); - const routeName = instance?.type.name; - if (routeName && ![ParentLayoutName].includes(routeName)) { - name.value = routeName; - } else { - const matched = currentRoute.value?.matched; - if (!matched) { - return; - } - const len = matched.length; - if (len < 2) return; - name.value = matched[len - 2].name as string; - } - - const { getOpenKeepAlive } = useRootSetting(); - - const getCaches = computed((): string[] => { - if (!unref(getOpenKeepAlive)) { - return []; - } - const cached = getters['app-tab/getCachedMapState']; - - if (isPage) { - // page Layout - return cached.get(PAGE_LAYOUT_KEY) || []; - } - const cacheSet = new Set(); - cacheSet.add(unref(name)); - - const list = cached.get(unref(name)); - - if (!list) { - return Array.from(cacheSet); - } - list.forEach((item) => { - cacheSet.add(item); - }); - - return Array.from(cacheSet); - }); - return { getCaches }; -} diff --git a/src/logics/mitt/tabChange.ts b/src/logics/mitt/tabChange.ts index 18692f5c..90c0522b 100644 --- a/src/logics/mitt/tabChange.ts +++ b/src/logics/mitt/tabChange.ts @@ -4,7 +4,7 @@ import Mitt from '/@/utils/mitt'; import type { RouteLocationNormalized } from 'vue-router'; -import { getRoute } from '/@/router/helper/routeHelper'; +import { getRawRoute } from '/@/utils'; const mitt = new Mitt(); @@ -13,7 +13,7 @@ const key = Symbol(); let lastChangeTab: RouteLocationNormalized; export function setLastChangeTab(lastChangeRoute: RouteLocationNormalized) { - const r = getRoute(lastChangeRoute); + const r = getRawRoute(lastChangeRoute); mitt.emit(key, r); lastChangeTab = r; } diff --git a/src/router/constant.ts b/src/router/constant.ts index 9f2f9513..3f7104e1 100644 --- a/src/router/constant.ts +++ b/src/router/constant.ts @@ -1,9 +1,7 @@ -import type { AppRouteRecordRaw } from '/@/router/types'; -import ParentLayout from '/@/layouts/page/ParentView.vue'; -import { t } from '/@/hooks/web/useI18n'; - export const REDIRECT_NAME = 'Redirect'; +export const PARENT_LAYOUT_NAME = 'ParentLayout'; + export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exception.vue'); /** @@ -12,78 +10,23 @@ export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exceptio export const LAYOUT = () => import('/@/layouts/default/index.vue'); /** - * @description: page-layout + * @description: parent-layout */ -export const getParentLayout = (name: string) => { +export const getParentLayout = (_name?: string) => { return () => new Promise((resolve) => { resolve({ - ...ParentLayout, - name, + name: PARENT_LAYOUT_NAME, }); }); }; -// 404 on a page -export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = { - path: '/:path(.*)*', - name: 'ErrorPage', - component: LAYOUT, - meta: { - title: 'ErrorPage', - hideBreadcrumb: true, - }, - children: [ - { - path: '/:path(.*)*', - name: 'ErrorPage', - component: EXCEPTION_COMPONENT, - meta: { - title: 'ErrorPage', - hideBreadcrumb: true, - }, - }, - ], -}; - -export const REDIRECT_ROUTE: AppRouteRecordRaw = { - path: '/redirect', - name: REDIRECT_NAME, - component: LAYOUT, - meta: { - title: REDIRECT_NAME, - hideBreadcrumb: true, - }, - children: [ - { - path: '/redirect/:path(.*)', - name: REDIRECT_NAME, - component: () => import('/@/views/sys/redirect/index.vue'), - meta: { - title: REDIRECT_NAME, - hideBreadcrumb: true, - }, - }, - ], -}; - -export const ERROR_LOG_ROUTE: AppRouteRecordRaw = { - path: '/error-log', - name: 'errorLog', - component: LAYOUT, - meta: { - title: 'ErrorLog', - hideBreadcrumb: true, - }, - children: [ - { - path: 'list', - name: 'errorLogList', - component: () => import('/@/views/sys/error-log/index.vue'), - meta: { - title: t('routes.basic.errorLogList'), - hideBreadcrumb: true, - }, - }, - ], -}; +// export const getParentLayout = (name: string) => { +// return () => +// new Promise((resolve) => { +// resolve({ +// ...ParentLayout, +// name, +// }); +// }); +// }; diff --git a/src/router/guard/permissionGuard.ts b/src/router/guard/permissionGuard.ts index ac20041b..5bd7d19d 100644 --- a/src/router/guard/permissionGuard.ts +++ b/src/router/guard/permissionGuard.ts @@ -5,7 +5,7 @@ import { permissionStore } from '/@/store/modules/permission'; import { PageEnum } from '/@/enums/pageEnum'; import { userStore } from '/@/store/modules/user'; -import { PAGE_NOT_FOUND_ROUTE } from '/@/router/constant'; +import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'; const LOGIN_PATH = PageEnum.BASE_LOGIN; diff --git a/src/router/helper/menuHelper.ts b/src/router/helper/menuHelper.ts index da48abcd..acb7dde0 100644 --- a/src/router/helper/menuHelper.ts +++ b/src/router/helper/menuHelper.ts @@ -1,44 +1,34 @@ import { AppRouteModule } from '/@/router/types'; import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'; -import { findPath, forEach, treeMap } from '/@/utils/helper/treeHelper'; +import { findPath, treeMap } from '/@/utils/helper/treeHelper'; import { cloneDeep } from 'lodash-es'; import { isUrl } from '/@/utils/is'; -export function getAllParentPath(treeData: any[], path: string) { +export function getAllParentPath(treeData: T[], path: string) { const menuList = findPath(treeData, (n) => n.path === path) as Menu[]; return (menuList || []).map((item) => item.path); } -// 拼接父级路径 -function joinParentPath(list: any, node: any) { - let allPaths = getAllParentPath(list, node.path); - - allPaths = allPaths.slice(0, allPaths.length - 1); - let parentPath = ''; - if (Array.isArray(allPaths) && allPaths.length >= 2) { - parentPath = allPaths[allPaths.length - 1]; - } else { - allPaths.forEach((p) => { - parentPath += /^\//.test(p) ? p : `/${p}`; - }); +function joinParentPath(menus: Menu[], parentPath = '') { + for (let index = 0; index < menus.length; index++) { + const menu = menus[index]; + const p = menu.path.startsWith('/') ? menu.path : `/${menu.path}`; + const parent = isUrl(menu.path) ? menu.path : `${parentPath}${p}`; + menus[index].path = parent; + if (menu?.children?.length) { + joinParentPath(menu.children, parent); + } } - node.path = `${/^\//.test(node.path) ? node.path : `${parentPath}/${node.path}`}`.replace( - /\/\//g, - '/' - ); - return node; } -// 解析菜单模块 +// Parsing the menu module export function transformMenuModule(menuModule: MenuModule): Menu { const { menu } = menuModule; const menuList = [menu]; - forEach(menuList, (m) => { - !isUrl(m.path) && joinParentPath(menuList, m); - }); + joinParentPath(menuList); return menuList[0]; } @@ -54,17 +44,16 @@ export function transformRouteToMenu(routeModList: AppRouteModule[]) { routeList.push(item); } }); - return treeMap(routeList, { + const list = treeMap(routeList, { conversion: (node: AppRouteRecordRaw) => { - const { meta: { title, icon, hideMenu = false } = {} } = node; - - !isUrl(node.path) && joinParentPath(routeList, node); + const { meta: { title, hideMenu = false } = {} } = node; return { + ...(node.meta || {}), name: title, - icon, - path: node.path, hideMenu, }; }, }); + joinParentPath(list); + return list; } diff --git a/src/router/helper/routeHelper.ts b/src/router/helper/routeHelper.ts index 03adedd8..10276175 100644 --- a/src/router/helper/routeHelper.ts +++ b/src/router/helper/routeHelper.ts @@ -1,22 +1,18 @@ import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types'; -import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'; +import type { Router, RouteRecordNormalized } from 'vue-router'; import { getParentLayout, LAYOUT } from '/@/router/constant'; import { cloneDeep } from 'lodash-es'; import { warn } from '/@/utils/log'; +import { createRouter, createWebHashHistory } from 'vue-router'; export type LayoutMapKey = 'LAYOUT'; const LayoutMap = new Map Promise>(); -let dynamicViewsModules: Record< - string, - () => Promise<{ - [key: string]: any; - }> ->; +let dynamicViewsModules: Record Promise>; -// 动态引入 +// Dynamic introduction function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}'); if (!routes) return; @@ -26,19 +22,14 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { if (component) { item.component = dynamicImport(dynamicViewsModules, component as string); } else if (name) { - item.component = getParentLayout(name); + item.component = getParentLayout(); } children && asyncImportRoute(children); }); } function dynamicImport( - dynamicViewsModules: Record< - string, - () => Promise<{ - [key: string]: any; - }> - >, + dynamicViewsModules: Record Promise>, component: string ) { const keys = Object.keys(dynamicViewsModules); @@ -84,18 +75,69 @@ export function transformObjToRoute(routeList: AppRouteModul return (routeList as unknown) as T[]; } -// Return to the new routing structure, not affected by the original example -export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized { - if (!route) return route; - const { matched, ...opt } = route; - return { - ...opt, - matched: (matched - ? matched.map((item) => ({ - meta: item.meta, - name: item.name, - path: item.path, - })) - : undefined) as RouteRecordNormalized[], - }; +/** + * Convert multi-level routing to level 2 routing + */ +export function flatRoutes(routeModules: AppRouteModule[]) { + for (let index = 0; index < routeModules.length; index++) { + const routeModule = routeModules[index]; + if (!isMultipleRoute(routeModule)) { + continue; + } + promoteRouteLevel(routeModule); + } +} + +// Routing level upgrade +function promoteRouteLevel(routeModule: AppRouteModule) { + // Use vue-router to splice menus + let router: Router | null = createRouter({ + routes: [routeModule as any], + history: createWebHashHistory(), + }); + + const routes = router.getRoutes(); + const children = cloneDeep(routeModule.children); + addToChildren(routes, children || [], routeModule); + router = null; + + routeModule.children = routeModule.children?.filter((item) => !item.children?.length); +} + +// Add all sub-routes to the secondary route +function addToChildren( + routes: RouteRecordNormalized[], + children: AppRouteRecordRaw[], + routeModule: AppRouteModule +) { + for (let index = 0; index < children.length; index++) { + const child = children[index]; + const route = routes.find((item) => item.name === child.name); + if (route) { + routeModule.children = routeModule.children || []; + routeModule.children?.push(route as any); + if (child.children?.length) { + addToChildren(routes, child.children, routeModule); + } + } + } +} + +// Determine whether the level exceeds 2 levels +function isMultipleRoute(routeModule: AppRouteModule) { + if (!routeModule || !Reflect.has(routeModule, 'children') || !routeModule.children?.length) { + return false; + } + + const children = routeModule.children; + + let flag = false; + for (let index = 0; index < children.length; index++) { + const child = children[index]; + if (child.children?.length) { + flag = true; + break; + } + } + return flag; } diff --git a/src/router/menus/index.ts b/src/router/menus/index.ts index ef3deb00..1c54d23e 100644 --- a/src/router/menus/index.ts +++ b/src/router/menus/index.ts @@ -5,6 +5,7 @@ import { appStore } from '/@/store/modules/app'; import { permissionStore } from '/@/store/modules/permission'; import { transformMenuModule, getAllParentPath } from '/@/router/helper/menuHelper'; import { filter } from '/@/utils/helper/treeHelper'; +import { isUrl } from '/@/utils/is'; import router from '/@/router'; import { PermissionModeEnum } from '/@/enums/appEnum'; import { pathToRegexp } from 'path-to-regexp'; @@ -19,8 +20,6 @@ Object.keys(modules).forEach((key) => { menuModules.push(...modList); }); -const reg = /(((https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; - // =========================== // ==========Helper=========== // =========================== @@ -40,18 +39,15 @@ const staticMenus: Menu[] = []; })(); async function getAsyncMenus() { - // 前端角色控制菜单 直接取菜单文件 return !isBackMode() ? staticMenus : permissionStore.getBackMenuListState; } -// 获取菜单 树级 export const getMenus = async (): Promise => { const menus = await getAsyncMenus(); const routes = router.getRoutes(); return !isBackMode() ? filter(menus, basicFilter(routes)) : menus; }; -// 获取当前路径的顶级路径 export async function getCurrentParentPath(currentPath: string) { const menus = await getAsyncMenus(); @@ -60,7 +56,7 @@ export async function getCurrentParentPath(currentPath: string) { return allParentPath?.[0]; } -// 获取1级菜单,删除children +// Get the level 1 menu, delete children export async function getShallowMenus(): Promise { const menus = await getAsyncMenus(); const routes = router.getRoutes(); @@ -68,7 +64,7 @@ export async function getShallowMenus(): Promise { return !isBackMode() ? shallowMenuList.filter(basicFilter(routes)) : shallowMenuList; } -// 获取菜单的children +// Get the children of the menu export async function getChildrenMenus(parentPath: string) { const menus = await getAsyncMenus(); const parent = menus.find((item) => item.path === parentPath); @@ -78,14 +74,10 @@ export async function getChildrenMenus(parentPath: string) { return !isBackMode() ? filter(parent.children, basicFilter(routes)) : parent.children; } -// 通用过滤方法 function basicFilter(routes: RouteRecordNormalized[]) { return (menu: Menu) => { const matchRoute = routes.find((route) => { - const match = route.path.match(reg)?.[0]; - if (match && match === menu.path) { - return true; - } + if (isUrl(menu.path)) return true; if (route.meta?.carryParam) { return pathToRegexp(route.path).test(menu.path); diff --git a/src/router/routes/basic.ts b/src/router/routes/basic.ts new file mode 100644 index 00000000..6ad8aa2f --- /dev/null +++ b/src/router/routes/basic.ts @@ -0,0 +1,67 @@ +import type { AppRouteRecordRaw } from '/@/router/types'; +import { t } from '/@/hooks/web/useI18n'; +import { REDIRECT_NAME, LAYOUT, EXCEPTION_COMPONENT } from '/@/router/constant'; + +// 404 on a page +export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = { + path: '/:path(.*)*', + name: 'ErrorPage', + component: LAYOUT, + meta: { + title: 'ErrorPage', + hideBreadcrumb: true, + }, + children: [ + { + path: '/:path(.*)*', + name: 'ErrorPage', + component: EXCEPTION_COMPONENT, + meta: { + title: 'ErrorPage', + hideBreadcrumb: true, + }, + }, + ], +}; + +export const REDIRECT_ROUTE: AppRouteRecordRaw = { + path: '/redirect', + name: REDIRECT_NAME, + component: LAYOUT, + meta: { + title: REDIRECT_NAME, + hideBreadcrumb: true, + }, + children: [ + { + path: '/redirect/:path(.*)', + name: REDIRECT_NAME, + component: () => import('/@/views/sys/redirect/index.vue'), + meta: { + title: REDIRECT_NAME, + hideBreadcrumb: true, + }, + }, + ], +}; + +export const ERROR_LOG_ROUTE: AppRouteRecordRaw = { + path: '/error-log', + name: 'errorLog', + component: LAYOUT, + meta: { + title: 'ErrorLog', + hideBreadcrumb: true, + }, + children: [ + { + path: 'list', + name: 'errorLogList', + component: () => import('/@/views/sys/error-log/index.vue'), + meta: { + title: t('routes.basic.errorLogList'), + hideBreadcrumb: true, + }, + }, + ], +}; diff --git a/src/router/routes/index.ts b/src/router/routes/index.ts index 281744ab..52c2015e 100644 --- a/src/router/routes/index.ts +++ b/src/router/routes/index.ts @@ -1,10 +1,11 @@ import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types'; -import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '../constant'; +import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic'; import { mainOutRoutes } from './mainOut'; import { PageEnum } from '/@/enums/pageEnum'; import { t } from '/@/hooks/web/useI18n'; +import { flatRoutes } from '/@/router/helper/routeHelper'; const modules = import.meta.globEager('./modules/**/*.ts'); @@ -16,6 +17,9 @@ Object.keys(modules).forEach((key) => { routeModuleList.push(...modList); }); +// Multi-level routing conversion +flatRoutes(routeModuleList); + export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList]; export const RootRoute: AppRouteRecordRaw = { diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts index 8729fe6b..6b4a5254 100644 --- a/src/store/modules/permission.ts +++ b/src/store/modules/permission.ts @@ -14,12 +14,12 @@ import { filter } from '/@/utils/helper/treeHelper'; import { toRaw } from 'vue'; import { getMenuListById } from '/@/api/sys/menu'; -import { transformObjToRoute } from '/@/router/helper/routeHelper'; +import { transformObjToRoute, flatRoutes } from '/@/router/helper/routeHelper'; import { transformRouteToMenu } from '/@/router/helper/menuHelper'; import { useMessage } from '/@/hooks/web/useMessage'; import { useI18n } from '/@/hooks/web/useI18n'; -import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '/@/router/constant'; +import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'; const { createMessage } = useMessage(); const NAME = 'app-permission'; @@ -113,11 +113,12 @@ class Permission extends VuexModule { // Dynamically introduce components routeList = transformObjToRoute(routeList); + // Background routing to menu structure const backMenuList = transformRouteToMenu(routeList); - this.commitBackMenuListState(backMenuList); + flatRoutes(routeList); routes = [PAGE_NOT_FOUND_ROUTE, ...routeList]; } routes.push(ERROR_LOG_ROUTE); diff --git a/src/store/modules/tab.ts b/src/store/modules/tab.ts index ad773304..549d88d7 100644 --- a/src/store/modules/tab.ts +++ b/src/store/modules/tab.ts @@ -8,8 +8,8 @@ import { PageEnum } from '/@/enums/pageEnum'; import store from '/@/store'; import router from '/@/router'; -import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/constant'; -import { getRoute } from '/@/router/helper/routeHelper'; +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'; @@ -18,8 +18,6 @@ const NAME = 'app-tab'; hotModuleUnregisterModule(NAME); -export const PAGE_LAYOUT_KEY = '__PAGE_LAYOUT__'; - function isGotoPage() { const go = useGo(); go(unref(router.currentRoute).path, true); @@ -27,7 +25,7 @@ function isGotoPage() { @Module({ namespaced: true, name: NAME, dynamic: true, store }) class Tab extends VuexModule { - cachedMapState = new Map(); + cachedTabsState: Set = new Set(); // tab list tabsState: RouteLocationNormalized[] = []; @@ -43,8 +41,8 @@ class Tab extends VuexModule { return this.tabsState.find((item) => item.path === route.path)!; } - get getCachedMapState(): Map { - return this.cachedMapState; + get getCachedTabsState(): string[] { + return Array.from(this.cachedTabsState); } get getLastDragEndIndexState(): number { @@ -53,7 +51,7 @@ class Tab extends VuexModule { @Mutation commitClearCache(): void { - this.cachedMapState = new Map(); + this.cachedTabsState = new Set(); } @Mutation @@ -77,46 +75,16 @@ class Tab extends VuexModule { @Mutation commitCachedMapState(): void { - const cacheMap = new Map(); + const cacheMap: Set = new Set(); - const pageCacheSet = new Set(); this.tabsState.forEach((tab) => { - const item = getRoute(tab); + const item = getRawRoute(tab); const needCache = !item.meta?.ignoreKeepAlive; if (!needCache) return; - - if (item.meta?.affix) { - const name = item.name as string; - pageCacheSet.add(name); - } else if (item?.matched && needCache) { - const matched = item?.matched; - if (!matched) return; - const len = matched.length; - - if (len < 2) return; - - for (let i = 0; i < matched.length; i++) { - const key = matched[i].name as string; - - if (i < 2) { - pageCacheSet.add(key); - } - if (i < len - 1) { - const { meta, name } = matched[i + 1]; - if (meta && (meta.affix || needCache)) { - const mapList = cacheMap.get(key) || []; - if (!mapList.includes(name as string)) { - mapList.push(name as string); - } - cacheMap.set(key, mapList); - } - } - } - } + const name = item.name as string; + cacheMap.add(name); }); - - cacheMap.set(PAGE_LAYOUT_KEY, Array.from(pageCacheSet)); - this.cachedMapState = cacheMap; + this.cachedTabsState = cacheMap; } @Mutation @@ -162,7 +130,7 @@ class Tab extends VuexModule { @Mutation commitResetState(): void { this.tabsState = []; - this.cachedMapState = new Map(); + this.cachedTabsState = new Set(); } @Mutation @@ -190,7 +158,7 @@ class Tab extends VuexModule { ) { return; } - this.commitTabRoutesState(getRoute(route)); + this.commitTabRoutesState(getRawRoute(route)); this.commitCachedMapState(); } @@ -198,17 +166,12 @@ class Tab extends VuexModule { @Mutation async commitRedoPage() { const route = router.currentRoute.value; - for (const [key, value] of this.cachedMapState) { - const index = value.findIndex((item) => item === (route.name as string)); - if (index === -1) { - continue; - } - if (value.length === 1) { - this.cachedMapState.delete(key); - continue; - } - value.splice(index, 1); - this.cachedMapState.set(key, 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(); diff --git a/src/utils/index.ts b/src/utils/index.ts index b9fb89df..81bf4322 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,9 +1,8 @@ -export const timestamp = () => +Date.now(); +import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'; import { unref } from 'vue'; import { isObject } from '/@/utils/is'; -export const clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n)); + export const noop = () => {}; -export const now = () => Date.now(); /** * @description: Set ui mount node @@ -91,3 +90,18 @@ export function setTitle(title: string, appTitle?: string) { setDocumentTitle(_title); } } + +export function getRawRoute(route: RouteLocationNormalized): RouteLocationNormalized { + if (!route) return route; + const { matched, ...opt } = route; + return { + ...opt, + matched: (matched + ? matched.map((item) => ({ + meta: item.meta, + name: item.name, + path: item.path, + })) + : undefined) as RouteRecordNormalized[], + }; +} diff --git a/vite.config.ts b/vite.config.ts index cacdd40b..114737f0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -43,7 +43,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { }, build: { - minify: 'esbuild', + // minify: 'esbuild', outDir: OUTPUT_DIR, polyfillDynamicImport: VITE_LEGACY, terserOptions: {