2026-01-16 11:15:44 +08:00
|
|
|
|
const VERSION = '1.0.0' // Build Time: 2026-01-07 15:59:36
|
|
|
|
|
|
const CACHE_NAME = `emailbill-${VERSION}`
|
2025-12-25 14:15:43 +08:00
|
|
|
|
const urlsToCache = [
|
|
|
|
|
|
'/',
|
|
|
|
|
|
'/index.html',
|
|
|
|
|
|
'/favicon.ico',
|
|
|
|
|
|
'/manifest.json'
|
2026-01-16 11:15:44 +08:00
|
|
|
|
]
|
2025-12-25 14:15:43 +08:00
|
|
|
|
|
|
|
|
|
|
// 安装 Service Worker
|
|
|
|
|
|
self.addEventListener('install', (event) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
console.log('[Service Worker] 安装中...')
|
2025-12-25 14:15:43 +08:00
|
|
|
|
event.waitUntil(
|
|
|
|
|
|
caches.open(CACHE_NAME)
|
|
|
|
|
|
.then((cache) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
console.log('[Service Worker] 缓存文件')
|
|
|
|
|
|
return cache.addAll(urlsToCache)
|
2025-12-25 14:15:43 +08:00
|
|
|
|
})
|
2026-01-16 11:15:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
})
|
2025-12-25 14:15:43 +08:00
|
|
|
|
|
2026-01-02 18:58:07 +08:00
|
|
|
|
// 监听跳过等待消息
|
|
|
|
|
|
self.addEventListener('message', (event) => {
|
|
|
|
|
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
self.skipWaiting()
|
2026-01-02 18:58:07 +08:00
|
|
|
|
}
|
2026-01-16 11:15:44 +08:00
|
|
|
|
})
|
2026-01-02 18:58:07 +08:00
|
|
|
|
|
2025-12-25 14:15:43 +08:00
|
|
|
|
// 激活 Service Worker
|
|
|
|
|
|
self.addEventListener('activate', (event) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
console.log('[Service Worker] 激活中...')
|
2025-12-25 14:15:43 +08:00
|
|
|
|
event.waitUntil(
|
|
|
|
|
|
caches.keys().then((cacheNames) => {
|
|
|
|
|
|
return Promise.all(
|
|
|
|
|
|
cacheNames.map((cacheName) => {
|
|
|
|
|
|
if (cacheName !== CACHE_NAME) {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
console.log('[Service Worker] 删除旧缓存:', cacheName)
|
|
|
|
|
|
return caches.delete(cacheName)
|
2025-12-25 14:15:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-01-16 11:15:44 +08:00
|
|
|
|
)
|
2025-12-25 14:15:43 +08:00
|
|
|
|
}).then(() => self.clients.claim())
|
2026-01-16 11:15:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
})
|
2025-12-25 14:15:43 +08:00
|
|
|
|
|
|
|
|
|
|
// 拦截请求
|
|
|
|
|
|
self.addEventListener('fetch', (event) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
const { request } = event
|
|
|
|
|
|
const url = new URL(request.url)
|
2025-12-25 14:15:43 +08:00
|
|
|
|
|
|
|
|
|
|
// 跳过跨域请求
|
|
|
|
|
|
if (url.origin !== location.origin) {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
return
|
2025-12-25 14:15:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// API请求使用网络优先策略
|
|
|
|
|
|
if (url.pathname.startsWith('/api/')) {
|
|
|
|
|
|
event.respondWith(
|
|
|
|
|
|
fetch(request)
|
|
|
|
|
|
.then((response) => {
|
2026-01-07 16:07:56 +08:00
|
|
|
|
// 只针对成功的GET请求进行缓存
|
|
|
|
|
|
if (request.method === 'GET' && response.status === 200) {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
const responseClone = response.clone()
|
2026-01-07 16:07:56 +08:00
|
|
|
|
caches.open(CACHE_NAME).then((cache) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
cache.put(request, responseClone)
|
|
|
|
|
|
})
|
2026-01-07 16:07:56 +08:00
|
|
|
|
}
|
2026-01-16 11:15:44 +08:00
|
|
|
|
return response
|
2026-01-07 16:07:56 +08:00
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
// 网络失败时尝试从缓存获取
|
2026-01-16 11:15:44 +08:00
|
|
|
|
return caches.match(request)
|
2026-01-07 16:07:56 +08:00
|
|
|
|
})
|
2026-01-16 11:15:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
return
|
2026-01-07 16:07:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 页面请求使用网络优先策略,确保能获取到最新的 index.html
|
|
|
|
|
|
if (request.mode === 'navigate') {
|
|
|
|
|
|
event.respondWith(
|
|
|
|
|
|
fetch(request)
|
|
|
|
|
|
.then((response) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
const responseClone = response.clone()
|
2025-12-25 14:15:43 +08:00
|
|
|
|
caches.open(CACHE_NAME).then((cache) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
cache.put(request, responseClone)
|
|
|
|
|
|
})
|
|
|
|
|
|
return response
|
2025-12-25 14:15:43 +08:00
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
return caches.match('/index.html') || caches.match(request)
|
2025-12-25 14:15:43 +08:00
|
|
|
|
})
|
2026-01-16 11:15:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
return
|
2025-12-25 14:15:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-07 16:07:56 +08:00
|
|
|
|
// 其他静态资源使用缓存优先策略
|
2025-12-25 14:15:43 +08:00
|
|
|
|
event.respondWith(
|
|
|
|
|
|
caches.match(request)
|
|
|
|
|
|
.then((response) => {
|
|
|
|
|
|
if (response) {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
return response
|
2025-12-25 14:15:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
return fetch(request).then((response) => {
|
|
|
|
|
|
// 检查是否是有效响应
|
|
|
|
|
|
if (!response || response.status !== 200 || response.type !== 'basic') {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
return response
|
2025-12-25 14:15:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 11:15:44 +08:00
|
|
|
|
const responseClone = response.clone()
|
2025-12-25 14:15:43 +08:00
|
|
|
|
caches.open(CACHE_NAME).then((cache) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
cache.put(request, responseClone)
|
|
|
|
|
|
})
|
2025-12-25 14:15:43 +08:00
|
|
|
|
|
2026-01-16 11:15:44 +08:00
|
|
|
|
return response
|
|
|
|
|
|
})
|
2025-12-25 14:15:43 +08:00
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
// 返回离线页面或默认内容
|
|
|
|
|
|
if (request.destination === 'document') {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
return caches.match('/index.html')
|
2025-12-25 14:15:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-01-16 11:15:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
})
|
2025-12-25 14:15:43 +08:00
|
|
|
|
|
|
|
|
|
|
// 后台同步
|
|
|
|
|
|
self.addEventListener('sync', (event) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
console.log('[Service Worker] 后台同步:', event.tag)
|
2025-12-25 14:15:43 +08:00
|
|
|
|
if (event.tag === 'sync-data') {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
event.waitUntil(syncData())
|
2025-12-25 14:15:43 +08:00
|
|
|
|
}
|
2026-01-16 11:15:44 +08:00
|
|
|
|
})
|
2025-12-25 14:15:43 +08:00
|
|
|
|
|
|
|
|
|
|
// 推送通知
|
|
|
|
|
|
self.addEventListener('push', (event) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
console.log('[Service Worker] 收到推送消息')
|
|
|
|
|
|
let data = { title: '账单管理', body: '您有新的消息', url: '/', icon: '/icons/icon-192x192.png' }
|
|
|
|
|
|
|
2026-01-02 12:25:44 +08:00
|
|
|
|
if (event.data) {
|
|
|
|
|
|
try {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
const json = event.data.json()
|
|
|
|
|
|
data = { ...data, ...json }
|
2026-01-07 14:33:30 +08:00
|
|
|
|
} catch {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
data.body = event.data.text()
|
2026-01-02 12:25:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 14:15:43 +08:00
|
|
|
|
const options = {
|
2026-01-02 12:25:44 +08:00
|
|
|
|
body: data.body,
|
|
|
|
|
|
icon: data.icon,
|
2025-12-25 14:15:43 +08:00
|
|
|
|
badge: '/icons/icon-72x72.png',
|
|
|
|
|
|
vibrate: [200, 100, 200],
|
|
|
|
|
|
tag: 'emailbill-notification',
|
2026-01-02 12:25:44 +08:00
|
|
|
|
requireInteraction: false,
|
|
|
|
|
|
data: { url: data.url }
|
2026-01-16 11:15:44 +08:00
|
|
|
|
}
|
2025-12-25 14:15:43 +08:00
|
|
|
|
|
|
|
|
|
|
event.waitUntil(
|
2026-01-02 12:25:44 +08:00
|
|
|
|
self.registration.showNotification(data.title, options)
|
2026-01-16 11:15:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
})
|
2025-12-25 14:15:43 +08:00
|
|
|
|
|
|
|
|
|
|
// 通知点击
|
|
|
|
|
|
self.addEventListener('notificationclick', (event) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
console.log('[Service Worker] 通知被点击')
|
|
|
|
|
|
event.notification.close()
|
|
|
|
|
|
const urlToOpen = event.notification.data?.url || '/'
|
2025-12-25 14:15:43 +08:00
|
|
|
|
event.waitUntil(
|
2026-01-02 12:25:44 +08:00
|
|
|
|
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windowClients) => {
|
|
|
|
|
|
// 如果已经打开了该 URL,则聚焦
|
|
|
|
|
|
for (let i = 0; i < windowClients.length; i++) {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
const client = windowClients[i]
|
2026-01-02 12:25:44 +08:00
|
|
|
|
if (client.url === urlToOpen && 'focus' in client) {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
return client.focus()
|
2026-01-02 12:25:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 否则打开新窗口
|
|
|
|
|
|
if (clients.openWindow) {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
return clients.openWindow(urlToOpen)
|
2026-01-02 12:25:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-01-16 11:15:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
})
|
2025-12-25 14:15:43 +08:00
|
|
|
|
|
|
|
|
|
|
// 数据同步函数
|
2026-01-16 11:15:44 +08:00
|
|
|
|
async function syncData () {
|
2025-12-25 14:15:43 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 这里添加需要同步的逻辑
|
2026-01-16 11:15:44 +08:00
|
|
|
|
console.log('[Service Worker] 执行数据同步')
|
2025-12-25 14:15:43 +08:00
|
|
|
|
} catch (error) {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
console.error('[Service Worker] 同步失败:', error)
|
2025-12-25 14:15:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|