154 lines
4.6 KiB
TypeScript
154 lines
4.6 KiB
TypeScript
import type { RouteLocationNormalized } from 'vue-router';
|
||
import { defineStore } from 'pinia';
|
||
import {
|
||
DEFAULT_ROUTE,
|
||
DEFAULT_ROUTE_NAME,
|
||
REDIRECT_ROUTE_NAME,
|
||
} from '@/router/constants';
|
||
import { isString } from '@/utils/is';
|
||
import { useRbacUserStore } from '@/store/modules/rbac-user';
|
||
import { buildMenuTitleMaps, resolveMenuTitleByPath, resolveMenuTitleFromTree } from '@/utils/menu-display';
|
||
import type { MenuTreeDto } from '@/api/rbac';
|
||
import { TabBarState, TagProps } from './types';
|
||
|
||
/** keep-alive 同时缓存的页面上限(LRU),避免页签开太多越切越顿 */
|
||
export const MAX_KEEP_ALIVE_TABS = 10;
|
||
|
||
function resolveRouteTabTitle(route: RouteLocationNormalized): string {
|
||
const rbacUserStore = useRbacUserStore();
|
||
const { pathToTitle } = buildMenuTitleMaps(rbacUserStore.menus as MenuTreeDto[]);
|
||
const menuTitle = resolveMenuTitleByPath(route.path, pathToTitle);
|
||
if (menuTitle) {
|
||
return menuTitle;
|
||
}
|
||
|
||
const metaTitle = route.meta?.title;
|
||
if (typeof metaTitle === 'string' && metaTitle.trim()) {
|
||
return metaTitle.trim();
|
||
}
|
||
|
||
return String(route.name || route.path || '');
|
||
}
|
||
|
||
const formatTag = (route: RouteLocationNormalized): TagProps => {
|
||
const { name, meta, fullPath, query } = route;
|
||
return {
|
||
title: resolveRouteTabTitle(route),
|
||
name: String(name),
|
||
fullPath,
|
||
query,
|
||
ignoreCache: meta.ignoreCache,
|
||
};
|
||
};
|
||
|
||
const BAN_LIST = [REDIRECT_ROUTE_NAME];
|
||
|
||
const useTabBarStore = defineStore('tabBar', {
|
||
state: (): TabBarState => ({
|
||
cacheTabList: new Set([DEFAULT_ROUTE_NAME]),
|
||
tagList: [DEFAULT_ROUTE],
|
||
}),
|
||
|
||
getters: {
|
||
getTabList(): TagProps[] {
|
||
return this.tagList;
|
||
},
|
||
getCacheList(): string[] {
|
||
return Array.from(this.cacheTabList);
|
||
},
|
||
},
|
||
|
||
actions: {
|
||
/** 超出上限时淘汰最久未访问的缓存(保留首页) */
|
||
pruneCache(exceptName?: string) {
|
||
while (this.cacheTabList.size > MAX_KEEP_ALIVE_TABS) {
|
||
let removed = false;
|
||
for (const name of this.cacheTabList) {
|
||
if (name === DEFAULT_ROUTE_NAME || name === exceptName) {
|
||
continue;
|
||
}
|
||
this.cacheTabList.delete(name);
|
||
removed = true;
|
||
break;
|
||
}
|
||
if (!removed) break;
|
||
}
|
||
},
|
||
|
||
/** LRU:访问时挪到末尾,并裁剪超量缓存 */
|
||
touchCache(name: string) {
|
||
if (!isString(name) || name === '') return;
|
||
if (this.cacheTabList.has(name)) {
|
||
this.cacheTabList.delete(name);
|
||
}
|
||
this.cacheTabList.add(name);
|
||
this.pruneCache(name);
|
||
},
|
||
|
||
updateTabList(route: RouteLocationNormalized) {
|
||
if (BAN_LIST.includes(route.name as string)) return;
|
||
this.tagList.push(formatTag(route));
|
||
if (!route.meta.ignoreCache) {
|
||
this.touchCache(String(route.name));
|
||
}
|
||
},
|
||
deleteTag(idx: number, tag: TagProps) {
|
||
this.tagList.splice(idx, 1);
|
||
this.cacheTabList.delete(tag.name);
|
||
},
|
||
addCache(name: string) {
|
||
if (isString(name) && name !== '') {
|
||
this.touchCache(name);
|
||
}
|
||
},
|
||
deleteCache(tag: TagProps) {
|
||
this.cacheTabList.delete(tag.name);
|
||
},
|
||
freshTabList(tags: TagProps[]) {
|
||
this.tagList = tags;
|
||
this.cacheTabList.clear();
|
||
this.cacheTabList.add(DEFAULT_ROUTE_NAME);
|
||
this.tagList
|
||
.filter((el) => !el.ignoreCache)
|
||
.map((el) => el.name)
|
||
.forEach((x) => {
|
||
if (x && x !== DEFAULT_ROUTE_NAME) {
|
||
this.cacheTabList.add(x);
|
||
}
|
||
});
|
||
this.pruneCache();
|
||
},
|
||
resetTabList() {
|
||
this.tagList = [DEFAULT_ROUTE];
|
||
this.cacheTabList.clear();
|
||
this.cacheTabList.add(DEFAULT_ROUTE_NAME);
|
||
},
|
||
delVisitedRoute(path: string) {
|
||
const idx = this.tagList.findIndex(tag => tag.fullPath === path);
|
||
if (idx !== -1) {
|
||
const tag = this.tagList[idx];
|
||
this.deleteTag(idx, tag);
|
||
}
|
||
},
|
||
/** 拖动排序标签:将 fromIndex 位置的标签移动到 toIndex */
|
||
reorderTabs(fromIndex: number, toIndex: number) {
|
||
if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0) return;
|
||
const list = [...this.tagList];
|
||
const [item] = list.splice(fromIndex, 1);
|
||
list.splice(toIndex, 0, item);
|
||
this.freshTabList(list);
|
||
},
|
||
/** 菜单加载后刷新标签标题(使用 RBAC 菜单名称) */
|
||
refreshTagTitles() {
|
||
const rbacUserStore = useRbacUserStore();
|
||
const menus = rbacUserStore.menus as MenuTreeDto[];
|
||
this.tagList = this.tagList.map((tag) => ({
|
||
...tag,
|
||
title: resolveMenuTitleFromTree(menus, tag.fullPath, tag.title) || tag.title,
|
||
}));
|
||
},
|
||
},
|
||
});
|
||
|
||
export default useTabBarStore;
|