1
0
Эх сурвалжийг харах

feat: configurable persistent tabs

vince 8 сар өмнө
parent
commit
db76325d68
23 өөрчлөгдсөн 176 нэмэгдсэн , 175 устгасан
  1. 2 2
      apps/web-antd/src/forward/request.ts
  2. 4 4
      apps/web-antd/src/store/modules/access.ts
  3. 1 0
      packages/@core/forward/preferences/src/config.ts
  4. 2 0
      packages/@core/forward/preferences/src/types.ts
  5. 3 3
      packages/@core/forward/stores/src/modules/access.test.ts
  6. 0 26
      packages/@core/forward/stores/src/modules/access.ts
  7. 4 4
      packages/@core/forward/stores/src/modules/tabbar.test.ts
  8. 95 91
      packages/@core/forward/stores/src/modules/tabbar.ts
  9. 1 2
      packages/@core/forward/stores/src/setup.ts
  10. 1 0
      packages/@core/locales/src/langs/en-US.json
  11. 1 0
      packages/@core/locales/src/langs/zh-CN.json
  12. 4 4
      packages/@core/ui-kit/tabs-ui/src/components/chrome-tabs/tab.vue
  13. 4 4
      packages/@core/ui-kit/tabs-ui/src/components/chrome-tabs/tabs.vue
  14. 2 2
      packages/business/access/src/use-access.ts
  15. 6 6
      packages/business/layouts/src/basic/content/content.vue
  16. 1 1
      packages/business/layouts/src/basic/header/header.vue
  17. 1 1
      packages/business/layouts/src/basic/menu/use-extra-menu.ts
  18. 1 1
      packages/business/layouts/src/basic/menu/use-mixed-menu.ts
  19. 15 2
      packages/business/layouts/src/basic/tabbar/tabbar.vue
  20. 18 18
      packages/business/layouts/src/basic/tabbar/use-tabs.ts
  21. 4 4
      packages/business/layouts/src/iframe/iframe-router-view.vue
  22. 4 0
      packages/business/layouts/src/widgets/preferences/blocks/layout/tabbar.vue
  23. 2 0
      packages/business/layouts/src/widgets/preferences/preferences-sheet.vue

+ 2 - 2
apps/web-antd/src/forward/request.ts

@@ -33,8 +33,8 @@ function createRequestClient() {
           // 这里不能用 useAccessStore,因为 useAccessStore 会导致循环引用
           const accessStore = useCoreAccessStore();
           return {
-            refreshToken: `Bearer ${accessStore.getRefreshToken}`,
-            token: `Bearer ${accessStore.getAccessToken}`,
+            refreshToken: `Bearer ${accessStore.refreshToken}`,
+            token: `Bearer ${accessStore.accessToken}`,
           };
         },
         // 默认

+ 4 - 4
apps/web-antd/src/store/modules/access.ts

