[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-vue3-yingyongbanbengengxincelueshixianliulanqizidongjiazaizuixinneirong":3},{"id":4,"title":5,"slug":6,"summary":7,"content":8,"contentHtml":9,"wordCount":10,"readingTime":11,"categoryId":12,"tags":13,"coverImage":13,"thumbnail":13,"status":14,"isTop":15,"isRecommended":15,"allowComments":15,"password":13,"viewCount":16,"likeCount":15,"commentCount":15,"seoTitle":5,"seoKeywords":17,"seoDescription":18,"source":13,"sourceUrl":13,"publishTime":19},"2008058850450665474","Vue3 应用版本更新策略：实现浏览器自动加载最新内容","vue3-yingyongbanbengengxincelueshixianliulanqizidongjiazaizuixinneirong","本文将详细介绍如何在 Vue3 应用中实现版本更新后浏览器自动加载最新内容的完整方案，涵盖构建优化、缓存策略与版本控制的各个方面。 ## 1. 构建工具配置：静态资源哈希命名 在 Vue3 项目中，使用 Vite 或 Webpack 等构建工具为静态资源生成带哈希值的文件名是版本控制的基础。当文件内容变化时，哈希值会更新，浏览器会因 URL 不同而重新加载资源。 ### Vite 配置示例 ```js \u002F\u002F vite.config.js export default defineConfig({ build: { rollupOptions: { output: { entryFileNames: `assets\u002F[name]-[hash].js`, chunkFileNames: `assets\u002F[name]-[hash].js`, assetFileNames:...","本文将详细介绍如何在 Vue3 应用中实现版本更新后浏览器自动加载最新内容的完整方案，涵盖构建优化、缓存策略与版本控制的各个方面。\n\n## 1. 构建工具配置：静态资源哈希命名\n\n在 Vue3 项目中，使用 Vite 或 Webpack 等构建工具为静态资源生成带哈希值的文件名是版本控制的基础。当文件内容变化时，哈希值会更新，浏览器会因 URL 不同而重新加载资源。\n\n### Vite 配置示例\n\n```js\n\u002F\u002F vite.config.js\nexport default defineConfig({\n  build: {\n    rollupOptions: {\n      output: {\n        entryFileNames: `assets\u002F[name]-[hash].js`,\n        chunkFileNames: `assets\u002F[name]-[hash].js`,\n        assetFileNames: `assets\u002F[name]-[hash].[ext]`\n      }\n    }\n  }\n})\n```\n\n**实现原理**：Vite 在构建过程中会自动为资源文件附加哈希值，确保客户端能够加载最新资源，减少浏览器缓存影响。这种基于内容哈希的命名方式，可以精确控制缓存：只有当文件内容实际发生变化时，URL才会改变，从而触发浏览器重新下载。\n\n### 进阶配置：生成版本信息文件\n\n除了哈希命名外，还可以在构建时生成版本信息文件：\n\n```js\n\u002F\u002F scripts\u002Fversion.js\nimport fs from 'fs'\nimport path from 'path'\n\nconst version = new Date().getTime()\nconst content = `export const APP_VERSION = ${version}`\n\nfs.writeFile(\n  path.resolve(__dirname, '..\u002Fsrc\u002Fversion.js'), \n  content, \n  (err) => err ? console.log(err) : console.log('版本文件创建成功')\n)\n```\n\n在 `package.json` 中配置构建命令：\n\n```json\n{\n  \"scripts\": {\n    \"build\": \"vite build && node scripts\u002Fversion.js\"\n  }\n}\n```\n\n## 2. 服务端缓存策略优化\n\n合理的缓存策略是平衡性能与版本更新的关键。不同的资源类型应采用不同的缓存策略。\n\n### HTML 文件缓存策略\n\nHTML 文件应当禁用缓存或设置很短的缓存时间，以确保浏览器能及时获取最新版本：\n\n```nginx\n# Nginx 配置\nserver {\n  location \u002Findex.html {\n    expires -1;\n    add_header Cache-Control \"no-cache, no-store, must-revalidate\";\n    add_header Pragma \"no-cache\";\n  }\n}\n```\n\n### 静态资源缓存策略\n\n对已哈希命名的静态资源可设置长期缓存：\n\n```nginx\nlocation ~ .*\\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf)$ {\n  expires 1y;\n  add_header Cache-Control \"public, max-age=31536000, immutable\";\n  access_log off;\n}\n```\n\n`immutable` 标记告知浏览器该资源在有效期内不会变化，即使刷新页面也不会发起验证请求。\n\n### 版本文件特殊处理\n\n版本检查文件应设置为不缓存：\n\n```nginx\nlocation ~ \u002Fversion\\.json$ {\n  expires -1;\n  add_header Cache-Control \"no-cache, no-store, must-revalidate\";\n}\n```\n\n## 3. 前端版本检测机制\n\n### 基于轮询的版本检测\n\n实现一个可靠的版本检测机制是自动更新的核心：\n\n```js\n\u002F\u002F src\u002Futils\u002Fversion-check.js\nlet checkTimer\n\nexport const initVersionCheck = () => {\n  const currentVersion = localStorage.getItem('app-version')\n  \n  const checkVersion = async () => {\n    try {\n      \u002F\u002F 添加时间戳防止缓存\n      const response = await fetch('\u002Fversion.json?t=' + Date.now())\n      const { version } = await response.json()\n      \n      if (version && version !== currentVersion) {\n        handleUpdateDetected(version)\n      }\n    } catch (error) {\n      console.error('版本检查失败:', error)\n    }\n  }\n  \n  \u002F\u002F 首次加载时检查\n  checkVersion()\n  \n  \u002F\u002F 定时检查（建议5-10分钟）\n  checkTimer = setInterval(checkVersion, 5 * 60 * 1000)\n}\n\nexport const stopVersionCheck = () => {\n  if (checkTimer) clearInterval(checkTimer)\n}\n```\n\n### 智能检测时机优化\n\n除了简单的轮询，还可以结合多种触发条件实现智能检测：\n\n```js\n\u002F\u002F 空闲检测 + 页面可见性变化\nconst initSmartVersionCheck = () => {\n  \u002F\u002F 利用 requestIdleCallback 在浏览器空闲时执行检测\n  if ('requestIdleCallback' in window) {\n    requestIdleCallback(() => checkVersion())\n  } else {\n    setTimeout(checkVersion, 2000) \u002F\u002F 降级方案\n  }\n  \n  \u002F\u002F 页面从后台切回前台时触发检测\n  document.addEventListener('visibilitychange', () => {\n    if (!document.hidden) {\n      checkVersion()\n    }\n  })\n  \n  \u002F\u002F 常规轮询作为保底方案\n  checkTimer = setInterval(checkVersion, 5 * 60 * 1000)\n}\n```\n\n这种方式可以减少不必要的请求，提升性能。\n\n## 4. 更新提示与用户体验\n\n### 优雅的更新提示\n\n检测到更新后，应通过友好的方式提示用户：\n\n```vue\n\u003Ctemplate>\n  \u003Cdiv v-if=\"updateAvailable\" class=\"update-notification\">\n    \u003Cdiv class=\"notification-content\">\n      \u003Ch3>发现新版本\u003C\u002Fh3>\n      \u003Cp>系统已更新，是否立即刷新以获取最新功能？\u003C\u002Fp>\n      \u003Cdiv class=\"actions\">\n        \u003Cbutton @click=\"refreshNow\" class=\"btn-primary\">立即更新\u003C\u002Fbutton>\n        \u003Cbutton @click=\"remindLater\" class=\"btn-secondary\">稍后提醒\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { ref } from 'vue'\nimport { ElMessageBox } from 'element-plus'\n\nconst updateAvailable = ref(false)\n\nconst handleUpdateDetected = (newVersion) => {\n  ElMessageBox.confirm(\n    '检测到新版本，包含性能优化和修复，是否立即更新？',\n    '版本更新',\n    {\n      confirmButtonText: '立即更新',\n      cancelButtonText: '稍后更新',\n      type: 'info'\n    }\n  ).then(() => {\n    \u002F\u002F 保存新版本号并刷新\n    localStorage.setItem('app-version', newVersion)\n    window.location.reload()\n  }).catch(() => {\n    \u002F\u002F 用户选择稍后更新，可以设置定时提醒\n    setTimeout(() => handleUpdateDetected(newVersion), 10 * 60 * 1000) \u002F\u002F 10分钟后再次提醒\n  })\n}\n\u003C\u002Fscript>\n```\n\n### 处理用户重要操作\n\n在触发更新前，应检查用户当前状态，避免中断重要操作：\n\n```js\nconst checkUserStatus = () => {\n  \u002F\u002F 检查是否有未保存的表单\n  const hasUnsavedChanges = document.querySelector('form[data-unsaved]')\n  if (hasUnsavedChanges) {\n    return confirm('系统有更新，但您有未保存的更改。是否先保存再更新？')\n  }\n  \n  \u002F\u002F 检查是否在重要流程中\n  const inImportantProcess = sessionStorage.getItem('current_workflow')\n  if (inImportantProcess) {\n    return false\n  }\n  \n  return true\n}\n\nconst safeRefresh = () => {\n  if (checkUserStatus()) {\n    window.location.reload()\n  } else {\n    \u002F\u002F 延迟更新，等待合适时机\n    setTimeout(safeRefresh, 60000) \u002F\u002F 1分钟后重试\n  }\n}\n```\n\n## 5. 基于 Service Worker 的高级缓存管理\n\n对于 Progressive Web App (PWA)，可以使用 Service Worker 实现更精细的缓存控制：\n\n### Service Worker 注册与更新\n\n```js\n\u002F\u002F main.js\nimport { Workbox } from 'workbox-window'\n\nif ('serviceWorker' in navigator) {\n  const wb = new Workbox('\u002Fsw.js')\n  \n  wb.addEventListener('waiting', () => {\n    \u002F\u002F 当检测到新版本时提示用户\n    showUpdateNotification()\n  })\n  \n  wb.register()\n}\n```\n\n### Service Worker 缓存策略\n\n```js\n\u002F\u002F sw.js\nimport { precacheAndRoute } from 'workbox-precaching'\n\n\u002F\u002F 预缓存关键资源\nprecacheAndRoute(self.__WB_MANIFEST)\n\nself.addEventListener('install', (event) => {\n  self.skipWaiting() \u002F\u002F 安装后立即激活\n})\n\nself.addEventListener('activate', (event) => {\n  clients.claim() \u002F\u002F 立即控制所有页面\n})\n```\n\nService Worker 能够拦截并管理所有网络请求，支持离线缓存和精准的资源更新控制。\n\n## 6. 完整实现示例\n\n### 构建版本管理插件\n\n创建一个 Vite 插件来自动化版本管理：\n\n```js\n\u002F\u002F plugins\u002Fversion-update-plugin.js\nimport fs from 'fs'\n\nexport default (options = {}) => {\n  const { versionFileName = 'version.json' } = options\n  \n  return {\n    name: 'version-update',\n    buildStart() {\n      const version = new Date().getTime()\n      const content = JSON.stringify({ version })\n      \n      fs.writeFileSync(`public\u002F${versionFileName}`, content)\n    }\n  }\n}\n```\n\n在 `vite.config.js` 中使用插件：\n\n```js\nimport versionUpdatePlugin from '.\u002Fplugins\u002Fversion-update-plugin'\n\nexport default defineConfig({\n  plugins: [\n    vue(),\n    versionUpdatePlugin()\n  ]\n})\n```\n\n### 集成到 Vue 应用\n\n在 Vue 应用的入口文件中集成版本检查：\n\n```js\n\u002F\u002F main.js\nimport { createApp } from 'vue'\nimport App from '.\u002FApp.vue'\nimport { initVersionCheck } from '.\u002Futils\u002Fversion-check'\n\nconst app = createApp(App)\napp.mount('#app')\n\n\u002F\u002F 启动版本检测\nif (process.env.NODE_ENV === 'production') {\n  initVersionCheck()\n}\n```\n\n## 7. 监控与错误处理\n\n### 更新成功率监控\n\n实现更新过程的监控，便于发现问题：\n\n```js\nconst reportUpdateResult = async (success, version, error = null) => {\n  const payload = {\n    timestamp: new Date().toISOString(),\n    version,\n    success,\n    error: error?.message || null,\n    userAgent: navigator.userAgent\n  }\n  \n  try {\n    await fetch('\u002Fapi\u002Fupdate-logs', {\n      method: 'POST',\n      body: JSON.stringify(payload)\n    })\n  } catch (e) {\n    console.error('更新日志上报失败:', e)\n  }\n}\n```\n\n### 错误处理与回退机制\n\n```js\nconst checkVersion = async () => {\n  try {\n    const response = await fetch('\u002Fversion.json?t=' + Date.now())\n    if (!response.ok) throw new Error('版本检查请求失败')\n    \n    const data = await response.json()\n    \u002F\u002F ... 处理版本比较逻辑\n  } catch (error) {\n    console.error('版本检查失败:', error)\n    \n    \u002F\u002F 错误重试机制\n    if (retryCount \u003C 3) {\n      retryCount++\n      setTimeout(checkVersion, 30000) \u002F\u002F 30秒后重试\n    }\n  }\n}\n```\n\n## 总结\n\nVue3 应用版本更新自动加载的最新内容需要构建优化、缓存策略与版本控制的有机结合。核心要点包括：\n\n1. **构建阶段**：使用哈希命名确保内容变化导致URL变化\n2. **缓存策略**：HTML文件不缓存，静态资源长期缓存\n3. **版本检测**：轮询 + 智能触发，平衡实时性与性能\n4. **用户体验**：友好提示，避免中断用户操作\n5. **监控保障**：完善错误处理和上报机制\n\n通过上述方案，可以确保用户在版本更新后能及时获取最新内容，同时保持优秀的性能和用户体验。实际项目中可根据具体需求选择适合的方案组合，如简单项目可使用基本轮询检测，复杂PWA项目可结合Service Worker实现更精细控制。","\u003Cp data-line=\"0\">本文将详细介绍如何在 Vue3 应用中实现版本更新后浏览器自动加载最新内容的完整方案，涵盖构建优化、缓存策略与版本控制的各个方面。\u003C\u002Fp>\n\u003Ch2 data-line=\"2\" id=\"1. 构建工具配置：静态资源哈希命名\">1. 构建工具配置：静态资源哈希命名\u003C\u002Fh2>\n\u003Cp data-line=\"4\">在 Vue3 项目中，使用 Vite 或 Webpack 等构建工具为静态资源生成带哈希值的文件名是版本控制的基础。当文件内容变化时，哈希值会更新，浏览器会因 URL 不同而重新加载资源。\u003C\u002Fp>\n\u003Ch3 data-line=\"6\" id=\"Vite 配置示例\">Vite 配置示例\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"8\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F vite.config.js\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">default\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">defineConfig\u003C\u002Fspan>({\n  \u003Cspan class=\"hljs-attr\">build\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">rollupOptions\u003C\u002Fspan>: {\n      \u003Cspan class=\"hljs-attr\">output\u003C\u002Fspan>: {\n        \u003Cspan class=\"hljs-attr\">entryFileNames\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`assets\u002F[name]-[hash].js`\u003C\u002Fspan>,\n        \u003Cspan class=\"hljs-attr\">chunkFileNames\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`assets\u002F[name]-[hash].js`\u003C\u002Fspan>,\n        \u003Cspan class=\"hljs-attr\">assetFileNames\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`assets\u002F[name]-[hash].[ext]`\u003C\u002Fspan>\n      }\n    }\n  }\n})\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"23\">\u003Cstrong>实现原理\u003C\u002Fstrong>：Vite 在构建过程中会自动为资源文件附加哈希值，确保客户端能够加载最新资源，减少浏览器缓存影响。这种基于内容哈希的命名方式，可以精确控制缓存：只有当文件内容实际发生变化时，URL才会改变，从而触发浏览器重新下载。\u003C\u002Fp>\n\u003Ch3 data-line=\"25\" id=\"进阶配置：生成版本信息文件\">进阶配置：生成版本信息文件\u003C\u002Fh3>\n\u003Cp data-line=\"27\">除了哈希命名外，还可以在构建时生成版本信息文件：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"29\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F scripts\u002Fversion.js\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> fs \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;fs&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> path \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;path&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> version = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>().\u003Cspan class=\"hljs-title function_\">getTime\u003C\u002Fspan>()\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> content = \u003Cspan class=\"hljs-string\">`export const APP_VERSION = \u003Cspan class=\"hljs-subst\">${version}\u003C\u002Fspan>`\u003C\u002Fspan>\n\nfs.\u003Cspan class=\"hljs-title function_\">writeFile\u003C\u002Fspan>(\n  path.\u003Cspan class=\"hljs-title function_\">resolve\u003C\u002Fspan>(__dirname, \u003Cspan class=\"hljs-string\">&#x27;..\u002Fsrc\u002Fversion.js&#x27;\u003C\u002Fspan>), \n  content, \n  \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">err\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> err ? \u003Cspan class=\"hljs-variable language_\">console\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(err) : \u003Cspan class=\"hljs-variable language_\">console\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;版本文件创建成功&#x27;\u003C\u002Fspan>)\n)\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"44\">在 \u003Ccode>package.json\u003C\u002Fcode> 中配置构建命令：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"46\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">json\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-json\" language=json>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-punctuation\">{\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">&quot;scripts&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan> \u003Cspan class=\"hljs-punctuation\">{\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-attr\">&quot;build&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;vite build &amp;&amp; node scripts\u002Fversion.js&quot;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-punctuation\">}\u003C\u002Fspan>\n\u003Cspan class=\"hljs-punctuation\">}\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"54\" id=\"2. 服务端缓存策略优化\">2. 服务端缓存策略优化\u003C\u002Fh2>\n\u003Cp data-line=\"56\">合理的缓存策略是平衡性能与版本更新的关键。不同的资源类型应采用不同的缓存策略。\u003C\u002Fp>\n\u003Ch3 data-line=\"58\" id=\"HTML 文件缓存策略\">HTML 文件缓存策略\u003C\u002Fh3>\n\u003Cp data-line=\"60\">HTML 文件应当禁用缓存或设置很短的缓存时间，以确保浏览器能及时获取最新版本：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"62\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">nginx\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-nginx\" language=nginx>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\"># Nginx 配置\u003C\u002Fspan>\nserver {\n  location \u002Findex.html {\n    expires -1;\n    add_header Cache-Control \u003Cspan class=\"hljs-string\">&quot;no-cache, no-store, must-revalidate&quot;\u003C\u002Fspan>;\n    add_header Pragma \u003Cspan class=\"hljs-string\">&quot;no-cache&quot;\u003C\u002Fspan>;\n  }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"73\" id=\"静态资源缓存策略\">静态资源缓存策略\u003C\u002Fh3>\n\u003Cp data-line=\"75\">对已哈希命名的静态资源可设置长期缓存：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"77\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">nginx\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-nginx\" language=nginx>\u003Cspan class=\"md-editor-code-block\">location ~ .*\\.(js|\u003Cspan class=\"hljs-params\">css\u003C\u002Fspan>|png|\u003Cspan class=\"hljs-params\">jpg\u003C\u002Fspan>|jpeg|\u003Cspan class=\"hljs-params\">gif\u003C\u002Fspan>|ico|\u003Cspan class=\"hljs-params\">svg\u003C\u002Fspan>|woff|\u003Cspan class=\"hljs-params\">woff2\u003C\u002Fspan>|ttf)\u003Cspan class=\"hljs-variable\">$ \u003C\u002Fspan>{\n  expires 1y;\n  add_header \u003Cspan class=\"hljs-title class_\">Cache\u003C\u002Fspan>-\u003Cspan class=\"hljs-title class_\">Control\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;public, max-age=31536000, immutable&quot;\u003C\u002Fspan>;\n  access_log off;\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"85\">\u003Ccode>immutable\u003C\u002Fcode> 标记告知浏览器该资源在有效期内不会变化，即使刷新页面也不会发起验证请求。\u003C\u002Fp>\n\u003Ch3 data-line=\"87\" id=\"版本文件特殊处理\">版本文件特殊处理\u003C\u002Fh3>\n\u003Cp data-line=\"89\">版本检查文件应设置为不缓存：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"91\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">nginx\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-nginx\" language=nginx>\u003Cspan class=\"md-editor-code-block\">location ~ \u002Fversion\\.json$ {\n  expires -1;\n  add_header Cache-Control \u003Cspan class=\"hljs-string\">&quot;no-cache, no-store, must-revalidate&quot;\u003C\u002Fspan>;\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"98\" id=\"3. 前端版本检测机制\">3. 前端版本检测机制\u003C\u002Fh2>\n\u003Ch3 data-line=\"100\" id=\"基于轮询的版本检测\">基于轮询的版本检测\u003C\u002Fh3>\n\u003Cp data-line=\"102\">实现一个可靠的版本检测机制是自动更新的核心：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"104\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F src\u002Futils\u002Fversion-check.js\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> checkTimer\n\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">initVersionCheck\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> currentVersion = \u003Cspan class=\"hljs-variable language_\">localStorage\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">getItem\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;app-version&#x27;\u003C\u002Fspan>)\n  \n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">checkVersion\u003C\u002Fspan> = \u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; {\n    \u003Cspan class=\"hljs-keyword\">try\u003C\u002Fspan> {\n      \u003Cspan class=\"hljs-comment\">\u002F\u002F 添加时间戳防止缓存\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> response = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">fetch\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;\u002Fversion.json?t=&#x27;\u003C\u002Fspan> + \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>())\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { version } = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> response.\u003Cspan class=\"hljs-title function_\">json\u003C\u002Fspan>()\n      \n      \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (version &amp;&amp; version !== currentVersion) {\n        \u003Cspan class=\"hljs-title function_\">handleUpdateDetected\u003C\u002Fspan>(version)\n      }\n    } \u003Cspan class=\"hljs-keyword\">catch\u003C\u002Fspan> (error) {\n      \u003Cspan class=\"hljs-variable language_\">console\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">error\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;版本检查失败:&#x27;\u003C\u002Fspan>, error)\n    }\n  }\n  \n  \u003Cspan class=\"hljs-comment\">\u002F\u002F 首次加载时检查\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-title function_\">checkVersion\u003C\u002Fspan>()\n  \n  \u003Cspan class=\"hljs-comment\">\u002F\u002F 定时检查（建议5-10分钟）\u003C\u002Fspan>\n  checkTimer = \u003Cspan class=\"hljs-built_in\">setInterval\u003C\u002Fspan>(checkVersion, \u003Cspan class=\"hljs-number\">5\u003C\u002Fspan> * \u003Cspan class=\"hljs-number\">60\u003C\u002Fspan> * \u003Cspan class=\"hljs-number\">1000\u003C\u002Fspan>)\n}\n\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">stopVersionCheck\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (checkTimer) \u003Cspan class=\"hljs-built_in\">clearInterval\u003C\u002Fspan>(checkTimer)\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"137\" id=\"智能检测时机优化\">智能检测时机优化\u003C\u002Fh3>\n\u003Cp data-line=\"139\">除了简单的轮询，还可以结合多种触发条件实现智能检测：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"141\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F 空闲检测 + 页面可见性变化\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">initSmartVersionCheck\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F 利用 requestIdleCallback 在浏览器空闲时执行检测\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (\u003Cspan class=\"hljs-string\">&#x27;requestIdleCallback&#x27;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan> \u003Cspan class=\"hljs-variable language_\">window\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-title function_\">requestIdleCallback\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">checkVersion\u003C\u002Fspan>())\n  } \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-built_in\">setTimeout\u003C\u002Fspan>(checkVersion, \u003Cspan class=\"hljs-number\">2000\u003C\u002Fspan>) \u003Cspan class=\"hljs-comment\">\u002F\u002F 降级方案\u003C\u002Fspan>\n  }\n  \n  \u003Cspan class=\"hljs-comment\">\u002F\u002F 页面从后台切回前台时触发检测\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">addEventListener\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;visibilitychange&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!\u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">hidden\u003C\u002Fspan>) {\n      \u003Cspan class=\"hljs-title function_\">checkVersion\u003C\u002Fspan>()\n    }\n  })\n  \n  \u003Cspan class=\"hljs-comment\">\u002F\u002F 常规轮询作为保底方案\u003C\u002Fspan>\n  checkTimer = \u003Cspan class=\"hljs-built_in\">setInterval\u003C\u002Fspan>(checkVersion, \u003Cspan class=\"hljs-number\">5\u003C\u002Fspan> * \u003Cspan class=\"hljs-number\">60\u003C\u002Fspan> * \u003Cspan class=\"hljs-number\">1000\u003C\u002Fspan>)\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"163\">这种方式可以减少不必要的请求，提升性能。\u003C\u002Fp>\n\u003Ch2 data-line=\"165\" id=\"4. 更新提示与用户体验\">4. 更新提示与用户体验\u003C\u002Fh2>\n\u003Ch3 data-line=\"167\" id=\"优雅的更新提示\">优雅的更新提示\u003C\u002Fh3>\n\u003Cp data-line=\"169\">检测到更新后，应通过友好的方式提示用户：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"171\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">vue\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-vue\" language=vue>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"language-xml\">\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;updateAvailable&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;update-notification&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;notification-content&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">h3\u003C\u002Fspan>&gt;\u003C\u002Fspan>发现新版本\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">h3\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>系统已更新，是否立即刷新以获取最新功能？\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;actions&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;refreshNow&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;btn-primary&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>立即更新\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;remindLater&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;btn-secondary&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>稍后提醒\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">setup\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"language-javascript\">\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { ref } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;vue&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { \u003Cspan class=\"hljs-title class_\">ElMessageBox\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;element-plus&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> updateAvailable = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>)\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">handleUpdateDetected\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">newVersion\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-title class_\">ElMessageBox\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">confirm\u003C\u002Fspan>(\n    \u003Cspan class=\"hljs-string\">&#x27;检测到新版本，包含性能优化和修复，是否立即更新？&#x27;\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">&#x27;版本更新&#x27;\u003C\u002Fspan>,\n    {\n      \u003Cspan class=\"hljs-attr\">confirmButtonText\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;立即更新&#x27;\u003C\u002Fspan>,\n      \u003Cspan class=\"hljs-attr\">cancelButtonText\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;稍后更新&#x27;\u003C\u002Fspan>,\n      \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;info&#x27;\u003C\u002Fspan>\n    }\n  ).\u003Cspan class=\"hljs-title function_\">then\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 保存新版本号并刷新\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-variable language_\">localStorage\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">setItem\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;app-version&#x27;\u003C\u002Fspan>, newVersion)\n    \u003Cspan class=\"hljs-variable language_\">window\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">location\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">reload\u003C\u002Fspan>()\n  }).\u003Cspan class=\"hljs-title function_\">catch\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 用户选择稍后更新，可以设置定时提醒\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-built_in\">setTimeout\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">handleUpdateDetected\u003C\u002Fspan>(newVersion), \u003Cspan class=\"hljs-number\">10\u003C\u002Fspan> * \u003Cspan class=\"hljs-number\">60\u003C\u002Fspan> * \u003Cspan class=\"hljs-number\">1000\u003C\u002Fspan>) \u003Cspan class=\"hljs-comment\">\u002F\u002F 10分钟后再次提醒\u003C\u002Fspan>\n  })\n}\n\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"212\" id=\"处理用户重要操作\">处理用户重要操作\u003C\u002Fh3>\n\u003Cp data-line=\"214\">在触发更新前，应检查用户当前状态，避免中断重要操作：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"216\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">checkUserStatus\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F 检查是否有未保存的表单\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> hasUnsavedChanges = \u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">querySelector\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;form[data-unsaved]&#x27;\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (hasUnsavedChanges) {\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">confirm\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;系统有更新，但您有未保存的更改。是否先保存再更新？&#x27;\u003C\u002Fspan>)\n  }\n  \n  \u003Cspan class=\"hljs-comment\">\u002F\u002F 检查是否在重要流程中\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> inImportantProcess = \u003Cspan class=\"hljs-variable language_\">sessionStorage\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">getItem\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;current_workflow&#x27;\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (inImportantProcess) {\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\n  }\n  \n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>\n}\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">safeRefresh\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (\u003Cspan class=\"hljs-title function_\">checkUserStatus\u003C\u002Fspan>()) {\n    \u003Cspan class=\"hljs-variable language_\">window\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">location\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">reload\u003C\u002Fspan>()\n  } \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 延迟更新，等待合适时机\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-built_in\">setTimeout\u003C\u002Fspan>(safeRefresh, \u003Cspan class=\"hljs-number\">60000\u003C\u002Fspan>) \u003Cspan class=\"hljs-comment\">\u002F\u002F 1分钟后重试\u003C\u002Fspan>\n  }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"243\" id=\"5. 基于 Service Worker 的高级缓存管理\">5. 基于 Service Worker 的高级缓存管理\u003C\u002Fh2>\n\u003Cp data-line=\"245\">对于 Progressive Web App (PWA)，可以使用 Service Worker 实现更精细的缓存控制：\u003C\u002Fp>\n\u003Ch3 data-line=\"247\" id=\"Service Worker 注册与更新\">Service Worker 注册与更新\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"249\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F main.js\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { \u003Cspan class=\"hljs-title class_\">Workbox\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;workbox-window&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (\u003Cspan class=\"hljs-string\">&#x27;serviceWorker&#x27;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan> navigator) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> wb = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Workbox\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;\u002Fsw.js&#x27;\u003C\u002Fspan>)\n  \n  wb.\u003Cspan class=\"hljs-title function_\">addEventListener\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;waiting&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 当检测到新版本时提示用户\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-title function_\">showUpdateNotification\u003C\u002Fspan>()\n  })\n  \n  wb.\u003Cspan class=\"hljs-title function_\">register\u003C\u002Fspan>()\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"265\" id=\"Service Worker 缓存策略\">Service Worker 缓存策略\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"267\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F sw.js\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { precacheAndRoute } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;workbox-precaching&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F 预缓存关键资源\u003C\u002Fspan>\n\u003Cspan class=\"hljs-title function_\">precacheAndRoute\u003C\u002Fspan>(self.\u003Cspan class=\"hljs-property\">__WB_MANIFEST\u003C\u002Fspan>)\n\nself.\u003Cspan class=\"hljs-title function_\">addEventListener\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;install&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">event\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> {\n  self.\u003Cspan class=\"hljs-title function_\">skipWaiting\u003C\u002Fspan>() \u003Cspan class=\"hljs-comment\">\u002F\u002F 安装后立即激活\u003C\u002Fspan>\n})\n\nself.\u003Cspan class=\"hljs-title function_\">addEventListener\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;activate&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">event\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> {\n  clients.\u003Cspan class=\"hljs-title function_\">claim\u003C\u002Fspan>() \u003Cspan class=\"hljs-comment\">\u002F\u002F 立即控制所有页面\u003C\u002Fspan>\n})\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"283\">Service Worker 能够拦截并管理所有网络请求，支持离线缓存和精准的资源更新控制。\u003C\u002Fp>\n\u003Ch2 data-line=\"285\" id=\"6. 完整实现示例\">6. 完整实现示例\u003C\u002Fh2>\n\u003Ch3 data-line=\"287\" id=\"构建版本管理插件\">构建版本管理插件\u003C\u002Fh3>\n\u003Cp data-line=\"289\">创建一个 Vite 插件来自动化版本管理：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"291\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F plugins\u002Fversion-update-plugin.js\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> fs \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;fs&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">default\u003C\u002Fspan> (options = {}) =&gt; {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { versionFileName = \u003Cspan class=\"hljs-string\">&#x27;version.json&#x27;\u003C\u002Fspan> } = options\n  \n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-attr\">name\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;version-update&#x27;\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-title function_\">buildStart\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> version = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>().\u003Cspan class=\"hljs-title function_\">getTime\u003C\u002Fspan>()\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> content = \u003Cspan class=\"hljs-title class_\">JSON\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">stringify\u003C\u002Fspan>({ version })\n      \n      fs.\u003Cspan class=\"hljs-title function_\">writeFileSync\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">`public\u002F\u003Cspan class=\"hljs-subst\">${versionFileName}\u003C\u002Fspan>`\u003C\u002Fspan>, content)\n    }\n  }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"310\">在 \u003Ccode>vite.config.js\u003C\u002Fcode> 中使用插件：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"312\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> versionUpdatePlugin \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;.\u002Fplugins\u002Fversion-update-plugin&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">default\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">defineConfig\u003C\u002Fspan>({\n  \u003Cspan class=\"hljs-attr\">plugins\u003C\u002Fspan>: [\n    \u003Cspan class=\"hljs-title function_\">vue\u003C\u002Fspan>(),\n    \u003Cspan class=\"hljs-title function_\">versionUpdatePlugin\u003C\u002Fspan>()\n  ]\n})\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"323\" id=\"集成到 Vue 应用\">集成到 Vue 应用\u003C\u002Fh3>\n\u003Cp data-line=\"325\">在 Vue 应用的入口文件中集成版本检查：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"327\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F main.js\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { createApp } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;vue&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">App\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;.\u002FApp.vue&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { initVersionCheck } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;.\u002Futils\u002Fversion-check&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> app = \u003Cspan class=\"hljs-title function_\">createApp\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">App\u003C\u002Fspan>)\napp.\u003Cspan class=\"hljs-title function_\">mount\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;#app&#x27;\u003C\u002Fspan>)\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F 启动版本检测\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (process.\u003Cspan class=\"hljs-property\">env\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">NODE_ENV\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;production&#x27;\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-title function_\">initVersionCheck\u003C\u002Fspan>()\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"342\" id=\"7. 监控与错误处理\">7. 监控与错误处理\u003C\u002Fh2>\n\u003Ch3 data-line=\"344\" id=\"更新成功率监控\">更新成功率监控\u003C\u002Fh3>\n\u003Cp data-line=\"346\">实现更新过程的监控，便于发现问题：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"348\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">reportUpdateResult\u003C\u002Fspan> = \u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> (\u003Cspan class=\"hljs-params\">success, version, error = \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan>\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> payload = {\n    \u003Cspan class=\"hljs-attr\">timestamp\u003C\u002Fspan>: \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>().\u003Cspan class=\"hljs-title function_\">toISOString\u003C\u002Fspan>(),\n    version,\n    success,\n    \u003Cspan class=\"hljs-attr\">error\u003C\u002Fspan>: error?.\u003Cspan class=\"hljs-property\">message\u003C\u002Fspan> || \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-attr\">userAgent\u003C\u002Fspan>: navigator.\u003Cspan class=\"hljs-property\">userAgent\u003C\u002Fspan>\n  }\n  \n  \u003Cspan class=\"hljs-keyword\">try\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">fetch\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;\u002Fapi\u002Fupdate-logs&#x27;\u003C\u002Fspan>, {\n      \u003Cspan class=\"hljs-attr\">method\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;POST&#x27;\u003C\u002Fspan>,\n      \u003Cspan class=\"hljs-attr\">body\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">JSON\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">stringify\u003C\u002Fspan>(payload)\n    })\n  } \u003Cspan class=\"hljs-keyword\">catch\u003C\u002Fspan> (e) {\n    \u003Cspan class=\"hljs-variable language_\">console\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">error\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;更新日志上报失败:&#x27;\u003C\u002Fspan>, e)\n  }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"369\" id=\"错误处理与回退机制\">错误处理与回退机制\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"371\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">js\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-js\" language=js>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">checkVersion\u003C\u002Fspan> = \u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-keyword\">try\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> response = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">fetch\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;\u002Fversion.json?t=&#x27;\u003C\u002Fspan> + \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>())\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!response.\u003Cspan class=\"hljs-property\">ok\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">throw\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Error\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;版本检查请求失败&#x27;\u003C\u002Fspan>)\n    \n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> data = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> response.\u003Cspan class=\"hljs-title function_\">json\u003C\u002Fspan>()\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F ... 处理版本比较逻辑\u003C\u002Fspan>\n  } \u003Cspan class=\"hljs-keyword\">catch\u003C\u002Fspan> (error) {\n    \u003Cspan class=\"hljs-variable language_\">console\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">error\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;版本检查失败:&#x27;\u003C\u002Fspan>, error)\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 错误重试机制\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (retryCount &lt; \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>) {\n      retryCount++\n      \u003Cspan class=\"hljs-built_in\">setTimeout\u003C\u002Fspan>(checkVersion, \u003Cspan class=\"hljs-number\">30000\u003C\u002Fspan>) \u003Cspan class=\"hljs-comment\">\u002F\u002F 30秒后重试\u003C\u002Fspan>\n    }\n  }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"391\" id=\"总结\">总结\u003C\u002Fh2>\n\u003Cp data-line=\"393\">Vue3 应用版本更新自动加载的最新内容需要构建优化、缓存策略与版本控制的有机结合。核心要点包括：\u003C\u002Fp>\n\u003Col data-line=\"395\">\n\u003Cli data-line=\"395\">\u003Cstrong>构建阶段\u003C\u002Fstrong>：使用哈希命名确保内容变化导致URL变化\u003C\u002Fli>\n\u003Cli data-line=\"396\">\u003Cstrong>缓存策略\u003C\u002Fstrong>：HTML文件不缓存，静态资源长期缓存\u003C\u002Fli>\n\u003Cli data-line=\"397\">\u003Cstrong>版本检测\u003C\u002Fstrong>：轮询 + 智能触发，平衡实时性与性能\u003C\u002Fli>\n\u003Cli data-line=\"398\">\u003Cstrong>用户体验\u003C\u002Fstrong>：友好提示，避免中断用户操作\u003C\u002Fli>\n\u003Cli data-line=\"399\">\u003Cstrong>监控保障\u003C\u002Fstrong>：完善错误处理和上报机制\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp data-line=\"401\">通过上述方案，可以确保用户在版本更新后能及时获取最新内容，同时保持优秀的性能和用户体验。实际项目中可根据具体需求选择适合的方案组合，如简单项目可使用基本轮询检测，复杂PWA项目可结合Service Worker实现更精细控制。\u003C\u002Fp>\n",7286,24,"0","",2,0,117,"&#x,&gt,更新,const,版本","本文将详细介绍如何在 Vue3 应用中实现版本更新后浏览器自动加载最新内容的完整方案，涵盖构建优化、缓存策略与版本控制的各个方面。\n1. 构建工具配置：静态资源哈希命名\n在 Vue3 项目中，使用 Vite 或 Webpack 等构建工具为静态资源生成带哈希值的文件名是版本控制的基础。当文件内容变化时，哈希值会更新，浏览器会因 URL 不同而重新加载资源。","2026-01-05 14:11:51"]