Files
shanxia2.0/docs/superpowers/specs/2026-10-16-web-admin-version-refresh-design.md
T

5.8 KiB
Raw Blame History

web-admin 发版后强制刷新

日期:2026-10-16
适用范围:web-admin 生产构建与运行时
交互参考:docs/superpowers/demos/2026-10-16-web-admin-version-refresh-demo.html(方案 A · 强制弹窗)
决策:静态 version.json + 强制弹窗,确认后刷新页面


1. 目标与已确认决策

1.1 目标

  1. 管理端发版后,仍停留在旧标签页的用户能得知已有新版本。
  2. 用户只能通过「立即刷新」进入新包,不能关掉提示继续用旧功能。
  3. 不依赖后端接口;随静态资源部署即生效。

1.2 决策摘要

项 约定
方案 A:Vite 构建写出 dist/version.json,前端比对 buildId
交互 强制弹窗,无「稍后 / 取消 / 关闭」;确认后 location.reload()
未保存表单 不另做二次确认,直接弹刷新提示
展示版本 现有 VITE_VERSION(注入 __APP_VERSION__)
比对指纹 buildId = 短 git hash + 构建时间戳(注入 __APP_BUILD_ID__)
轮询 生产环境启动后立即查一次,之后每 10 分钟
回前台 document.visibilityState === 'visible' 时立刻再查;距上次请求不足 30 秒则跳过
开发环境 pnpm dev 不检查

1.3 非目标

  • 后端版本接口、按发版类型切换「可稍后」
  • 小程序更新(已有 miniprogram/utils/mpUpdate.js)
  • 未保存表单的草稿保护或刷新前二次确认
  • 修改 VITE_VERSION 的人工发版流程(展示号可不变,buildId 每次构建都会变)

2. 背景

web-admin 是 Vite 打包的 SPA(hash 路由)。用户长时间不关标签页时,内存里仍是旧的 JS。现有 __APP_VERSION__(来自 VITE_VERSION,当前 3.0.2)只用于本地存储键隔离(web-admin/src/utils/storage/storage-config.ts),发版后不会提示刷新。若发版时忘记改 VITE_VERSION,仅比对该字段还会漏报。因此展示用版本号,是否更新用每次构建都变化的 buildId。


3. 构建产物

3.1 dist/version.json

构建结束时写入,与 index.html 同目录(受 VITE_BASE_URL 影响时,文件仍在 outDir 根上,由静态服务器按 base 提供):

{
  "version": "3.0.2",
  "buildId": "a1b2c3d-1730000000000"
}
字段 来源 用途
version VITE_VERSION 弹窗展示
buildId git rev-parse --short HEAD,失败时用 nogit;再拼接 - + 构建时刻毫秒时间戳 是否为新包的唯一比对键

同一 commit 再次 pnpm build,时间戳不同,buildId 必须变化。

3.2 包内常量

在现有 define.__APP_VERSION__ 旁增加 __APP_BUILD_ID__,值与当次 version.json 的 buildId 相同。类型声明写在 web-admin/src/env.d.ts。

3.3 插件位置

在 web-admin/vite.config.ts 的 plugins 中增加一个仅 apply: 'build' 的插件:closeBundle(或等价的构建结束钩子)写文件,并在 config 阶段把 buildId 配进 define。开发服务器不写该文件、不注入会触发检查的生产逻辑。


4. 运行时

4.1 模块

新增 web-admin/src/utils/check-app-update.ts,由 web-admin/src/main.ts 在 app.mount 之后调用 startAppUpdateCheck()。

职责:

  1. import.meta.env.PROD 为假时直接返回。
  2. 请求 ${import.meta.env.BASE_URL}version.json?t=${Date.now()},cache: 'no-store'。
  3. 解析 JSON。buildId 为非空字符串且与 __APP_BUILD_ID__ 不同时弹窗。
  4. 弹窗已打开或已经弹过则不再弹。弹出时清除定时器并移除 visibilitychange 监听,不再继续检查。

4.2 调度

  • 启动后立即检查一次。
  • setInterval 10 分钟。
  • visibilitychange:变为 visible 时检查;若距上次发起请求不足 30 秒则跳过。
  • 页面卸载时移除监听并清除定时器。

4.3 弹窗

使用 Element Plus ElMessageBox.alert(自动导入,不手写 import):

项 值
标题 发现新版本
正文 系统已发布更新,当前页面仍在使用旧功能。请刷新后继续操作。
版本行 远端 version 为非空字符串时追加:当前 v{__APP_VERSION__} / 最新 v{远端 version}
确认按钮 立即刷新
showClose false
closeOnClickModal false
closeOnPressEscape false
showCancelButton false

用户点击「立即刷新」后执行一次 location.reload()(模块内标志位保证只触发一次)。遮罩无背景模糊,与现有弹窗一致。

多个标签页各自检查、各自弹窗。


5. 失败处理

情况 行为
网络失败、非 2xx、JSON 无法解析、缺少 buildId 不提示,保留定时器,下一轮再试
buildId 相同 不提示
buildId 不同 弹一次窗并停止后续检查
远端无 version 仍按 buildId 弹窗,正文不带版本行

6. 部署注意

前端请求已绕过 HTTP 缓存。静态服务器仍应为 index.html 与 version.json 设置短缓存或 no-cache。二者若被长期强缓存,用户点刷新后仍可能拿到旧的 index.html。带 hash 的 JS/CSS 可继续长期缓存。

本设计不新增 Nginx 配置文件;上线时由现有发布方式保证这两份入口文件可被重新验证。


7. 验证

  1. 连续构建两次,两次 dist/version.json 的 buildId 不同。
  2. 用第一版打开页面,再换成第二版静态资源:等待轮询,或切走标签页超过 30 秒后再切回,出现不可关闭的「发现新版本」。
  3. 点击「立即刷新」后加载第二版,不再弹窗。
  4. 删除或无法访问 version.json 时,业务页面仍可操作,控制台不把该失败当成未捕获异常打断应用。
  5. pnpm dev 不请求 version.json。