Skip to content

Commit 3d9dba9

Browse files
authored
perf: perf the control logic of Tab (#6220)
* perf: perf the control logic of Tab * 每个标签页Tab使用唯一的key来控制关闭打开等逻辑 * 统一函数获取tab的key * 通过3种方式设置tab key:1、使用router query参数pageKey 2、使用路由meta参数fullPathKey设置使用fullPath或path作为key * 单个路由可以打开多个标签页 * 如果设置fullPathKey为false,则query变更不会打开新的标签(这很实用) * perf: perf the control logic of Tab * perf: perf the control logic of Tab * 测试用例适配 * perf: perf the control logic of Tab * 解决AI提示的警告
1 parent 024c01d commit 3d9dba9

File tree

10 files changed

+199
-104
lines changed

10 files changed

+199
-104
lines changed

docs/src/guide/essentials/route.md

+40
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,10 @@ interface RouteMeta {
339339
| 'success'
340340
| 'warning'
341341
| string;
342+
/**
343+
* 路由的完整路径作为key(默认true)
344+
*/
345+
fullPathKey?: boolean;
342346
/**
343347
* 当前路由的子级在菜单中不展现
344348
* @default false
@@ -502,6 +506,13 @@ interface RouteMeta {
502506

503507
用于配置页面的徽标颜色。
504508

509+
### fullPathKey
510+
511+
- 类型:`boolean`
512+
- 默认值:`true`
513+
514+
是否将路由的完整路径作为tab key(默认true)
515+
505516
### activePath
506517

507518
- 类型:`string`
@@ -602,3 +613,32 @@ const { refresh } = useRefresh();
602613
refresh();
603614
</script>
604615
```
616+
617+
## 标签页与路由控制
618+
619+
在某些场景下,需要单个路由打开多个标签页,或者修改路由的query不打开新的标签页
620+
621+
每个标签页Tab使用唯一的key标识,设置Tab key有三种方式,优先级由高到低:
622+
623+
- 使用路由query参数pageKey
624+
625+
```vue
626+
<script setup lang="ts">
627+
import { useRouter } from 'vue-router';
628+
// 跳转路由
629+
const router = useRouter();
630+
router.push({
631+
path: 'path',
632+
query: {
633+
pageKey: 'key',
634+
},
635+
});
636+
```
637+
638+
- 路由的完整路径作为key
639+
640+
`meta` 属性中的 `fullPathKey`不为false,则使用路由`fullPath`作为key
641+
642+
- 路由的path作为key
643+
644+
`meta` 属性中的 `fullPathKey`为false,则使用路由`path`作为key
+6-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
import type { RouteLocationNormalized } from 'vue-router';
22

3-
export type TabDefinition = RouteLocationNormalized;
3+
export interface TabDefinition extends RouteLocationNormalized {
4+
/**
5+
* 标签页的key
6+
*/
7+
key?: string;
8+
}

packages/@core/base/typings/src/vue-router.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ interface RouteMeta {
4343
| 'success'
4444
| 'warning'
4545
| string;
46+
/**
47+
* 路由的完整路径作为key(默认true)
48+
*/
49+
fullPathKey?: boolean;
4650
/**
4751
* 当前路由的子级在菜单中不展现
4852
* @default false

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ const style = computed(() => {
4040
4141
const tabsView = computed(() => {
4242
return props.tabs.map((tab) => {
43-
const { fullPath, meta, name, path } = tab || {};
43+
const { fullPath, meta, name, path, key } = tab || {};
4444
const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};
4545
return {
4646
affixTab: !!affixTab,
4747
closable: Reflect.has(meta, 'tabClosable') ? !!tabClosable : true,
4848
fullPath,
4949
icon: icon as string,
50-
key: fullPath || path,
50+
key,
5151
meta,
5252
name,
5353
path,

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@ const typeWithClass = computed(() => {
4747
4848
const tabsView = computed(() => {
4949
return props.tabs.map((tab) => {
50-
const { fullPath, meta, name, path } = tab || {};
50+
const { fullPath, meta, name, path, key } = tab || {};
5151
const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};
5252
return {
5353
affixTab: !!affixTab,
5454
closable: Reflect.has(meta, 'tabClosable') ? !!tabClosable : true,
5555
fullPath,
5656
icon: icon as string,
57-
key: fullPath || path,
57+
key,
5858
meta,
5959
name,
6060
path,

packages/effects/layouts/src/basic/content/content.vue

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { computed } from 'vue';
99
import { RouterView } from 'vue-router';
1010
1111
import { preferences, usePreferences } from '@vben/preferences';
12-
import { storeToRefs, useTabbarStore } from '@vben/stores';
12+
import { getTabKey, storeToRefs, useTabbarStore } from '@vben/stores';
1313
1414
import { IFrameRouterView } from '../../iframe';
1515
@@ -115,13 +115,13 @@ function transformComponent(
115115
:is="transformComponent(Component, route)"
116116
v-if="renderRouteView"
117117
v-show="!route.meta.iframeSrc"
118-
:key="route.fullPath"
118+
:key="getTabKey(route)"
119119
/>
120120
</KeepAlive>
121121
<component
122122
:is="Component"
123123
v-else-if="renderRouteView"
124-
:key="route.fullPath"
124+
:key="getTabKey(route)"
125125
/>
126126
</Transition>
127127
<template v-else>
@@ -134,13 +134,13 @@ function transformComponent(
134134
:is="transformComponent(Component, route)"
135135
v-if="renderRouteView"
136136
v-show="!route.meta.iframeSrc"
137-
:key="route.fullPath"
137+
:key="getTabKey(route)"
138138
/>
139139
</KeepAlive>
140140
<component
141141
:is="Component"
142142
v-else-if="renderRouteView"
143-
:key="route.fullPath"
143+
:key="getTabKey(route)"
144144
/>
145145
</template>
146146
</RouterView>

packages/effects/layouts/src/basic/tabbar/tabbar.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const {
3030
} = useTabbar();
3131
3232
const menus = computed(() => {
33-
const tab = tabbarStore.getTabByPath(currentActive.value);
33+
const tab = tabbarStore.getTabByKey(currentActive.value);
3434
const menus = createContextMenus(tab);
3535
return menus.map((item) => {
3636
return {

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
X,
2323
} from '@vben/icons';
2424
import { $t, useI18n } from '@vben/locales';
25-
import { useAccessStore, useTabbarStore } from '@vben/stores';
25+
import { getTabKey, useAccessStore, useTabbarStore } from '@vben/stores';
2626
import { filterTree } from '@vben/utils';
2727

2828
export function useTabbar() {
@@ -44,8 +44,11 @@ export function useTabbar() {
4444
toggleTabPin,
4545
} = useTabs();
4646

47+
/**
48+
* 当前路径对应的tab的key
49+
*/
4750
const currentActive = computed(() => {
48-
return route.fullPath;
51+
return getTabKey(route);
4952
});
5053

5154
const { locale } = useI18n();
@@ -73,7 +76,8 @@ export function useTabbar() {
7376

7477
// 点击tab,跳转路由
7578
const handleClick = (key: string) => {
76-
router.push(key);
79+
const { fullPath, path } = tabbarStore.getTabByKey(key);
80+
router.push(fullPath || path);
7781
};
7882

7983
// 关闭tab
@@ -100,7 +104,7 @@ export function useTabbar() {
100104
);
101105

102106
watch(
103-
() => route.path,
107+
() => route.fullPath,
104108
() => {
105109
const meta = route.matched?.[route.matched.length - 1]?.meta;
106110
tabbarStore.addTab({

packages/stores/src/modules/tabbar.test.ts

+22-17
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ describe('useAccessStore', () => {
2222
const tab: any = {
2323
fullPath: '/home',
2424
meta: {},
25+
key: '/home',
2526
name: 'Home',
2627
path: '/home',
2728
};
28-
store.addTab(tab);
29+
const addNewTab = store.addTab(tab);
2930
expect(store.tabs.length).toBe(1);
30-
expect(store.tabs[0]).toEqual(tab);
31+
expect(store.tabs[0]).toEqual(addNewTab);
3132
});
3233

3334
it('adds a new tab if it does not exist', () => {
@@ -38,20 +39,22 @@ describe('useAccessStore', () => {
3839
name: 'New',
3940
path: '/new',
4041
};
41-
store.addTab(newTab);
42-
expect(store.tabs).toContainEqual(newTab);
42+
const addNewTab = store.addTab(newTab);
43+
expect(store.tabs).toContainEqual(addNewTab);
4344
});
4445

4546
it('updates an existing tab instead of adding a new one', () => {
4647
const store = useTabbarStore();
4748
const initialTab: any = {
4849
fullPath: '/existing',
49-
meta: {},
50+
meta: {
51+
fullPathKey: false,
52+
},
5053
name: 'Existing',
5154
path: '/existing',
5255
query: {},
5356
};
54-
store.tabs.push(initialTab);
57+
store.addTab(initialTab);
5558
const updatedTab = { ...initialTab, query: { id: '1' } };
5659
store.addTab(updatedTab);
5760
expect(store.tabs.length).toBe(1);
@@ -60,9 +63,12 @@ describe('useAccessStore', () => {
6063

6164
it('closes all tabs', async () => {
6265
const store = useTabbarStore();
63-
store.tabs = [
64-
{ fullPath: '/home', meta: {}, name: 'Home', path: '/home' },
65-
] as any;
66+
store.addTab({
67+
fullPath: '/home',
68+
meta: {},
69+
name: 'Home',
70+
path: '/home',
71+
} as any);
6672
router.replace = vi.fn();
6773

6874
await store.closeAllTabs(router);
@@ -157,7 +163,7 @@ describe('useAccessStore', () => {
157163
path: '/contact',
158164
} as any);
159165

160-
await store._bulkCloseByPaths(['/home', '/contact']);
166+
await store._bulkCloseByKeys(['/home', '/contact']);
161167

162168
expect(store.tabs).toHaveLength(1);
163169
expect(store.tabs[0]?.name).toBe('About');
@@ -183,9 +189,8 @@ describe('useAccessStore', () => {
183189
name: 'Contact',
184190
path: '/contact',
185191
};
186-
store.addTab(targetTab);
187-
188-
await store.closeLeftTabs(targetTab);
192+
const addTargetTab = store.addTab(targetTab);
193+
await store.closeLeftTabs(addTargetTab);
189194

190195
expect(store.tabs).toHaveLength(1);
191196
expect(store.tabs[0]?.name).toBe('Contact');
@@ -205,15 +210,15 @@ describe('useAccessStore', () => {
205210
name: 'About',
206211
path: '/about',
207212
};
208-
store.addTab(targetTab);
213+
const addTargetTab = store.addTab(targetTab);
209214
store.addTab({
210215
fullPath: '/contact',
211216
meta: {},
212217
name: 'Contact',
213218
path: '/contact',
214219
} as any);
215220

216-
await store.closeOtherTabs(targetTab);
221+
await store.closeOtherTabs(addTargetTab);
217222

218223
expect(store.tabs).toHaveLength(1);
219224
expect(store.tabs[0]?.name).toBe('About');
@@ -227,7 +232,7 @@ describe('useAccessStore', () => {
227232
name: 'Home',
228233
path: '/home',
229234
};
230-
store.addTab(targetTab);
235+
const addTargetTab = store.addTab(targetTab);
231236
store.addTab({
232237
fullPath: '/about',
233238
meta: {},
@@ -241,7 +246,7 @@ describe('useAccessStore', () => {
241246
path: '/contact',
242247
} as any);
243248

244-
await store.closeRightTabs(targetTab);
249+
await store.closeRightTabs(addTargetTab);
245250

246251
expect(store.tabs).toHaveLength(1);
247252
expect(store.tabs[0]?.name).toBe('Home');

0 commit comments

Comments
 (0)