前端页面缓存与刷新

lishihuan大约 5 分钟

前端页面缓存与刷新

技术储备文档:说明 GOMS 前端 Tags + keep-alive 缓存策略、侧栏重进刷新、表单保存返回刷新列表的完整实现。
代码路径均相对于 ruoyi-ui/src/
快速接入见 ruoyi-ui/README.md

1. 背景与演进

阶段方案问题
LegacyApp 遗留mixin/refresh.js 全局混入 + refreshMixin.js 按需混入全局 $on('resetQuery') 污染无关组件;backRefresh 双通道(old/new event)难维护
GOMS 现行utils/pageCache.js + pageTabMixin + listPageLayout职责分层;侧栏与保存返回分离;仅路由页组件注册 refresh 监听

LegacyApp 遗留文件 mixin/refresh.jsrefreshMixin.js 已从 SVN 删除,勿再引入。


2. 总体架构

模块职责

模块文件职责
缓存容器layout/components/AppMain.vue<keep-alive :include="cachedViews"> 包裹 router-view
Tags 状态store/modules/tagsView.jsvisitedViews(Tab 栏)、cachedViews(keep-alive include 名单)
侧栏导航layout/components/Sidebar/Link.vue内部链接 <span> + 单次 navigateFromSidebar(禁止与 router-link 双导航)
缓存工具utils/pageCache.js侧栏导航、关 Tab、triggerPageRefreshpageCacheRefreshMixin
表单 Tabmixins/pageTabMixin.js全局 onlyBack / closeBack / triggerRefresh
列表 mixinmixins/listPageLayout.js表格布局 + 自动混入 pageCacheRefreshMixin
事件总线main.jsVue.prototype.$bus = new Vue()

3. keep-alive 机制

3.1 何时进入缓存

  1. 路由变化 → TagsView 监听 $routeaddView(route)
  2. addViewaddVisitedView + addCachedView
  3. ADD_CACHED_VIEW:当 route.meta.noCache !== true 时,将 route.name 推入 cachedViews
  4. AppMain<keep-alive :include="cachedViews">组件 name 匹配缓存

3.2 关键约定:path ≠ name

概念示例用途
菜单 path/baseLedger/goms/basMjtd侧栏 URL、Tags path
路由 nameBasMjtdkeep-alive include、refresh 事件名
组件 nameBasMjtd(须与 route.nameopen in new window 一致)Vue keep-alive 匹配

后端动态路由对末段 path 做首字母大写生成 name。页面组件 export default { name: 'BasMjtd' } 必须与之一致,否则无法缓存或 refresh 监听失效。

3.3 何时清除缓存

  • 用户关闭 Tags → tagsView/delView → 同时 DEL_VISITED_VIEW + DEL_CACHED_VIEW
  • 侧栏进入已在 Tags 的页 → closeOpenedPageTagdelView 后再导航,组件重新 created

3.4 不缓存的页面

表单/详情独立路由设置 meta.noCache: true(如 BasMjtdForm),不进入 cachedViews


4. 场景行为矩阵

场景是否刷新数据实现要点
Tags 切回已打开列表keep-alive 命中,activated 不默认调 getList
侧栏首次进入是(1 次)正常 createdgetList()
侧栏进入已在 Tags 的页是(1 次)delView 清缓存 → 重新挂载 → created
关闭 Tags 后再侧栏进入是(1 次)同首次
表单保存返回列表closeBack$busresetQuery
表单取消返回onlyBack,不 emit refresh
Tags 右键「刷新页面」若依内置 /redirect 重挂载(与侧栏同页 redirect 策略一致)

5. 侧栏导航(navigateFromSidebar)

入口layout/components/Sidebar/Link.vue

// 伪代码
function navigateFromSidebar(router, store, to) {
  if (!isRouteAlreadyOpened(store, to)) {
    return router.push(to)
  }
  return closeOpenedPageTag(store, to).then(() => {
    if (currentPath === targetPath) {
      // 同页重复点击:push 同路由无效,走 redirect 强制重挂载
      return router.replace({ path: '/redirect' + target.path, query })
    }
    return router.push(to)
  })
}

