fix(web): 修复代码格式和文件清理问题

清理无用文件和资源,调整代码格式统一双引号,修复部分组件路径引用问题

删除大量无用图片资源和组件文件,统一代码风格使用双引号,修复国际化文件路径引用,调整部分组件逻辑,优化项目结构
This commit is contained in:
zhangtao
2026-05-01 01:05:16 +08:00
parent 80263e98e1
commit b7800e2ddd
595 changed files with 24834 additions and 103011 deletions
+25 -64
View File
@@ -1,77 +1,38 @@
/**
* Pinia Store 配置模块
*
* 提供全局状态管理的初始化和配置
*
* ## 主要功能
*
* - Pinia Store 实例创建
* - 持久化插件配置(pinia-plugin-persistedstate)
* - 版本化存储键管理
* - 自动数据迁移(跨版本)
* - LocalStorage 序列化配置
* - Store 初始化函数
*
* ## 持久化策略
*
* - 使用 StorageKeyManager 生成版本化的存储键
* - 格式:sys-v{version}-{storeId}
* - 自动迁移旧版本数据到当前版本
* - 使用 localStorage 作为存储介质
*
* @module store/index
* @author Fastapi Admin Team
*/
import type { App } from "vue";
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
import type { App } from 'vue';
import { createPinia } from 'pinia';
import piniaPluginPersistedstate, { createPersistedState } from 'pinia-plugin-persistedstate';
import { StorageKeyManager } from '@/utils/storage/storage-key-manager';
const store = createPinia();
export const store = createPinia();
// 创建存储键管理器实例
const storageKeyManager = new StorageKeyManager();
store.use(piniaPluginPersistedstate);
// 配置持久化插件
store.use(
createPersistedState({
key: (storeId: string) => storageKeyManager.getStorageKey(storeId),
storage: localStorage,
serializer: {
serialize: JSON.stringify,
deserialize: JSON.parse,
},
})
);
/**
* 初始化 Store
*/
export function initStore(app: App<Element>): void {
// 全局注册 store
export function initStore(app: App<Element>) {
app.use(store);
}
export * from './modules/app.store';
export * from './modules/settings.store';
export * from './modules/tags-view.store';
export * from './modules/lock.store';
export * from './modules/permission.store';
export * from './modules/user.store';
export * from './modules/dict.store';
export * from './modules/config.store';
export * from './modules/notice.store';
export * from "./modules/app.store";
export * from "./modules/settings.store";
export * from "./modules/tags-view.store";
export * from "./modules/lock.store";
export * from "./modules/permission.store";
export * from "./modules/user.store";
export * from "./modules/dict.store";
export * from "./modules/config.store";
export * from "./modules/notice.store";
export { store };
// ------------------------------
// 🔄 刷新缓存统一入口
// ------------------------------
import router from '@/router';
import { useUserStoreHook } from './modules/user.store';
import { usePermissionStoreHook } from './modules/permission.store';
import { useDictStoreHook } from './modules/dict.store';
import { useConfigStoreHook } from './modules/config.store';
import { useNoticeStoreHook } from './modules/notice.store';
import { useTagsViewStore } from './modules/tags-view.store';
import router from "@/router";
import { useUserStoreHook } from "./modules/user.store";
import { usePermissionStoreHook } from "./modules/permission.store";
import { useDictStoreHook } from "./modules/dict.store";
import { useConfigStoreHook } from "./modules/config.store";
import { useNoticeStoreHook } from "./modules/notice.store";
import { useTagsViewStore } from "./modules/tags-view.store";
import { RouteRecordRaw } from "vue-router";
export interface RefreshCacheOptions {
/** 需要刷新的字典类型列表,不传则不刷新字典 */
@@ -136,7 +97,7 @@ export async function refreshAppCaches(opts: RefreshCacheOptions = {}) {
permStore.resetRouter();
const dynamicRoutes = await permStore.generateRoutes();
// 将新生成的动态路由注册到路由器,确保可用
dynamicRoutes.forEach((route) => router.addRoute(route));
dynamicRoutes.forEach((route: RouteRecordRaw) => router.addRoute(route));
}
// 可选:清空标签视图,避免路由变更后的不一致
+17 -14
View File
@@ -1,36 +1,39 @@
import { defaultSettings } from '@/settings';
import { defaultSettings } from "@/settings";
// 导入 Element Plus 中英文语言包
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import en from 'element-plus/es/locale/lang/en';
import { store } from '@/store';
import { DeviceEnum } from '@/enums/settings/device.enum';
import { SidebarStatus } from '@/enums/settings/layout.enum';
import zhCn from "element-plus/es/locale/lang/zh-cn";
import en from "element-plus/es/locale/lang/en";
import { store } from "@/store";
import { DeviceEnum } from "@/enums/settings/device.enum";
import { SidebarStatus } from "@/enums/settings/layout.enum";
import { defineStore } from "pinia";
import { useStorage } from "@vueuse/core";
import { computed, reactive } from "vue";
export const useAppStore = defineStore('app', () => {
export const useAppStore = defineStore("app", () => {
// 设备类型
const device = useStorage('device', DeviceEnum.DESKTOP);
const device = useStorage("device", DeviceEnum.DESKTOP);
// 布局大小
const size = useStorage('size', defaultSettings.size);
const size = useStorage("size", defaultSettings.size);
// 语言
const language = useStorage('language', defaultSettings.language);
const language = useStorage("language", defaultSettings.language);
// 侧边栏状态
const sidebarStatus = useStorage('sidebarStatus', SidebarStatus.CLOSED);
const sidebarStatus = useStorage("sidebarStatus", SidebarStatus.CLOSED);
const sidebar = reactive({
opened: sidebarStatus.value === SidebarStatus.OPENED,
withoutAnimation: false,
});
// 顶部菜单激活路径
const activeTopMenuPath = useStorage('activeTopMenuPath', '');
const activeTopMenuPath = useStorage("activeTopMenuPath", "");
// 项目引导
const guideVisible = useStorage('guideVisible', defaultSettings.guideVisible);
const guideVisible = useStorage("guideVisible", defaultSettings.guideVisible);
/**
* 根据语言标识读取对应的语言包
*/
const locale = computed(() => {
if (language?.value == 'en') {
if (language?.value == "en") {
return en;
} else {
return zhCn;
@@ -1,5 +1,6 @@
import { store } from '@/store';
import ParamsAPI, { ConfigTable } from '@/api/module_system/params';
import { store } from "@/store";
import ParamsAPI, { ConfigTable } from "@/api/module_system/params";
import { defineStore } from "pinia";
interface ConfigState {
// 网站信息
@@ -25,7 +26,7 @@ interface ConfigState {
ip_white_list: ConfigTable;
}
export const useConfigStore = defineStore('config', {
export const useConfigStore = defineStore("config", {
state: () => ({
configData: {} as ConfigState,
isConfigLoaded: false,
+5 -4
View File
@@ -1,7 +1,8 @@
import { store } from '@/store';
import DictAPI, { DictDataTable } from '@/api/module_system/dict';
import { store } from "@/store";
import DictAPI, { DictDataTable } from "@/api/module_system/dict";
import { defineStore } from "pinia";
export const useDictStore = defineStore('dict', {
export const useDictStore = defineStore("dict", {
state: () => ({
dictData: {} as Record<string, DictDataTable[]>,
isLoaded: false,
@@ -46,7 +47,7 @@ export const useDictStore = defineStore('dict', {
{} as Record<string, DictDataTable[]>
);
} catch (error) {
console.error('获取字典数据失败', error);
console.error("获取字典数据失败", error);
return {};
}
},
+3 -3
View File
@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import { store } from '../index';
import { defineStore } from "pinia";
import { store } from "../index";
interface lockInfo {
isLock?: boolean;
@@ -10,7 +10,7 @@ interface LockState {
lockInfo: lockInfo;
}
export const useLockStore = defineStore('lock', {
export const useLockStore = defineStore("lock", {
state: (): LockState => {
return {
lockInfo: {
-109
View File
@@ -1,109 +0,0 @@
/**
* 菜单状态管理模块
*
* 提供菜单数据和动态路由的状态管理
*
* ## 主要功能
*
* - 菜单列表存储和管理
* - 首页路径配置
* - 动态路由注册和移除
* - 路由移除函数管理
* - 菜单宽度配置
*
* ## 使用场景
*
* - 动态菜单加载和渲染
* - 路由权限控制
* - 首页路径动态设置
* - 登出时清理动态路由
*
* ## 工作流程
*
* 1. 获取菜单数据(前端/后端模式)
* 2. 设置菜单列表和首页路径
* 3. 注册动态路由并保存移除函数
* 4. 登出时调用移除函数清理路由
*
* @module store/modules/menu
* @author Fastapi Admin Team
*/
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { AppRouteRecord } from '@/types/router';
import { getFirstMenuPath } from '@/utils';
import { HOME_PAGE_PATH } from '@/router';
/**
* 菜单状态管理
* 管理应用的菜单列表、首页路径、菜单宽度和动态路由移除函数
*/
export const useMenuStore = defineStore('menuStore', () => {
/** 首页路径 */
const homePath = ref(HOME_PAGE_PATH);
/** 菜单列表 */
const menuList = ref<AppRouteRecord[]>([]);
/** 菜单宽度 */
const menuWidth = ref('');
/** 存储路由移除函数的数组 */
const removeRouteFns = ref<(() => void)[]>([]);
/**
* 设置菜单列表
* @param list 菜单路由记录数组
*/
const setMenuList = (list: AppRouteRecord[]) => {
menuList.value = list;
setHomePath(HOME_PAGE_PATH || getFirstMenuPath(list));
};
/**
* 获取首页路径
* @returns 首页路径字符串
*/
const getHomePath = () => homePath.value;
/**
* 设置主页路径
* @param path 主页路径
*/
const setHomePath = (path: string) => {
homePath.value = path;
};
/**
* 添加路由移除函数
* @param fns 要添加的路由移除函数数组
*/
const addRemoveRouteFns = (fns: (() => void)[]) => {
removeRouteFns.value.push(...fns);
};
/**
* 移除所有动态路由
* 执行所有存储的路由移除函数并清空数组
*/
const removeAllDynamicRoutes = () => {
removeRouteFns.value.forEach((fn) => fn());
removeRouteFns.value = [];
};
/**
* 清空路由移除函数数组
*/
const clearRemoveRouteFns = () => {
removeRouteFns.value = [];
};
return {
menuList,
menuWidth,
removeRouteFns,
setMenuList,
getHomePath,
setHomePath,
addRemoveRouteFns,
removeAllDynamicRoutes,
clearRemoveRouteFns,
};
});
@@ -1,7 +1,8 @@
import { store } from '@/store';
import NoticeAPI, { NoticeTable } from '@/api/module_system/notice';
import { store } from "@/store";
import NoticeAPI, { NoticeTable } from "@/api/module_system/notice";
import { defineStore } from "pinia";
export const useNoticeStore = defineStore('notice', {
export const useNoticeStore = defineStore("notice", {
state: () => ({
noticeList: [] as NoticeTable[],
total: 0,
@@ -1,10 +1,12 @@
import type { RouteRecordRaw } from 'vue-router';
import router, { constantRoutes } from '@/router';
import { store, useUserStore } from '@/store';
import { MenuTable } from '@/api/module_system/menu';
import type { RouteRecordRaw } from "vue-router";
import router, { constantRoutes } from "@/router";
import { store, useUserStore } from "@/store";
import { MenuTable } from "@/api/module_system/menu";
import { defineStore } from "pinia";
import { Component, ref } from "vue";
const modules = import.meta.glob('../../views/**/**.vue');
const Layout = () => import('@/layouts/index.vue');
const modules = import.meta.glob("../../views/**/**.vue");
const Layout = () => import("@/layouts/index.vue");
export interface Meta {
/** 【目录】只有一个子路由是否始终显示 */
@@ -49,12 +51,12 @@ export interface RouteVO {
*/
function normalizeMenuNestedPaths(items: MenuTable[], parentAbsolutePath?: string): MenuTable[] {
return items.map((node) => {
const raw = (node.route_path ?? '').trim();
const raw = (node.route_path ?? "").trim();
let routePath: string | null | undefined = node.route_path;
if (parentAbsolutePath && raw && raw === parentAbsolutePath) {
routePath = '';
routePath = "";
}
const canonical = raw.startsWith('/') ? raw : parentAbsolutePath;
const canonical = raw.startsWith("/") ? raw : parentAbsolutePath;
const children = node.children?.length
? normalizeMenuNestedPaths(node.children, canonical)
: undefined;
@@ -89,7 +91,7 @@ export const generator = (routers: MenuTable[]): RouteVO[] => {
});
};
export const usePermissionStore = defineStore('permission', () => {
export const usePermissionStore = defineStore("permission", () => {
// 存储所有路由,包括静态路由和动态路由
const routes = ref<RouteRecordRaw[]>([]);
// 混合模式左侧菜单路由
@@ -204,8 +206,8 @@ const transformRoutes = (routes: RouteVO[], isTopLevel: boolean = true): RouteRe
// 叶子路由,使用实际组件
normalizedRoute.component = normalizedRoute.component
? modules[`../../views/${normalizedRoute.component}.vue`] ||
modules['../../views/error/404.vue']
: modules['../../views/error/404.vue'];
modules["../../views/error/404.vue"]
: modules["../../views/error/404.vue"];
}
return normalizedRoute;
-450
View File
@@ -1,450 +0,0 @@
/**
* 系统设置状态管理模块
*
* 提供完整的系统设置状态管理
*
* ## 主要功能
*
* - 菜单布局配置(左侧、顶部、混合、双栏)
* - 主题管理(亮色、暗色、自动)
* - 菜单主题样式配置
* - 界面显示开关(面包屑、标签页、语言切换等)
* - 功能开关(手风琴模式、色弱模式、水印等)
* - 样式配置(边框、圆角、容器宽度、页面过渡)
* - 节日功能配置
* - Element Plus 主题色动态设置
*
* ## 使用场景
*
* - 设置面板配置管理
* - 主题切换和样式定制
* - 界面功能开关控制
* - 用户偏好设置持久化
*
* ## 持久化
*
* - 使用 localStorage 存储
* - 存储键:sys-v{version}-setting
* - 支持跨版本数据迁移
*
* @module store/modules/setting
* @author Fastapi Admin Team
*/
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { MenuThemeType } from '@/types/store';
import AppConfig from '@/config';
import { SystemThemeEnum, MenuThemeEnum, MenuTypeEnum, ContainerWidthEnum } from '@/enums/appEnum';
import { setElementThemeColor } from '@/utils/ui';
import { useCeremony } from '@/hooks/core/useCeremony';
import { StorageConfig } from '@/utils/storage/storage-config';
import { SETTING_DEFAULT_CONFIG } from '@/config/setting';
/**
* 系统设置状态管理
* 管理应用的菜单、主题、界面显示等各项设置
*/
export const useSettingStore = defineStore(
'settingStore',
() => {
// 菜单相关设置
/** 菜单类型 */
const menuType = ref(SETTING_DEFAULT_CONFIG.menuType);
/** 菜单展开宽度 */
const menuOpenWidth = ref(SETTING_DEFAULT_CONFIG.menuOpenWidth);
/** 菜单是否展开 */
const menuOpen = ref(SETTING_DEFAULT_CONFIG.menuOpen);
/** 双菜单是否显示文本 */
const dualMenuShowText = ref(SETTING_DEFAULT_CONFIG.dualMenuShowText);
// 主题相关设置
/** 系统主题类型 */
const systemThemeType = ref(SETTING_DEFAULT_CONFIG.systemThemeType);
/** 系统主题模式 */
const systemThemeMode = ref(SETTING_DEFAULT_CONFIG.systemThemeMode);
/** 菜单主题类型 */
const menuThemeType = ref(SETTING_DEFAULT_CONFIG.menuThemeType);
/** 系统主题颜色 */
const systemThemeColor = ref(SETTING_DEFAULT_CONFIG.systemThemeColor);
// 界面显示设置
/** 是否显示菜单按钮 */
const showMenuButton = ref(SETTING_DEFAULT_CONFIG.showMenuButton);
/** 是否显示快速入口 */
const showFastEnter = ref(SETTING_DEFAULT_CONFIG.showFastEnter);
/** 是否显示刷新按钮 */
const showRefreshButton = ref(SETTING_DEFAULT_CONFIG.showRefreshButton);
/** 是否显示面包屑 */
const showCrumbs = ref(SETTING_DEFAULT_CONFIG.showCrumbs);
/** 是否显示工作台标签 */
const showWorkTab = ref(SETTING_DEFAULT_CONFIG.showWorkTab);
/** 是否显示语言切换 */
const showLanguage = ref(SETTING_DEFAULT_CONFIG.showLanguage);
/** 是否显示进度条 */
const showNprogress = ref(SETTING_DEFAULT_CONFIG.showNprogress);
/** 是否显示设置引导 */
const showSettingGuide = ref(SETTING_DEFAULT_CONFIG.showSettingGuide);
/** 是否显示节日文本 */
const showFestivalText = ref(SETTING_DEFAULT_CONFIG.showFestivalText);
/** 是否显示水印 */
const watermarkVisible = ref(SETTING_DEFAULT_CONFIG.watermarkVisible);
// 功能设置
/** 是否自动关闭 */
const autoClose = ref(SETTING_DEFAULT_CONFIG.autoClose);
/** 是否唯一展开 */
const uniqueOpened = ref(SETTING_DEFAULT_CONFIG.uniqueOpened);
/** 是否色弱模式 */
const colorWeak = ref(SETTING_DEFAULT_CONFIG.colorWeak);
/** 是否刷新 */
const refresh = ref(SETTING_DEFAULT_CONFIG.refresh);
/** 是否加载节日烟花 */
const holidayFireworksLoaded = ref(SETTING_DEFAULT_CONFIG.holidayFireworksLoaded);
// 样式设置
/** 边框模式 */
const boxBorderMode = ref(SETTING_DEFAULT_CONFIG.boxBorderMode);
/** 页面过渡效果 */
const pageTransition = ref(SETTING_DEFAULT_CONFIG.pageTransition);
/** 标签页样式 */
const tabStyle = ref(SETTING_DEFAULT_CONFIG.tabStyle);
/** 自定义圆角 */
const customRadius = ref(SETTING_DEFAULT_CONFIG.customRadius);
/** 容器宽度 */
const containerWidth = ref(SETTING_DEFAULT_CONFIG.containerWidth);
// 节日相关
/** 节日日期 */
const festivalDate = ref('');
/**
* 获取菜单主题
* 根据当前主题类型和暗色模式返回对应的主题配置
*/
const getMenuTheme = computed((): MenuThemeType => {
const list = AppConfig.themeList.filter((item) => item.theme === menuThemeType.value);
if (isDark.value) {
return AppConfig.darkMenuStyles[0];
} else {
return list[0];
}
});
/**
* 判断是否为暗色模式
*/
const isDark = computed((): boolean => {
return systemThemeType.value === SystemThemeEnum.DARK;
});
/**
* 获取菜单展开宽度
*/
const getMenuOpenWidth = computed((): string => {
return menuOpenWidth.value + 'px' || SETTING_DEFAULT_CONFIG.menuOpenWidth + 'px';
});
/**
* 获取自定义圆角
*/
const getCustomRadius = computed((): string => {
return customRadius.value + 'rem' || SETTING_DEFAULT_CONFIG.customRadius + 'rem';
});
/**
* 是否显示烟花
* 根据当前日期和节日日期判断是否显示烟花效果
*/
const isShowFireworks = computed((): boolean => {
return festivalDate.value === useCeremony().currentFestivalData.value?.date ? false : true;
});
/**
* 切换菜单布局
* @param type 菜单类型
*/
const switchMenuLayouts = (type: MenuTypeEnum) => {
menuType.value = type;
};
/**
* 设置菜单展开宽度
* @param width 宽度值
*/
const setMenuOpenWidth = (width: number) => {
menuOpenWidth.value = width;
};
/**
* 设置全局主题
* @param theme 主题类型
* @param themeMode 主题模式
*/
const setGlopTheme = (theme: SystemThemeEnum, themeMode: SystemThemeEnum) => {
systemThemeType.value = theme;
systemThemeMode.value = themeMode;
localStorage.setItem(StorageConfig.THEME_KEY, theme);
};
/**
* 切换菜单样式
* @param theme 菜单主题
*/
const switchMenuStyles = (theme: MenuThemeEnum) => {
menuThemeType.value = theme;
};
/**
* 设置Element Plus主题颜色
* @param theme 主题颜色
*/
const setElementTheme = (theme: string) => {
systemThemeColor.value = theme;
setElementThemeColor(theme);
};
/**
* 切换边框模式
*/
const setBorderMode = () => {
boxBorderMode.value = !boxBorderMode.value;
};
/**
* 设置容器宽度
* @param width 容器宽度枚举值
*/
const setContainerWidth = (width: ContainerWidthEnum) => {
containerWidth.value = width;
};
/**
* 切换唯一展开模式
*/
const setUniqueOpened = () => {
uniqueOpened.value = !uniqueOpened.value;
};
/**
* 切换菜单按钮显示
*/
const setButton = () => {
showMenuButton.value = !showMenuButton.value;
};
/**
* 切换快速入口显示
*/
const setFastEnter = () => {
showFastEnter.value = !showFastEnter.value;
};
/**
* 切换自动关闭
*/
const setAutoClose = () => {
autoClose.value = !autoClose.value;
};
/**
* 切换刷新按钮显示
*/
const setShowRefreshButton = () => {
showRefreshButton.value = !showRefreshButton.value;
};
/**
* 切换面包屑显示
*/
const setCrumbs = () => {
showCrumbs.value = !showCrumbs.value;
};
/**
* 设置工作台标签显示
* @param show 是否显示
*/
const setWorkTab = (show: boolean) => {
showWorkTab.value = show;
};
/**
* 切换语言切换显示
*/
const setLanguage = () => {
showLanguage.value = !showLanguage.value;
};
/**
* 切换进度条显示
*/
const setNprogress = () => {
showNprogress.value = !showNprogress.value;
};
/**
* 切换色弱模式
*/
const setColorWeak = () => {
colorWeak.value = !colorWeak.value;
};
/**
* 隐藏设置引导
*/
const hideSettingGuide = () => {
showSettingGuide.value = false;
};
/**
* 显示设置引导
*/
const openSettingGuide = () => {
showSettingGuide.value = true;
};
/**
* 设置页面过渡效果
* @param transition 过渡效果名称
*/
const setPageTransition = (transition: string) => {
pageTransition.value = transition;
};
/**
* 设置标签页样式
* @param style 样式名称
*/
const setTabStyle = (style: string) => {
tabStyle.value = style;
};
/**
* 设置菜单展开状态
* @param open 是否展开
*/
const setMenuOpen = (open: boolean) => {
menuOpen.value = open;
};
/**
* 刷新页面
*/
const reload = () => {
refresh.value = !refresh.value;
};
/**
* 设置水印显示
* @param visible 是否显示
*/
const setWatermarkVisible = (visible: boolean) => {
watermarkVisible.value = visible;
};
/**
* 设置自定义圆角
* @param radius 圆角值
*/
const setCustomRadius = (radius: string) => {
customRadius.value = radius;
document.documentElement.style.setProperty('--custom-radius', `${radius}rem`);
};
/**
* 设置节日烟花加载状态
* @param isLoad 是否已加载
*/
const setholidayFireworksLoaded = (isLoad: boolean) => {
holidayFireworksLoaded.value = isLoad;
};
/**
* 设置节日文本显示
* @param show 是否显示
*/
const setShowFestivalText = (show: boolean) => {
showFestivalText.value = show;
};
const setFestivalDate = (date: string) => {
festivalDate.value = date;
};
const setDualMenuShowText = (show: boolean) => {
dualMenuShowText.value = show;
};
return {
menuType,
menuOpenWidth,
systemThemeType,
systemThemeMode,
menuThemeType,
systemThemeColor,
boxBorderMode,
uniqueOpened,
showMenuButton,
showFastEnter,
showRefreshButton,
showCrumbs,
autoClose,
showWorkTab,
showLanguage,
showNprogress,
colorWeak,
showSettingGuide,
pageTransition,
tabStyle,
menuOpen,
refresh,
watermarkVisible,
customRadius,
holidayFireworksLoaded,
showFestivalText,
festivalDate,
dualMenuShowText,
containerWidth,
getMenuTheme,
isDark,
getMenuOpenWidth,
getCustomRadius,
isShowFireworks,
switchMenuLayouts,
setMenuOpenWidth,
setGlopTheme,
switchMenuStyles,
setElementTheme,
setBorderMode,
setContainerWidth,
setUniqueOpened,
setButton,
setFastEnter,
setAutoClose,
setShowRefreshButton,
setCrumbs,
setWorkTab,
setLanguage,
setNprogress,
setColorWeak,
hideSettingGuide,
openSettingGuide,
setPageTransition,
setTabStyle,
setMenuOpen,
reload,
setWatermarkVisible,
setCustomRadius,
setholidayFireworksLoaded,
setShowFestivalText,
setFestivalDate,
setDualMenuShowText,
};
},
{
persist: {
key: 'setting',
storage: localStorage,
},
}
);
@@ -1,8 +1,11 @@
import { defaultSettings } from '@/settings';
import { SidebarColor, ThemeMode } from '@/enums/settings/theme.enum';
import type { LayoutMode } from '@/enums/settings/layout.enum';
import { applyTheme, generateThemeColors, toggleDarkMode, toggleSidebarColor } from '@/utils/theme';
import { SETTINGS_KEYS } from '@/constants';
import { defaultSettings } from "@/settings";
import { SidebarColor, ThemeMode } from "@/enums/settings/theme.enum";
import type { LayoutMode } from "@/enums/settings/layout.enum";
import { applyTheme, generateThemeColors, toggleDarkMode, toggleSidebarColor } from "@/utils/theme";
import { SETTINGS_KEYS } from "@/constants";
import { defineStore } from "pinia";
import { Ref, ref, watch } from "vue";
import { useStorage } from "@vueuse/core";
// 🎯 设置项类型定义
interface SettingsState {
@@ -34,10 +37,10 @@ interface SettingsState {
}
// 🎯 可变更的设置项类型
type MutableSetting = Exclude<keyof SettingsState, 'settingsVisible'>;
type MutableSetting = Exclude<keyof SettingsState, "settingsVisible">;
type SettingValue<K extends MutableSetting> = SettingsState[K];
export const useSettingsStore = defineStore('setting', () => {
export const useSettingsStore = defineStore("setting", () => {
// 🎯 基础设置 - 非持久化
const settingsVisible = ref<boolean>(false);
@@ -157,7 +160,7 @@ export const useSettingsStore = defineStore('setting', () => {
watch(
grayMode,
(v) => {
document.documentElement.style.filter = v ? 'grayscale(100%)' : '';
document.documentElement.style.filter = v ? "grayscale(100%)" : "";
},
{ immediate: true }
);
-97
View File
@@ -1,97 +0,0 @@
/**
* 表格状态管理模块
*
* 提供表格显示配置的状态管理
*
* ## 主要功能
*
* - 表格尺寸配置(紧凑、默认、宽松)
* - 斑马纹显示开关
* - 边框显示开关
* - 表头背景显示开关
* - 全屏模式开关
*
* ## 使用场景
* - 表格组件样式配置
* - 用户表格偏好设置
* - 表格工具栏功能控制
*
* ## 持久化
*
* - 使用 localStorage 存储
* - 存储键:sys-v{version}-table
* - 用户配置跨页面保持
*
* @module store/modules/table
* @author Fastapi Admin Team
*/
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { TableSizeEnum } from '@/enums/formEnum';
// 表格
export const useTableStore = defineStore(
'tableStore',
() => {
// 表格大小
const tableSize = ref(TableSizeEnum.DEFAULT);
// 斑马纹
const isZebra = ref(false);
// 边框
const isBorder = ref(false);
// 表头背景
const isHeaderBackground = ref(false);
// 是否全屏
const isFullScreen = ref(false);
/**
* 设置表格大小
* @param size 表格大小枚举值
*/
const setTableSize = (size: TableSizeEnum) => (tableSize.value = size);
/**
* 设置斑马纹显示状态
* @param value 是否显示斑马纹
*/
const setIsZebra = (value: boolean) => (isZebra.value = value);
/**
* 设置表格边框显示状态
* @param value 是否显示边框
*/
const setIsBorder = (value: boolean) => (isBorder.value = value);
/**
* 设置表头背景显示状态
* @param value 是否显示表头背景
*/
const setIsHeaderBackground = (value: boolean) => (isHeaderBackground.value = value);
/**
* 设置是否全屏
* @param value 是否全屏
*/
const setIsFullScreen = (value: boolean) => (isFullScreen.value = value);
return {
tableSize,
isZebra,
isBorder,
isHeaderBackground,
setTableSize,
setIsZebra,
setIsBorder,
setIsHeaderBackground,
isFullScreen,
setIsFullScreen,
};
},
{
persist: {
key: 'table',
storage: localStorage,
},
}
);
@@ -1,5 +1,9 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { useRoute, useRouter } from "vue-router";
// tags缓存
export const useTagsViewStore = defineStore('tagsView', () => {
export const useTagsViewStore = defineStore("tagsView", () => {
const visitedViews = ref<TagView[]>([]);
const cachedViews = ref<string[]>([]);
const router = useRouter();
@@ -10,7 +14,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
*/
function addVisitedView(view: TagView) {
// 如果已经存在于已访问的视图列表中或者是重定向地址,则不再添加
if (view.path.startsWith('/redirect')) {
if (view.path.startsWith("/redirect")) {
return;
}
if (visitedViews.value.some((v) => v.name === view.name)) {
@@ -224,11 +228,11 @@ export const useTagsViewStore = defineStore('tagsView', () => {
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view?.name === 'Dashboard') {
if (view?.name === "Dashboard") {
// to reload home page
router.replace('/redirect' + view.fullPath);
router.replace("/redirect" + view.fullPath);
} else {
router.push('/');
router.push("/");
}
}
}
+15 -12
View File
@@ -1,12 +1,15 @@
import { store, useTagsViewStore, usePermissionStoreHook, useDictStoreHook } from '@/store';
import { store, useTagsViewStore, usePermissionStoreHook, useDictStoreHook } from "@/store";
import AuthAPI, { type LoginFormData } from '@/api/module_system/auth';
import UserAPI, { type UserInfo } from '@/api/module_system/user';
import type { MenuTable } from '@/api/module_system/menu';
import { Auth } from '@/utils/auth';
import { ResultEnum } from '@/enums';
import AuthAPI, { type LoginFormData } from "@/api/module_system/auth";
import UserAPI, { type UserInfo } from "@/api/module_system/user";
import type { MenuTable } from "@/api/module_system/menu";
import { Auth } from "@/utils/auth";
import { ResultEnum } from "@/enums";
import { defineStore } from "pinia";
import { ref } from "vue";
import { ElNotification } from "element-plus";
export const useUserStore = defineStore('user', {
export const useUserStore = defineStore("user", {
state: () => ({
basicInfo: {} as UserInfo,
routeList: [] as MenuTable[],
@@ -100,9 +103,9 @@ export const useUserStore = defineStore('user', {
const response = await AuthAPI.login(LoginFormData);
if (response.data.code === ResultEnum.SUCCESS) {
ElNotification({
title: '通知',
title: "通知",
message: response.data.msg,
type: 'success',
type: "success",
});
}
this.rememberMe = LoginFormData.remember;
@@ -118,9 +121,9 @@ export const useUserStore = defineStore('user', {
const response = await AuthAPI.logout({ token: Auth.getAccessToken() });
if (response.data.code === ResultEnum.SUCCESS) {
ElNotification({
title: '通知',
title: "通知",
message: response.data.msg,
type: 'success',
type: "success",
});
}
this.resetAllState();
@@ -147,7 +150,7 @@ export const useUserStore = defineStore('user', {
const refreshToken = Auth.getRefreshToken();
if (!refreshToken) {
return Promise.reject(new Error('没有有效的刷新令牌'));
return Promise.reject(new Error("没有有效的刷新令牌"));
}
return new Promise<void>((resolve, reject) => {
-235
View File
@@ -1,235 +0,0 @@
/**
* 用户状态管理模块
*
* 提供用户相关的状态管理
*
* ## 主要功能
*
* - 用户登录状态管理
* - 用户信息存储
* - 访问令牌和刷新令牌管理
* - 语言设置
* - 搜索历史记录
* - 锁屏状态和密码管理
* - 登出清理逻辑
*
* ## 使用场景
*
* - 用户登录和认证
* - 权限验证
* - 个人信息展示
* - 多语言切换
* - 锁屏功能
* - 搜索历史管理
*
* ## 持久化
*
* - 使用 localStorage 存储
* - 存储键:sys-v{version}-user
* - 登出时自动清理
*
* @module store/modules/user
* @author Fastapi Admin Team
*/
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { LanguageEnum } from '@/enums/appEnum';
import router from '@/router';
import { useSettingStore } from './setting';
import { useWorktabStore } from './worktab';
import { AppRouteRecord } from '@/types/router';
import { setPageTitle } from '@/utils/router';
import { resetRouterState } from '@/router/guards/beforeEach';
import { useMenuStore } from './menu';
import { StorageConfig } from '@/utils/storage/storage-config';
/**
* 用户状态管理
* 管理用户登录状态、个人信息、语言设置、搜索历史、锁屏状态等
*/
export const useUserStore = defineStore(
'userStore',
() => {
// 语言设置
const language = ref(LanguageEnum.ZH);
// 登录状态
const isLogin = ref(false);
// 锁屏状态
const isLock = ref(false);
// 锁屏密码
const lockPassword = ref('');
// 用户信息
const info = ref<Partial<Api.Auth.UserInfo>>({});
// 搜索历史记录
const searchHistory = ref<AppRouteRecord[]>([]);
// 访问令牌
const accessToken = ref('');
// 刷新令牌
const refreshToken = ref('');
// 计算属性:获取用户信息
const getUserInfo = computed(() => info.value);
// 计算属性:获取设置状态
const getSettingState = computed(() => useSettingStore().$state);
// 计算属性:获取工作台状态
const getWorktabState = computed(() => useWorktabStore().$state);
/**
* 设置用户信息
* @param newInfo 新的用户信息
*/
const setUserInfo = (newInfo: Api.Auth.UserInfo) => {
info.value = newInfo;
};
/**
* 设置登录状态
* @param status 登录状态
*/
const setLoginStatus = (status: boolean) => {
isLogin.value = status;
};
/**
* 设置语言
* @param lang 语言枚举值
*/
const setLanguage = (lang: LanguageEnum) => {
setPageTitle(router.currentRoute.value);
language.value = lang;
};
/**
* 设置搜索历史
* @param list 搜索历史列表
*/
const setSearchHistory = (list: AppRouteRecord[]) => {
searchHistory.value = list;
};
/**
* 设置锁屏状态
* @param status 锁屏状态
*/
const setLockStatus = (status: boolean) => {
isLock.value = status;
};
/**
* 设置锁屏密码
* @param password 锁屏密码
*/
const setLockPassword = (password: string) => {
lockPassword.value = password;
};
/**
* 设置令牌
* @param newAccessToken 访问令牌
* @param newRefreshToken 刷新令牌(可选)
*/
const setToken = (newAccessToken: string, newRefreshToken?: string) => {
accessToken.value = newAccessToken;
if (newRefreshToken) {
refreshToken.value = newRefreshToken;
}
};
/**
* 退出登录
* 清空所有用户相关状态并跳转到登录页
* 如果是同一账号重新登录,保留工作台标签页
*/
const logOut = () => {
// 保存当前用户 ID,用于下次登录时判断是否为同一用户
const currentUserId = info.value.userId;
if (currentUserId) {
localStorage.setItem(StorageConfig.LAST_USER_ID_KEY, String(currentUserId));
}
// 清空用户信息
info.value = {};
// 重置登录状态
isLogin.value = false;
// 重置锁屏状态
isLock.value = false;
// 清空锁屏密码
lockPassword.value = '';
// 清空访问令牌
accessToken.value = '';
// 清空刷新令牌
refreshToken.value = '';
// 注意:不清空工作台标签页,等下次登录时根据用户判断
// 移除iframe路由缓存
sessionStorage.removeItem('iframeRoutes');
// 清空主页路径
useMenuStore().setHomePath('');
// 重置路由状态
resetRouterState(500);
// 跳转到登录页,携带当前路由作为 redirect 参数
const currentRoute = router.currentRoute.value;
const redirect = currentRoute.path !== '/login' ? currentRoute.fullPath : undefined;
router.push({
name: 'Login',
query: redirect ? { redirect } : undefined,
});
};
/**
* 检查并清理工作台标签页
* 如果不是同一用户登录,清空工作台标签页
* 应在登录成功后调用
*/
const checkAndClearWorktabs = () => {
const lastUserId = localStorage.getItem(StorageConfig.LAST_USER_ID_KEY);
const currentUserId = info.value.userId;
// 无法获取当前用户 ID,跳过检查
if (!currentUserId) return;
// 首次登录或缓存已清除,保留现有标签页
if (!lastUserId) {
return;
}
// 不同用户登录,清空工作台标签页
if (String(currentUserId) !== lastUserId) {
const worktabStore = useWorktabStore();
worktabStore.opened = [];
worktabStore.keepAliveExclude = [];
}
// 清除临时存储
localStorage.removeItem(StorageConfig.LAST_USER_ID_KEY);
};
return {
language,
isLogin,
isLock,
lockPassword,
info,
searchHistory,
accessToken,
refreshToken,
getUserInfo,
getSettingState,
getWorktabState,
setUserInfo,
setLoginStatus,
setLanguage,
setSearchHistory,
setLockStatus,
setLockPassword,
setToken,
logOut,
checkAndClearWorktabs,
};
},
{
persist: {
key: 'user',
storage: localStorage,
},
}
);
-568
View File
@@ -1,568 +0,0 @@
/**
* 工作标签页状态管理模块
*
* 提供多标签页功能的完整状态管理
*
* ## 主要功能
*
* - 标签页打开和关闭
* - 标签页固定和取消固定
* - 批量关闭(左侧、右侧、其他、全部)
* - 标签页缓存管理(KeepAlive)
* - 标签页标题自定义
* - 标签页路由验证
* - 动态路由参数处理
*
* ## 使用场景
*
* - 多标签页导航
* - 页面缓存控制
* - 标签页右键菜单
* - 固定常用页面
* - 批量关闭标签
*
* ## 核心特性
*
* - 智能标签页复用(同路由名称复用)
* - 固定标签页保护(不可关闭)
* - KeepAlive 缓存排除管理
* - 路由有效性验证
* - 首页自动保留
*
* ## 持久化
* - 使用 localStorage 存储
* - 存储键:sys-v{version}-worktab
* - 刷新页面保持标签状态
*
* @module store/modules/worktab
* @author Fastapi Admin Team
*/
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import router from '@/router';
import { LocationQueryRaw, Router } from 'vue-router';
import { WorkTab } from '@/types';
import { useCommon } from '@/hooks/core/useCommon';
interface WorktabState {
current: Partial<WorkTab>;
opened: WorkTab[];
keepAliveExclude: string[];
}
/**
* 工作台标签页管理 Store
*/
export const useWorktabStore = defineStore(
'worktabStore',
() => {
// 状态定义
const current = ref<Partial<WorkTab>>({});
const opened = ref<WorkTab[]>([]);
const keepAliveExclude = ref<string[]>([]);
// 计算属性
const hasOpenedTabs = computed(() => opened.value.length > 0);
const hasMultipleTabs = computed(() => opened.value.length > 1);
const currentTabIndex = computed(() =>
current.value.path ? opened.value.findIndex((tab) => tab.path === current.value.path) : -1
);
/**
* 查找标签页索引
*/
const findTabIndex = (path: string): number => {
return opened.value.findIndex((tab) => tab.path === path);
};
/**
* 获取标签页
*/
const getTab = (path: string): WorkTab | undefined => {
return opened.value.find((tab) => tab.path === path);
};
/**
* 检查标签页是否可关闭
*/
const isTabClosable = (tab: WorkTab): boolean => {
return !tab.fixedTab;
};
/**
* 安全的路由跳转
*/
const safeRouterPush = (tab: Partial<WorkTab>): void => {
if (!tab.path) {
console.warn('尝试跳转到无效路径的标签页');
return;
}
try {
router.push({
path: tab.path,
query: tab.query as LocationQueryRaw,
});
} catch (error) {
console.error('路由跳转失败:', error);
}
};
/**
* 打开或激活一个选项卡
*/
const openTab = (tab: WorkTab): void => {
if (!tab.path) {
console.warn('尝试打开无效的标签页');
return;
}
// 从 keepAlive 排除列表中移除
if (tab.name) {
removeKeepAliveExclude(tab.name);
}
// 先根据路由名称查找(应对动态路由参数导致的多开问题),找不到再根据路径查找
let existingIndex = -1;
if (tab.name) {
existingIndex = opened.value.findIndex((t) => t.name === tab.name);
}
if (existingIndex === -1) {
existingIndex = findTabIndex(tab.path);
}
if (existingIndex === -1) {
// 新增标签页
const insertIndex = tab.fixedTab ? findFixedTabInsertIndex() : opened.value.length;
const newTab = { ...tab };
if (tab.fixedTab) {
opened.value.splice(insertIndex, 0, newTab);
} else {
opened.value.push(newTab);
}
current.value = newTab;
} else {
// 更新现有标签页(当动态路由参数或查询变更时,复用同一标签)
const existingTab = opened.value[existingIndex];
opened.value[existingIndex] = {
...existingTab,
path: tab.path,
params: tab.params,
query: tab.query,
title: tab.title || existingTab.title,
fixedTab: tab.fixedTab ?? existingTab.fixedTab,
keepAlive: tab.keepAlive ?? existingTab.keepAlive,
name: tab.name || existingTab.name,
icon: tab.icon || existingTab.icon,
};
current.value = opened.value[existingIndex];
}
};
/**
* 查找固定标签页的插入位置
*/
const findFixedTabInsertIndex = (): number => {
let insertIndex = 0;
for (let i = 0; i < opened.value.length; i++) {
if (opened.value[i].fixedTab) {
insertIndex = i + 1;
} else {
break;
}
}
return insertIndex;
};
/**
* 关闭指定的选项卡
*/
const removeTab = (path: string): void => {
const targetTab = getTab(path);
const targetIndex = findTabIndex(path);
if (targetIndex === -1) {
console.warn(`尝试关闭不存在的标签页: ${path}`);
return;
}
if (targetTab && !isTabClosable(targetTab)) {
console.warn(`尝试关闭固定标签页: ${path}`);
return;
}
// 从标签页列表中移除
opened.value.splice(targetIndex, 1);
// 处理缓存排除
if (targetTab?.name) {
addKeepAliveExclude(targetTab);
}
const { homePath } = useCommon();
// 如果关闭后无标签页,跳转首页
if (!hasOpenedTabs.value) {
if (path !== homePath.value) {
current.value = {};
safeRouterPush({ path: homePath.value });
}
return;
}
// 如果关闭的是当前激活标签,需要激活其他标签
if (current.value.path === path) {
const newIndex = targetIndex >= opened.value.length ? opened.value.length - 1 : targetIndex;
current.value = opened.value[newIndex];
safeRouterPush(current.value);
}
};
/**
* 关闭左侧选项卡
*/
const removeLeft = (path: string): void => {
const targetIndex = findTabIndex(path);
if (targetIndex === -1) {
console.warn(`尝试关闭左侧标签页,但目标标签页不存在: ${path}`);
return;
}
// 获取左侧可关闭的标签页
const leftTabs = opened.value.slice(0, targetIndex);
const closableLeftTabs = leftTabs.filter(isTabClosable);
if (closableLeftTabs.length === 0) {
console.warn('左侧没有可关闭的标签页');
return;
}
// 标记为缓存排除
markTabsToRemove(closableLeftTabs);
// 移除左侧可关闭的标签页
opened.value = opened.value.filter(
(tab, index) => index >= targetIndex || !isTabClosable(tab)
);
// 确保当前标签是激活状态
const targetTab = getTab(path);
if (targetTab) {
current.value = targetTab;
}
};
/**
* 关闭右侧选项卡
*/
const removeRight = (path: string): void => {
const targetIndex = findTabIndex(path);
if (targetIndex === -1) {
console.warn(`尝试关闭右侧标签页,但目标标签页不存在: ${path}`);
return;
}
// 获取右侧可关闭的标签页
const rightTabs = opened.value.slice(targetIndex + 1);
const closableRightTabs = rightTabs.filter(isTabClosable);
if (closableRightTabs.length === 0) {
console.warn('右侧没有可关闭的标签页');
return;
}
// 标记为缓存排除
markTabsToRemove(closableRightTabs);
// 移除右侧可关闭的标签页
opened.value = opened.value.filter(
(tab, index) => index <= targetIndex || !isTabClosable(tab)
);
// 确保当前标签是激活状态
const targetTab = getTab(path);
if (targetTab) {
current.value = targetTab;
}
};
/**
* 关闭其他选项卡
*/
const removeOthers = (path: string): void => {
const targetTab = getTab(path);
if (!targetTab) {
console.warn(`尝试关闭其他标签页,但目标标签页不存在: ${path}`);
return;
}
// 获取其他可关闭的标签页
const otherTabs = opened.value.filter((tab) => tab.path !== path);
const closableTabs = otherTabs.filter(isTabClosable);
if (closableTabs.length === 0) {
console.warn('没有其他可关闭的标签页');
return;
}
// 标记为缓存排除
markTabsToRemove(closableTabs);
// 只保留当前标签和固定标签
opened.value = opened.value.filter((tab) => tab.path === path || !isTabClosable(tab));
// 确保当前标签是激活状态
current.value = targetTab;
};
/**
* 关闭所有可关闭的标签页
*/
const removeAll = (): void => {
const { homePath } = useCommon();
const hasFixedTabs = opened.value.some((tab) => tab.fixedTab);
// 获取可关闭的标签页
const closableTabs = opened.value.filter((tab) => {
if (!isTabClosable(tab)) return false;
// 如果有固定标签,则所有可关闭的都可以关闭;否则保留首页
return hasFixedTabs || tab.path !== homePath.value;
});
if (closableTabs.length === 0) {
console.warn('没有可关闭的标签页');
return;
}
// 标记为缓存排除
markTabsToRemove(closableTabs);
// 保留不可关闭的标签页和首页(当没有固定标签时)
opened.value = opened.value.filter((tab) => {
return !isTabClosable(tab) || (!hasFixedTabs && tab.path === homePath.value);
});
// 处理激活状态
if (!hasOpenedTabs.value) {
current.value = {};
safeRouterPush({ path: homePath.value });
return;
}
// 选择激活的标签页:优先首页,其次第一个可用标签
const homeTab = opened.value.find((tab) => tab.path === homePath.value);
const targetTab = homeTab || opened.value[0];
current.value = targetTab;
safeRouterPush(targetTab);
};
/**
* 将指定选项卡添加到 keepAlive 排除列表中
*/
const addKeepAliveExclude = (tab: WorkTab): void => {
if (!tab.keepAlive || !tab.name) return;
if (!keepAliveExclude.value.includes(tab.name)) {
keepAliveExclude.value.push(tab.name);
}
};
/**
* 从 keepAlive 排除列表中移除指定组件名称
*/
const removeKeepAliveExclude = (name: string): void => {
if (!name) return;
keepAliveExclude.value = keepAliveExclude.value.filter((item) => item !== name);
};
/**
* 将传入的一组选项卡的组件名称标记为排除缓存
*/
const markTabsToRemove = (tabs: WorkTab[]): void => {
tabs.forEach((tab) => {
if (tab.name) {
addKeepAliveExclude(tab);
}
});
};
/**
* 切换指定标签页的固定状态
*/
const toggleFixedTab = (path: string): void => {
const targetIndex = findTabIndex(path);
if (targetIndex === -1) {
console.warn(`尝试切换不存在标签页的固定状态: ${path}`);
return;
}
const tab = { ...opened.value[targetIndex] };
tab.fixedTab = !tab.fixedTab;
// 移除原位置
opened.value.splice(targetIndex, 1);
if (tab.fixedTab) {
// 固定标签插入到所有固定标签的末尾
const firstNonFixedIndex = opened.value.findIndex((t) => !t.fixedTab);
const insertIndex = firstNonFixedIndex === -1 ? opened.value.length : firstNonFixedIndex;
opened.value.splice(insertIndex, 0, tab);
} else {
// 非固定标签插入到所有固定标签后
const fixedCount = opened.value.filter((t) => t.fixedTab).length;
opened.value.splice(fixedCount, 0, tab);
}
// 更新当前标签引用
if (current.value.path === path) {
current.value = tab;
}
};
/**
* 验证工作台标签页的路由有效性
*/
const validateWorktabs = (routerInstance: Router): void => {
try {
// 动态路由校验:优先使用路由 name 判断有效性;否则用 resolve 匹配参数化路径
const isTabRouteValid = (tab: Partial<WorkTab>): boolean => {
try {
if (tab.name) {
const routes = routerInstance.getRoutes();
if (routes.some((r) => r.name === tab.name)) return true;
}
if (tab.path) {
const resolved = routerInstance.resolve({
path: tab.path,
query: (tab.query as LocationQueryRaw) || undefined,
});
return resolved.matched.length > 0;
}
return false;
} catch {
return false;
}
};
// 过滤出有效的标签页
const validTabs = opened.value.filter((tab) => isTabRouteValid(tab));
if (validTabs.length !== opened.value.length) {
console.warn('发现无效的标签页路由,已自动清理');
opened.value = validTabs;
}
// 验证当前激活标签的有效性
const isCurrentValid = current.value && isTabRouteValid(current.value);
if (!isCurrentValid && validTabs.length > 0) {
console.warn('当前激活标签无效,已自动切换');
current.value = validTabs[0];
} else if (!isCurrentValid) {
current.value = {};
}
} catch (error) {
console.error('验证工作台标签页失败:', error);
}
};
/**
* 清空所有状态(用于登出等场景)
*/
const clearAll = (): void => {
current.value = {};
opened.value = [];
keepAliveExclude.value = [];
};
/**
* 获取状态快照(用于持久化存储)
*/
const getStateSnapshot = (): WorktabState => {
return {
current: { ...current.value },
opened: [...opened.value],
keepAliveExclude: [...keepAliveExclude.value],
};
};
/**
* 获取标签页标题
*/
const getTabTitle = (path: string): WorkTab | undefined => {
const tab = getTab(path);
return tab;
};
/**
* 更新标签页标题
*/
const updateTabTitle = (path: string, title: string): void => {
const tab = getTab(path);
if (tab) {
tab.customTitle = title;
}
};
/**
* 重置标签页标题
*/
const resetTabTitle = (path: string): void => {
const tab = getTab(path);
if (tab) {
tab.customTitle = '';
}
};
return {
// 状态
current,
opened,
keepAliveExclude,
// 计算属性
hasOpenedTabs,
hasMultipleTabs,
currentTabIndex,
// 方法
openTab,
removeTab,
removeLeft,
removeRight,
removeOthers,
removeAll,
toggleFixedTab,
validateWorktabs,
clearAll,
getStateSnapshot,
// 工具方法
findTabIndex,
getTab,
isTabClosable,
addKeepAliveExclude,
removeKeepAliveExclude,
markTabsToRemove,
getTabTitle,
updateTabTitle,
resetTabTitle,
};
},
{
persist: {
key: 'worktab',
storage: localStorage,
},
}
);