Browse Source

feat: add 'maxNumOfOpenTab' to limit the maximum number of tabs with the same name

vben 8 months ago
parent
commit
480580f104

+ 2 - 1
apps/web-antd/src/locales/langs/en-US.json

@@ -38,7 +38,8 @@
         "title": "Features",
         "hideChildrenInMenu": "Hide Menu Children",
         "loginExpired": "Login Expired",
-        "tabs": "Tabs"
+        "tabs": "Tabs",
+        "tabDetail": "Tab Detail Page"
       },
       "breadcrumb": {
         "navigation": "Breadcrumb Navigation",

+ 2 - 1
apps/web-antd/src/locales/langs/zh-CN.json

@@ -40,7 +40,8 @@
         "title": "功能",
         "hideChildrenInMenu": "隐藏子菜单",
         "loginExpired": "登录过期",
-        "tabs": "标签页"
+        "tabs": "标签页",
+        "tabDetail": "标签详情页"
       },
       "breadcrumb": {
         "navigation": "面包屑导航",

+ 12 - 0
apps/web-antd/src/router/routes/modules/demos.ts

@@ -107,6 +107,18 @@ const routes: RouteRecordRaw[] = [
               title: $t('page.demos.features.tabs'),
             },
           },
+          {
+            name: 'FeatureTabDetailDemo',
+            path: 'tabs/detail/:id',
+            component: () =>
+              import('#/views/demos/features/tabs/tab-detail.vue'),
+            meta: {
+              activePath: '/demos/features/tabs',
+              hideInMenu: true,
+              maxNumOfOpenTab: 3,
+              title: $t('page.demos.features.tabDetail'),
+            },
+          },
           {
             name: 'HideChildrenInMenuParent',
             path: 'hide-menu-children',

+ 19 - 0
apps/web-antd/src/views/demos/features/tabs/index.vue

@@ -28,6 +28,11 @@ function openTab() {
   router.push({ name: 'VbenAbout' });
 }
 
+function openTabWithParams(id: number) {
+  // 这里就是路由跳转,也可以用path
+  router.push({ name: 'FeatureTabDetailDemo', params: { id } });
+}
+
 function reset() {
   newTabTitle.value = '';
   resetTabTitle();
@@ -92,5 +97,19 @@ function reset() {
         <Button @click="reset"> 重置 </Button>
       </div>
     </div>
+
+    <div class="card-box mt-5 p-5">
+      <div class="text-lg font-semibold">最大打开数量</div>
+      <div class="text-foreground/80 my-3">
+        限制带参数的tab打开的最大数量,由 `route.meta.maxNumOfOpenTab` 控制
+      </div>
+      <div class="flex flex-wrap items-center gap-3">
+        <template v-for="item in 5" :key="item">
+          <Button type="primary" @click="openTabWithParams(item)">
+            打开{{ item }}详情页
+          </Button>
+        </template>
+      </div>
+    </div>
   </div>
 </template>

+ 29 - 0
apps/web-antd/src/views/demos/features/tabs/tab-detail.vue

@@ -0,0 +1,29 @@
+<script lang="ts" setup>
+import { computed } from 'vue';
+import { useRoute } from 'vue-router';
+
+import { useTabs } from '@vben/hooks';
+
+defineOptions({ name: 'FeatureTabDetailDemo' });
+
+const route = useRoute();
+
+const { setTabTitle } = useTabs();
+
+const index = computed(() => {
+  return route.params?.id ?? -1;
+});
+
+setTabTitle(`No.${index.value} - 详情信息`);
+</script>
+
+<template>
+  <div class="p-5">
+    <div class="card-box p-5">
+      <h1 class="text-xl font-semibold">标签详情页</h1>
+      <div class="text-foreground/80 mt-2">
+        <div>{{ index }} - 详情页内容在此</div>
+      </div>
+    </div>
+  </div>
+</template>

+ 1 - 0
internal/lint-configs/eslint-config/src/custom-config.ts

@@ -121,6 +121,7 @@ const customConfig: Linter.FlatConfig[] = [
     files: ['apps/backend-mock/**/**'],
     rules: {
       '@typescript-eslint/no-extraneous-class': 'off',
+      'n/no-extraneous-import': 'off',
       'n/prefer-global/buffer': 'off',
       'no-console': 'off',
       'unicorn/prefer-module': 'off',

+ 17 - 0
packages/@core/forward/stores/src/modules/tabbar.ts

@@ -100,6 +100,23 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
       });
 
       if (tabIndex === -1) {
+        // 获取动态路由打开数,超过 0 即代表需要控制打开数
+        const maxNumOfOpenTab = (routeTab?.meta?.maxNumOfOpenTab ??
+          -1) as number;
+        // 如果动态路由层级大于 0 了,那么就要限制该路由的打开数限制了
+        // 获取到已经打开的动态路由数, 判断是否大于某一个值
+        if (
+          maxNumOfOpenTab > 0 &&
+          this.tabs.filter((tab) => tab.name === routeTab.name).length >=
+            maxNumOfOpenTab
+        ) {
+          // 关闭第一个
+          const index = this.tabs.findIndex(
+            (item) => item.name === routeTab.name,
+          );
+          index !== -1 && this.tabs.splice(index, 1);
+        }
+
         this.tabs.push(tab);
       } else {
         // 页面已经存在,不重复添加选项卡,只更新选项卡参数

+ 5 - 0
packages/@core/shared/typings/src/vue-router.d.ts

@@ -85,6 +85,11 @@ interface RouteMeta {
    * 路由是否已经加载过
    */
   loaded?: boolean;
+  /**
+   * 标签页最大打开数量
+   * @default false
+   */
+  maxNumOfOpenTab?: number;
   /**
    * 菜单可以看到,但是访问会被重定向到403
    */

+ 1 - 1
packages/effects/layouts/src/basic/tabbar/use-tabbar.ts

@@ -50,7 +50,7 @@ export function useTabbar() {
     toggleTabPin,
   } = useTabs();
   const currentActive = computed(() => {
-    return route.path;
+    return route.fullPath;
   });
 
   const { locale } = useI18n();

+ 4 - 0
turbo.json

@@ -18,6 +18,10 @@
       "dependsOn": ["^build"],
       "outputs": ["dist/**"]
     },
+    "@vben/backend-mock#build": {
+      "dependsOn": ["^build"],
+      "outputs": [".nitro/**", ".output/**"]
+    },
     "stub": {},
     "dev": {
       "dependsOn": [],