feat(projects): 新增多页签缓存功能

This commit is contained in:
Soybean 2021-11-20 21:30:49 +08:00
parent ffe987832f
commit d86f891c64
15 changed files with 132 additions and 43 deletions

View File

@ -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__'
} }

View File

@ -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;

View File

@ -96,6 +96,8 @@ interface MultiTabStyle {
bgColor: string; bgColor: string;
/** 多页签模式 */ /** 多页签模式 */
mode: MultiTabMode; mode: MultiTabMode;
/** 开启多页签缓存 */
isCache: boolean;
/** 多页签模式列表 */ /** 多页签模式列表 */
modeList: MultiTabModeList[]; modeList: MultiTabModeList[];
} }

View File

@ -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>

View File

@ -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>

View File

@ -61,6 +61,7 @@
"visible": true, "visible": true,
"bgColor": "#fff", "bgColor": "#fff",
"mode": "chrome", "mode": "chrome",
"isCache": true,
"modeList": [ "modeList": [
{ {
"value": "button", "value": "button",

View File

@ -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 }

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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';

View File

@ -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
View 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([]);
}

View File

@ -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) {

View File

@ -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) {