diff --git a/src/composables/useVersionChecker.ts b/src/composables/useVersionChecker.ts index b4574492..3ddb329f 100644 --- a/src/composables/useVersionChecker.ts +++ b/src/composables/useVersionChecker.ts @@ -1,17 +1,20 @@ -import { ref, computed, h } from 'vue' +import { ref, h } from 'vue' import { useToast } from 'vue-toastification' +import { Workbox } from 'workbox-window' import i18n from '@/plugins/i18n' import VersionUpdateToast from '@/components/toast/VersionUpdateToast.vue' // 全局状态 const currentVersion = ref(__APP_VERSION__) -let isListenerAdded = false -let notificationShowTime = 0 -const serverVersion = ref(null) -const versionChecked = ref(false) -const needsUpdate = computed(() => { - return serverVersion.value !== null && serverVersion.value !== currentVersion.value -}) +let isUpdateToastShown = false +let wb: Workbox | null = null + +/** + * 普通刷新页面 + */ +export const reloadPage = (): void => { + window.location.reload() +} /** * 刷新页面并添加时间戳 @@ -45,26 +48,38 @@ export const clearCachesAndServiceWorker = async (): Promise => { } } +/** + * 清除缓存并刷新 + */ +const clearCacheAndReload = async (): Promise => { + await clearCachesAndServiceWorker() + reloadWithTimestamp() +} + /** * 版本检查 Composable * * 功能: - * - 检查前端版本与服务端版本是否一致 - * - 检测到版本更新时清除缓存和 Service Worker + * - 使用 Workbox 监听 Service Worker 更新 + * - 检查浏览器版本与服务端版本是否一致 * - 显示持久化更新通知 */ export function useVersionChecker() { const toast = useToast() /** - * 显示版本更新通知(带刷新按钮) + * 显示版本更新通知 + * @param message 通知消息文本 + * @param refreshText 按钮文本,不传则不显示按钮 + * @param onRefresh 按钮点击事件 */ - const showUpdateNotification = (): void => { - // 使用自定义 Vue 组件作为 toast 内容,传递翻译后的文本作为 props + const showUpdateNotification = (message: string, refreshText?: string, onRefresh?: () => void): void => { + if (isUpdateToastShown) return + isUpdateToastShown = true const component = h(VersionUpdateToast, { - message: i18n.global.t('common.newVersionAvailable'), - refreshText: i18n.global.t('common.refresh'), - onRefresh: reloadWithTimestamp, + message, + refreshText, + onRefresh, }) toast.info(component, { @@ -72,105 +87,88 @@ export function useVersionChecker() { closeButton: false, closeOnClick: false, draggable: false, - toastClassName: 'version-update-toast-container', }) } + // 初始化 Workbox + if (!wb && 'serviceWorker' in navigator) { + wb = new Workbox('/service-worker.js') + + // Service Worker 激活事件 (install -> activate) + wb.addEventListener('activated', event => { + // 只有在更新时才显示通知 + if (event.isUpdate) { + console.log('[VersionChecker] Service Worker 更新已就绪,等待用户刷新') + + showUpdateNotification(i18n.global.t('common.swUpdateReady'), i18n.global.t('common.refresh'), reloadPage) + } + }) + + // 注册 Service Worker + wb.register() + } + /** * 检查版本并在需要时显示更新通知 * @param latestVersion 服务端返回的最新版本号 */ const checkVersion = async (latestVersion: string): Promise => { - // 如果已经检查过,则跳过 - if (versionChecked.value) { + // 如果已经显示过通知,说明已经检查过了 + if (isUpdateToastShown) return + + // 版本一致,无需操作 + if (latestVersion === currentVersion.value) { + console.log('[VersionChecker] 版本号一致,无需操作') return } - // 更新服务端版本 - serverVersion.value = latestVersion + console.log(`[VersionChecker] 检测到版本不一致: ${currentVersion.value} -> ${latestVersion}`) - // 执行版本不一致时的处理逻辑 - const handleVersionMismatch = async () => { - if (needsUpdate.value) { - versionChecked.value = true - console.log(`[VersionChecker] 检测到版本更新: ${currentVersion.value} -> ${latestVersion}`) - - // 清除缓存和 Service Worker - await clearCachesAndServiceWorker() - - // 显示持久化通知 - showUpdateNotification() - } - } - - // 优先尝试通过 Service Worker 检查更新 + // 尝试触发 Service Worker 更新检查 if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { - console.log('[VersionChecker] 正在请求 Service Worker 检查更新...') + try { + const registration = await navigator.serviceWorker.getRegistration() + if (registration) { + console.log('[VersionChecker] 触发 Service Worker 更新检查...') - const registration = await navigator.serviceWorker.getRegistration() + // 标记是否发现更新 + let updateFound = false + const onUpdateFound = () => { + updateFound = true + } - // 如果已经有等待中的更新,直接处理 - if (registration?.waiting) { - console.log('[VersionChecker] Service Worker 发现新版本,跳过版本号对比') - handleVersionMismatch() - return - } + // 监听 updatefound 事件 + registration.addEventListener('updatefound', onUpdateFound, { once: true }) - const messageChannel = new MessageChannel() + // 等待检查完成 + await registration.update() - messageChannel.port1.onmessage = event => { - if (event.data && event.data.type === 'SW_NO_UPDATE_DETECTED') { - console.log('[VersionChecker] Service Worker 报告无更新, 进行版本号检查...') - handleVersionMismatch() + // 检查是否有更新正在进行 + // 如果发现更新,或者正在安装/等待中,则直接返回(交由 SW activated 事件处理) + if (updateFound || registration.installing || registration.waiting) { + console.log('[VersionChecker] Service Worker 更新中...') + return + } + + console.log('[VersionChecker] SW 无更新,但版本号不一致,可能是缓存问题') } + } catch (error) { + console.log('[VersionChecker] Service Worker 更新检查失败:', error) + // 失败继续向下执行,显示通知 } - - navigator.serviceWorker.controller.postMessage({ type: 'CHECK_SW_UPDATE' }, [messageChannel.port2]) } else { - // 如果没有 Service Worker 控制,直接进行版本比较 - await handleVersionMismatch() + console.log('[VersionChecker] 无 Service Worker, 直接显示通知') } - } - // 监听 Service Worker 版本更新消息 - if (!isListenerAdded && 'serviceWorker' in navigator) { - navigator.serviceWorker.addEventListener('message', event => { - // 1. 发现新版本 -> 弹出通知 - if (event.data && event.data.type === 'SW_VERSION_DETECTED') { - console.log('[VersionChecker] 发现新版本:', event.data.version) - notificationShowTime = Date.now() - - const component = h(VersionUpdateToast, { - message: i18n.global.t('common.newVersionFound'), - }) - - toast.info(component, { - timeout: false, - hideProgressBar: true, - closeButton: false, - toastClassName: 'version-update-toast-container', - }) - } - // 2. 安装完成 -> 刷新页面 - else if (event.data && event.data.type === 'SW_RELOAD_PAGE') { - const elapsed = Date.now() - notificationShowTime - const delay = Math.max(0, 1500 - elapsed) - console.log(`[VersionChecker] 更新已安装, 延迟 ${delay}ms 后刷新...`) - setTimeout(() => { - reloadWithTimestamp() - }, delay) - } - }) - isListenerAdded = true + // 最终兜底:显示版本不一致通知(清除缓存) + showUpdateNotification( + i18n.global.t('common.versionMismatch'), + i18n.global.t('common.clearCache'), + clearCacheAndReload, + ) } return { - // 状态 - currentVersion: computed(() => currentVersion.value), - serverVersion: computed(() => serverVersion.value), - needsUpdate, - versionChecked: computed(() => versionChecked.value), - // 方法 checkVersion, } } diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index aaf49b12..5e2e8175 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -68,8 +68,9 @@ export default { status: 'Status', preset: 'Preset', refresh: 'Refresh', - newVersionAvailable: 'New version detected, please refresh the page to get the latest features', - newVersionFound: 'New version found, updating...', + swUpdateReady: 'New version is ready, please refresh the page to get the latest features', + versionMismatch: 'Browser cache version does not match server version, please try clearing cache', + clearCache: 'Clear Cache', }, mediaType: { movie: 'Movie', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index a8e9df96..cc01cdc2 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -68,8 +68,9 @@ export default { status: '状态', preset: '预设', refresh: '刷新', - newVersionAvailable: '检测到新版本,请刷新页面以获取最新功能', - newVersionFound: '发现新版本,正在更新...', + swUpdateReady: '新版本已就绪,请刷新页面以获取最新功能', + versionMismatch: '浏览器缓存版本与服务端版本不一致,请尝试清除缓存', + clearCache: '清除缓存', }, mediaType: { movie: '电影', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index abde346c..25bbf75b 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -68,8 +68,9 @@ export default { status: '狀態', preset: '預設', refresh: '刷新', - newVersionAvailable: '檢測到新版本,請刷新頁面以獲取最新功能', - newVersionFound: '發現新版本,正在更新...', + swUpdateReady: '新版本已就緒,請刷新頁面以獲取最新功能', + versionMismatch: '瀏覽器快取版本與伺服器版本不一致,請嘗試清除快取', + clearCache: '清除快取', }, mediaType: { movie: '電影', diff --git a/src/service-worker.ts b/src/service-worker.ts index ca7579d5..02fe04d1 100644 --- a/src/service-worker.ts +++ b/src/service-worker.ts @@ -23,15 +23,15 @@ cleanupOutdatedCaches() // 预缓存并路由 precacheAndRoute(self.__WB_MANIFEST) -// 变量记录是否为更新安装 +// 变量记录是否为更新安装(兼容旧版前端监听逻辑) let isUpdate = false -// 监听安装事件以检测更新 +// 监听安装事件 self.addEventListener('install', () => { // 强制等待中的 Service Worker 立即激活 self.skipWaiting() - // 检查是否是更新(即是否已经有激活的 Service Worker) + // 检查是否是更新(兼容旧版前端监听逻辑) if (self.registration.active) { isUpdate = true // 通知客户端发现新版本 @@ -56,7 +56,7 @@ self.addEventListener('activate', event => { // 清理旧版本的运行时缓存 await cleanupRuntimeCaches(true) - // 如果是更新,则通知客户端刷新页面 + // 如果是更新,则通知客户端刷新页面(兼容旧版前端监听逻辑) if (isUpdate) { const clients = await self.clients.matchAll({ type: 'window' }) clients.forEach(client => { @@ -492,20 +492,6 @@ self.addEventListener('message', function (event) { .catch(error => { event.ports[0]?.postMessage({ success: false, error: error instanceof Error ? error.message : String(error) }) }) - } else if (event.data && event.data.type === 'CHECK_SW_UPDATE') { - // 检查 Service Worker 更新 - self.registration - .update() - .then(() => { - // 如果没有正在安装或等待的 worker,说明没有检测到更新 - if (!self.registration.installing && !self.registration.waiting) { - event.ports[0]?.postMessage({ type: 'SW_NO_UPDATE_DETECTED' }) - } - }) - .catch(error => { - console.error('Failed to check for SW update:', error) - event.ports[0]?.postMessage({ type: 'SW_NO_UPDATE_DETECTED' }) - }) } else if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting() } diff --git a/src/styles/main.scss b/src/styles/main.scss index b7692a03..5f0be75f 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -11,36 +11,3 @@ @import 'vue-toastification/dist/index.css'; @import 'vue3-perfect-scrollbar/style.css'; @import '@vue-js-cron/vuetify/dist/vuetify.css'; - -/* 版本更新通知专用样式 */ -.version-update-toast-container { - min-width: unset !important; - width: fit-content !important; - - // 移动端适配:强制靠右并修正位置 - @media only screen and (width <= 600px) { - max-width: calc(100vw - 1rem) !important; - margin-inline: 0 0.5rem !important; - border-radius: 8px !important; - position: relative !important; - top: calc(100vh - 12rem) !important; - } -} - -// 使用 :has 选择器精准控制包含更新通知的容器 -.Vue-Toastification__container:has(.version-update-toast-container) { - @media only screen and (width <= 600px) { - top: auto !important; - bottom: 0 !important; - display: flex !important; - flex-direction: column !important; - align-items: flex-end !important; - padding-block-end: 4.5rem !important; - pointer-events: none; - - .version-update-toast-container { - pointer-events: auto; - margin-inline-end: 0.5rem !important; - } - } -}