Initial commit: One Pipe System

完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sexygoat
2026-01-22 16:35:33 +08:00
commit 222e5bb11a
495 changed files with 145440 additions and 0 deletions

28
src/store/index.ts Normal file
View File

@@ -0,0 +1,28 @@
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)
}

20
src/store/modules/menu.ts Normal file
View File

@@ -0,0 +1,20 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { AppRouteRecord } from '@/types/router'
// 菜单
export const useMenuStore = defineStore('menuStore', () => {
const menuList = ref<AppRouteRecord[]>([])
const menuWidth = ref('')
const setMenuList = (list: AppRouteRecord[]) => (menuList.value = list)
const setMenuWidth = (width: string) => (menuWidth.value = width)
return {
menuList,
menuWidth,
setMenuList,
setMenuWidth
}
})

View File

@@ -0,0 +1,264 @@
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 '@/composables/useCeremony'
const { defaultMenuWidth, defaultCustomRadius, defaultTabStyle } = AppConfig.systemSetting
// 系统设置
export const useSettingStore = defineStore(
'settingStore',
() => {
const menuType = ref(MenuTypeEnum.LEFT)
const menuOpenWidth = ref(defaultMenuWidth)
const systemThemeType = ref(SystemThemeEnum.AUTO)
const systemThemeMode = ref(SystemThemeEnum.AUTO)
const menuThemeType = ref(MenuThemeEnum.DESIGN)
const systemThemeColor = ref(AppConfig.elementPlusTheme.primary)
const boxBorderMode = ref(true)
const uniqueOpened = ref(true)
const showMenuButton = ref(true)
const showRefreshButton = ref(true)
const showCrumbs = ref(true)
const autoClose = ref(false)
const showWorkTab = ref(true)
const showLanguage = ref(true)
const showNprogress = ref(true)
const colorWeak = ref(false)
const showSettingGuide = ref(true)
const pageTransition = ref('slide-left')
const tabStyle = ref(defaultTabStyle)
const menuOpen = ref(true)
const refresh = ref(false)
const watermarkVisible = ref(false)
const customRadius = ref(defaultCustomRadius)
const holidayFireworksLoaded = ref(false)
const showFestivalText = ref(false)
const festivalDate = ref('')
const dualMenuShowText = ref(false)
const containerWidth = ref(ContainerWidthEnum.FULL)
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' || defaultMenuWidth + 'px'
})
const getCustomRadius = computed((): string => {
return customRadius.value + 'rem' || defaultCustomRadius + 'rem'
})
const isShowFireworks = computed((): boolean => {
return festivalDate.value === useCeremony().currentFestivalData.value?.date ? false : true
})
const switchMenuLayouts = (type: MenuTypeEnum) => {
menuType.value = type
}
const setMenuOpenWidth = (width: number) => {
menuOpenWidth.value = width
}
const setGlopTheme = (theme: SystemThemeEnum, themeMode: SystemThemeEnum) => {
systemThemeType.value = theme
systemThemeMode.value = themeMode
}
const switchMenuStyles = (theme: MenuThemeEnum) => {
menuThemeType.value = theme
}
const setElementTheme = (theme: string) => {
systemThemeColor.value = theme
setElementThemeColor(theme)
}
const setBorderMode = () => {
boxBorderMode.value = !boxBorderMode.value
}
const setContainerWidth = (width: ContainerWidthEnum) => {
containerWidth.value = width
}
const setUniqueOpened = () => {
uniqueOpened.value = !uniqueOpened.value
}
const setButton = () => {
showMenuButton.value = !showMenuButton.value
}
const setAutoClose = () => {
autoClose.value = !autoClose.value
}
const setShowRefreshButton = () => {
showRefreshButton.value = !showRefreshButton.value
}
const setCrumbs = () => {
showCrumbs.value = !showCrumbs.value
}
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
}
const setPageTransition = (transition: string) => {
pageTransition.value = transition
}
const setTabStyle = (style: string) => {
tabStyle.value = style
}
const setMenuOpen = (open: boolean) => {
menuOpen.value = open
}
const reload = () => {
refresh.value = !refresh.value
}
const setWatermarkVisible = (visible: boolean) => {
watermarkVisible.value = visible
}
const setCustomRadius = (radius: string) => {
customRadius.value = radius
document.documentElement.style.setProperty('--custom-radius', `${radius}rem`)
}
const setholidayFireworksLoaded = (isLoad: boolean) => {
holidayFireworksLoaded.value = isLoad
}
const setShowFestivalText = (show: boolean) => {
showFestivalText.value = show
}
const setFestivalDate = (date: string) => {
festivalDate.value = date
}
const setDualMenuShowText = (show: boolean) => {
dualMenuShowText.value = show
}
// 初始化主题样式
const initThemeStyles = () => {
setElementThemeColor(systemThemeColor.value)
document.documentElement.style.setProperty('--custom-radius', `${customRadius.value}rem`)
}
nextTick(() => {
initThemeStyles()
})
return {
menuType,
menuOpenWidth,
systemThemeType,
systemThemeMode,
menuThemeType,
systemThemeColor,
boxBorderMode,
uniqueOpened,
showMenuButton,
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,
setAutoClose,
setShowRefreshButton,
setCrumbs,
setWorkTab,
setLanguage,
setNprogress,
setColorWeak,
hideSettingGuide,
openSettingGuide,
setPageTransition,
setTabStyle,
setMenuOpen,
reload,
setWatermarkVisible,
setCustomRadius,
setholidayFireworksLoaded,
setShowFestivalText,
setFestivalDate,
setDualMenuShowText
}
},
{
persist: {
key: 'setting',
storage: localStorage
}
}
)

