Browse Source

Merge 331da3c8c7 into f4dfb68b7b

pull/7518/merge
ming4762 9 hours ago
committed by GitHub
parent
commit
400b6567a0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 107
      packages/@core/base/shared/src/utils/__tests__/stack.test.ts
  2. 1
      packages/@core/base/shared/src/utils/index.ts
  3. 103
      packages/@core/base/shared/src/utils/stack.ts
  4. 50
      packages/stores/src/modules/tabbar.ts

107
packages/@core/base/shared/src/utils/__tests__/stack.test.ts

@ -0,0 +1,107 @@
import { beforeEach, describe, expect, it } from 'vitest';
import { createStack, Stack } from '../stack';
describe('stack', () => {
let stack: Stack<number>;
beforeEach(() => {
stack = new Stack<number>();
});
it('push & size should work', () => {
stack.push(1, 2);
expect(stack.size).toBe(2);
});
it('peek should return top element without removing it', () => {
stack.push(1, 2);
expect(stack.peek()).toBe(2);
expect(stack.size).toBe(2);
});
it('pop should remove and return top element', () => {
stack.push(1, 2);
expect(stack.pop()).toBe(2);
expect(stack.size).toBe(1);
expect(stack.peek()).toBe(1);
});
it('pop on empty stack should return undefined', () => {
expect(stack.pop()).toBeUndefined();
expect(stack.peek()).toBeUndefined();
});
it('clear should remove all elements', () => {
stack.push(1, 2);
stack.clear();
expect(stack.size).toBe(0);
expect(stack.peek()).toBeUndefined();
});
it('toArray should return a shallow copy', () => {
stack.push(1, 2);
const arr = stack.toArray();
arr.push(3);
expect(stack.size).toBe(2);
expect(stack.toArray()).toEqual([1, 2]);
});
it('dedup should remove existing item before push', () => {
stack.push(1, 2, 1);
expect(stack.toArray()).toEqual([2, 1]);
expect(stack.size).toBe(2);
});
it('dedup = false should allow duplicate items', () => {
const s = new Stack<number>(false);
s.push(1, 1, 1);
expect(s.toArray()).toEqual([1, 1, 1]);
expect(s.size).toBe(3);
});
it('remove should delete all matching items', () => {
stack.push(1, 2, 1);
stack.remove(1);
expect(stack.toArray()).toEqual([2]);
expect(stack.size).toBe(1);
});
it('maxSize should limit stack capacity', () => {
const s = new Stack<number>(true, 3);
s.push(1, 2, 3, 4);
expect(s.toArray()).toEqual([2, 3, 4]);
expect(s.size).toBe(3);
});
it('dedup + maxSize should work together', () => {
const s = new Stack<number>(true, 3);
s.push(1, 2, 3, 2); // 去重并重新入栈
expect(s.toArray()).toEqual([1, 3, 2]);
expect(s.size).toBe(3);
});
it('createStack should create a stack instance', () => {
const s = createStack<number>(true, 2);
s.push(1, 2, 3);
expect(s.toArray()).toEqual([2, 3]);
});
});

1
packages/@core/base/shared/src/utils/index.ts

@ -8,6 +8,7 @@ export * from './letter';
export * from './merge';
export * from './nprogress';
export * from './resources';
export * from './stack';
export * from './state-handler';
export * from './to';
export * from './tree';

103
packages/@core/base/shared/src/utils/stack.ts

@ -0,0 +1,103 @@
/**
* @zh_CN
*/
export class Stack<T> {
/**
* @zh_CN
*/
get size() {
return this.items.length;
}
/**
* @zh_CN
*/
private readonly dedup: boolean;
/**
* @zh_CN
*/
private items: T[] = [];
/**
* @zh_CN
*/
private readonly maxSize?: number;
constructor(dedup = true, maxSize?: number) {
this.maxSize = maxSize;
this.dedup = dedup;
}
/**
* @zh_CN
*/
clear() {
this.items.length = 0;
}
/**
* @zh_CN
* @returns
*/
peek(): T | undefined {
return this.items[this.items.length - 1];
}
/**
* @zh_CN
* @returns
*/
pop(): T | undefined {
return this.items.pop();
}
/**
* @zh_CN
* @param items
*/
push(...items: T[]) {
items.forEach((item) => {
// 去重
if (this.dedup) {
const index = this.items.indexOf(item);
if (index !== -1) {
this.items.splice(index, 1);
}
}
this.items.push(item);
if (this.maxSize && this.items.length > this.maxSize) {
this.items.splice(0, this.items.length - this.maxSize);
}
});
}
/**
* @zh_CN
* @param itemList
*/
remove(...itemList: T[]) {
this.items = this.items.filter((i) => !itemList.includes(i));
}
/**
* @zh_CN
* @param itemList
*/
retain(itemList: T[]) {
this.items = this.items.filter((i) => itemList.includes(i));
}
/**
* @zh_CN
* @returns
*/
toArray(): T[] {
return [...this.items];
}
}
/**
* @zh_CN
* @param dedup
* @param maxSize
* @returns
*/
export const createStack = <T>(dedup = true, maxSize?: number) =>
new Stack<T>(dedup, maxSize);

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