@@ -17,10 +17,10 @@ export const useAccessStore = defineStore('access', () => {
   const router = useRouter();
   const loading = ref(false);
 
-  const accessToken = computed(() => coreStoreAccess.getAccessToken);
-  const userRoles = computed(() => coreStoreAccess.getUserRoles);
-  const userInfo = computed(() => coreStoreAccess.getUserInfo);
-  const accessRoutes = computed(() => coreStoreAccess.getAccessRoutes);
+  const accessToken = computed(() => coreStoreAccess.accessToken);
+  const userRoles = computed(() => coreStoreAccess.userRoles);
+  const userInfo = computed(() => coreStoreAccess.userInfo);
+  const accessRoutes = computed(() => coreStoreAccess.accessRoutes);
 
   function setAccessMenus(menus: MenuRecordRaw[]) {
     coreStoreAccess.setAccessMenus(menus);

+ 1 - 0
packages/@core/forward/preferences/src/config.ts

@@ -71,6 +71,7 @@ const defaultPreferences: Preferences = {
   tabbar: {
     enable: true,
     keepAlive: true,
+    persist: true,
     showIcon: true,
   },
   theme: {

+ 2 - 0
packages/@core/forward/preferences/src/types.ts

@@ -143,6 +143,8 @@ interface TabbarPreferences {
   enable: boolean;
   /** 开启标签页缓存功能 */
   keepAlive: boolean;
+  /** 是否持久化标签 */
+  persist: boolean;
   /** 是否开启多标签页图标 */
   showIcon: boolean;
 }

+ 3 - 3
packages/@core/forward/stores/src/modules/access.test.ts

@@ -33,7 +33,7 @@ describe('useCoreAccessStore', () => {
     const store = useCoreAccessStore();
     const userInfo: any = { name: 'Jane Doe', roles: [{ value: 'user' }] };
     store.setUserInfo(userInfo);
-    expect(store.getUserInfo).toEqual(userInfo);
+    expect(store.userInfo).toEqual(userInfo);
   });
 
   it('updates accessToken state correctly', () => {
@@ -60,13 +60,13 @@ describe('useCoreAccessStore', () => {
   it('returns the correct accessToken', () => {
     const store = useCoreAccessStore();
     store.setAccessToken('xyz789');
-    expect(store.getAccessToken).toBe('xyz789');
+    expect(store.accessToken).toBe('xyz789');
   });
 
   // 测试在没有用户角色时返回空数组
   it('returns an empty array for userRoles if not set', () => {
     const store = useCoreAccessStore();
-    expect(store.getUserRoles).toEqual([]);
+    expect(store.userRoles).toEqual([]);
   });
 
   // 测试设置空的访问菜单列表

+ 0 - 26
packages/@core/forward/stores/src/modules/access.ts

@@ -90,32 +90,6 @@ const useCoreAccessStore = defineStore('core-access', {
       this.userRoles = roles;
     },
   },
-  getters: {
-    getAccessCodes(): string[] {
-      return this.accessCodes;
-    },
-    getAccessMenus(): MenuRecordRaw[] {
-      return this.accessMenus;
-    },
-    getAccessRoutes(): RouteRecordRaw[] {
-      return this.accessRoutes;
-    },
-    getAccessToken(): AccessToken {
-      return this.accessToken;
-    },
-    getRefreshToken(): AccessToken {
-      return this.refreshToken;
-    },
-    getUserInfo(): BasicUserInfo | null {
-      return this.userInfo;
-    },
-    getUserRoles(): string[] {
-      return this.userRoles;
-    },
-    string(): string[] {
-      return this.accessCodes;
-    },
-  },
   persist: {
     // 持久化
     paths: ['accessToken', 'refreshToken', 'accessCodes'],

+ 4 - 4
packages/@core/forward/stores/src/modules/tabbar.test.ts

@@ -99,9 +99,9 @@ describe('useCoreAccessStore', () => {
 
   it('returns all cache tabs', () => {
     const store = useCoreTabbarStore();
-    store.cacheTabs.add('Home');
-    store.cacheTabs.add('About');
-    expect(store.getCacheTabs).toEqual(['Home', 'About']);
+    store.cachedTabs.add('Home');
+    store.cachedTabs.add('About');
+    expect(store.cachedTabs).toEqual(['Home', 'About']);
   });
 
   it('returns all tabs, including affix tabs', () => {
@@ -290,7 +290,7 @@ describe('useCoreAccessStore', () => {
 
     await store.refresh(router);
 
-    expect(store.excludeCacheTabs.has('Dashboard')).toBe(false);
+    expect(store.excludeCachedTabs.has('Dashboard')).toBe(false);
     expect(store.renderRouteView).toBe(true);
   });
 });

+ 95 - 91
packages/@core/forward/stores/src/modules/tabbar.ts

@@ -7,60 +7,15 @@ import { startProgress, stopProgress } from '@vben-core/toolkit';
 
 import { acceptHMRUpdate, defineStore } from 'pinia';
 
-/**
- * @zh_CN 克隆路由,防止路由被修改
- * @param route
- */
-function cloneTab(route: TabItem): TabItem {
-  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[],
-  };
-}
-
-/**
- * @zh_CN 是否是固定标签页
- * @param tab
- */
-function isAffixTab(tab: TabItem) {
-  return tab.meta?.affixTab ?? false;
-}
-
-/**
- * @zh_CN 是否显示标签
- * @param tab
- */
-function isTabShow(tab: TabItem) {
-  return !tab.meta.hideInTab;
-}
-
-function routeToTab(route: RouteRecordNormalized) {
-  return {
-    meta: route.meta,
-    name: route.name,
-    path: route.path,
-  } as unknown as TabItem;
-}
-
 interface TabsState {
   /**
    * @zh_CN 当前打开的标签页列表缓存
    */
-  cacheTabs: Set<string>;
+  cachedTabs: Set<string>;
   /**
    * @zh_CN 需要排除缓存的标签页
    */
-  excludeCacheTabs: Set<string>;
+  excludeCachedTabs: Set<string>;
   /**
    * @zh_CN 是否刷新
    */
@@ -81,7 +36,7 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
      */
     async _bulkCloseByPaths(paths: string[]) {
       this.tabs = this.tabs.filter((item) => {
-        return !paths.includes(this.getTabPath(item));
+        return !paths.includes(getTabPath(item));
       });
 
       this.updateCacheTab();
@@ -128,12 +83,12 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
      */
     addTab(routeTab: TabItem) {
       const tab = cloneTab(routeTab);
-      if (!isTabShow(tab)) {
+      if (!isTabShown(tab)) {
         return;
       }
 
       const tabIndex = this.tabs.findIndex((tab) => {
-        return this.getTabPath(tab) === this.getTabPath(routeTab);
+        return getTabPath(tab) === getTabPath(routeTab);
       });
 
       if (tabIndex === -1) {
@@ -159,19 +114,22 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
      */
     async closeLeftTabs(tab: TabItem) {
       const index = this.tabs.findIndex(
-        (item) => this.getTabPath(item) === this.getTabPath(tab),
+        (item) => getTabPath(item) === getTabPath(tab),
       );
 
-      if (index > 0) {
-        const leftTabs = this.tabs.slice(0, index);
-        const paths: string[] = [];
-        for (const item of leftTabs) {
-          if (!isAffixTab(tab)) {
-            paths.push(this.getTabPath(item));
-          }
+      if (index < 1) {
+        return;
+      }
+
+      const leftTabs = this.tabs.slice(0, index);
+      const paths: string[] = [];
+
+      for (const item of leftTabs) {
+        if (!isAffixTab(item)) {
+          paths.push(getTabPath(item));
         }
-        await this._bulkCloseByPaths(paths);
       }
+      await this._bulkCloseByPaths(paths);
     },
 
     /**
@@ -179,20 +137,18 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
      * @param tab
      */
     async closeOtherTabs(tab: TabItem) {
-      const closePaths = this.tabs.map((item) => this.getTabPath(item));
+      const closePaths = this.tabs.map((item) => getTabPath(item));
 
       const paths: string[] = [];
 
       for (const path of closePaths) {
         if (path !== tab.fullPath) {
-          const closeTab = this.tabs.find(
-            (item) => this.getTabPath(item) === path,
-          );
+          const closeTab = this.tabs.find((item) => getTabPath(item) === path);
           if (!closeTab) {
             continue;
           }
-          if (!isAffixTab(tab)) {
-            paths.push(this.getTabPath(closeTab));
+          if (!isAffixTab(closeTab)) {
+            paths.push(getTabPath(closeTab));
           }
         }
       }
@@ -205,7 +161,7 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
      */
     async closeRightTabs(tab: TabItem) {
       const index = this.tabs.findIndex(
-        (item) => this.getTabPath(item) === this.getTabPath(tab),
+        (item) => getTabPath(item) === getTabPath(tab),
       );
 
       if (index >= 0 && index < this.tabs.length - 1) {
@@ -213,8 +169,8 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
 
         const paths: string[] = [];
         for (const item of rightTabs) {
-          if (!isAffixTab(tab)) {
-            paths.push(this.getTabPath(item));
+          if (!isAffixTab(item)) {
+            paths.push(getTabPath(item));
           }
         }
         await this._bulkCloseByPaths(paths);
@@ -230,13 +186,13 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
       const { currentRoute } = router;
 
       // 关闭不是激活选项卡
-      if (this.getTabPath(currentRoute.value) !== this.getTabPath(tab)) {
+      if (getTabPath(currentRoute.value) !== getTabPath(tab)) {
         this._close(tab);
         this.updateCacheTab();
         return;
       }
       const index = this.getTabs.findIndex(
-        (item) => this.getTabPath(item) === this.getTabPath(currentRoute.value),
+        (item) => getTabPath(item) === getTabPath(currentRoute.value),
       );
 
       const before = this.getTabs[index - 1];
@@ -259,25 +215,21 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
      * @param key
      */
     async closeTabByKey(key: string, router: Router) {
-      const index = this.tabs.findIndex(
-        (item) => this.getTabPath(item) === key,
-      );
+      const index = this.tabs.findIndex((item) => getTabPath(item) === key);
       if (index === -1) {
         return;
       }
 
       await this.closeTab(this.tabs[index], router);
     },
-    getTabPath(tab: RouteRecordNormalized | TabItem) {
-      return decodeURIComponent((tab as TabItem).fullPath || tab.path);
-    },
+
     /**
      * @zh_CN 固定标签页
      * @param tab
      */
-    async pushPinTab(tab: TabItem) {
+    async pinTab(tab: TabItem) {
       const index = this.tabs.findIndex(
-        (item) => this.getTabPath(item) === this.getTabPath(tab),
+        (item) => getTabPath(item) === getTabPath(tab),
       );
       if (index !== -1) {
         tab.meta.affixTab = true;
@@ -291,13 +243,13 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
       const { currentRoute } = router;
       const { name } = currentRoute.value;
 
-      this.excludeCacheTabs.add(name as string);
+      this.excludeCachedTabs.add(name as string);
       this.renderRouteView = false;
       startProgress();
 
       await new Promise((resolve) => setTimeout(resolve, 200));
 
-      this.excludeCacheTabs.delete(name as string);
+      this.excludeCachedTabs.delete(name as string);
       this.renderRouteView = true;
       stopProgress();
     },
@@ -315,9 +267,9 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
      * @zh_CN 取消固定标签页
      * @param tab
      */
-    async unPushPinTab(tab: TabItem) {
+    async unpinTab(tab: TabItem) {
       const index = this.tabs.findIndex(
-        (item) => this.getTabPath(item) === this.getTabPath(tab),
+        (item) => getTabPath(item) === getTabPath(tab),
       );
 
       if (index !== -1) {
@@ -347,33 +299,32 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
         const name = tab.name as string;
         cacheMap.add(name);
       }
-      this.cacheTabs = cacheMap;
+      this.cachedTabs = cacheMap;
     },
   },
   getters: {
     affixTabs(): TabItem[] {
       return this.tabs.filter((tab) => isAffixTab(tab));
     },
-    getCacheTabs(): string[] {
-      return [...this.cacheTabs];
+    getCachedTabs(): string[] {
+      return [...this.cachedTabs];
     },
-    getExcludeTabs(): string[] {
-      return [...this.excludeCacheTabs];
+    getExcludeCachedTabs(): string[] {
+      return [...this.excludeCachedTabs];
     },
     getTabs(): TabItem[] {
       const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
       const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab));
-
       return [...affixTabs, ...normalTabs];
     },
   },
   persist: {
     // 持久化
-    paths: [],
+    paths: ['tabs'],
   },
   state: (): TabsState => ({
-    cacheTabs: new Set(),
-    excludeCacheTabs: new Set(),
+    cachedTabs: new Set(),
+    excludeCachedTabs: new Set(),
     renderRouteView: true,
     tabs: [],
   }),
@@ -385,4 +336,57 @@ if (hot) {
   hot.accept(acceptHMRUpdate(useCoreTabbarStore, hot));
 }
 
+/**
+ * @zh_CN 克隆路由,防止路由被修改
+ * @param route
+ */
+function cloneTab(route: TabItem): TabItem {
+  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[],
+  };
+}
+
+/**
+ * @zh_CN 是否是固定标签页
+ * @param tab
+ */
+function isAffixTab(tab: TabItem) {
+  return tab.meta?.affixTab ?? false;
+}
+
+/**
+ * @zh_CN 是否显示标签
+ * @param tab
+ */
+function isTabShown(tab: TabItem) {
+  return !tab.meta.hideInTab;
+}
+
+/**
+ * @zh_CN 获取标签页路径
+ * @param tab
+ */
+function getTabPath(tab: RouteRecordNormalized | TabItem) {
+  return decodeURIComponent((tab as TabItem).fullPath || tab.path);
+}
+
+function routeToTab(route: RouteRecordNormalized) {
+  return {
+    meta: route.meta,
+    name: route.name,
+    path: route.path,
+  } as TabItem;
+}
+
 export { useCoreTabbarStore };

+ 1 - 2
packages/@core/forward/stores/src/setup.ts

@@ -2,8 +2,7 @@ import { createPinia } from 'pinia';
 
 interface InitStoreOptions {
   /**
-   * @zh_CN 应用名,由于 @vben-core/stores 是公用的,后续可能有多个app,为了防止多个app缓存冲突,可在这里配置应用名
-   * 应用名将被用于持久化的前缀
+   * @zh_CN 应用名,由于 @vben-core/stores 是公用的,后续可能有多个app,为了防止多个app缓存冲突,可在这里配置应用名,应用名将被用于持久化的前缀
    */
   namespace: string;
 }

+ 1 - 0
packages/@core/locales/src/langs/en-US.json

@@ -159,6 +159,7 @@
       "title": "Tabbar",
       "enable": "Enable Tab Bar",
       "icon": "Display Tabbar Icon",
+      "persist": "Persistent tabs",
       "context-menu": {
         "reload": "Reload",
         "close": "Close",

+ 1 - 0
packages/@core/locales/src/langs/zh-CN.json

@@ -158,6 +158,7 @@
       "title": "标签栏",
       "enable": "启用标签栏",
       "icon": "显示标签栏图标",
+      "persist": "持久化标签页",
       "context-menu": {
         "reload": "重新加载",
         "close": "关闭标签页",

+ 4 - 4
packages/@core/ui-kit/tabs-ui/src/components/chrome-tabs/tab.vue

@@ -25,15 +25,15 @@ defineOptions({
 withDefaults(defineProps<Props>(), {
   icon: '',
 });
-const emit = defineEmits<{ close: []; unPushPin: [] }>();
+const emit = defineEmits<{ close: []; unpinTab: [] }>();
 
 const { b, e, is } = useNamespace('chrome-tab');
 
 function handleClose() {
   emit('close');
 }
-function handleUnPushPin() {
-  emit('unPushPin');
+function handleUnpinTab() {
+  emit('unpinTab');
 }
 </script>
 
@@ -66,7 +66,7 @@ function handleUnPushPin() {
         <div
           v-show="affixTab && !onlyOne"
           :class="[e('extra'), is('pin', true)]"
-          @click.stop="handleUnPushPin"
+          @click.stop="handleUnpinTab"
         >
           <MdiPin :class="e('extra-icon')" />
         </div>

+ 4 - 4
packages/@core/ui-kit/tabs-ui/src/components/chrome-tabs/tabs.vue

@@ -22,7 +22,7 @@ const props = withDefaults(defineProps<Props>(), {
   tabs: () => [],
 });
 
-const emit = defineEmits<{ close: [string]; unPushPin: [TabItem] }>();
+const emit = defineEmits<{ close: [string]; unpinTab: [TabItem] }>();
 
 const gap = 7;
 
@@ -77,8 +77,8 @@ onMounted(() => {
 function handleClose(key: string) {
   emit('close', key);
 }
-function handleUnPushPin(tab: TabItem) {
-  emit('unPushPin', tab);
+function handleUnpinTab(tab: TabItem) {
+  emit('unpinTab', tab);
 }
 </script>
 
@@ -103,7 +103,7 @@ function handleUnPushPin(tab: TabItem) {
           :title="tab.title"
           @click="active = tab.key"
           @close="() => handleClose(tab.key)"
-          @un-push-pin="() => handleUnPushPin(tab)"
+          @unpin-tab="() => handleUnpinTab(tab)"
         />
       </TransitionGroup>
     </div>

+ 2 - 2
packages/business/access/src/use-access.ts

@@ -15,7 +15,7 @@ function useAccess() {
    * @param roles
    */
   function hasAuthByRoles(roles: string[]) {
-    const userRoleSet = new Set(coreAccessStore.getUserRoles);
+    const userRoleSet = new Set(coreAccessStore.userRoles);
     const intersection = roles.filter((item) => userRoleSet.has(item));
     return intersection.length > 0;
   }
@@ -26,7 +26,7 @@ function useAccess() {
    * @param codes
    */
   function hasAuthByCodes(codes: string[]) {
-    const userCodesSet = new Set(coreAccessStore.getAccessCodes);
+    const userCodesSet = new Set(coreAccessStore.accessCodes);
 
     const intersection = codes.filter((item) => userCodesSet.has(item));
     return intersection.length > 0;

+ 6 - 6
packages/business/layouts/src/basic/content/content.vue

@@ -10,12 +10,12 @@ import { useContentSpinner } from './use-content-spinner';
 
 defineOptions({ name: 'LayoutContent' });
 
-const tabsStore = useCoreTabbarStore();
+const tabbarStore = useCoreTabbarStore();
 const { keepAlive } = usePreferences();
 const { spinning } = useContentSpinner();
 
-const { getCacheTabs, getExcludeTabs, renderRouteView } =
-  storeToRefs(tabsStore);
+const { getCachedTabs, getExcludeCachedTabs, renderRouteView } =
+  storeToRefs(tabbarStore);
 
 // 页面切换动画
 function getTransitionName(route: RouteLocationNormalizedLoaded) {
@@ -36,7 +36,7 @@ function getTransitionName(route: RouteLocationNormalizedLoaded) {
   //   return;
   // }
   // 已经打开且已经加载过的页面不使用动画
-  const inTabs = getCacheTabs.value.includes(route.name as string);
+  const inTabs = getCachedTabs.value.includes(route.name as string);
 
   return inTabs && route.meta.loaded ? undefined : transitionName;
 }
@@ -54,8 +54,8 @@ function getTransitionName(route: RouteLocationNormalizedLoaded) {
       <Transition :name="getTransitionName(route)" appear mode="out-in">
         <KeepAlive
           v-if="keepAlive"
-          :exclude="getExcludeTabs"
-          :include="getCacheTabs"
+          :exclude="getExcludeCachedTabs"
+          :include="getCachedTabs"
         >
           <component
             :is="Component"

+ 1 - 1
packages/business/layouts/src/basic/header/header.vue

@@ -34,7 +34,7 @@ const { globalSearchShortcutKey } = usePreferences();
   <div class="flex h-full min-w-0 flex-shrink-0 items-center">
     <GlobalSearch
       :enable-shortcut-key="globalSearchShortcutKey"
-      :menus="accessStore.getAccessMenus"
+      :menus="accessStore.accessMenus"
       class="mr-4"
     />
     <ThemeToggle class="mr-2" />

+ 1 - 1
packages/business/layouts/src/basic/menu/use-extra-menu.ts

@@ -13,7 +13,7 @@ function useExtraMenu() {
   const accessStore = useCoreAccessStore();
   const { navigation } = useNavigation();
 
-  const menus = computed(() => accessStore.getAccessMenus);
+  const menus = computed(() => accessStore.accessMenus);
 
   const route = useRoute();
   const extraMenus = ref<MenuRecordRaw[]>([]);

+ 1 - 1
packages/business/layouts/src/basic/menu/use-mixed-menu.ts

@@ -29,7 +29,7 @@ function useMixedMenu() {
     }
     return enableSidebar;
   });
-  const menus = computed(() => accessStore.getAccessMenus);
+  const menus = computed(() => accessStore.accessMenus);
 
   /**
    * 头部菜单

+ 15 - 2
packages/business/layouts/src/basic/tabbar/tabbar.vue

@@ -1,4 +1,8 @@
 <script lang="ts" setup>
+import { useRoute } from 'vue-router';
+
+import { preferences } from '@vben-core/preferences';
+import { useCoreTabbarStore } from '@vben-core/stores';
 import { TabsView } from '@vben-core/tabs-ui';
 
 import { useTabs } from './use-tabs';
@@ -9,14 +13,23 @@ defineOptions({
 
 defineProps<{ showIcon?: boolean }>();
 
+const route = useRoute();
+
+const coreTabbarStore = useCoreTabbarStore();
+
 const {
   createContextMenus,
   currentActive,
   currentTabs,
   handleClick,
   handleClose,
-  handleUnPushPin,
+  handleUnpinTab,
 } = useTabs();
+
+// 刷新后如果不保持tab状态,关闭其他tab
+if (!preferences.tabbar.persist) {
+  coreTabbarStore.closeOtherTabs(route);
+}
 </script>
 
 <template>
@@ -26,7 +39,7 @@ const {
     :show-icon="showIcon"
     :tabs="currentTabs"
     @close="handleClose"
-    @un-push-pin="handleUnPushPin"
+    @unpin-tab="handleUnpinTab"
     @update:active="handleClick"
   />
 </template>

+ 18 - 18
packages/business/layouts/src/basic/tabbar/use-tabs.ts

@@ -30,7 +30,7 @@ function useTabs() {
   const router = useRouter();
   const route = useRoute();
   const accessStore = useCoreAccessStore();
-  const tabbarStore = useCoreTabbarStore();
+  const coreTabbarStore = useCoreTabbarStore();
   const { accessMenus } = storeToRefs(accessStore);
 
   const currentActive = computed(() => {
@@ -39,7 +39,7 @@ function useTabs() {
 
   const { locale } = useI18n();
   const currentTabs = ref<RouteLocationNormalizedGeneric[]>();
-  watch([() => tabbarStore.getTabs, () => locale.value], ([tabs, _]) => {
+  watch([() => coreTabbarStore.getTabs, () => locale.value], ([tabs, _]) => {
     currentTabs.value = tabs.map((item) => wrapperTabLocale(item));
   });
 
@@ -50,7 +50,7 @@ function useTabs() {
     const affixTabs = filterTree(router.getRoutes(), (route) => {
       return !!route.meta?.affixTab;
     });
-    tabbarStore.setAffixTabs(affixTabs);
+    coreTabbarStore.setAffixTabs(affixTabs);
   };
 
   // 点击tab,跳转路由
@@ -60,7 +60,7 @@ function useTabs() {
 
   // 关闭tab
   const handleClose = async (key: string) => {
-    await tabbarStore.closeTabByKey(key, router);
+    await coreTabbarStore.closeTabByKey(key, router);
   };
 
   function wrapperTabLocale(tab: RouteLocationNormalizedGeneric) {
@@ -84,14 +84,14 @@ function useTabs() {
   watch(
     () => route.path,
     () => {
-      tabbarStore.addTab(route as RouteLocationNormalized);
+      coreTabbarStore.addTab(route as RouteLocationNormalized);
     },
     { immediate: true },
   );
 
   const createContextMenus = (tab: TabItem) => {
-    const tabs = tabbarStore.getTabs;
-    const affixTabs = tabbarStore.affixTabs;
+    const tabs = coreTabbarStore.getTabs;
+    const affixTabs = coreTabbarStore.affixTabs;
     const index = tabs.findIndex((item) => item.path === tab.path);
 
     const disabled = tabs.length <= 1;
@@ -113,7 +113,7 @@ function useTabs() {
       {
         disabled: !isCurrentTab,
         handler: async () => {
-          await tabbarStore.refresh(router);
+          await coreTabbarStore.refresh(router);
         },
         icon: IcRoundRefresh,
         key: 'reload',
@@ -122,7 +122,7 @@ function useTabs() {
       {
         disabled: !!affixTab || disabled,
         handler: async () => {
-          await tabbarStore.closeTab(tab, router);
+          await coreTabbarStore.closeTab(tab, router);
         },
         icon: IcRoundClose,
         key: 'close',
@@ -131,8 +131,8 @@ function useTabs() {
       {
         handler: async () => {
           await (affixTab
-            ? tabbarStore.unPushPinTab(tab)
-            : tabbarStore.pushPinTab(tab));
+            ? coreTabbarStore.unpinTab(tab)
+            : coreTabbarStore.pinTab(tab));
         },
         icon: affixTab ? MdiPinOff : MdiPin,
         key: 'affix',
@@ -144,7 +144,7 @@ function useTabs() {
       {
         disabled: closeLeftDisabled,
         handler: async () => {
-          await tabbarStore.closeLeftTabs(tab);
+          await coreTabbarStore.closeLeftTabs(tab);
         },
         icon: MdiFormatHorizontalAlignLeft,
         key: 'close-left',
@@ -153,7 +153,7 @@ function useTabs() {
       {
         disabled: closeRightDisabled,
         handler: async () => {
-          await tabbarStore.closeRightTabs(tab);
+          await coreTabbarStore.closeRightTabs(tab);
         },
         icon: MdiFormatHorizontalAlignRight,
         key: 'close-right',
@@ -163,7 +163,7 @@ function useTabs() {
       {
         disabled: closeOtherDisabled,
         handler: async () => {
-          await tabbarStore.closeOtherTabs(tab);
+          await coreTabbarStore.closeOtherTabs(tab);
         },
         icon: MdiArrowExpandHorizontal,
         key: 'close-other',
@@ -172,7 +172,7 @@ function useTabs() {
       {
         disabled,
         handler: async () => {
-          await tabbarStore.closeAllTabs(router);
+          await coreTabbarStore.closeAllTabs(router);
         },
         icon: IcRoundMultipleStop,
         key: 'close-all',
@@ -190,8 +190,8 @@ function useTabs() {
   /**
    * 取消固定标签页
    */
-  const handleUnPushPin = async (tab: TabItem) => {
-    await tabbarStore.unPushPinTab(tab);
+  const handleUnpinTab = async (tab: TabItem) => {
+    await coreTabbarStore.unpinTab(tab);
   };
 
   return {
@@ -200,7 +200,7 @@ function useTabs() {
     currentTabs,
     handleClick,
     handleClose,
-    handleUnPushPin,
+    handleUnpinTab,
   };
 }
 

+ 4 - 4
packages/business/layouts/src/iframe/iframe-router-view.vue

@@ -11,7 +11,7 @@ import { useCoreTabbarStore } from '@vben-core/stores';
 defineOptions({ name: 'IFrameRouterView' });
 
 const spinningList = ref<boolean[]>([]);
-const tabsStore = useCoreTabbarStore();
+const coreTabbarStore = useCoreTabbarStore();
 const route = useRoute();
 
 const enableTabbar = computed(() => preferences.tabbar.enable);
@@ -20,7 +20,7 @@ const iframeRoutes = computed(() => {
   if (!enableTabbar.value) {
     return route.meta.iframeSrc ? [route] : [];
   }
-  return tabsStore.getTabs.filter((tab) => !!tab.meta?.iframeSrc);
+  return coreTabbarStore.getTabs.filter((tab) => !!tab.meta?.iframeSrc);
 });
 
 const tabNames = computed(
@@ -36,7 +36,7 @@ function routeShow(tabItem: RouteLocationNormalized) {
 function canRender(tabItem: RouteLocationNormalized) {
   const { meta, name } = tabItem;
 
-  if (!name || !tabsStore.renderRouteView) {
+  if (!name || !coreTabbarStore.renderRouteView) {
     return false;
   }
 
@@ -52,7 +52,7 @@ function canRender(tabItem: RouteLocationNormalized) {
   ) {
     return false;
   }
-  return tabsStore.getTabs.some((tab) => tab.name === name);
+  return coreTabbarStore.getTabs.some((tab) => tab.name === name);
 }
 
 function hideLoading(index: number) {

+ 4 - 0
packages/business/layouts/src/widgets/preferences/blocks/layout/tabbar.vue

@@ -11,6 +11,7 @@ defineProps<{ disabled?: boolean }>();
 
 const tabbarEnable = defineModel<boolean>('tabbarEnable');
 const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
+const tabbarPersist = defineModel<boolean>('tabbarPersist');
 </script>
 
 <template>
@@ -20,4 +21,7 @@ const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
   <SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable">
     {{ $t('preferences.tabbar.icon') }}
   </SwitchItem>
+  <SwitchItem v-model="tabbarPersist" :disabled="!tabbarEnable">
+    {{ $t('preferences.tabbar.persist') }}
+  </SwitchItem>
 </template>

+ 2 - 0
packages/business/layouts/src/widgets/preferences/preferences-sheet.vue

@@ -96,6 +96,7 @@ const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');
 
 const tabbarEnable = defineModel<boolean>('tabbarEnable');
 const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
+const tabbarPersist = defineModel<boolean>('tabbarPersist');
 
 const navigationStyleType = defineModel<NavigationStyleType>(
   'navigationStyleType',
@@ -341,6 +342,7 @@ async function handleReset() {
             <Block :title="$t('preferences.tabbar.title')">
               <Tabbar
                 v-model:tabbar-enable="tabbarEnable"
+                v-model:tabbar-persist="tabbarPersist"
                 v-model:tabbar-show-icon="tabbarShowIcon"
               />
             </Block>