设计要点

  1. 禁止 router-link + 编程式 push 并存 — 会导致双导航、Tags 加不进去、接口双请求。
  2. 侧栏不走 $bus refresh — 刷新靠销毁缓存后重新 created,避免与 created 竞态双请求。
  3. 同页 redirect/redirect/:path 组件 replace 回真实 path,触发一次完整挂载。

6. 保存返回刷新(closeBack)

入口:表单页 this.closeBack()pageTabMixin 全局可用)

closeBack()
  → tagsView/delView(当前表单 Tab)
  → router.go(-1)
  → setTimeout 500ms
  → triggerPageRefresh(上一页 route.name)
  → $bus.$emit('refresh-{name}')
  → 列表页 pageCacheRefreshMixin 收到
  → resetQuery()
  → getList()

refresh 监听注册条件

pageCacheRefreshMixin.bindPageCacheRefresh()listPageLayoutcreated 中调用,同时满足

  1. 组件实现了 resetQuery
  2. this.$options.name === this.$route.name(路由页组件)
  3. Tab 内子列表(如 basOpTeamList满足第 2 条,不会重复注册

Tab 容器(如 BasOpTeam)应在自身实现 resetQuery(),转发给当前列表子组件。


7. 页面类型接入指南

7.1 普通列表(单页)

参考:views/goms/base/basMjtd/index.vue

export default {
  name: 'BasMjtd',  // 与 route.name 一致
  created() {
    this.getList()
  },
  methods: {
    resetQuery() {
      // 清筛选 + 回第一页 + getList
    }
  }
}

7.2 Tab 容器 + 子列表(旧模式,新功能勿用)

参考:views/goms/base/basOpTeam/index.vue + basOpTeamList.vue
独立路由表单页参考:views/goms/base/basInsulator/index.vue + form.vueviews/goms/base/basTowerLj/index.vue + form.vue

层级职责
路由容器 index.vuename = route.nameopen in new windowresetQuery() 转发;loadTabData 统一触发子列表加载
子列表 *List.vue不要createdloadData(会与父容器 mounted → loadTabData 双请求);实现 loadData / resetQuery

7.3 表单 / 详情

参考:views/goms/base/basMjtd/form.vue

// 取消
this.onlyBack()

// 保存成功
this.closeBack()

表单路由建议 meta.noCache: true


8. API 速查

utils/pageCache.js

导出说明
navigateFromSidebar(router, store, to)侧栏单次导航
closeOpenedPageTag(store, target)关闭目标 Tags + 清 cachedViews
isRouteAlreadyOpened(store, target)是否已在 Tags
triggerPageRefresh(routeName)emit refresh-{routeName}
pageCacheRefreshMixin列表 refresh 监听 mixin

pageTabMixin(全局 methods)

方法说明
onlyBack()关当前 Tab,回上一 Tags,不刷新
closeBack()关当前 Tab,后退并 refresh 上一页
triggerRefresh(name)手动触发指定页 refresh

9. 常见问题排查

现象可能原因处理
Tags 切回不缓存组件 nameroute.name对齐命名
接口请求 2 次Tab 子列表 created + 父 loadTabData 双调子列表去掉 created 加载
接口请求 2 次侧栏 router-link 与 push 双导航Link 仅用 span + navigateFromSidebar
保存返回列表不刷新列表未实现 resetQuery 或 name 不一致检查路由页 resetQuery
refresh 未触发Tab 子列表注册了监听但容器未转发仅在路由容器实现 resetQuery
侧栏同页点击无反应同 path push 被忽略已走 /redirect 分支,检查 Link 是否最新

10. 与若依原生 Tags 的关系

能力若依原生GOMS 扩展
Tab 增删TagsView + tagsView store无改动
keep-aliveAppMain :include="cachedViews"无改动
侧栏点击router-link 直接跳转Link.vue 改为编程式 + 已开 Tab 重进策略
表单返回各项目自定义统一 onlyBack / closeBack
列表刷新$bus resetQuery统一 refresh-{route.name}

11. 变更记录

日期说明
2026-07-09初版:替代 LegacyApp refresh.js;侧栏 Link 单次导航;Tab 子列表防双加载
2026-07-09收拢至 utils/pageCache.js;删除 navSource;本文档入 Wiki

相关变更摘要:ai-doc/changes/2026-07-09-page-cache-refresh.md