refactor(project): re-adjust the overall folder

This commit is contained in:
vince
2024-07-23 00:03:59 +08:00
parent a1a566cb2f
commit 14538f7ed5
281 changed files with 1365 additions and 1659 deletions

View File

@@ -0,0 +1,3 @@
export * from './modules';
export * from './setup';
export { storeToRefs } from 'pinia';

View File

@@ -0,0 +1,85 @@
import { createPinia, setActivePinia } from 'pinia';
import { beforeEach, describe, expect, it } from 'vitest';
import { useCoreAccessStore } from './access';
describe('useCoreAccessStore', () => {
beforeEach(() => {
setActivePinia(createPinia());
});
it('updates accessMenus state', () => {
const store = useCoreAccessStore();
expect(store.accessMenus).toEqual([]);
store.setAccessMenus([{ name: 'Dashboard', path: '/dashboard' }]);
expect(store.accessMenus).toEqual([
{ name: 'Dashboard', path: '/dashboard' },
]);
});
it('updates userInfo and userRoles state', () => {
const store = useCoreAccessStore();
expect(store.userInfo).toBeNull();
expect(store.userRoles).toEqual([]);
const userInfo: any = { name: 'John Doe', roles: ['admin'] };
store.setUserInfo(userInfo);
expect(store.userInfo).toEqual(userInfo);
expect(store.userRoles).toEqual(['admin']);
});
it('returns correct userInfo', () => {
const store = useCoreAccessStore();
const userInfo: any = { name: 'Jane Doe', roles: [{ value: 'user' }] };
store.setUserInfo(userInfo);
expect(store.userInfo).toEqual(userInfo);
});
it('updates accessToken state correctly', () => {
const store = useCoreAccessStore();
expect(store.accessToken).toBeNull(); // 初始状态
store.setAccessToken('abc123');
expect(store.accessToken).toBe('abc123');
});
// 测试重置用户信息时的行为
it('clears userInfo and userRoles when setting null userInfo', () => {
const store = useCoreAccessStore();
store.setUserInfo({
roles: [{ roleName: 'User', value: 'user' }],
} as any);
expect(store.userInfo).not.toBeNull();
expect(store.userRoles.length).toBeGreaterThan(0);
store.setUserInfo(null as any); // 重置用户信息
expect(store.userInfo).toBeNull();
expect(store.userRoles).toEqual([]);
});
it('returns the correct accessToken', () => {
const store = useCoreAccessStore();
store.setAccessToken('xyz789');
expect(store.accessToken).toBe('xyz789');
});
// 测试在没有用户角色时返回空数组
it('returns an empty array for userRoles if not set', () => {
const store = useCoreAccessStore();
expect(store.userRoles).toEqual([]);
});
// 测试设置空的访问菜单列表
it('handles empty accessMenus correctly', () => {
const store = useCoreAccessStore();
store.setAccessMenus([]);
expect(store.accessMenus).toEqual([]);
});
// 测试设置空的访问路由列表
it('handles empty accessRoutes correctly', () => {
const store = useCoreAccessStore();
store.setAccessRoutes([]);
expect(store.accessRoutes).toEqual([]);
});
});

View File

@@ -0,0 +1,112 @@
import type { MenuRecordRaw } from '@vben-core/typings';
import type { RouteRecordRaw } from 'vue-router';
import { acceptHMRUpdate, defineStore } from 'pinia';
type AccessToken = null | string;
interface BasicUserInfo {
/**
* 头像
*/
avatar: string;
/**
* 用户昵称
*/
realName: string;
/**
* 用户角色
*/
roles?: string[];
/**
* 用户id
*/
userId: string;
/**
* 用户名
*/
username: string;
}
interface AccessState {
/**
* 权限码
*/
accessCodes: string[];
/**
* 可访问的菜单列表
*/
accessMenus: MenuRecordRaw[];
/**
* 可访问的路由列表
*/
accessRoutes: RouteRecordRaw[];
/**
* 登录 accessToken
*/
accessToken: AccessToken;
/**
* 登录 accessToken
*/
refreshToken: AccessToken;
/**
* 用户信息
*/
userInfo: BasicUserInfo | null;
/**
* 用户角色
*/
userRoles: string[];
}
/**
* @zh_CN 访问权限相关
*/
export const useCoreAccessStore = defineStore('core-access', {
actions: {
setAccessCodes(codes: string[]) {
this.accessCodes = codes;
},
setAccessMenus(menus: MenuRecordRaw[]) {
this.accessMenus = menus;
},
setAccessRoutes(routes: RouteRecordRaw[]) {
this.accessRoutes = routes;
},
setAccessToken(token: AccessToken) {
this.accessToken = token;
},
setRefreshToken(token: AccessToken) {
this.refreshToken = token;
},
setUserInfo(userInfo: BasicUserInfo | null) {
// 设置用户信息
this.userInfo = userInfo;
// 设置角色信息
const roles = userInfo?.roles ?? [];
this.setUserRoles(roles);
},
setUserRoles(roles: string[]) {
this.userRoles = roles;
},
},
persist: {
// 持久化
paths: ['accessToken', 'refreshToken', 'accessCodes'],
},
state: (): AccessState => ({
accessCodes: [],
accessMenus: [],
accessRoutes: [],
accessToken: null,
refreshToken: null,
userInfo: null,
userRoles: [],
}),
});
// 解决热更新问题
const hot = import.meta.hot;
if (hot) {
hot.accept(acceptHMRUpdate(useCoreAccessStore, hot));
}

