diff --git a/docs/src/guide/essentials/route.md b/docs/src/guide/essentials/route.md index 985c48a9..8383e762 100644 --- a/docs/src/guide/essentials/route.md +++ b/docs/src/guide/essentials/route.md @@ -599,6 +599,13 @@ _注意:_ 排序仅针对一级菜单有效,二级菜单的排序需要在对 用于配置当前路由不使用基础布局,仅在顶级时生效。默认情况下,所有的路由都会被包裹在基础布局中(包含顶部以及侧边等导航部件),如果你的页面不需要这些部件,可以设置 `noBasicLayout` 为 `true`。 +### domCached + +- 类型:`boolean` +- 默认值:`false` + +用于配置当前路由是否要将route对应dom元素缓存起来。对于一些复杂页面切换tab浏览器回流/重绘会导致卡顿, `domCached` 设为 `true`可解决该问题,但是也有代价:1、内存占用升高 2、vue的部分生命周期不会触发 + ## 路由刷新 路由刷新方式如下: diff --git a/packages/@core/base/typings/src/vue-router.d.ts b/packages/@core/base/typings/src/vue-router.d.ts index 1577520e..d7a80fe4 100644 --- a/packages/@core/base/typings/src/vue-router.d.ts +++ b/packages/@core/base/typings/src/vue-router.d.ts @@ -43,6 +43,10 @@ interface RouteMeta { | 'success' | 'warning' | string; + /** + * 路由对应dom是否缓存起来 + */ + domCached?: boolean; /** * 路由的完整路径作为key(默认true) */ diff --git a/packages/effects/layouts/src/basic/content/content.vue b/packages/effects/layouts/src/basic/content/content.vue index 8b8a6391..2d6bb916 100644 --- a/packages/effects/layouts/src/basic/content/content.vue +++ b/packages/effects/layouts/src/basic/content/content.vue @@ -1,17 +1,15 @@ diff --git a/packages/effects/layouts/src/hooks/index.ts b/packages/effects/layouts/src/hooks/index.ts new file mode 100644 index 00000000..5f825ac9 --- /dev/null +++ b/packages/effects/layouts/src/hooks/index.ts @@ -0,0 +1,98 @@ +import type { VNode } from 'vue'; +import type { + RouteLocationNormalizedLoaded, + RouteLocationNormalizedLoadedGeneric, +} from 'vue-router'; + +import { computed } from 'vue'; + +import { preferences, usePreferences } from '@vben/preferences'; + +/** + * 转换组件,自动添加 name + * @param component + * @param route + */ +export function transformComponent( + component: VNode, + route: RouteLocationNormalizedLoadedGeneric, +) { + // 组件视图未找到,如果有设置后备视图,则返回后备视图,如果没有,则抛出错误 + if (!component) { + console.error( + 'Component view not found,please check the route configuration', + ); + return undefined; + } + + const routeName = route.name as string; + // 如果组件没有 name,则直接返回 + if (!routeName) { + return component; + } + const componentName = (component?.type as any)?.name; + + // 已经设置过 name,则直接返回 + if (componentName) { + return component; + } + + // componentName 与 routeName 一致,则直接返回 + if (componentName === routeName) { + return component; + } + + // 设置 name + component.type ||= {}; + (component.type as any).name = routeName; + + return component; +} + +/** + * Layout相关hook + */ +export function useLayoutHook() { + const { keepAlive } = usePreferences(); + /** + * 是否使用动画 + */ + const getEnabledTransition = computed(() => { + const { transition } = preferences; + const transitionName = transition.name; + return transitionName && transition.enable; + }); + + /** + * 获取路由过渡动画 + * @param _route + */ + function getTransitionName(_route: RouteLocationNormalizedLoaded) { + // 如果偏好设置未设置,则不使用动画 + const { tabbar, transition } = preferences; + const transitionName = transition.name; + if (!transitionName || !transition.enable) { + return; + } + + // 标签页未启用或者未开启缓存,则使用全局配置动画 + if (!tabbar.enable || !keepAlive) { + return transitionName; + } + + // 如果页面已经加载过,则不使用动画 + // if (route.meta.loaded) { + // return; + // } + // 已经打开且已经加载过的页面不使用动画 + // const inTabs = getCachedTabs.value.includes(route.name as string); + + // return inTabs && route.meta.loaded ? undefined : transitionName; + return transitionName; + } + + return { + getEnabledTransition, + getTransitionName, + }; +} diff --git a/packages/effects/layouts/src/route-cached/index.ts b/packages/effects/layouts/src/route-cached/index.ts new file mode 100644 index 00000000..daae4a07 --- /dev/null +++ b/packages/effects/layouts/src/route-cached/index.ts @@ -0,0 +1,2 @@ +export { default as RouteCachedPage } from './route-cached-page.vue'; +export { default as RouteCachedView } from './route-cached-view.vue'; diff --git a/packages/effects/layouts/src/route-cached/route-cached-page.vue b/packages/effects/layouts/src/route-cached/route-cached-page.vue new file mode 100644 index 00000000..63708ce8 --- /dev/null +++ b/packages/effects/layouts/src/route-cached/route-cached-page.vue @@ -0,0 +1,36 @@ + + diff --git a/packages/effects/layouts/src/route-cached/route-cached-view.vue b/packages/effects/layouts/src/route-cached/route-cached-view.vue new file mode 100644 index 00000000..96616aa1 --- /dev/null +++ b/packages/effects/layouts/src/route-cached/route-cached-view.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/packages/stores/src/modules/tabbar.ts b/packages/stores/src/modules/tabbar.ts index 66a3758b..14af7be9 100644 --- a/packages/stores/src/modules/tabbar.ts +++ b/packages/stores/src/modules/tabbar.ts @@ -1,13 +1,15 @@ -import type { ComputedRef } from 'vue'; +import type { ComputedRef, VNode } from 'vue'; import type { RouteLocationNormalized, + RouteLocationNormalizedLoaded, + RouteLocationNormalizedLoadedGeneric, Router, RouteRecordNormalized, } from 'vue-router'; import type { TabDefinition } from '@vben-core/typings'; -import { toRaw } from 'vue'; +import { markRaw, toRaw } from 'vue'; import { preferences } from '@vben-core/preferences'; import { @@ -20,7 +22,14 @@ import { import { acceptHMRUpdate, defineStore } from 'pinia'; +interface RouteCached { + component: VNode; + key: string; + route: RouteLocationNormalizedLoadedGeneric; +} + interface TabbarState { + cachedRoutes: Map; /** * @zh_CN 当前打开的标签页列表缓存 */ @@ -553,6 +562,25 @@ export const useTabbarStore = defineStore('core-tabbar', { } this.cachedTabs = cacheMap; }, + /** + * 添加缓存的route + * @param component + * @param route + */ + addCachedRoute(component: VNode, route: RouteLocationNormalizedLoaded) { + const key = getTabKey(route); + if (this.cachedRoutes.has(key)) { + return; + } + this.cachedRoutes.set(key, { + key, + component: markRaw(component), + route: markRaw(route), + }); + }, + removeCachedRoute(key: string) { + this.cachedRoutes.delete(key); + }, }, getters: { affixTabs(): TabDefinition[] { @@ -577,6 +605,9 @@ export const useTabbarStore = defineStore('core-tabbar', { const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab)); return [...this.affixTabs, ...normalTabs].filter(Boolean); }, + getCachedRoutes(): Map { + return this.cachedRoutes; + }, }, persist: [ // tabs不需要保存在localStorage @@ -604,6 +635,7 @@ export const useTabbarStore = defineStore('core-tabbar', { ], state: (): TabbarState => ({ visitHistory: createStack(true, MAX_VISIT_HISTORY), + cachedRoutes: new Map(), cachedTabs: new Set(), dragEndIndex: 0, excludeCachedTabs: new Set(), diff --git a/playground/src/router/routes/modules/demos.ts b/playground/src/router/routes/modules/demos.ts index b8d27ef5..8cb9f511 100644 --- a/playground/src/router/routes/modules/demos.ts +++ b/playground/src/router/routes/modules/demos.ts @@ -30,6 +30,7 @@ const routes: RouteRecordRaw[] = [ meta: { icon: 'mdi:page-previous-outline', title: $t('demos.access.pageAccess'), + domCached: true, }, }, {