feat(projects): 新增多页签缓存功能
This commit is contained in:
parent
ffe987832f
commit
d86f891c64
@ -4,5 +4,7 @@ export enum EnumStorageKey {
|
|||||||
/** 用户刷新token */
|
/** 用户刷新token */
|
||||||
'refresh-koken' = '__REFRESH_TOKEN__',
|
'refresh-koken' = '__REFRESH_TOKEN__',
|
||||||
/** 用户信息 */
|
/** 用户信息 */
|
||||||
'user-info' = '__USER_INFO__'
|
'user-info' = '__USER_INFO__',
|
||||||
|
/** 多页签路由信息 */
|
||||||
|
'tab-route' = '__TAB_ROUTE__'
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { RouteLocationNormalizedLoaded } from 'vue-router';
|
||||||
import type { MenuOption } from 'naive-ui';
|
import type { MenuOption } from 'naive-ui';
|
||||||
import { EnumLoginModule } from '@/enum';
|
import { EnumLoginModule } from '@/enum';
|
||||||
|
|
||||||
@ -7,6 +8,16 @@ export type GlobalMenuOption = MenuOption & {
|
|||||||
routePath: string;
|
routePath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 多页签 */
|
||||||
|
export interface MultiTab {
|
||||||
|
routes: MultiTabRoute[];
|
||||||
|
activeRoute: string;
|
||||||
|
}
|
||||||
|
export type MultiTabRoute = Partial<RouteLocationNormalizedLoaded> & {
|
||||||
|
path: string;
|
||||||
|
fullPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
/** 登录模块 */
|
/** 登录模块 */
|
||||||
export type LoginModuleType = keyof typeof EnumLoginModule;
|
export type LoginModuleType = keyof typeof EnumLoginModule;
|
||||||
|
|
||||||
|
@ -96,6 +96,8 @@ interface MultiTabStyle {
|
|||||||
bgColor: string;
|
bgColor: string;
|
||||||
/** 多页签模式 */
|
/** 多页签模式 */
|
||||||
mode: MultiTabMode;
|
mode: MultiTabMode;
|
||||||
|
/** 开启多页签缓存 */
|
||||||
|
isCache: boolean;
|
||||||
/** 多页签模式列表 */
|
/** 多页签模式列表 */
|
||||||
modeList: MultiTabModeList[];
|
modeList: MultiTabModeList[];
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,12 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, nextTick } from 'vue';
|
import { reactive, nextTick } from 'vue';
|
||||||
|
import { useEventListener } from '@vueuse/core';
|
||||||
import { useThemeStore, useAppStore } from '@/store';
|
import { useThemeStore, useAppStore } from '@/store';
|
||||||
import { ROUTE_HOME } from '@/router';
|
import { ROUTE_HOME } from '@/router';
|
||||||
import { ChromeTab, ButtonTab } from '@/components';
|
import { ChromeTab, ButtonTab } from '@/components';
|
||||||
import { useBoolean } from '@/hooks';
|
import { useBoolean } from '@/hooks';
|
||||||
|
import { setTabRouteStorage } from '@/utils';
|
||||||
import { ContextMenu } from './components';
|
import { ContextMenu } from './components';
|
||||||
|
|
||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
@ -69,5 +71,10 @@ function handleContextMenu(e: MouseEvent, fullPath: string) {
|
|||||||
showDropdown();
|
showDropdown();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 页面离开时缓存多页签数据 */
|
||||||
|
useEventListener(window, 'beforeunload', () => {
|
||||||
|
setTabRouteStorage(app.multiTab.routes);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -51,13 +51,25 @@
|
|||||||
@update:value="handleMultiTabHeight"
|
@update:value="handleMultiTabHeight"
|
||||||
/>
|
/>
|
||||||
</setting-menu-item>
|
</setting-menu-item>
|
||||||
|
<setting-menu-item label="多页签缓存">
|
||||||
|
<n-switch :value="theme.multiTabStyle.isCache" @update:value="handleSetMultiTabCache" />
|
||||||
|
</setting-menu-item>
|
||||||
|
<setting-menu-item label="清空多页签缓存">
|
||||||
|
<n-popconfirm placement="top-end" @positive-click="handleRemoveTabRouteCache">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button type="primary" size="small">清空</n-button>
|
||||||
|
</template>
|
||||||
|
确定要清空多页签缓存吗?
|
||||||
|
</n-popconfirm>
|
||||||
|
</setting-menu-item>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { NDivider, NSpace, NSwitch, NSelect, NInputNumber } from 'naive-ui';
|
import { NDivider, NSpace, NSwitch, NSelect, NInputNumber, NButton, NPopconfirm, useMessage } from 'naive-ui';
|
||||||
import { useThemeStore } from '@/store';
|
import { useThemeStore, useAppStore } from '@/store';
|
||||||
|
import { clearTabRoutes } from '@/utils';
|
||||||
import { SettingMenuItem } from '../common';
|
import { SettingMenuItem } from '../common';
|
||||||
|
|
||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
@ -67,8 +79,11 @@ const {
|
|||||||
handleHeaderHeight,
|
handleHeaderHeight,
|
||||||
handleMultiTabHeight,
|
handleMultiTabHeight,
|
||||||
handleMenuWidth,
|
handleMenuWidth,
|
||||||
handleMixMenuWidth
|
handleMixMenuWidth,
|
||||||
|
handleSetMultiTabCache
|
||||||
} = useThemeStore();
|
} = useThemeStore();
|
||||||
|
const { initMultiTab } = useAppStore();
|
||||||
|
const message = useMessage();
|
||||||
|
|
||||||
const isHorizontalMix = computed(() => theme.navStyle.mode === 'horizontal-mix');
|
const isHorizontalMix = computed(() => theme.navStyle.mode === 'horizontal-mix');
|
||||||
const disabledMenuWidth = computed(() => {
|
const disabledMenuWidth = computed(() => {
|
||||||
@ -77,5 +92,11 @@ const disabledMenuWidth = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const disabledMixMenuWidth = computed(() => theme.navStyle.mode !== 'vertical-mix');
|
const disabledMixMenuWidth = computed(() => theme.navStyle.mode !== 'vertical-mix');
|
||||||
|
|
||||||
|
function handleRemoveTabRouteCache() {
|
||||||
|
clearTabRoutes();
|
||||||
|
initMultiTab();
|
||||||
|
message.success('操作成功!');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
"visible": true,
|
"visible": true,
|
||||||
"bgColor": "#fff",
|
"bgColor": "#fff",
|
||||||
"mode": "chrome",
|
"mode": "chrome",
|
||||||
|
"isCache": true,
|
||||||
"modeList": [
|
"modeList": [
|
||||||
{
|
{
|
||||||
"value": "button",
|
"value": "button",
|
||||||
|
@ -58,6 +58,7 @@ const defaultThemeSettings: ThemeSettings = {
|
|||||||
visible: true,
|
visible: true,
|
||||||
bgColor: '#fff',
|
bgColor: '#fff',
|
||||||
mode: 'chrome',
|
mode: 'chrome',
|
||||||
|
isCache: true,
|
||||||
modeList: [
|
modeList: [
|
||||||
{ value: 'button', label: EnumMultiTabMode.button },
|
{ value: 'button', label: EnumMultiTabMode.button },
|
||||||
{ value: 'chrome', label: EnumMultiTabMode.chrome }
|
{ value: 'chrome', label: EnumMultiTabMode.chrome }
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
import type { RouteLocationNormalizedLoaded } from 'vue-router';
|
||||||
|
import { ROUTE_HOME } from '@/router';
|
||||||
import { brightenColor, darkenColor } from '@/utils';
|
import { brightenColor, darkenColor } from '@/utils';
|
||||||
|
import type { MultiTabRoute } from '@/interface';
|
||||||
|
|
||||||
export function getHoverAndPressedColor(color: string) {
|
export function getHoverAndPressedColor(color: string) {
|
||||||
return {
|
return {
|
||||||
@ -6,3 +9,37 @@ export function getHoverAndPressedColor(color: string) {
|
|||||||
pressed: darkenColor(color)
|
pressed: darkenColor(color)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取路由首页信息 */
|
||||||
|
export function getHomeTabRoute(route: RouteLocationNormalizedLoaded) {
|
||||||
|
const { name, path, meta } = ROUTE_HOME;
|
||||||
|
const isHome = route.name === ROUTE_HOME.name;
|
||||||
|
const home: MultiTabRoute = {
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
fullPath: path,
|
||||||
|
meta
|
||||||
|
};
|
||||||
|
if (isHome) {
|
||||||
|
Object.assign(home, route);
|
||||||
|
}
|
||||||
|
return home;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取该页签在多页签数据中的索引
|
||||||
|
* @param routes - 多页签数据
|
||||||
|
* @param fullPath - 该页签的路径
|
||||||
|
*/
|
||||||
|
export function getIndexInTabRoutes(routes: MultiTabRoute[], fullPath: string) {
|
||||||
|
return routes.findIndex(route => route.fullPath === fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断该页签是否在多页签数据中
|
||||||
|
* @param routes - 多页签数据
|
||||||
|
* @param fullPath - 该页签的路径
|
||||||
|
*/
|
||||||
|
export function isInTabRoutes(routes: MultiTabRoute[], fullPath: string) {
|
||||||
|
return getIndexInTabRoutes(routes, fullPath) > -1;
|
||||||
|
}
|
||||||
|
@ -2,8 +2,11 @@ import { nextTick } from 'vue';
|
|||||||
import type { RouteLocationNormalizedLoaded } from 'vue-router';
|
import type { RouteLocationNormalizedLoaded } from 'vue-router';
|
||||||
import type { ScrollbarInst } from 'naive-ui';
|
import type { ScrollbarInst } from 'naive-ui';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { store } from '@/store';
|
|
||||||
import { router, ROUTE_HOME } from '@/router';
|
import { router, ROUTE_HOME } from '@/router';
|
||||||
|
import { store, useThemeStore } from '@/store';
|
||||||
|
import { getTabRouteStorage } from '@/utils';
|
||||||
|
import type { MultiTab, MultiTabRoute } from '@/interface';
|
||||||
|
import { getHomeTabRoute, isInTabRoutes } from './helpers';
|
||||||
|
|
||||||
/** app状态 */
|
/** app状态 */
|
||||||
interface AppState {
|
interface AppState {
|
||||||
@ -28,16 +31,6 @@ interface MenuState {
|
|||||||
fixedMix: boolean;
|
fixedMix: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 多页签 */
|
|
||||||
interface MultiTab {
|
|
||||||
routes: MultiTabRoute[];
|
|
||||||
activeRoute: string;
|
|
||||||
}
|
|
||||||
type MultiTabRoute = Partial<RouteLocationNormalizedLoaded> & {
|
|
||||||
path: string;
|
|
||||||
fullPath: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 项目配置抽屉的状态 */
|
/** 项目配置抽屉的状态 */
|
||||||
interface SettingDrawer {
|
interface SettingDrawer {
|
||||||
/** 设置抽屉可见性 */
|
/** 设置抽屉可见性 */
|
||||||
@ -74,9 +67,7 @@ const appStore = defineStore({
|
|||||||
setScrollbarInstance(scrollbar: ScrollbarInst) {
|
setScrollbarInstance(scrollbar: ScrollbarInst) {
|
||||||
this.layout.scrollbar = scrollbar;
|
this.layout.scrollbar = scrollbar;
|
||||||
},
|
},
|
||||||
/**
|
/** 重置滚动条行为 */
|
||||||
* 重置滚动条行为
|
|
||||||
*/
|
|
||||||
resetScrollBehavior() {
|
resetScrollBehavior() {
|
||||||
const { scrollbar } = this.layout;
|
const { scrollbar } = this.layout;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -95,14 +86,10 @@ const appStore = defineStore({
|
|||||||
toggleMenu() {
|
toggleMenu() {
|
||||||
this.menu.collapsed = !this.menu.collapsed;
|
this.menu.collapsed = !this.menu.collapsed;
|
||||||
},
|
},
|
||||||
/** 判断页签路由是否存在某个路由 */
|
|
||||||
getIndexInTabRoutes(fullPath: string) {
|
|
||||||
return this.multiTab.routes.findIndex(item => item.fullPath === fullPath);
|
|
||||||
},
|
|
||||||
/** 添加多页签的数据 */
|
/** 添加多页签的数据 */
|
||||||
addMultiTab(route: RouteLocationNormalizedLoaded) {
|
addMultiTab(route: RouteLocationNormalizedLoaded) {
|
||||||
const { fullPath } = route;
|
const { fullPath } = route;
|
||||||
const isExist = this.getIndexInTabRoutes(fullPath) > -1;
|
const isExist = isInTabRoutes(this.multiTab.routes, fullPath);
|
||||||
if (!isExist) {
|
if (!isExist) {
|
||||||
this.multiTab.routes.push({ ...route });
|
this.multiTab.routes.push({ ...route });
|
||||||
}
|
}
|
||||||
@ -174,28 +161,19 @@ const appStore = defineStore({
|
|||||||
setActiveMultiTab(fullPath: string) {
|
setActiveMultiTab(fullPath: string) {
|
||||||
this.multiTab.activeRoute = fullPath;
|
this.multiTab.activeRoute = fullPath;
|
||||||
},
|
},
|
||||||
/** 获取路由首页信息 */
|
|
||||||
getHomeTabRoute(route: RouteLocationNormalizedLoaded) {
|
|
||||||
const { name, path, meta } = ROUTE_HOME;
|
|
||||||
const isHome = route.name === ROUTE_HOME.name;
|
|
||||||
const home: MultiTabRoute = {
|
|
||||||
name,
|
|
||||||
path,
|
|
||||||
fullPath: path,
|
|
||||||
meta
|
|
||||||
};
|
|
||||||
if (isHome) {
|
|
||||||
Object.assign(home, route);
|
|
||||||
}
|
|
||||||
return home;
|
|
||||||
},
|
|
||||||
/** 初始化多页签数据 */
|
/** 初始化多页签数据 */
|
||||||
initMultiTab() {
|
initMultiTab() {
|
||||||
|
const theme = useThemeStore();
|
||||||
const { currentRoute } = router;
|
const { currentRoute } = router;
|
||||||
const isHome = currentRoute.value.name === ROUTE_HOME.name;
|
const isHome = currentRoute.value.name === ROUTE_HOME.name;
|
||||||
const home = this.getHomeTabRoute(currentRoute.value);
|
const home = getHomeTabRoute(currentRoute.value);
|
||||||
const routes = [home];
|
const routes: MultiTabRoute[] = theme.multiTabStyle.isCache ? getTabRouteStorage() : [];
|
||||||
if (!isHome) {
|
const hasHome = isInTabRoutes(routes, home.fullPath);
|
||||||
|
const hasCurrent = isInTabRoutes(routes, currentRoute.value.fullPath);
|
||||||
|
if (!hasHome) {
|
||||||
|
routes.unshift(home);
|
||||||
|
}
|
||||||
|
if (!isHome && !hasCurrent) {
|
||||||
routes.push(currentRoute.value);
|
routes.push(currentRoute.value);
|
||||||
}
|
}
|
||||||
this.multiTab.routes = routes;
|
this.multiTab.routes = routes;
|
||||||
|
@ -104,6 +104,10 @@ const themeStore = defineStore({
|
|||||||
handleMultiTabMode(mode: MultiTabMode) {
|
handleMultiTabMode(mode: MultiTabMode) {
|
||||||
this.multiTabStyle.mode = mode;
|
this.multiTabStyle.mode = mode;
|
||||||
},
|
},
|
||||||
|
/** 设置多页签缓存 */
|
||||||
|
handleSetMultiTabCache(isCache: boolean) {
|
||||||
|
this.multiTabStyle.isCache = isCache;
|
||||||
|
},
|
||||||
/** 设置面包屑的显示 */
|
/** 设置面包屑的显示 */
|
||||||
handleCrumbsVisible(visible: boolean) {
|
handleCrumbsVisible(visible: boolean) {
|
||||||
this.crumbsStyle.visible = visible;
|
this.crumbsStyle.visible = visible;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export * from './helpers';
|
export * from './helpers';
|
||||||
export * from './cache';
|
export * from './cache';
|
||||||
export * from './menus';
|
export * from './menus';
|
||||||
|
export * from './tab';
|
||||||
|
@ -18,6 +18,7 @@ function addPartialProps(menuItem: GlobalMenuOption, icon?: string, children?: G
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 将路由转换成菜单 */
|
||||||
export function transformRouteToMenu(routes: CustomRoute[]) {
|
export function transformRouteToMenu(routes: CustomRoute[]) {
|
||||||
const globalMenu: GlobalMenuOption[] = [];
|
const globalMenu: GlobalMenuOption[] = [];
|
||||||
routes.forEach(route => {
|
routes.forEach(route => {
|
||||||
|
23
src/utils/router/tab.ts
Normal file
23
src/utils/router/tab.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { EnumStorageKey } from '@/enum';
|
||||||
|
import type { MultiTabRoute } from '@/interface';
|
||||||
|
import { setLocal, getLocal } from '../storage';
|
||||||
|
|
||||||
|
/** 缓存多页签数据 */
|
||||||
|
export function setTabRouteStorage(data: MultiTabRoute[]) {
|
||||||
|
setLocal(EnumStorageKey['tab-route'], data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取缓存的多页签数据 */
|
||||||
|
export function getTabRouteStorage() {
|
||||||
|
const routes: MultiTabRoute[] = [];
|
||||||
|
const data = getLocal<MultiTabRoute[]>(EnumStorageKey['tab-route']);
|
||||||
|
if (data) {
|
||||||
|
routes.push(...data);
|
||||||
|
}
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 清空多页签数据 */
|
||||||
|
export function clearTabRoutes() {
|
||||||
|
setTabRouteStorage([]);
|
||||||
|
}
|
@ -8,7 +8,7 @@ export function getLocal<T>(key: string) {
|
|||||||
if (json) {
|
if (json) {
|
||||||
return JSON.parse(json) as T;
|
return JSON.parse(json) as T;
|
||||||
}
|
}
|
||||||
return json;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeLocal(key: string) {
|
export function removeLocal(key: string) {
|
||||||
|
@ -8,7 +8,7 @@ export function getSession<T>(key: string) {
|
|||||||
if (json) {
|
if (json) {
|
||||||
return JSON.parse(json) as T;
|
return JSON.parse(json) as T;
|
||||||
}
|
}
|
||||||
return json;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeSession(key: string) {
|
export function removeSession(key: string) {
|
||||||
|
Loading…
Reference in New Issue
Block a user