View File

@@ -0,0 +1,3 @@
export * from './access';
export * from './lock';
export * from './tabbar';

View File

@@ -0,0 +1,32 @@
import { createPinia, setActivePinia } from 'pinia';
import { beforeEach, describe, expect, it } from 'vitest';
import { useCoreLockStore } from './lock';
describe('useCoreLockStore', () => {
beforeEach(() => {
// 每个测试前重置 Pinia
setActivePinia(createPinia());
});
it('should initialize with correct default state', () => {
const store = useCoreLockStore();
expect(store.isLockScreen).toBe(false);
expect(store.lockScreenPassword).toBeUndefined();
});
it('should lock screen with a password', () => {
const store = useCoreLockStore();
store.lockScreen('1234');
expect(store.isLockScreen).toBe(true);
expect(store.lockScreenPassword).toBe('1234');
});
it('should unlock screen and clear password', () => {
const store = useCoreLockStore();
store.lockScreen('1234');
store.unlockScreen();
expect(store.isLockScreen).toBe(false);
expect(store.lockScreenPassword).toBeUndefined();
});
});

View File

@@ -0,0 +1,33 @@
import { defineStore } from 'pinia';
interface AppState {
/**
* 是否锁屏状态
*/
isLockScreen: boolean;
/**
* 锁屏密码
*/
lockScreenPassword?: string;
}
export const useCoreLockStore = defineStore('core-lock', {
actions: {
lockScreen(password: string) {
this.isLockScreen = true;
this.lockScreenPassword = password;
},
unlockScreen() {
this.isLockScreen = false;
this.lockScreenPassword = undefined;
},
},
persist: {
paths: ['isLockScreen', 'lockScreenPassword'],
},
state: (): AppState => ({
isLockScreen: false,
lockScreenPassword: undefined,
}),
});

View File

