前端页面缓存与刷新
前端页面缓存与刷新
技术储备文档:说明 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.js、refreshMixin.js 已从 SVN 删除,勿再引入。
2. 总体架构
模块职责
| 模块 | 文件 | 职责 |
|---|---|---|
| 缓存容器 | layout/components/AppMain.vue | <keep-alive :include="cachedViews"> 包裹 router-view |
| Tags 状态 | store/modules/tagsView.js | visitedViews(Tab 栏)、cachedViews(keep-alive include 名单) |
| 侧栏导航 | layout/components/Sidebar/Link.vue | 内部链接 <span> + 单次 navigateFromSidebar(禁止与 router-link 双导航) |
| 缓存工具 | utils/pageCache.js | 侧栏导航、关 Tab、triggerPageRefresh、pageCacheRefreshMixin |
| 表单 Tab | mixins/pageTabMixin.js | 全局 onlyBack / closeBack / triggerRefresh |
| 列表 mixin | mixins/listPageLayout.js | 表格布局 + 自动混入 pageCacheRefreshMixin |
| 事件总线 | main.js | Vue.prototype.$bus = new Vue() |
3. keep-alive 机制
3.1 何时进入缓存
- 路由变化 →
TagsView监听$route→addView(route) addView→addVisitedView+addCachedViewADD_CACHED_VIEW:当route.meta.noCache !== true时,将route.name推入cachedViewsAppMain的<keep-alive :include="cachedViews">按组件 name 匹配缓存
3.2 关键约定:path ≠ name
| 概念 | 示例 | 用途 |
|---|---|---|
| 菜单 path | /baseLedger/goms/basMjtd | 侧栏 URL、Tags path |
| 路由 name | BasMjtd | keep-alive include、refresh 事件名 |
| 组件 name | BasMjtd(须与 route.name 一致) | Vue keep-alive 匹配 |
后端动态路由对末段 path 做首字母大写生成 name。页面组件 export default { name: 'BasMjtd' } 必须与之一致,否则无法缓存或 refresh 监听失效。
3.3 何时清除缓存
- 用户关闭 Tags →
tagsView/delView→ 同时DEL_VISITED_VIEW+DEL_CACHED_VIEW - 侧栏进入已在 Tags 的页 →
closeOpenedPageTag→delView后再导航,组件重新created
3.4 不缓存的页面
表单/详情独立路由设置 meta.noCache: true(如 BasMjtdForm),不进入 cachedViews。
4. 场景行为矩阵
| 场景 | 是否刷新数据 | 实现要点 |
|---|---|---|
| Tags 切回已打开列表 | 否 | keep-alive 命中,activated 不默认调 getList |
| 侧栏首次进入 | 是(1 次) | 正常 created → getList() |
| 侧栏进入已在 Tags 的页 | 是(1 次) | delView 清缓存 → 重新挂载 → created |
| 关闭 Tags 后再侧栏进入 | 是(1 次) | 同首次 |
| 表单保存返回列表 | 是 | closeBack → $bus → resetQuery |
| 表单取消返回 | 否 | 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)
})
}
设计要点
- 禁止
router-link+ 编程式 push 并存 — 会导致双导航、Tags 加不进去、接口双请求。 - 侧栏不走
$busrefresh — 刷新靠销毁缓存后重新created,避免与created竞态双请求。 - 同页 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() 在 listPageLayout 的 created 中调用,同时满足:
- 组件实现了
resetQuery this.$options.name === this.$route.name(路由页组件)- 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.vue、views/goms/base/basTowerLj/index.vue + form.vue
| 层级 | 职责 |
|---|---|
路由容器 index.vue | name = route.name;resetQuery() 转发;loadTabData 统一触发子列表加载 |
子列表 *List.vue | 不要在 created 调 loadData(会与父容器 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 切回不缓存 | 组件 name ≠ route.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-alive | AppMain :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 |