初始化
This commit is contained in:
52
saiadmin-artd/src/store/index.ts
Normal file
52
saiadmin-artd/src/store/index.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Pinia Store 配置模块
|
||||
*
|
||||
* 提供全局状态管理的初始化和配置
|
||||
*
|
||||
* ## 主要功能
|
||||
*
|
||||
* - Pinia Store 实例创建
|
||||
* - 持久化插件配置(pinia-plugin-persistedstate)
|
||||
* - 版本化存储键管理
|
||||
* - 自动数据迁移(跨版本)
|
||||
* - LocalStorage 序列化配置
|
||||
* - Store 初始化函数
|
||||
*
|
||||
* ## 持久化策略
|
||||
*
|
||||
* - 使用 StorageKeyManager 生成版本化的存储键
|
||||
* - 格式:sys-v{version}-{storeId}
|
||||
* - 自动迁移旧版本数据到当前版本
|
||||
* - 使用 localStorage 作为存储介质
|
||||
*
|
||||
* @module store/index
|
||||
* @author Art Design Pro Team
|
||||
*/
|
||||
import type { App } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createPersistedState } from 'pinia-plugin-persistedstate'
|
||||
import { StorageKeyManager } from '@/utils/storage/storage-key-manager'
|
||||
|
||||
export const store = createPinia()
|
||||
|
||||
// 创建存储键管理器实例
|
||||
const storageKeyManager = new StorageKeyManager()
|
||||
|
||||
// 配置持久化插件
|
||||
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 {
|
||||
app.use(store)
|
||||
}
|
||||
80
saiadmin-artd/src/store/modules/dict.ts
Normal file
80
saiadmin-artd/src/store/modules/dict.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 字典状态管理模块
|
||||
*
|
||||
* 提供字典数据的状态管理
|
||||
*
|
||||
*
|
||||
* @module store/modules/dict
|
||||
* @author saithink
|
||||
*/
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { fetchGetDictList } from '@/api/auth'
|
||||
|
||||
/**
|
||||
* 字典状态管理模块
|
||||
* - 负责全局字典的加载、缓存、查询
|
||||
* - 建议在菜单加载完成后调用 ensureLoaded() 进行初始化
|
||||
*/
|
||||
export const useDictStore = defineStore(
|
||||
'dictStore',
|
||||
() => {
|
||||
/** 字典是否已初始化加载 */
|
||||
const initialized = ref(false)
|
||||
/** 原始字典列表 */
|
||||
const dictList = ref<Api.Auth.DictData>()
|
||||
|
||||
/**
|
||||
* 加载字典数据并建立索引
|
||||
*/
|
||||
const refresh = async () => {
|
||||
try {
|
||||
const list = await fetchGetDictList()
|
||||
dictList.value = list
|
||||
initialized.value = true
|
||||
} catch (e) {
|
||||
// 保持状态一致:加载失败也标记为未初始化
|
||||
initialized.value = false
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/** 根据 code 获取字典数据 */
|
||||
const getByCode = (code: any): Api.Auth.DictItem[] => {
|
||||
return dictList.value?.[code] || []
|
||||
}
|
||||
|
||||
/** 根据 code 和 value 获取字典标签 */
|
||||
const getDataByValue = (code: any, value: any): Api.Auth.DictItem | undefined => {
|
||||
const dict = getByCode(code)
|
||||
if (!dict) return undefined
|
||||
|
||||
const item = dict.find((item) => item.value == value)
|
||||
return item || undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字典列表
|
||||
* @param list 字典响应数组
|
||||
*/
|
||||
const setDictList = (list: Api.Auth.DictData) => {
|
||||
dictList.value = list
|
||||
initialized.value = true
|
||||
}
|
||||
|
||||
return {
|
||||
initialized,
|
||||
dictList,
|
||||
refresh,
|
||||
setDictList,
|
||||
getByCode,
|
||||
getDataByValue
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: 'dict',
|
||||
storage: localStorage
|
||||
}
|
||||
}
|
||||
)
|
||||
109
saiadmin-artd/src/store/modules/menu.ts
Normal file
109
saiadmin-artd/src/store/modules/menu.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 菜单状态管理模块
|
||||
*
|
||||
* 提供菜单数据和动态路由的状态管理
|
||||
*
|
||||
* ## 主要功能
|
||||
*
|
||||
* - 菜单列表存储和管理
|
||||
* - 首页路径配置
|
||||
* - 动态路由注册和移除
|
||||
* - 路由移除函数管理
|
||||
* - 菜单宽度配置
|
||||
*
|
||||
* ## 使用场景
|
||||
*
|
||||
* - 动态菜单加载和渲染
|
||||
* - 路由权限控制
|
||||
* - 首页路径动态设置
|
||||
* - 登出时清理动态路由
|
||||
*
|
||||
* ## 工作流程
|
||||
*
|
||||
* 1. 获取菜单数据(前端/后端模式)
|
||||
* 2. 设置菜单列表和首页路径
|
||||
* 3. 注册动态路由并保存移除函数
|
||||
* 4. 登出时调用移除函数清理路由
|
||||
*
|
||||
* @module store/modules/menu
|
||||
* @author Art Design Pro 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
|
||||
}
|
||||
})
|
||||
450
saiadmin-artd/src/store/modules/setting.ts
Normal file
450
saiadmin-artd/src/store/modules/setting.ts
Normal file
@@ -0,0 +1,450 @@
|
||||
/**
|
||||
* 系统设置状态管理模块
|
||||
*
|
||||
* 提供完整的系统设置状态管理
|
||||
*
|
||||
* ## 主要功能
|
||||
*
|
||||
* - 菜单布局配置(左侧、顶部、混合、双栏)
|
||||
* - 主题管理(亮色、暗色、自动)
|
||||
* - 菜单主题样式配置
|
||||
* - 界面显示开关(面包屑、标签页、语言切换等)
|
||||
* - 功能开关(手风琴模式、色弱模式、水印等)
|
||||
* - 样式配置(边框、圆角、容器宽度、页面过渡)
|
||||
* - 节日功能配置
|
||||
* - Element Plus 主题色动态设置
|
||||
*
|
||||
* ## 使用场景
|
||||
*
|
||||
* - 设置面板配置管理
|
||||
* - 主题切换和样式定制
|
||||
* - 界面功能开关控制
|
||||
* - 用户偏好设置持久化
|
||||
*
|
||||
* ## 持久化
|
||||
*
|
||||
* - 使用 localStorage 存储
|
||||
* - 存储键:sys-v{version}-setting
|
||||
* - 支持跨版本数据迁移
|
||||
*
|
||||
* @module store/modules/setting
|
||||
* @author Art Design Pro 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'
|
||||
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
|
||||
}
|
||||
}
|
||||
)
|
||||
97
saiadmin-artd/src/store/modules/table.ts
Normal file
97
saiadmin-artd/src/store/modules/table.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 表格状态管理模块
|
||||
*
|
||||
* 提供表格显示配置的状态管理
|
||||
*
|
||||
* ## 主要功能
|
||||
*
|
||||
* - 表格尺寸配置(紧凑、默认、宽松)
|
||||
* - 斑马纹显示开关
|
||||
* - 边框显示开关
|
||||
* - 表头背景显示开关
|
||||
* - 全屏模式开关
|
||||
*
|
||||
* ## 使用场景
|
||||
* - 表格组件样式配置
|
||||
* - 用户表格偏好设置
|
||||
* - 表格工具栏功能控制
|
||||
*
|
||||
* ## 持久化
|
||||
*
|
||||
* - 使用 localStorage 存储
|
||||
* - 存储键:sys-v{version}-table
|
||||
* - 用户配置跨页面保持
|
||||
*
|
||||
* @module store/modules/table
|
||||
* @author Art Design Pro 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(true)
|
||||
|
||||
// 是否全屏
|
||||
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
|
||||
}
|
||||
}
|
||||
)
|
||||
253
saiadmin-artd/src/store/modules/user.ts
Normal file
253
saiadmin-artd/src/store/modules/user.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* 用户状态管理模块
|
||||
*
|
||||
* 提供用户相关的状态管理
|
||||
*
|
||||
* ## 主要功能
|
||||
*
|
||||
* - 用户登录状态管理
|
||||
* - 用户信息存储
|
||||
* - 访问令牌和刷新令牌管理
|
||||
* - 语言设置
|
||||
* - 搜索历史记录
|
||||
* - 锁屏状态和密码管理
|
||||
* - 登出清理逻辑
|
||||
*
|
||||
* ## 使用场景
|
||||
*
|
||||
* - 用户登录和认证
|
||||
* - 权限验证
|
||||
* - 个人信息展示
|
||||
* - 多语言切换
|
||||
* - 锁屏功能
|
||||
* - 搜索历史管理
|
||||
*
|
||||
* ## 持久化
|
||||
*
|
||||
* - 使用 localStorage 存储
|
||||
* - 存储键:sys-v{version}-user
|
||||
* - 登出时自动清理
|
||||
*
|
||||
* @module store/modules/user
|
||||
* @author Art Design Pro 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'
|
||||
import { fetchClearCache } from '@/api/auth'
|
||||
|
||||
/**
|
||||
* 用户状态管理
|
||||
* 管理用户登录状态、个人信息、语言设置、搜索历史、锁屏状态等
|
||||
*/
|
||||
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 newAvatar 新的头像 URL
|
||||
*/
|
||||
const setAvatar = (newAvatar: string) => {
|
||||
info.value.avatar = newAvatar
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置登录状态
|
||||
* @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 clearCache = () => {
|
||||
fetchClearCache()
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
* 清空所有用户相关状态并跳转到登录页
|
||||
* 如果是同一账号重新登录,保留工作台标签页
|
||||
*/
|
||||
const logOut = () => {
|
||||
// 保存当前用户 ID,用于下次登录时判断是否为同一用户
|
||||
const currentUserId = info.value.id
|
||||
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.id
|
||||
|
||||
// 无法获取当前用户 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,
|
||||
setAvatar,
|
||||
setLoginStatus,
|
||||
setLanguage,
|
||||
setSearchHistory,
|
||||
setLockStatus,
|
||||
setLockPassword,
|
||||
setToken,
|
||||
clearCache,
|
||||
logOut,
|
||||
checkAndClearWorktabs
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: 'user',
|
||||
storage: localStorage
|
||||
}
|
||||
}
|
||||
)
|
||||
568
saiadmin-artd/src/store/modules/worktab.ts
Normal file
568
saiadmin-artd/src/store/modules/worktab.ts
Normal file
@@ -0,0 +1,568 @@
|
||||
/**
|
||||
* 工作标签页状态管理模块
|
||||
*
|
||||
* 提供多标签页功能的完整状态管理
|
||||
*
|
||||
* ## 主要功能
|
||||
*
|
||||
* - 标签页打开和关闭
|
||||
* - 标签页固定和取消固定
|
||||
* - 批量关闭(左侧、右侧、其他、全部)
|
||||
* - 标签页缓存管理(KeepAlive)
|
||||
* - 标签页标题自定义
|
||||
* - 标签页路由验证
|
||||
* - 动态路由参数处理
|
||||
*
|
||||
* ## 使用场景
|
||||
*
|
||||
* - 多标签页导航
|
||||
* - 页面缓存控制
|
||||
* - 标签页右键菜单
|
||||
* - 固定常用页面
|
||||
* - 批量关闭标签
|
||||
*
|
||||
* ## 核心特性
|
||||
*
|
||||
* - 智能标签页复用(同路由名称复用)
|
||||
* - 固定标签页保护(不可关闭)
|
||||
* - KeepAlive 缓存排除管理
|
||||
* - 路由有效性验证
|
||||
* - 首页自动保留
|
||||
*
|
||||
* ## 持久化
|
||||
* - 使用 localStorage 存储
|
||||
* - 存储键:sys-v{version}-worktab
|
||||
* - 刷新页面保持标签状态
|
||||
*
|
||||
* @module store/modules/worktab
|
||||
* @author Art Design Pro 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
|
||||
}
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user