@@ -0,0 +1,296 @@
import { createRouter, createWebHistory } from 'vue-router';
import { createPinia, setActivePinia } from 'pinia';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useCoreTabbarStore } from './tabbar';
describe('useCoreAccessStore', () => {
const router = createRouter({
history: createWebHistory(),
routes: [],
});
router.push = vi.fn();
router.replace = vi.fn();
beforeEach(() => {
setActivePinia(createPinia());
vi.clearAllMocks();
});
it('adds a new tab', () => {
const store = useCoreTabbarStore();
const tab: any = {
fullPath: '/home',
meta: {},
name: 'Home',
path: '/home',
};
store.addTab(tab);
expect(store.tabs.length).toBe(1);
expect(store.tabs[0]).toEqual(tab);
});
it('adds a new tab if it does not exist', () => {
const store = useCoreTabbarStore();
const newTab: any = {
fullPath: '/new',
meta: {},
name: 'New',
path: '/new',
};
store.addTab(newTab);
expect(store.tabs).toContainEqual(newTab);
});
it('updates an existing tab instead of adding a new one', () => {
const store = useCoreTabbarStore();
const initialTab: any = {
fullPath: '/existing',
meta: {},
name: 'Existing',
path: '/existing',
query: {},
};
store.tabs.push(initialTab);
const updatedTab = { ...initialTab, query: { id: '1' } };
store.addTab(updatedTab);
expect(store.tabs.length).toBe(1);
expect(store.tabs[0].query).toEqual({ id: '1' });
});
it('closes all tabs', async () => {
const store = useCoreTabbarStore();
store.tabs = [
{ fullPath: '/home', meta: {}, name: 'Home', path: '/home' },
] as any;
router.replace = vi.fn(); // 使用 vitest 的 mock 函数
await store.closeAllTabs(router);
expect(store.tabs.length).toBe(0); // 假设没有固定的标签页
// expect(router.replace).toHaveBeenCalled();
});
it('closes a non-affix tab', () => {
const store = useCoreTabbarStore();
const tab: any = {
fullPath: '/closable',
meta: {},
name: 'Closable',
path: '/closable',
};
store.tabs.push(tab);
store._close(tab);
expect(store.tabs.length).toBe(0);
});
it('does not close an affix tab', () => {
const store = useCoreTabbarStore();
const affixTab: any = {
fullPath: '/affix',
meta: { affixTab: true },
name: 'Affix',
path: '/affix',
};
store.tabs.push(affixTab);
store._close(affixTab);
expect(store.tabs.length).toBe(1); // Affix tab should not be closed
});
it('returns all cache tabs', () => {
const store = useCoreTabbarStore();
store.cachedTabs.add('Home');
store.cachedTabs.add('About');
expect(store.getCachedTabs).toEqual(['Home', 'About']);
});
it('returns all tabs, including affix tabs', () => {
const store = useCoreTabbarStore();
const normalTab: any = {
fullPath: '/normal',
meta: {},
name: 'Normal',
path: '/normal',
};
const affixTab: any = {
fullPath: '/affix',
meta: { affixTab: true },
name: 'Affix',
path: '/affix',
};
store.tabs.push(normalTab);
store.affixTabs.push(affixTab);
expect(store.getTabs).toContainEqual(normalTab);
expect(store.affixTabs).toContainEqual(affixTab);
});
it('navigates to a specific tab', async () => {
const store = useCoreTabbarStore();
const tab: any = { meta: {}, name: 'Dashboard', path: '/dashboard' };
await store._goToTab(tab, router);
expect(router.replace).toHaveBeenCalledWith({
params: {},
path: '/dashboard',
query: {},
});
});
it('closes multiple tabs by paths', async () => {
const store = useCoreTabbarStore();
store.addTab({
fullPath: '/home',
meta: {},
name: 'Home',
path: '/home',
} as any);
store.addTab({
fullPath: '/about',
meta: {},
name: 'About',
path: '/about',
} as any);
store.addTab({
fullPath: '/contact',
meta: {},
name: 'Contact',
path: '/contact',
} as any);
await store._bulkCloseByPaths(['/home', '/contact']);
expect(store.tabs).toHaveLength(1);
expect(store.tabs[0].name).toBe('About');
});
it('closes all tabs to the left of the specified tab', async () => {
const store = useCoreTabbarStore();
store.addTab({
fullPath: '/home',
meta: {},
name: 'Home',
path: '/home',
} as any);
store.addTab({
fullPath: '/about',
meta: {},
name: 'About',
path: '/about',
} as any);
const targetTab: any = {
fullPath: '/contact',
meta: {},
name: 'Contact',
path: '/contact',
};
store.addTab(targetTab);
await store.closeLeftTabs(targetTab);
expect(store.tabs).toHaveLength(1);
expect(store.tabs[0].name).toBe('Contact');
});
it('closes all tabs except the specified tab', async () => {
const store = useCoreTabbarStore();
store.addTab({
fullPath: '/home',
meta: {},
name: 'Home',
path: '/home',
} as any);
const targetTab: any = {
fullPath: '/about',
meta: {},
name: 'About',
path: '/about',
};
store.addTab(targetTab);
store.addTab({
fullPath: '/contact',
meta: {},
name: 'Contact',
path: '/contact',
} as any);
await store.closeOtherTabs(targetTab);
expect(store.tabs).toHaveLength(1);
expect(store.tabs[0].name).toBe('About');
});
it('closes all tabs to the right of the specified tab', async () => {
const store = useCoreTabbarStore();
const targetTab: any = {
fullPath: '/home',
meta: {},
name: 'Home',
path: '/home',
};
store.addTab(targetTab);
store.addTab({
fullPath: '/about',
meta: {},
name: 'About',
path: '/about',
} as any);
store.addTab({
fullPath: '/contact',
meta: {},
name: 'Contact',
path: '/contact',
} as any);
await store.closeRightTabs(targetTab);
expect(store.tabs).toHaveLength(1);
expect(store.tabs[0].name).toBe('Home');
});
it('closes the tab with the specified key', async () => {
const store = useCoreTabbarStore();
const keyToClose = '/about';
store.addTab({
fullPath: '/home',
meta: {},
name: 'Home',
path: '/home',
} as any);
store.addTab({
fullPath: keyToClose,
meta: {},
name: 'About',
path: '/about',
} as any);
store.addTab({
fullPath: '/contact',
meta: {},
name: 'Contact',
path: '/contact',
} as any);
await store.closeTabByKey(keyToClose, router);
expect(store.tabs).toHaveLength(2);
expect(
store.tabs.find((tab) => tab.fullPath === keyToClose),
).toBeUndefined();
});
it('refreshes the current tab', async () => {
const store = useCoreTabbarStore();
const currentTab: any = {
fullPath: '/dashboard',
meta: { name: 'Dashboard' },
name: 'Dashboard',
path: '/dashboard',
};
router.currentRoute.value = currentTab;
await store.refresh(router);
expect(store.excludeCachedTabs.has('Dashboard')).toBe(false);
expect(store.renderRouteView).toBe(true);
});
});