View File

@@ -0,0 +1,40 @@
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 setTableSize = (size: TableSizeEnum) => (tableSize.value = size)
const setIsZebra = (value: boolean) => (isZebra.value = value)
const setIsBorder = (value: boolean) => (isBorder.value = value)
const setIsHeaderBackground = (value: boolean) => (isHeaderBackground.value = value)
return {
tableSize,
isZebra,
isBorder,
isHeaderBackground,
setTableSize,
setIsZebra,
setIsBorder,
setIsHeaderBackground
}
},
{
persist: {
key: 'table',
storage: localStorage
}
}
)

114
src/store/modules/user.ts Normal file
View File

@@ -0,0 +1,114 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { LanguageEnum } from '@/enums/appEnum'
import { router } from '@/router'
import { UserInfo } from '@/types/store'
import { useSettingStore } from './setting'
import { useWorktabStore } from './worktab'
import { AppRouteRecord } from '@/types/router'
import { setPageTitle } from '@/router/utils/utils'
import { resetRouterState } from '@/router/guards/beforeEach'
import { RoutesAlias } from '@/router/routesAlias'
import { AuthService } from '@/api/modules/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<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)
const setUserInfo = (newInfo: UserInfo) => {
info.value = newInfo
}
const setLoginStatus = (status: boolean) => {
isLogin.value = status
}
const setLanguage = (lang: LanguageEnum) => {
setPageTitle(router.currentRoute.value)
language.value = lang
}
const setSearchHistory = (list: AppRouteRecord[]) => {
searchHistory.value = list
}
const setLockStatus = (status: boolean) => {
isLock.value = status
}
const setLockPassword = (password: string) => {
lockPassword.value = password
}
const setToken = (newAccessToken: string, newRefreshToken?: string) => {
accessToken.value = newAccessToken
if (newRefreshToken) {
refreshToken.value = newRefreshToken
}
}
const logOut = async () => {
try {
// 调用退出登录接口
await AuthService.logout()
} catch (error) {
console.error('退出登录接口调用失败:', error)
} finally {
// 无论接口成功与否,都清理本地状态
info.value = {}
isLogin.value = false
isLock.value = false
lockPassword.value = ''
accessToken.value = ''
refreshToken.value = ''
useWorktabStore().opened = []
sessionStorage.removeItem('iframeRoutes')
resetRouterState(router)
router.push(RoutesAlias.Login)
}
}
return {
language,
isLogin,
isLock,
lockPassword,
info,
searchHistory,
accessToken,
refreshToken,
getUserInfo,
getSettingState,
getWorktabState,
setUserInfo,
setLoginStatus,
setLanguage,
setSearchHistory,
setLockStatus,
setLockPassword,
setToken,
logOut
}
},
{
persist: {
key: 'user',
storage: localStorage,
// 只持久化 token 和登录状态,用户信息每次刷新都从接口获取
paths: ['accessToken', 'refreshToken', 'isLogin', 'language', 'isLock', 'lockPassword']
}
}
)

View File

