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

This commit is contained in:
vben 2024-07-20 11:14:32 +08:00
parent bcd5e49117
commit 480580f104
10 changed files with 92 additions and 3 deletions

View File

@ -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",

View File

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

View File

@ -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',

View File

@ -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>

View File

@ -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>

View File

@ -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',

View File

@ -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 {
// 页面已经存在,不重复添加选项卡,只更新选项卡参数

View File

@ -85,6 +85,11 @@ interface RouteMeta {
*
*/
loaded?: boolean;
/**
*
* @default false
*/
maxNumOfOpenTab?: number;
/**
* 访403
*/

View File

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

View File

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