修复前端tabView缓存的问题

This commit is contained in:
Shenlijun
2026-07-02 20:57:59 +08:00
parent d6ad44b53b
commit ec090e4d5b
6 changed files with 76 additions and 21 deletions
@@ -260,14 +260,18 @@
currentContextMenuTabIndex.value = Array.from(tabList).findIndex((tab) => tab === target)
}
// 先恢复刷新前打开过的标签(顺带重建 keepAlive 列表),去重交给 pushViewTags
// 放最前面,避免被下方菜单逻辑的异常挡住导致恢复不执行
vStore.restoreViewTags()
const module = router.getMenu()
const indexMenu = routerUtil.getIndexMenu(module).path
const dashboardRoute = treeFind(module, (node) => node.path === indexMenu)
if (dashboardRoute) {
dashboardRoute.fullPath = dashboardRoute.path
addViewTags(dashboardRoute)
addViewTags(route)
}
// 当前页始终加入并高亮
addViewTags(route)
const onTabRemove = (tabKey, action) => {
if (action === 'remove') {
@@ -66,7 +66,7 @@
import loginApi from '@/api/auth/loginApi'
import DevUserMessage from './message.vue'
import PanelSearch from './panel-search/index.vue'
import { globalStore } from '@/store'
import { globalStore, viewTagsStore, keepAliveStore } from '@/store'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
@@ -127,6 +127,9 @@
tool.data.remove('USER_INFO')
tool.data.remove('MENU')
tool.data.remove('PERMISSIONS')
// 清理标签与缓存,避免同一标签页内换号后残留
viewTagsStore().clearViewTags()
keepAliveStore().clearKeepLive()
router.replace({ path: '/login' })
nextTick(() => {
// 清理缓存内的个人信息
@@ -122,12 +122,8 @@
})
})
const displayLayoutResult = () => {
// 根据route.meta.keepLive动态管理keepLiveRoute
if (route.meta.keepLive === true) {
props.kStore.pushKeepLive(route.name)
} else {
props.kStore.removeKeepLive(route.name)
}
// keep-alive 缓存名单由「打开/关闭标签」生命周期管理(tags.vue 的 addViewTags/closeSelectedTag),
// 这里不再按 meta.keepLive 踢出,保证已打开的标签切换时不重新挂载
if (
route.meta.displayLayout === undefined ||
route.meta.displayLayout === null ||
@@ -146,12 +146,8 @@
})
})
const displayLayoutResult = () => {
// 根据route.meta.keepLive动态管理keepLiveRoute
if (route.meta.keepLive === true) {
props.kStore.pushKeepLive(route.name)
} else {
props.kStore.removeKeepLive(route.name)
}
// keep-alive 缓存名单由「打开/关闭标签」生命周期管理(tags.vue 的 addViewTags/closeSelectedTag),
// 这里不再按 meta.keepLive 踢出,保证已打开的标签切换时不重新挂载
if (
route.meta.displayLayout === undefined ||
route.meta.displayLayout === null ||
+2 -6
View File
@@ -100,12 +100,8 @@
})
})
const displayLayoutResult = () => {
// 根据route.meta.keepLive动态管理keepLiveRoute
if (route.meta.keepLive === true) {
props.kStore.pushKeepLive(route.name)
} else {
props.kStore.removeKeepLive(route.name)
}
// keep-alive 缓存名单由「打开/关闭标签」生命周期管理(tags.vue 的 addViewTags/closeSelectedTag),
// 这里不再按 meta.keepLive 踢出,保证已打开的标签切换时不重新挂载
if (
route.meta.displayLayout === undefined ||
route.meta.displayLayout === null ||
+61 -1
View File
@@ -10,6 +10,28 @@
*/
import { defineStore } from 'pinia'
import { useRouter } from 'vue-router'
import tool from '@/utils/tool'
import { keepAliveStore } from '@/store/keepAlive'
// 标签持久化到 sessionStorage 的 key(刷新后可恢复,关闭标签页/窗口自动清空)
const VIEW_TAGS_STORAGE_KEY = 'SNOWY_VIEW_TAGS'
// 路由对象里的 matched / components 不可序列化,持久化时只保留恢复所需字段。
// 注意:meta.breadcrumb 是扁平化时塞进来的带循环引用的数组(其元素是路由对象本身,
// 又含 meta.breadcrumb),直接 stringify 会抛 "Converting circular structure to JSON",
// 所以这里必须浅拷贝 meta 并剔除 breadcrumb(标签栏不需要它,跳转后面包屑会从实时路由重新生成)。
const slimRoute = (route) => {
const meta = route.meta ? { ...route.meta } : {}
delete meta.breadcrumb
return {
path: route.path,
fullPath: route.fullPath,
name: route.name,
query: route.query,
params: route.params,
meta
}
}
export const viewTagsStore = defineStore('viewTags', () => {
// 定义state
@@ -27,6 +49,23 @@ export const viewTagsStore = defineStore('viewTags', () => {
{ immediate: true }
)
// ===== 持久化(参考 soybean admin 的 beforeunload 方案)=====
// 把当前标签快照写入 sessionStorage
const cacheViewTags = () => {
try {
tool.session.set(
VIEW_TAGS_STORAGE_KEY,
viewTags.value.map(slimRoute)
)
} catch (e) {
console.warn('viewTags 写入 sessionStorage 失败:', e)
}
}
// 兜底:标签变化时也写一份,保证会话中途数据最新
watch(viewTags, cacheViewTags, { deep: true })
// 关键:刷新(F5)/关闭页面前同步写一次,刷新后必定能读到
window.addEventListener('beforeunload', cacheViewTags)
// 定义action
const pushViewTags = (route) => {
const target = viewTags.value.find((item) => item.path === route.path)
@@ -77,8 +116,27 @@ export const viewTagsStore = defineStore('viewTags', () => {
}
})
}
// ===== 恢复:刷新后从 sessionStorage 还原标签 + keepAlive 列表 =====
// 注意:刷新时动态路由是异步注册的(refreshApiMenu 未 await),执行到这里时
// router.getRoutes() 往往还没有动态菜单路由,所以这里不能用路由表校验。
// 失效标签(权限/菜单变更)由 layout 内 updateOrRemoveViewTags 在路由加载后统一清理。
const restoreViewTags = () => {
const cached = tool.session.get(VIEW_TAGS_STORAGE_KEY)
if (!Array.isArray(cached) || cached.length === 0) return
const kStore = keepAliveStore()
cached.forEach((route) => {
if (!route || !route.name || route.meta?.fullpage) return
if (viewTags.value.find((item) => item.path === route.path)) return
viewTags.value.push(route)
kStore.pushKeepLive(route.name)
})
}
const clearViewTags = () => {
viewTags.value = []
// 同步清掉 sessionStorage,避免同一标签页内换号后残留
tool.session.remove(VIEW_TAGS_STORAGE_KEY)
}
return {
@@ -88,6 +146,8 @@ export const viewTagsStore = defineStore('viewTags', () => {
updateViewTags,
updateViewTagsTitle,
clearViewTags,
updateOrRemoveViewTags
updateOrRemoveViewTags,
restoreViewTags,
cacheViewTags
}
})