feat: configurable persistent tabs
parent
41d0495630
commit
db76325d68
|
@ -33,8 +33,8 @@ function createRequestClient() {
|
||||||
// 这里不能用 useAccessStore,因为 useAccessStore 会导致循环引用
|
// 这里不能用 useAccessStore,因为 useAccessStore 会导致循环引用
|
||||||
const accessStore = useCoreAccessStore();
|
const accessStore = useCoreAccessStore();
|
||||||
return {
|
return {
|
||||||
refreshToken: `Bearer ${accessStore.getRefreshToken}`,
|
refreshToken: `Bearer ${accessStore.refreshToken}`,
|
||||||
token: `Bearer ${accessStore.getAccessToken}`,
|
token: `Bearer ${accessStore.accessToken}`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
// 默认
|
// 默认
|
||||||
|
|
|
@ -17,10 +17,10 @@ export const useAccessStore = defineStore('access', () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const accessToken = computed(() => coreStoreAccess.getAccessToken);
|
const accessToken = computed(() => coreStoreAccess.accessToken);
|
||||||
const userRoles = computed(() => coreStoreAccess.getUserRoles);
|
const userRoles = computed(() => coreStoreAccess.userRoles);
|
||||||
const userInfo = computed(() => coreStoreAccess.getUserInfo);
|
const userInfo = computed(() => coreStoreAccess.userInfo);
|
||||||
const accessRoutes = computed(() => coreStoreAccess.getAccessRoutes);
|
const accessRoutes = computed(() => coreStoreAccess.accessRoutes);
|
||||||
|
|
||||||
function setAccessMenus(menus: MenuRecordRaw[]) {
|
function setAccessMenus(menus: MenuRecordRaw[]) {
|
||||||
coreStoreAccess.setAccessMenus(menus);
|
coreStoreAccess.setAccessMenus(menus);
|
||||||
|
|
|
@ -71,6 +71,7 @@ const defaultPreferences: Preferences = {
|
||||||
tabbar: {
|
tabbar: {
|
||||||
enable: true,
|
enable: true,
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
|
persist: true,
|
||||||
showIcon: true,
|
showIcon: true,
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
|
|
|
@ -143,6 +143,8 @@ interface TabbarPreferences {
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
/** 开启标签页缓存功能 */
|
/** 开启标签页缓存功能 */
|
||||||
keepAlive: boolean;
|
keepAlive: boolean;
|
||||||
|
/** 是否持久化标签 */
|
||||||
|
persist: boolean;
|
||||||
/** 是否开启多标签页图标 */
|
/** 是否开启多标签页图标 */
|
||||||
showIcon: boolean;
|
showIcon: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ describe('useCoreAccessStore', () => {
|
||||||
const store = useCoreAccessStore();
|
const store = useCoreAccessStore();
|
||||||
const userInfo: any = { name: 'Jane Doe', roles: [{ value: 'user' }] };
|
const userInfo: any = { name: 'Jane Doe', roles: [{ value: 'user' }] };
|
||||||
store.setUserInfo(userInfo);
|
store.setUserInfo(userInfo);
|
||||||
expect(store.getUserInfo).toEqual(userInfo);
|
expect(store.userInfo).toEqual(userInfo);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates accessToken state correctly', () => {
|
it('updates accessToken state correctly', () => {
|
||||||
|
@ -60,13 +60,13 @@ describe('useCoreAccessStore', () => {
|
||||||
it('returns the correct accessToken', () => {
|
it('returns the correct accessToken', () => {
|
||||||
const store = useCoreAccessStore();
|
const store = useCoreAccessStore();
|
||||||
store.setAccessToken('xyz789');
|
store.setAccessToken('xyz789');
|
||||||
expect(store.getAccessToken).toBe('xyz789');
|
expect(store.accessToken).toBe('xyz789');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 测试在没有用户角色时返回空数组
|
// 测试在没有用户角色时返回空数组
|
||||||
it('returns an empty array for userRoles if not set', () => {
|
it('returns an empty array for userRoles if not set', () => {
|
||||||
const store = useCoreAccessStore();
|
const store = useCoreAccessStore();
|
||||||
expect(store.getUserRoles).toEqual([]);
|
expect(store.userRoles).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 测试设置空的访问菜单列表
|
// 测试设置空的访问菜单列表
|
||||||
|
|
|
@ -90,32 +90,6 @@ const useCoreAccessStore = defineStore('core-access', {
|
||||||
this.userRoles = roles;
|
this.userRoles = roles;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
|
||||||
getAccessCodes(): string[] {
|
|
||||||
return this.accessCodes;
|
|
||||||
},
|
|
||||||
getAccessMenus(): MenuRecordRaw[] {
|
|
||||||
return this.accessMenus;
|
|
||||||
},
|
|
||||||
getAccessRoutes(): RouteRecordRaw[] {
|
|
||||||
return this.accessRoutes;
|
|
||||||
},
|
|
||||||
getAccessToken(): AccessToken {
|
|
||||||
return this.accessToken;
|
|
||||||
},
|
|
||||||
getRefreshToken(): AccessToken {
|
|
||||||
return this.refreshToken;
|
|
||||||
},
|
|
||||||
getUserInfo(): BasicUserInfo | null {
|
|
||||||
return this.userInfo;
|
|
||||||
},
|
|
||||||
getUserRoles(): string[] {
|
|
||||||
return this.userRoles;
|
|
||||||
},
|
|
||||||
string(): string[] {
|
|
||||||
return this.accessCodes;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
persist: {
|
persist: {
|
||||||
// 持久化
|
// 持久化
|
||||||
paths: ['accessToken', 'refreshToken', 'accessCodes'],
|
paths: ['accessToken', 'refreshToken', 'accessCodes'],
|
||||||
|
|
|
@ -99,9 +99,9 @@ describe('useCoreAccessStore', () => {
|
||||||
|
|
||||||
it('returns all cache tabs', () => {
|
it('returns all cache tabs', () => {
|
||||||
const store = useCoreTabbarStore();
|
const store = useCoreTabbarStore();
|
||||||
store.cacheTabs.add('Home');
|
store.cachedTabs.add('Home');
|
||||||
store.cacheTabs.add('About');
|
store.cachedTabs.add('About');
|
||||||
expect(store.getCacheTabs).toEqual(['Home', 'About']);
|
expect(store.cachedTabs).toEqual(['Home', 'About']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns all tabs, including affix tabs', () => {
|
it('returns all tabs, including affix tabs', () => {
|
||||||
|
@ -290,7 +290,7 @@ describe('useCoreAccessStore', () => {
|
||||||
|
|
||||||
await store.refresh(router);
|
await store.refresh(router);
|
||||||
|
|
||||||
expect(store.excludeCacheTabs.has('Dashboard')).toBe(false);
|
expect(store.excludeCachedTabs.has('Dashboard')).toBe(false);
|
||||||
expect(store.renderRouteView).toBe(true);
|
expect(store.renderRouteView).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,60 +7,15 @@ import { startProgress, stopProgress } from '@vben-core/toolkit';
|
||||||
|
|
||||||
import { acceptHMRUpdate, defineStore } from 'pinia';
|
import { acceptHMRUpdate, defineStore } from 'pinia';
|
||||||
|
|
||||||
/**
|
|
||||||
* @zh_CN 克隆路由,防止路由被修改
|
|
||||||
* @param route
|
|
||||||
*/
|
|
||||||
function cloneTab(route: TabItem): TabItem {
|
|
||||||
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: TabItem) {
|
|
||||||
return tab.meta?.affixTab ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @zh_CN 是否显示标签
|
|
||||||
* @param tab
|
|
||||||
*/
|
|
||||||
function isTabShow(tab: TabItem) {
|
|
||||||
return !tab.meta.hideInTab;
|
|
||||||
}
|
|
||||||
|
|
||||||
function routeToTab(route: RouteRecordNormalized) {
|
|
||||||
return {
|
|
||||||
meta: route.meta,
|
|
||||||
name: route.name,
|
|
||||||
path: route.path,
|
|
||||||
} as unknown as TabItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TabsState {
|
interface TabsState {
|
||||||
/**
|
/**
|
||||||
* @zh_CN 当前打开的标签页列表缓存
|
* @zh_CN 当前打开的标签页列表缓存
|
||||||
*/
|
*/
|
||||||
cacheTabs: Set<string>;
|
cachedTabs: Set<string>;
|
||||||
/**
|
/**
|
||||||
* @zh_CN 需要排除缓存的标签页
|
* @zh_CN 需要排除缓存的标签页
|
||||||
*/
|
*/
|
||||||
excludeCacheTabs: Set<string>;
|
excludeCachedTabs: Set<string>;
|
||||||
/**
|
/**
|
||||||
* @zh_CN 是否刷新
|
* @zh_CN 是否刷新
|
||||||
*/
|
*/
|
||||||
|
@ -81,7 +36,7 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
|
||||||
*/
|
*/
|
||||||
async _bulkCloseByPaths(paths: string[]) {
|
async _bulkCloseByPaths(paths: string[]) {
|
||||||
this.tabs = this.tabs.filter((item) => {
|
this.tabs = this.tabs.filter((item) => {
|
||||||
return !paths.includes(this.getTabPath(item));
|
return !paths.includes(getTabPath(item));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updateCacheTab();
|
this.updateCacheTab();
|
||||||
|
@ -128,12 +83,12 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
|
||||||
*/
|
*/
|
||||||
addTab(routeTab: TabItem) {
|
addTab(routeTab: TabItem) {
|
||||||
const tab = cloneTab(routeTab);
|
const tab = cloneTab(routeTab);
|
||||||
if (!isTabShow(tab)) {
|
if (!isTabShown(tab)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabIndex = this.tabs.findIndex((tab) => {
|
const tabIndex = this.tabs.findIndex((tab) => {
|
||||||
return this.getTabPath(tab) === this.getTabPath(routeTab);
|
return getTabPath(tab) === getTabPath(routeTab);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (tabIndex === -1) {
|
if (tabIndex === -1) {
|
||||||
|
@ -159,19 +114,22 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
|
||||||
*/
|
*/
|
||||||
async closeLeftTabs(tab: TabItem) {
|
async closeLeftTabs(tab: TabItem) {
|
||||||
const index = this.tabs.findIndex(
|
const index = this.tabs.findIndex(
|
||||||
(item) => this.getTabPath(item) === this.getTabPath(tab),
|
(item) => getTabPath(item) === getTabPath(tab),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (index > 0) {
|
if (index < 1) {
|
||||||
const leftTabs = this.tabs.slice(0, index);
|
return;
|
||||||
const paths: string[] = [];
|
|
||||||
for (const item of leftTabs) {
|
|
||||||
if (!isAffixTab(tab)) {
|
|
||||||
paths.push(this.getTabPath(item));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await this._bulkCloseByPaths(paths);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -179,20 +137,18 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
|
||||||
* @param tab
|
* @param tab
|
||||||
*/
|
*/
|
||||||
async closeOtherTabs(tab: TabItem) {
|
async closeOtherTabs(tab: TabItem) {
|
||||||
const closePaths = this.tabs.map((item) => this.getTabPath(item));
|
const closePaths = this.tabs.map((item) => getTabPath(item));
|
||||||
|
|
||||||
const paths: string[] = [];
|
const paths: string[] = [];
|
||||||
|
|
||||||
for (const path of closePaths) {
|
for (const path of closePaths) {
|
||||||
if (path !== tab.fullPath) {
|
if (path !== tab.fullPath) {
|
||||||
const closeTab = this.tabs.find(
|
const closeTab = this.tabs.find((item) => getTabPath(item) === path);
|
||||||
(item) => this.getTabPath(item) === path,
|
|
||||||
);
|
|
||||||
if (!closeTab) {
|
if (!closeTab) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!isAffixTab(tab)) {
|
if (!isAffixTab(closeTab)) {
|
||||||
paths.push(this.getTabPath(closeTab));
|
paths.push(getTabPath(closeTab));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +161,7 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
|
||||||
*/
|
*/
|
||||||
async closeRightTabs(tab: TabItem) {
|
async closeRightTabs(tab: TabItem) {
|
||||||
const index = this.tabs.findIndex(
|
const index = this.tabs.findIndex(
|
||||||
(item) => this.getTabPath(item) === this.getTabPath(tab),
|
(item) => getTabPath(item) === getTabPath(tab),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (index >= 0 && index < this.tabs.length - 1) {
|
if (index >= 0 && index < this.tabs.length - 1) {
|
||||||
|
@ -213,8 +169,8 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
|
||||||
|
|
||||||
const paths: string[] = [];
|
const paths: string[] = [];
|
||||||
for (const item of rightTabs) {
|
for (const item of rightTabs) {
|
||||||
if (!isAffixTab(tab)) {
|
if (!isAffixTab(item)) {
|
||||||
paths.push(this.getTabPath(item));
|
paths.push(getTabPath(item));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this._bulkCloseByPaths(paths);
|
await this._bulkCloseByPaths(paths);
|
||||||
|
@ -230,13 +186,13 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
|
||||||
const { currentRoute } = router;
|
const { currentRoute } = router;
|
||||||
|
|
||||||
// 关闭不是激活选项卡
|
// 关闭不是激活选项卡
|
||||||
if (this.getTabPath(currentRoute.value) !== this.getTabPath(tab)) {
|
if (getTabPath(currentRoute.value) !== getTabPath(tab)) {
|
||||||
this._close(tab);
|
this._close(tab);
|
||||||
this.updateCacheTab();
|
this.updateCacheTab();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const index = this.getTabs.findIndex(
|
const index = this.getTabs.findIndex(
|
||||||
(item) => this.getTabPath(item) === this.getTabPath(currentRoute.value),
|
(item) => getTabPath(item) === getTabPath(currentRoute.value),
|
||||||
);
|
);
|
||||||
|
|
||||||
const before = this.getTabs[index - 1];
|
const before = this.getTabs[index - 1];
|
||||||
|
@ -259,25 +215,21 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
|
||||||
* @param key
|
* @param key
|
||||||
*/
|
*/
|
||||||
async closeTabByKey(key: string, router: Router) {
|
async closeTabByKey(key: string, router: Router) {
|
||||||
const index = this.tabs.findIndex(
|
const index = this.tabs.findIndex((item) => getTabPath(item) === key);
|
||||||
(item) => this.getTabPath(item) === key,
|
|
||||||
);
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.closeTab(this.tabs[index], router);
|
await this.closeTab(this.tabs[index], router);
|
||||||
},
|
},
|
||||||
getTabPath(tab: RouteRecordNormalized | TabItem) {
|
|
||||||
return decodeURIComponent((tab as TabItem).fullPath || tab.path);
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 固定标签页
|
* @zh_CN 固定标签页
|
||||||
* @param tab
|
* @param tab
|
||||||
*/
|
*/
|
||||||
async pushPinTab(tab: TabItem) {
|
async pinTab(tab: TabItem) {
|
||||||
const index = this.tabs.findIndex(
|
const index = this.tabs.findIndex(
|
||||||
(item) => this.getTabPath(item) === this.getTabPath(tab),
|
(item) => getTabPath(item) === getTabPath(tab),
|
||||||
);
|
);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
tab.meta.affixTab = true;
|
tab.meta.affixTab = true;
|
||||||
|
@ -291,13 +243,13 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
|
||||||
const { currentRoute } = router;
|
const { currentRoute } = router;
|
||||||
const { name } = currentRoute.value;
|
const { name } = currentRoute.value;
|
||||||
|
|
||||||
this.excludeCacheTabs.add(name as string);
|
this.excludeCachedTabs.add(name as string);
|
||||||
this.renderRouteView = false;
|
this.renderRouteView = false;
|
||||||
startProgress();
|
startProgress();
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||||
|
|
||||||
this.excludeCacheTabs.delete(name as string);
|
this.excludeCachedTabs.delete(name as string);
|
||||||
this.renderRouteView = true;
|
this.renderRouteView = true;
|
||||||
stopProgress();
|
stopProgress();
|
||||||
},
|
},
|
||||||
|
@ -315,9 +267,9 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
|
||||||
* @zh_CN 取消固定标签页
|
* @zh_CN 取消固定标签页
|
||||||
* @param tab
|
* @param tab
|
||||||
*/
|
*/
|
||||||
async unPushPinTab(tab: TabItem) {
|
async unpinTab(tab: TabItem) {
|
||||||
const index = this.tabs.findIndex(
|
const index = this.tabs.findIndex(
|
||||||
(item) => this.getTabPath(item) === this.getTabPath(tab),
|
(item) => getTabPath(item) === getTabPath(tab),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
|
@ -347,33 +299,32 @@ const useCoreTabbarStore = defineStore('core-tabbar', {
|
||||||
const name = tab.name as string;
|
const name = tab.name as string;
|
||||||
cacheMap.add(name);
|
cacheMap.add(name);
|
||||||
}
|
}
|
||||||
this.cacheTabs = cacheMap;
|
this.cachedTabs = cacheMap;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
affixTabs(): TabItem[] {
|
affixTabs(): TabItem[] {
|
||||||
return this.tabs.filter((tab) => isAffixTab(tab));
|
return this.tabs.filter((tab) => isAffixTab(tab));
|
||||||
},
|
},
|
||||||
getCacheTabs(): string[] {
|
getCachedTabs(): string[] {
|
||||||
return [...this.cacheTabs];
|
return [...this.cachedTabs];
|
||||||
},
|
},
|
||||||
getExcludeTabs(): string[] {
|
getExcludeCachedTabs(): string[] {
|
||||||
return [...this.excludeCacheTabs];
|
return [...this.excludeCachedTabs];
|
||||||
},
|
},
|
||||||
getTabs(): TabItem[] {
|
getTabs(): TabItem[] {
|
||||||
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
|
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
|
||||||
const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab));
|
const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab));
|
||||||
|
|
||||||
return [...affixTabs, ...normalTabs];
|
return [...affixTabs, ...normalTabs];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
persist: {
|
persist: {
|
||||||
// 持久化
|
// 持久化
|
||||||
paths: [],
|
paths: ['tabs'],
|
||||||
},
|
},
|
||||||
state: (): TabsState => ({
|
state: (): TabsState => ({
|
||||||
cacheTabs: new Set(),
|
cachedTabs: new Set(),
|
||||||
excludeCacheTabs: new Set(),
|
excludeCachedTabs: new Set(),
|
||||||
renderRouteView: true,
|
renderRouteView: true,
|
||||||
tabs: [],
|
tabs: [],
|
||||||
}),
|
}),
|
||||||
|
@ -385,4 +336,57 @@ if (hot) {
|
||||||
hot.accept(acceptHMRUpdate(useCoreTabbarStore, hot));
|
hot.accept(acceptHMRUpdate(useCoreTabbarStore, hot));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @zh_CN 克隆路由,防止路由被修改
|
||||||
|
* @param route
|
||||||
|
*/
|
||||||
|
function cloneTab(route: TabItem): TabItem {
|
||||||
|
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: TabItem) {
|
||||||
|
return tab.meta?.affixTab ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @zh_CN 是否显示标签
|
||||||
|
* @param tab
|
||||||
|
*/
|
||||||
|
function isTabShown(tab: TabItem) {
|
||||||
|
return !tab.meta.hideInTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @zh_CN 获取标签页路径
|
||||||
|
* @param tab
|
||||||
|
*/
|
||||||
|
function getTabPath(tab: RouteRecordNormalized | TabItem) {
|
||||||
|
return decodeURIComponent((tab as TabItem).fullPath || tab.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function routeToTab(route: RouteRecordNormalized) {
|
||||||
|
return {
|
||||||
|
meta: route.meta,
|
||||||
|
name: route.name,
|
||||||
|
path: route.path,
|
||||||
|
} as TabItem;
|
||||||
|
}
|
||||||
|
|
||||||
export { useCoreTabbarStore };
|
export { useCoreTabbarStore };
|
||||||
|
|
|
@ -2,8 +2,7 @@ import { createPinia } from 'pinia';
|
||||||
|
|
||||||
interface InitStoreOptions {
|
interface InitStoreOptions {
|
||||||
/**
|
/**
|
||||||
* @zh_CN 应用名,由于 @vben-core/stores 是公用的,后续可能有多个app,为了防止多个app缓存冲突,可在这里配置应用名
|
* @zh_CN 应用名,由于 @vben-core/stores 是公用的,后续可能有多个app,为了防止多个app缓存冲突,可在这里配置应用名,应用名将被用于持久化的前缀
|
||||||
* 应用名将被用于持久化的前缀
|
|
||||||
*/
|
*/
|
||||||
namespace: string;
|
namespace: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,7 @@
|
||||||
"title": "Tabbar",
|
"title": "Tabbar",
|
||||||
"enable": "Enable Tab Bar",
|
"enable": "Enable Tab Bar",
|
||||||
"icon": "Display Tabbar Icon",
|
"icon": "Display Tabbar Icon",
|
||||||
|
"persist": "Persistent tabs",
|
||||||
"context-menu": {
|
"context-menu": {
|
||||||
"reload": "Reload",
|
"reload": "Reload",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
|
|
|
@ -158,6 +158,7 @@
|
||||||
"title": "标签栏",
|
"title": "标签栏",
|
||||||
"enable": "启用标签栏",
|
"enable": "启用标签栏",
|
||||||
"icon": "显示标签栏图标",
|
"icon": "显示标签栏图标",
|
||||||
|
"persist": "持久化标签页",
|
||||||
"context-menu": {
|
"context-menu": {
|
||||||
"reload": "重新加载",
|
"reload": "重新加载",
|
||||||
"close": "关闭标签页",
|
"close": "关闭标签页",
|
||||||
|
|
|
@ -25,15 +25,15 @@ defineOptions({
|
||||||
withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<Props>(), {
|
||||||
icon: '',
|
icon: '',
|
||||||
});
|
});
|
||||||
const emit = defineEmits<{ close: []; unPushPin: [] }>();
|
const emit = defineEmits<{ close: []; unpinTab: [] }>();
|
||||||
|
|
||||||
const { b, e, is } = useNamespace('chrome-tab');
|
const { b, e, is } = useNamespace('chrome-tab');
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
emit('close');
|
emit('close');
|
||||||
}
|
}
|
||||||
function handleUnPushPin() {
|
function handleUnpinTab() {
|
||||||
emit('unPushPin');
|
emit('unpinTab');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ function handleUnPushPin() {
|
||||||
<div
|
<div
|
||||||
v-show="affixTab && !onlyOne"
|
v-show="affixTab && !onlyOne"
|
||||||
:class="[e('extra'), is('pin', true)]"
|
:class="[e('extra'), is('pin', true)]"
|
||||||
@click.stop="handleUnPushPin"
|
@click.stop="handleUnpinTab"
|
||||||
>
|
>
|
||||||
<MdiPin :class="e('extra-icon')" />
|
<MdiPin :class="e('extra-icon')" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
tabs: () => [],
|
tabs: () => [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{ close: [string]; unPushPin: [TabItem] }>();
|
const emit = defineEmits<{ close: [string]; unpinTab: [TabItem] }>();
|
||||||
|
|
||||||
const gap = 7;
|
const gap = 7;
|
||||||
|
|
||||||
|
@ -77,8 +77,8 @@ onMounted(() => {
|
||||||
function handleClose(key: string) {
|
function handleClose(key: string) {
|
||||||
emit('close', key);
|
emit('close', key);
|
||||||
}
|
}
|
||||||
function handleUnPushPin(tab: TabItem) {
|
function handleUnpinTab(tab: TabItem) {
|
||||||
emit('unPushPin', tab);
|
emit('unpinTab', tab);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ function handleUnPushPin(tab: TabItem) {
|
||||||
:title="tab.title"
|
:title="tab.title"
|
||||||
@click="active = tab.key"
|
@click="active = tab.key"
|
||||||
@close="() => handleClose(tab.key)"
|
@close="() => handleClose(tab.key)"
|
||||||
@un-push-pin="() => handleUnPushPin(tab)"
|
@unpin-tab="() => handleUnpinTab(tab)"
|
||||||
/>
|
/>
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,7 +15,7 @@ function useAccess() {
|
||||||
* @param roles
|
* @param roles
|
||||||
*/
|
*/
|
||||||
function hasAuthByRoles(roles: string[]) {
|
function hasAuthByRoles(roles: string[]) {
|
||||||
const userRoleSet = new Set(coreAccessStore.getUserRoles);
|
const userRoleSet = new Set(coreAccessStore.userRoles);
|
||||||
const intersection = roles.filter((item) => userRoleSet.has(item));
|
const intersection = roles.filter((item) => userRoleSet.has(item));
|
||||||
return intersection.length > 0;
|
return intersection.length > 0;
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ function useAccess() {
|
||||||
* @param codes
|
* @param codes
|
||||||
*/
|
*/
|
||||||
function hasAuthByCodes(codes: string[]) {
|
function hasAuthByCodes(codes: string[]) {
|
||||||
const userCodesSet = new Set(coreAccessStore.getAccessCodes);
|
const userCodesSet = new Set(coreAccessStore.accessCodes);
|
||||||
|
|
||||||
const intersection = codes.filter((item) => userCodesSet.has(item));
|
const intersection = codes.filter((item) => userCodesSet.has(item));
|
||||||
return intersection.length > 0;
|
return intersection.length > 0;
|
||||||
|
|
|
@ -10,12 +10,12 @@ import { useContentSpinner } from './use-content-spinner';
|
||||||
|
|
||||||
defineOptions({ name: 'LayoutContent' });
|
defineOptions({ name: 'LayoutContent' });
|
||||||
|
|
||||||
const tabsStore = useCoreTabbarStore();
|
const tabbarStore = useCoreTabbarStore();
|
||||||
const { keepAlive } = usePreferences();
|
const { keepAlive } = usePreferences();
|
||||||
const { spinning } = useContentSpinner();
|
const { spinning } = useContentSpinner();
|
||||||
|
|
||||||
const { getCacheTabs, getExcludeTabs, renderRouteView } =
|
const { getCachedTabs, getExcludeCachedTabs, renderRouteView } =
|
||||||
storeToRefs(tabsStore);
|
storeToRefs(tabbarStore);
|
||||||
|
|
||||||
// 页面切换动画
|
// 页面切换动画
|
||||||
function getTransitionName(route: RouteLocationNormalizedLoaded) {
|
function getTransitionName(route: RouteLocationNormalizedLoaded) {
|
||||||
|
@ -36,7 +36,7 @@ function getTransitionName(route: RouteLocationNormalizedLoaded) {
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
// 已经打开且已经加载过的页面不使用动画
|
// 已经打开且已经加载过的页面不使用动画
|
||||||
const inTabs = getCacheTabs.value.includes(route.name as string);
|
const inTabs = getCachedTabs.value.includes(route.name as string);
|
||||||
|
|
||||||
return inTabs && route.meta.loaded ? undefined : transitionName;
|
return inTabs && route.meta.loaded ? undefined : transitionName;
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,8 @@ function getTransitionName(route: RouteLocationNormalizedLoaded) {
|
||||||
<Transition :name="getTransitionName(route)" appear mode="out-in">
|
<Transition :name="getTransitionName(route)" appear mode="out-in">
|
||||||
<KeepAlive
|
<KeepAlive
|
||||||
v-if="keepAlive"
|
v-if="keepAlive"
|
||||||
:exclude="getExcludeTabs"
|
:exclude="getExcludeCachedTabs"
|
||||||
:include="getCacheTabs"
|
:include="getCachedTabs"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="Component"
|
:is="Component"
|
||||||
|
|
|
@ -34,7 +34,7 @@ const { globalSearchShortcutKey } = usePreferences();
|
||||||
<div class="flex h-full min-w-0 flex-shrink-0 items-center">
|
<div class="flex h-full min-w-0 flex-shrink-0 items-center">
|
||||||
<GlobalSearch
|
<GlobalSearch
|
||||||
:enable-shortcut-key="globalSearchShortcutKey"
|
:enable-shortcut-key="globalSearchShortcutKey"
|
||||||
:menus="accessStore.getAccessMenus"
|
:menus="accessStore.accessMenus"
|
||||||
class="mr-4"
|
class="mr-4"
|
||||||
/>
|
/>
|
||||||
<ThemeToggle class="mr-2" />
|
<ThemeToggle class="mr-2" />
|
||||||
|
|
|
@ -13,7 +13,7 @@ function useExtraMenu() {
|
||||||
const accessStore = useCoreAccessStore();
|
const accessStore = useCoreAccessStore();
|
||||||
const { navigation } = useNavigation();
|
const { navigation } = useNavigation();
|
||||||
|
|
||||||
const menus = computed(() => accessStore.getAccessMenus);
|
const menus = computed(() => accessStore.accessMenus);
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const extraMenus = ref<MenuRecordRaw[]>([]);
|
const extraMenus = ref<MenuRecordRaw[]>([]);
|
||||||
|
|
|
@ -29,7 +29,7 @@ function useMixedMenu() {
|
||||||
}
|
}
|
||||||
return enableSidebar;
|
return enableSidebar;
|
||||||
});
|
});
|
||||||
const menus = computed(() => accessStore.getAccessMenus);
|
const menus = computed(() => accessStore.accessMenus);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 头部菜单
|
* 头部菜单
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import { preferences } from '@vben-core/preferences';
|
||||||
|
import { useCoreTabbarStore } from '@vben-core/stores';
|
||||||
import { TabsView } from '@vben-core/tabs-ui';
|
import { TabsView } from '@vben-core/tabs-ui';
|
||||||
|
|
||||||
import { useTabs } from './use-tabs';
|
import { useTabs } from './use-tabs';
|
||||||
|
@ -9,14 +13,23 @@ defineOptions({
|
||||||
|
|
||||||
defineProps<{ showIcon?: boolean }>();
|
defineProps<{ showIcon?: boolean }>();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const coreTabbarStore = useCoreTabbarStore();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
createContextMenus,
|
createContextMenus,
|
||||||
currentActive,
|
currentActive,
|
||||||
currentTabs,
|
currentTabs,
|
||||||
handleClick,
|
handleClick,
|
||||||
handleClose,
|
handleClose,
|
||||||
handleUnPushPin,
|
handleUnpinTab,
|
||||||
} = useTabs();
|
} = useTabs();
|
||||||
|
|
||||||
|
// 刷新后如果不保持tab状态,关闭其他tab
|
||||||
|
if (!preferences.tabbar.persist) {
|
||||||
|
coreTabbarStore.closeOtherTabs(route);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -26,7 +39,7 @@ const {
|
||||||
:show-icon="showIcon"
|
:show-icon="showIcon"
|
||||||
:tabs="currentTabs"
|
:tabs="currentTabs"
|
||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
@un-push-pin="handleUnPushPin"
|
@unpin-tab="handleUnpinTab"
|
||||||
@update:active="handleClick"
|
@update:active="handleClick"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -30,7 +30,7 @@ function useTabs() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const accessStore = useCoreAccessStore();
|
const accessStore = useCoreAccessStore();
|
||||||
const tabbarStore = useCoreTabbarStore();
|
const coreTabbarStore = useCoreTabbarStore();
|
||||||
const { accessMenus } = storeToRefs(accessStore);
|
const { accessMenus } = storeToRefs(accessStore);
|
||||||
|
|
||||||
const currentActive = computed(() => {
|
const currentActive = computed(() => {
|
||||||
|
@ -39,7 +39,7 @@ function useTabs() {
|
||||||
|
|
||||||
const { locale } = useI18n();
|
const { locale } = useI18n();
|
||||||
const currentTabs = ref<RouteLocationNormalizedGeneric[]>();
|
const currentTabs = ref<RouteLocationNormalizedGeneric[]>();
|
||||||
watch([() => tabbarStore.getTabs, () => locale.value], ([tabs, _]) => {
|
watch([() => coreTabbarStore.getTabs, () => locale.value], ([tabs, _]) => {
|
||||||
currentTabs.value = tabs.map((item) => wrapperTabLocale(item));
|
currentTabs.value = tabs.map((item) => wrapperTabLocale(item));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ function useTabs() {
|
||||||
const affixTabs = filterTree(router.getRoutes(), (route) => {
|
const affixTabs = filterTree(router.getRoutes(), (route) => {
|
||||||
return !!route.meta?.affixTab;
|
return !!route.meta?.affixTab;
|
||||||
});
|
});
|
||||||
tabbarStore.setAffixTabs(affixTabs);
|
coreTabbarStore.setAffixTabs(affixTabs);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 点击tab,跳转路由
|
// 点击tab,跳转路由
|
||||||
|
@ -60,7 +60,7 @@ function useTabs() {
|
||||||
|
|
||||||
// 关闭tab
|
// 关闭tab
|
||||||
const handleClose = async (key: string) => {
|
const handleClose = async (key: string) => {
|
||||||
await tabbarStore.closeTabByKey(key, router);
|
await coreTabbarStore.closeTabByKey(key, router);
|
||||||
};
|
};
|
||||||
|
|
||||||
function wrapperTabLocale(tab: RouteLocationNormalizedGeneric) {
|
function wrapperTabLocale(tab: RouteLocationNormalizedGeneric) {
|
||||||
|
@ -84,14 +84,14 @@ function useTabs() {
|
||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.path,
|
||||||
() => {
|
() => {
|
||||||
tabbarStore.addTab(route as RouteLocationNormalized);
|
coreTabbarStore.addTab(route as RouteLocationNormalized);
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const createContextMenus = (tab: TabItem) => {
|
const createContextMenus = (tab: TabItem) => {
|
||||||
const tabs = tabbarStore.getTabs;
|
const tabs = coreTabbarStore.getTabs;
|
||||||
const affixTabs = tabbarStore.affixTabs;
|
const affixTabs = coreTabbarStore.affixTabs;
|
||||||
const index = tabs.findIndex((item) => item.path === tab.path);
|
const index = tabs.findIndex((item) => item.path === tab.path);
|
||||||
|
|
||||||
const disabled = tabs.length <= 1;
|
const disabled = tabs.length <= 1;
|
||||||
|
@ -113,7 +113,7 @@ function useTabs() {
|
||||||
{
|
{
|
||||||
disabled: !isCurrentTab,
|
disabled: !isCurrentTab,
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
await tabbarStore.refresh(router);
|
await coreTabbarStore.refresh(router);
|
||||||
},
|
},
|
||||||
icon: IcRoundRefresh,
|
icon: IcRoundRefresh,
|
||||||
key: 'reload',
|
key: 'reload',
|
||||||
|
@ -122,7 +122,7 @@ function useTabs() {
|
||||||
{
|
{
|
||||||
disabled: !!affixTab || disabled,
|
disabled: !!affixTab || disabled,
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
await tabbarStore.closeTab(tab, router);
|
await coreTabbarStore.closeTab(tab, router);
|
||||||
},
|
},
|
||||||
icon: IcRoundClose,
|
icon: IcRoundClose,
|
||||||
key: 'close',
|
key: 'close',
|
||||||
|
@ -131,8 +131,8 @@ function useTabs() {
|
||||||
{
|
{
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
await (affixTab
|
await (affixTab
|
||||||
? tabbarStore.unPushPinTab(tab)
|
? coreTabbarStore.unpinTab(tab)
|
||||||
: tabbarStore.pushPinTab(tab));
|
: coreTabbarStore.pinTab(tab));
|
||||||
},
|
},
|
||||||
icon: affixTab ? MdiPinOff : MdiPin,
|
icon: affixTab ? MdiPinOff : MdiPin,
|
||||||
key: 'affix',
|
key: 'affix',
|
||||||
|
@ -144,7 +144,7 @@ function useTabs() {
|
||||||
{
|
{
|
||||||
disabled: closeLeftDisabled,
|
disabled: closeLeftDisabled,
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
await tabbarStore.closeLeftTabs(tab);
|
await coreTabbarStore.closeLeftTabs(tab);
|
||||||
},
|
},
|
||||||
icon: MdiFormatHorizontalAlignLeft,
|
icon: MdiFormatHorizontalAlignLeft,
|
||||||
key: 'close-left',
|
key: 'close-left',
|
||||||
|
@ -153,7 +153,7 @@ function useTabs() {
|
||||||
{
|
{
|
||||||
disabled: closeRightDisabled,
|
disabled: closeRightDisabled,
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
await tabbarStore.closeRightTabs(tab);
|
await coreTabbarStore.closeRightTabs(tab);
|
||||||
},
|
},
|
||||||
icon: MdiFormatHorizontalAlignRight,
|
icon: MdiFormatHorizontalAlignRight,
|
||||||
key: 'close-right',
|
key: 'close-right',
|
||||||
|
@ -163,7 +163,7 @@ function useTabs() {
|
||||||
{
|
{
|
||||||
disabled: closeOtherDisabled,
|
disabled: closeOtherDisabled,
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
await tabbarStore.closeOtherTabs(tab);
|
await coreTabbarStore.closeOtherTabs(tab);
|
||||||
},
|
},
|
||||||
icon: MdiArrowExpandHorizontal,
|
icon: MdiArrowExpandHorizontal,
|
||||||
key: 'close-other',
|
key: 'close-other',
|
||||||
|
@ -172,7 +172,7 @@ function useTabs() {
|
||||||
{
|
{
|
||||||
disabled,
|
disabled,
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
await tabbarStore.closeAllTabs(router);
|
await coreTabbarStore.closeAllTabs(router);
|
||||||
},
|
},
|
||||||
icon: IcRoundMultipleStop,
|
icon: IcRoundMultipleStop,
|
||||||
key: 'close-all',
|
key: 'close-all',
|
||||||
|
@ -190,8 +190,8 @@ function useTabs() {
|
||||||
/**
|
/**
|
||||||
* 取消固定标签页
|
* 取消固定标签页
|
||||||
*/
|
*/
|
||||||
const handleUnPushPin = async (tab: TabItem) => {
|
const handleUnpinTab = async (tab: TabItem) => {
|
||||||
await tabbarStore.unPushPinTab(tab);
|
await coreTabbarStore.unpinTab(tab);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -200,7 +200,7 @@ function useTabs() {
|
||||||
currentTabs,
|
currentTabs,
|
||||||
handleClick,
|
handleClick,
|
||||||
handleClose,
|
handleClose,
|
||||||
handleUnPushPin,
|
handleUnpinTab,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { useCoreTabbarStore } from '@vben-core/stores';
|
||||||
defineOptions({ name: 'IFrameRouterView' });
|
defineOptions({ name: 'IFrameRouterView' });
|
||||||
|
|
||||||
const spinningList = ref<boolean[]>([]);
|
const spinningList = ref<boolean[]>([]);
|
||||||
const tabsStore = useCoreTabbarStore();
|
const coreTabbarStore = useCoreTabbarStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const enableTabbar = computed(() => preferences.tabbar.enable);
|
const enableTabbar = computed(() => preferences.tabbar.enable);
|
||||||
|
@ -20,7 +20,7 @@ const iframeRoutes = computed(() => {
|
||||||
if (!enableTabbar.value) {
|
if (!enableTabbar.value) {
|
||||||
return route.meta.iframeSrc ? [route] : [];
|
return route.meta.iframeSrc ? [route] : [];
|
||||||
}
|
}
|
||||||
return tabsStore.getTabs.filter((tab) => !!tab.meta?.iframeSrc);
|
return coreTabbarStore.getTabs.filter((tab) => !!tab.meta?.iframeSrc);
|
||||||
});
|
});
|
||||||
|
|
||||||
const tabNames = computed(
|
const tabNames = computed(
|
||||||
|
@ -36,7 +36,7 @@ function routeShow(tabItem: RouteLocationNormalized) {
|
||||||
function canRender(tabItem: RouteLocationNormalized) {
|
function canRender(tabItem: RouteLocationNormalized) {
|
||||||
const { meta, name } = tabItem;
|
const { meta, name } = tabItem;
|
||||||
|
|
||||||
if (!name || !tabsStore.renderRouteView) {
|
if (!name || !coreTabbarStore.renderRouteView) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ function canRender(tabItem: RouteLocationNormalized) {
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return tabsStore.getTabs.some((tab) => tab.name === name);
|
return coreTabbarStore.getTabs.some((tab) => tab.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideLoading(index: number) {
|
function hideLoading(index: number) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ defineProps<{ disabled?: boolean }>();
|
||||||
|
|
||||||
const tabbarEnable = defineModel<boolean>('tabbarEnable');
|
const tabbarEnable = defineModel<boolean>('tabbarEnable');
|
||||||
const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
|
const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
|
||||||
|
const tabbarPersist = defineModel<boolean>('tabbarPersist');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -20,4 +21,7 @@ const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
|
||||||
<SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable">
|
<SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable">
|
||||||
{{ $t('preferences.tabbar.icon') }}
|
{{ $t('preferences.tabbar.icon') }}
|
||||||
</SwitchItem>
|
</SwitchItem>
|
||||||
|
<SwitchItem v-model="tabbarPersist" :disabled="!tabbarEnable">
|
||||||
|
{{ $t('preferences.tabbar.persist') }}
|
||||||
|
</SwitchItem>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -96,6 +96,7 @@ const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');
|
||||||
|
|
||||||
const tabbarEnable = defineModel<boolean>('tabbarEnable');
|
const tabbarEnable = defineModel<boolean>('tabbarEnable');
|
||||||
const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
|
const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
|
||||||
|
const tabbarPersist = defineModel<boolean>('tabbarPersist');
|
||||||
|
|
||||||
const navigationStyleType = defineModel<NavigationStyleType>(
|
const navigationStyleType = defineModel<NavigationStyleType>(
|
||||||
'navigationStyleType',
|
'navigationStyleType',
|
||||||
|
@ -341,6 +342,7 @@ async function handleReset() {
|
||||||
<Block :title="$t('preferences.tabbar.title')">
|
<Block :title="$t('preferences.tabbar.title')">
|
||||||
<Tabbar
|
<Tabbar
|
||||||
v-model:tabbar-enable="tabbarEnable"
|
v-model:tabbar-enable="tabbarEnable"
|
||||||
|
v-model:tabbar-persist="tabbarPersist"
|
||||||
v-model:tabbar-show-icon="tabbarShowIcon"
|
v-model:tabbar-show-icon="tabbarShowIcon"
|
||||||
/>
|
/>
|
||||||
</Block>
|
</Block>
|
||||||
|
|
Loading…
Reference in New Issue