@@ -0,0 +1,530 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { HOME_PAGE } from '@/router/routesAlias'
import { router } from '@/router'
import { LocationQueryRaw, Router } from 'vue-router'
import { WorkTab } from '@/types'
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 initializeAfterRefresh = (currentPath?: string): void => {
// 保留固定标签页
const fixedTabs = opened.value.filter((tab) => tab.fixedTab)
// 如果当前路径存在且不是固定标签页,则将其从已打开的标签页中移除
if (currentPath) {
const currentIsFixed = fixedTabs.some((tab) => tab.path === currentPath)
if (!currentIsFixed) {
// 移除所有非固定的标签页
opened.value = fixedTabs
current.value = {}
} else {
// 当前路径是固定标签页,保留所有固定标签页
opened.value = fixedTabs
current.value = fixedTabs.find((tab) => tab.path === currentPath) || {}
}
} else {
// 如果没有当前路径,只保留固定标签页
opened.value = fixedTabs
current.value = fixedTabs.length > 0 ? fixedTabs[0] : {}
}
}
/**
* 查询对象比较
*/
const areQueriesEqual = (
query1: LocationQueryRaw | undefined,
query2: LocationQueryRaw | undefined
): boolean => {
if (!query1 && !query2) return true
if (!query1 || !query2) return false
return JSON.stringify(query1) === JSON.stringify(query2)
}
/**
* 查找标签页索引
*/
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)
}
const 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]
if (!areQueriesEqual(existingTab.query, tab.query)) {
opened.value[existingIndex] = {
...existingTab,
query: tab.query,
title: tab.title || existingTab.title
}
}
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)
}
// 如果关闭后无标签页,跳转首页
if (!hasOpenedTabs.value) {
if (path !== HOME_PAGE) {
current.value = {}
safeRouterPush({ path: HOME_PAGE })
}
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 hasFixedTabs = opened.value.some((tab) => tab.fixedTab)
// 获取可关闭的标签页
const closableTabs = opened.value.filter((tab) => {
if (!isTabClosable(tab)) return false
// 如果有固定标签,则所有可关闭的都可以关闭;否则保留首页
return hasFixedTabs || tab.path !== HOME_PAGE
})
if (closableTabs.length === 0) {
console.warn('没有可关闭的标签页')
return
}
// 标记为缓存排除
markTabsToRemove(closableTabs)
// 保留不可关闭的标签页和首页(当没有固定标签时)
opened.value = opened.value.filter((tab) => {
return !isTabClosable(tab) || (!hasFixedTabs && tab.path === HOME_PAGE)
})
// 处理激活状态
if (!hasOpenedTabs.value) {
current.value = {}
safeRouterPush({ path: HOME_PAGE })
return
}
// 选择激活的标签页:优先首页,其次第一个可用标签
const homeTab = opened.value.find((tab) => tab.path === HOME_PAGE)
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 {
const validPaths = new Set(routerInstance.getRoutes().map((route) => route.path))
// 过滤出有效的标签页
const validTabs = opened.value.filter((tab) => validPaths.has(tab.path))
if (validTabs.length !== opened.value.length) {
console.warn('发现无效的标签页路由,已自动清理')
opened.value = validTabs
}
// 验证当前激活标签的有效性
const isCurrentValid =
current.value.path && validTabs.some((tab) => tab.path === current.value.path)
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]
}
}
return {
// 状态
current,
opened,
keepAliveExclude,
// 计算属性
hasOpenedTabs,
hasMultipleTabs,
currentTabIndex,
// 方法
openTab,
removeTab,
removeLeft,
removeRight,
removeOthers,
removeAll,
toggleFixedTab,
validateWorktabs,
clearAll,
getStateSnapshot,
initializeAfterRefresh,
// 工具方法
findTabIndex,
getTab,
isTabClosable,
addKeepAliveExclude,
removeKeepAliveExclude,
markTabsToRemove
}
},
{
persist: {
key: 'worktab',
storage: localStorage,
paths: ['opened'],
// 自定义序列化,只保存固定标签页
serializer: {
serialize: (value: any) => {
const fixedTabs = value.opened?.filter((tab: WorkTab) => tab.fixedTab) || []
return JSON.stringify({
opened: fixedTabs,
keepAliveExclude: value.keepAliveExclude || []
})
},
deserialize: (value: string) => {
try {
const parsed = JSON.parse(value)
return {
opened: parsed.opened || [],
keepAliveExclude: parsed.keepAliveExclude || [],
current: {} // 刷新后不恢复当前标签状态
}
} catch {
return { opened: [], keepAliveExclude: [], current: {} }
}
}
}
}
}
)