+
+
@@ -132,14 +72,14 @@ function transformComponent(
>
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,
},
},
{