View File

@@ -0,0 +1,490 @@
import type { TabDefinition } from '@vben-core/typings';
import type { Router, RouteRecordNormalized } from 'vue-router';
import { toRaw } from 'vue';
import { openWindow, startProgress, stopProgress } from '@vben-core/toolkit';
import { acceptHMRUpdate, defineStore } from 'pinia';
interface TabsState {
/**
* @zh_CN 当前打开的标签页列表缓存
*/
cachedTabs: Set<string>;
/**
* @zh_CN 拖拽结束的索引
*/
dragEndIndex: number;
/**
* @zh_CN 需要排除缓存的标签页
*/
excludeCachedTabs: Set<string>;
/**
* @zh_CN 是否刷新
*/
renderRouteView?: boolean;
/**
* @zh_CN 当前打开的标签页列表
*/
tabs: TabDefinition[];
/**
* @zh_CN 更新时间用于一些更新场景使用watch深度监听的话会损耗性能
*/
updateTime?: number;
}
/**
* @zh_CN 访问权限相关
*/
export const useCoreTabbarStore = defineStore('core-tabbar', {
actions: {
/**
* Close tabs in bulk
*/
async _bulkCloseByPaths(paths: string[]) {
this.tabs = this.tabs.filter((item) => {
return !paths.includes(getTabPath(item));
});
this.updateCacheTab();
},
/**
* @zh_CN 关闭标签页
* @param tab
*/
_close(tab: TabDefinition) {
const { fullPath } = tab;
if (isAffixTab(tab)) {
return;
}
const index = this.tabs.findIndex((item) => item.fullPath === fullPath);
index !== -1 && this.tabs.splice(index, 1);
},
/**
* @zh_CN 跳转到默认标签页
*/
async _goToDefaultTab(router: Router) {
if (this.getTabs.length <= 0) {
// TODO: 跳转首页
return;
}
const firstTab = this.getTabs[0];
await this._goToTab(firstTab, router);
},
/**
* @zh_CN 跳转到标签页
* @param tab
*/
async _goToTab(tab: TabDefinition, router: Router) {
const { params, path, query } = tab;
const toParams = {
params: params || {},
path,
query: query || {},
};
await router.replace(toParams);
},
/**
* @zh_CN 添加标签页
* @param routeTab
*/
addTab(routeTab: TabDefinition) {
const tab = cloneTab(routeTab);
if (!isTabShown(tab)) {
return;
}
const tabIndex = this.tabs.findIndex((tab) => {
return getTabPath(tab) === getTabPath(routeTab);
});
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 {
// 页面已经存在,不重复添加选项卡,只更新选项卡参数
const currentTab = toRaw(this.tabs)[tabIndex];
this.tabs.splice(tabIndex, 1, { ...currentTab, ...tab });
}
this.updateCacheTab();
},
/**
* @zh_CN 关闭所有标签页
*/
async closeAllTabs(router: Router) {
this.tabs = this.tabs.filter((tab) => isAffixTab(tab));
await this._goToDefaultTab(router);
this.updateCacheTab();
},
/**
* @zh_CN 关闭左侧标签页
* @param tab
*/
async closeLeftTabs(tab: TabDefinition) {
const index = this.tabs.findIndex(
(item) => getTabPath(item) === getTabPath(tab),
);
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);
},
/**
* @zh_CN 关闭其他标签页
* @param tab
*/
async closeOtherTabs(tab: TabDefinition) {
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) => getTabPath(item) === path);
if (!closeTab) {
continue;
}
if (!isAffixTab(closeTab)) {
paths.push(getTabPath(closeTab));
}
}
}
await this._bulkCloseByPaths(paths);
},
/**
* @zh_CN 关闭右侧标签页
* @param tab
*/
async closeRightTabs(tab: TabDefinition) {
const index = this.tabs.findIndex(
(item) => getTabPath(item) === getTabPath(tab),
);
if (index >= 0 && index < this.tabs.length - 1) {
const rightTabs = this.tabs.slice(index + 1, this.tabs.length);
const paths: string[] = [];
for (const item of rightTabs) {
if (!isAffixTab(item)) {
paths.push(getTabPath(item));
}
}
await this._bulkCloseByPaths(paths);
}
},
/**
* @zh_CN 关闭标签页
* @param tab
* @param router
*/
async closeTab(tab: TabDefinition, router: Router) {
const { currentRoute } = router;
// 关闭不是激活选项卡
if (getTabPath(currentRoute.value) !== getTabPath(tab)) {
this._close(tab);
this.updateCacheTab();
return;
}
const index = this.getTabs.findIndex(
(item) => getTabPath(item) === getTabPath(currentRoute.value),
);
const before = this.getTabs[index - 1];
const after = this.getTabs[index + 1];
// 下一个tab存在跳转到下一个
if (after) {
this._close(currentRoute.value);
await this._goToTab(after, router);
// 上一个tab存在跳转到上一个
} else if (before) {
this._close(currentRoute.value);
await this._goToTab(before, router);
} else {
console.error('Failed to close the tab; only one tab remains open.');
}
},
/**
* @zh_CN 通过key关闭标签页
* @param key
*/
async closeTabByKey(key: string, router: Router) {
const index = this.tabs.findIndex((item) => getTabPath(item) === key);
if (index === -1) {
return;
}
await this.closeTab(this.tabs[index], router);
},
/**
* @zh_CN 新窗口打开标签页
* @param tab
*/
async openTabInNewWindow(tab: TabDefinition) {
const { hash, origin } = location;
const path = tab.fullPath;
const fullPath = path.startsWith('/') ? path : `/${path}`;
const url = `${origin}${hash ? '/#' : ''}${fullPath}`;
openWindow(url, { target: '_blank' });
},
/**
* @zh_CN 固定标签页
* @param tab
*/
async pinTab(tab: TabDefinition) {
const index = this.tabs.findIndex(
(item) => getTabPath(item) === getTabPath(tab),
);
if (index !== -1) {
tab.meta.affixTab = true;
this.addTab(tab);
}
},
/**
* 刷新标签页
*/
async refresh(router: Router) {
const { currentRoute } = router;
const { name } = currentRoute.value;
this.excludeCachedTabs.add(name as string);
this.renderRouteView = false;
startProgress();
await new Promise((resolve) => setTimeout(resolve, 200));
this.excludeCachedTabs.delete(name as string);
this.renderRouteView = true;
stopProgress();
},
/**
* @zh_CN 重置标签页标题
*/
async resetTabTitle(tab: TabDefinition) {
if (!tab?.meta?.newTabTitle) {
return;
}
const findTab = this.tabs.find(
(item) => getTabPath(item) === getTabPath(tab),
);
if (findTab) {
findTab.meta.newTabTitle = undefined;
await this.updateCacheTab();
}
},
/**
* 设置固定标签页
* @param tabs
*/
setAffixTabs(tabs: RouteRecordNormalized[]) {
for (const tab of tabs) {
tab.meta.affixTab = true;
this.addTab(routeToTab(tab));
}
},
/**
* @zh_CN 设置标签页标题
* @param tab
* @param title
*/
async setTabTitle(tab: TabDefinition, title: string) {
const findTab = this.tabs.find(
(item) => getTabPath(item) === getTabPath(tab),
);
if (findTab) {
findTab.meta.newTabTitle = title;
await this.updateCacheTab();
}
},
async setUpdateTime() {
this.updateTime = Date.now();
},
/**
* @zh_CN 设置标签页顺序
* @param oldIndex
* @param newIndex
*/
async sortTabs(oldIndex: number, newIndex: number) {
const currentTab = this.tabs[oldIndex];
this.tabs.splice(oldIndex, 1);
this.tabs.splice(newIndex, 0, currentTab);
this.dragEndIndex = this.dragEndIndex + 1;
},
/**
* @zh_CN 切换固定标签页
* @param tab
*/
async toggleTabPin(tab: TabDefinition) {
const affixTab = tab?.meta?.affixTab ?? false;
await (affixTab ? this.unpinTab(tab) : this.pinTab(tab));
},
/**
* @zh_CN 取消固定标签页
* @param tab
*/
async unpinTab(tab: TabDefinition) {
const index = this.tabs.findIndex(
(item) => getTabPath(item) === getTabPath(tab),
);
if (index !== -1) {
tab.meta.affixTab = false;
this.addTab(tab);
}
},
/**
* 根据当前打开的选项卡更新缓存
*/
async updateCacheTab() {
const cacheMap = new Set<string>();
for (const tab of this.tabs) {
// 跳过不需要持久化的标签页
const keepAlive = tab.meta?.keepAlive;
if (!keepAlive) {
continue;
}
tab.matched.forEach((t, i) => {
if (i > 0) {
cacheMap.add(t.name as string);
}
});
const name = tab.name as string;
cacheMap.add(name);
}
this.cachedTabs = cacheMap;
},
},
getters: {
affixTabs(): TabDefinition[] {
return this.tabs.filter((tab) => isAffixTab(tab));
},
getCachedTabs(): string[] {
return [...this.cachedTabs];
},
getExcludeCachedTabs(): string[] {
return [...this.excludeCachedTabs];
},
getTabs(): TabDefinition[] {
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab));
return [...affixTabs, ...normalTabs].filter(Boolean);
},
},
persist: [
// tabs不需要保存在localStorage
{
paths: ['tabs'],
storage: sessionStorage,
},
],
state: (): TabsState => ({
cachedTabs: new Set(),
dragEndIndex: 0,
excludeCachedTabs: new Set(),
renderRouteView: true,
tabs: [],
updateTime: Date.now(),
}),
});
// 解决热更新问题
const hot = import.meta.hot;
if (hot) {
hot.accept(acceptHMRUpdate(useCoreTabbarStore, hot));
}
/**
* @zh_CN 克隆路由,防止路由被修改
* @param route
*/
function cloneTab(route: TabDefinition): TabDefinition {
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: TabDefinition) {
return tab?.meta?.affixTab ?? false;
}
/**
* @zh_CN 是否显示标签
* @param tab
*/
function isTabShown(tab: TabDefinition) {
return !tab.meta.hideInTab;
}
/**
* @zh_CN 获取标签页路径
* @param tab
*/
function getTabPath(tab: RouteRecordNormalized | TabDefinition) {
return decodeURIComponent((tab as TabDefinition).fullPath || tab.path);
}
function routeToTab(route: RouteRecordNormalized) {
return {
meta: route.meta,
name: route.name,
path: route.path,
} as TabDefinition;
}

View File

@@ -0,0 +1,41 @@
import type { Pinia } from 'pinia';
import { createPinia } from 'pinia';
let pinia: Pinia;
export interface InitStoreOptions {
/**
* @zh_CN 应用名,由于 @vben/stores 是公用的后续可能有多个app为了防止多个app缓存冲突可在这里配置应用名,应用名将被用于持久化的前缀
*/
namespace: string;
}
/**
* @zh_CN 初始化pinia
*/
export async function initStore(options: InitStoreOptions) {
const { createPersistedState } = await import('pinia-plugin-persistedstate');
pinia = createPinia();
const { namespace } = options;
pinia.use(
createPersistedState({
// key $appName-$store.id
key: (storeKey) => `${namespace}-${storeKey}`,
storage: localStorage,
}),
);
return pinia;
}
export function resetAllStores() {
if (!pinia) {
console.error('Pinia is not installed');
return;
}
const allStores = (pinia as any)._s;
for (const [_key, store] of allStores) {
store.$reset();
}
}