mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-11-04 07:43:42 +08:00 
			
		
		
		
	feat(projects): 新增多页签缓存功能
This commit is contained in:
		@@ -4,5 +4,7 @@ export enum EnumStorageKey {
 | 
			
		||||
  /** 用户刷新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 { EnumLoginModule } from '@/enum';
 | 
			
		||||
 | 
			
		||||
@@ -7,6 +8,16 @@ export type GlobalMenuOption = MenuOption & {
 | 
			
		||||
  routePath: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 多页签 */
 | 
			
		||||
export interface MultiTab {
 | 
			
		||||
  routes: MultiTabRoute[];
 | 
			
		||||
  activeRoute: string;
 | 
			
		||||
}
 | 
			
		||||
export type MultiTabRoute = Partial<RouteLocationNormalizedLoaded> & {
 | 
			
		||||
  path: string;
 | 
			
		||||
  fullPath: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 登录模块 */
 | 
			
		||||
export type LoginModuleType = keyof typeof EnumLoginModule;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -96,6 +96,8 @@ interface MultiTabStyle {
 | 
			
		||||
  bgColor: string;
 | 
			
		||||
  /** 多页签模式 */
 | 
			
		||||
  mode: MultiTabMode;
 | 
			
		||||
  /** 开启多页签缓存 */
 | 
			
		||||
  isCache: boolean;
 | 
			
		||||
  /** 多页签模式列表 */
 | 
			
		||||
  modeList: MultiTabModeList[];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,10 +40,12 @@
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { reactive, nextTick } from 'vue';
 | 
			
		||||
import { useEventListener } from '@vueuse/core';
 | 
			
		||||
import { useThemeStore, useAppStore } from '@/store';
 | 
			
		||||
import { ROUTE_HOME } from '@/router';
 | 
			
		||||
import { ChromeTab, ButtonTab } from '@/components';
 | 
			
		||||
import { useBoolean } from '@/hooks';
 | 
			
		||||
import { setTabRouteStorage } from '@/utils';
 | 
			
		||||
import { ContextMenu } from './components';
 | 
			
		||||
 | 
			
		||||
const theme = useThemeStore();
 | 
			
		||||
@@ -69,5 +71,10 @@ function handleContextMenu(e: MouseEvent, fullPath: string) {
 | 
			
		||||
    showDropdown();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 页面离开时缓存多页签数据 */
 | 
			
		||||
useEventListener(window, 'beforeunload', () => {
 | 
			
		||||
  setTabRouteStorage(app.multiTab.routes);
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -51,13 +51,25 @@
 | 
			
		||||
        @update:value="handleMultiTabHeight"
 | 
			
		||||
      />
 | 
			
		||||
    </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>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { NDivider, NSpace, NSwitch, NSelect, NInputNumber } from 'naive-ui';
 | 
			
		||||
import { useThemeStore } from '@/store';
 | 
			
		||||
import { NDivider, NSpace, NSwitch, NSelect, NInputNumber, NButton, NPopconfirm, useMessage } from 'naive-ui';
 | 
			
		||||
import { useThemeStore, useAppStore } from '@/store';
 | 
			
		||||
import { clearTabRoutes } from '@/utils';
 | 
			
		||||
import { SettingMenuItem } from '../common';
 | 
			
		||||
 | 
			
		||||
const theme = useThemeStore();
 | 
			
		||||
@@ -67,8 +79,11 @@ const {
 | 
			
		||||
  handleHeaderHeight,
 | 
			
		||||
  handleMultiTabHeight,
 | 
			
		||||
  handleMenuWidth,
 | 
			
		||||
  handleMixMenuWidth
 | 
			
		||||
  handleMixMenuWidth,
 | 
			
		||||
  handleSetMultiTabCache
 | 
			
		||||
} = useThemeStore();
 | 
			
		||||
const { initMultiTab } = useAppStore();
 | 
			
		||||
const message = useMessage();
 | 
			
		||||
 | 
			
		||||
const isHorizontalMix = computed(() => theme.navStyle.mode === 'horizontal-mix');
 | 
			
		||||
const disabledMenuWidth = computed(() => {
 | 
			
		||||
@@ -77,5 +92,11 @@ const disabledMenuWidth = computed(() => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const disabledMixMenuWidth = computed(() => theme.navStyle.mode !== 'vertical-mix');
 | 
			
		||||
 | 
			
		||||
function handleRemoveTabRouteCache() {
 | 
			
		||||
  clearTabRoutes();
 | 
			
		||||
  initMultiTab();
 | 
			
		||||
  message.success('操作成功!');
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -61,6 +61,7 @@
 | 
			
		||||
		"visible": true,
 | 
			
		||||
		"bgColor": "#fff",
 | 
			
		||||
		"mode": "chrome",
 | 
			
		||||
		"isCache": true,
 | 
			
		||||
		"modeList": [
 | 
			
		||||
			{
 | 
			
		||||
				"value": "button",
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,7 @@ const defaultThemeSettings: ThemeSettings = {
 | 
			
		||||
    visible: true,
 | 
			
		||||
    bgColor: '#fff',
 | 
			
		||||
    mode: 'chrome',
 | 
			
		||||
    isCache: true,
 | 
			
		||||
    modeList: [
 | 
			
		||||
      { value: 'button', label: EnumMultiTabMode.button },
 | 
			
		||||
      { 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 type { MultiTabRoute } from '@/interface';
 | 
			
		||||
 | 
			
		||||
export function getHoverAndPressedColor(color: string) {
 | 
			
		||||
  return {
 | 
			
		||||
@@ -6,3 +9,37 @@ export function getHoverAndPressedColor(color: string) {
 | 
			
		||||
    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 { ScrollbarInst } from 'naive-ui';
 | 
			
		||||
import { defineStore } from 'pinia';
 | 
			
		||||
import { store } from '@/store';
 | 
			
		||||
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状态 */
 | 
			
		||||
interface AppState {
 | 
			
		||||
@@ -28,16 +31,6 @@ interface MenuState {
 | 
			
		||||
  fixedMix: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 多页签 */
 | 
			
		||||
interface MultiTab {
 | 
			
		||||
  routes: MultiTabRoute[];
 | 
			
		||||
  activeRoute: string;
 | 
			
		||||
}
 | 
			
		||||
type MultiTabRoute = Partial<RouteLocationNormalizedLoaded> & {
 | 
			
		||||
  path: string;
 | 
			
		||||
  fullPath: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 项目配置抽屉的状态 */
 | 
			
		||||
interface SettingDrawer {
 | 
			
		||||
  /** 设置抽屉可见性 */
 | 
			
		||||
@@ -74,9 +67,7 @@ const appStore = defineStore({
 | 
			
		||||
    setScrollbarInstance(scrollbar: ScrollbarInst) {
 | 
			
		||||
      this.layout.scrollbar = scrollbar;
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * 重置滚动条行为
 | 
			
		||||
     */
 | 
			
		||||
    /** 重置滚动条行为 */
 | 
			
		||||
    resetScrollBehavior() {
 | 
			
		||||
      const { scrollbar } = this.layout;
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
@@ -95,14 +86,10 @@ const appStore = defineStore({
 | 
			
		||||
    toggleMenu() {
 | 
			
		||||
      this.menu.collapsed = !this.menu.collapsed;
 | 
			
		||||
    },
 | 
			
		||||
    /** 判断页签路由是否存在某个路由 */
 | 
			
		||||
    getIndexInTabRoutes(fullPath: string) {
 | 
			
		||||
      return this.multiTab.routes.findIndex(item => item.fullPath === fullPath);
 | 
			
		||||
    },
 | 
			
		||||
    /** 添加多页签的数据 */
 | 
			
		||||
    addMultiTab(route: RouteLocationNormalizedLoaded) {
 | 
			
		||||
      const { fullPath } = route;
 | 
			
		||||
      const isExist = this.getIndexInTabRoutes(fullPath) > -1;
 | 
			
		||||
      const isExist = isInTabRoutes(this.multiTab.routes, fullPath);
 | 
			
		||||
      if (!isExist) {
 | 
			
		||||
        this.multiTab.routes.push({ ...route });
 | 
			
		||||
      }
 | 
			
		||||
@@ -174,28 +161,19 @@ const appStore = defineStore({
 | 
			
		||||
    setActiveMultiTab(fullPath: string) {
 | 
			
		||||
      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() {
 | 
			
		||||
      const theme = useThemeStore();
 | 
			
		||||
      const { currentRoute } = router;
 | 
			
		||||
      const isHome = currentRoute.value.name === ROUTE_HOME.name;
 | 
			
		||||
      const home = this.getHomeTabRoute(currentRoute.value);
 | 
			
		||||
      const routes = [home];
 | 
			
		||||
      if (!isHome) {
 | 
			
		||||
      const home = getHomeTabRoute(currentRoute.value);
 | 
			
		||||
      const routes: MultiTabRoute[] = theme.multiTabStyle.isCache ? getTabRouteStorage() : [];
 | 
			
		||||
      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);
 | 
			
		||||
      }
 | 
			
		||||
      this.multiTab.routes = routes;
 | 
			
		||||
 
 | 
			
		||||
@@ -104,6 +104,10 @@ const themeStore = defineStore({
 | 
			
		||||
    handleMultiTabMode(mode: MultiTabMode) {
 | 
			
		||||
      this.multiTabStyle.mode = mode;
 | 
			
		||||
    },
 | 
			
		||||
    /** 设置多页签缓存 */
 | 
			
		||||
    handleSetMultiTabCache(isCache: boolean) {
 | 
			
		||||
      this.multiTabStyle.isCache = isCache;
 | 
			
		||||
    },
 | 
			
		||||
    /** 设置面包屑的显示 */
 | 
			
		||||
    handleCrumbsVisible(visible: boolean) {
 | 
			
		||||
      this.crumbsStyle.visible = visible;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
export * from './helpers';
 | 
			
		||||
export * from './cache';
 | 
			
		||||
export * from './menus';
 | 
			
		||||
export * from './tab';
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ function addPartialProps(menuItem: GlobalMenuOption, icon?: string, children?: G
 | 
			
		||||
  return item;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 将路由转换成菜单 */
 | 
			
		||||
export function transformRouteToMenu(routes: CustomRoute[]) {
 | 
			
		||||
  const globalMenu: GlobalMenuOption[] = [];
 | 
			
		||||
  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) {
 | 
			
		||||
    return JSON.parse(json) as T;
 | 
			
		||||
  }
 | 
			
		||||
  return json;
 | 
			
		||||
  return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function removeLocal(key: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ export function getSession<T>(key: string) {
 | 
			
		||||
  if (json) {
 | 
			
		||||
    return JSON.parse(json) as T;
 | 
			
		||||
  }
 | 
			
		||||
  return json;
 | 
			
		||||
  return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function removeSession(key: string) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user