@ -11,7 +11,9 @@ import { toRaw } from 'vue';
import { preferences } from '@vben-core/preferences';
import {
createStack,
openRouteInNewWindow,
Stack,
startProgress,
stopProgress,
} from '@vben-core/shared/utils';
@ -47,8 +49,17 @@ interface TabbarState {
* @zh_CN 使watch深度监听的话
*/
updateTime?: number;
/**
* @zh_CN
*/
visitHistory: Stack<string>;
}
/**
* @zh_CN 访
*/
const MAX_VISIT_HISTORY = 50;
/**
* @zh_CN 访
*/
@ -62,6 +73,7 @@ export const useTabbarStore = defineStore('core-tabbar', {
this.tabs = this.tabs.filter(
(item) => !keySet.has(getTabKeyFromTab(item)),
);
this.visitHistory.remove(...keys);
await this.updateCacheTabs();
},
@ -166,6 +178,8 @@ export const useTabbarStore = defineStore('core-tabbar', {
this.tabs.splice(tabIndex, 1, mergedTab);
}
this.updateCacheTabs();
// 添加访问历史记录
this.visitHistory.push(tab.key as string);
return tab;
},
/**
@ -174,6 +188,8 @@ export const useTabbarStore = defineStore('core-tabbar', {
async closeAllTabs(router: Router) {
const newTabs = this.tabs.filter((tab) => isAffixTab(tab));
this.tabs = newTabs.length > 0 ? newTabs : [...this.tabs].splice(0, 1);
// 设置访问历史
this.visitHistory.retain(this.tabs.map((item) => getTabKeyFromTab(item)));
await this._goToDefaultTab(router);
this.updateCacheTabs();
},
@ -249,29 +265,26 @@ export const useTabbarStore = defineStore('core-tabbar', {
*/
async closeTab(tab: TabDefinition, router: Router) {
const { currentRoute } = router;
const currentTabKey = getTabKey(currentRoute.value);
// 关闭不是激活选项卡
if (getTabKey(currentRoute.value) !== getTabKeyFromTab(tab)) {
if (currentTabKey !== getTabKeyFromTab(tab)) {
this._close(tab);
this.updateCacheTabs();
// 移除访问历史
this.visitHistory.remove(getTabKeyFromTab(tab));
return;
}
const index = this.getTabs.findIndex(
(item) => getTabKeyFromTab(item) === getTabKey(currentRoute.value),
);
const before = this.getTabs[index - 1];
const after = this.getTabs[index + 1];
// 下一个tab存在,跳转到下一个
if (after) {
this._close(tab);
await this._goToTab(after, router);
// 上一个tab存在,跳转到上一个
} else if (before) {
this._close(tab);
await this._goToTab(before, router);
} else {
if (this.getTabs.length <= 1) {
console.error('Failed to close the tab; only one tab remains open.');
return;
}
// 从访问历史记录中移除当前关闭的tab
this.visitHistory.remove(currentTabKey);
this._close(tab);
const previousTabKey = this.visitHistory.pop();
if (previousTabKey) {
// 跳转到上一个tab
await this._goToTab(this.getTabByKey(previousTabKey), router);
}
},
@ -527,11 +540,12 @@ export const useTabbarStore = defineStore('core-tabbar', {
persist: [
// tabs不需要保存在localStorage
{
pick: ['tabs'],
pick: ['tabs', 'visitHistory'],
storage: sessionStorage,
},
],
state: (): TabbarState => ({
visitHistory: createStack<string>(true, MAX_VISIT_HISTORY),
cachedTabs: new Set(),
dragEndIndex: 0,
excludeCachedTabs: new Set(),

Loading…
Cancel
Save