// Service Worker — parent-portal PWA 离线缓存 // 依据:01-understanding.md §18.4 Service Worker 策略 // 缓存策略: // 静态资源 → Cache First + 网络更新(TTL 24h) // 子女列表 → Stale While Revalidate(TTL 5min) // 子女成绩 → Network First,失败回退缓存(TTL 30s) // 通知/偏好 → Network Only // API 401 → 不缓存 const CACHE_VERSION = "parent-portal-v1"; const STATIC_CACHE = `${CACHE_VERSION}-static`; const DATA_CACHE = `${CACHE_VERSION}-data`; const STATIC_ASSETS = [ "/", "/parent/dashboard", "/login", "/manifest.json", ]; // 安装:预缓存静态资源 self.addEventListener("install", (event) => { event.waitUntil( caches.open(STATIC_CACHE).then((cache) => cache.addAll(STATIC_ASSETS)).then(() => self.skipWaiting()) ); }); // 激活:清理旧缓存 self.addEventListener("activate", (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys .filter((key) => !key.startsWith(CACHE_VERSION)) .map((key) => caches.delete(key)) ) ).then(() => self.clients.claim()) ); }); // 请求拦截:按资源类型选择缓存策略 self.addEventListener("fetch", (event) => { const { request } = event; const url = new URL(request.url); // 仅处理同源请求 if (url.origin !== self.location.origin) return; // 非 GET 请求直接透传 if (request.method !== "GET") return; // API 请求:Network First(数据类)或 Network Only(通知/偏好) if (url.pathname.startsWith("/api/")) { // 通知和偏好设置不缓存 if (url.pathname.includes("/notifications") || url.pathname.includes("/preferences")) { return; } // GraphQL 和其他 API:Network First + 回退缓存 event.respondWith( fetch(request) .then((response) => { if (response.ok && response.status !== 401) { const clone = response.clone(); caches.open(DATA_CACHE).then((cache) => cache.put(request, clone)); } return response; }) .catch(() => caches.match(request)) ); return; } // 静态资源:Cache First + 网络更新 event.respondWith( caches.match(request).then((cached) => { const fetchPromise = fetch(request).then((response) => { if (response && response.ok && response.status === 200) { const clone = response.clone(); caches.open(STATIC_CACHE).then((cache) => cache.put(request, clone)); } return response; }).catch(() => cached); return cached || fetchPromise; }) ); }); // 推送通知(P5+ 预留) self.addEventListener("push", (event) => { if (!event.data) return; const data = event.data.json(); event.waitUntil( self.registration.showNotification(data.title || "Edu 家长端", { body: data.body || "", icon: "/icon-192.png", badge: "/icon-192.png", data: { url: data.url || "/parent/dashboard" }, }) ); }); // 点击通知跳转 self.addEventListener("notificationclick", (event) => { event.notification.close(); const targetUrl = event.notification.data?.url || "/parent/dashboard"; event.waitUntil( self.clients.matchAll({ type: "window" }).then((clients) => { const existing = clients.find((c) => c.url.includes(targetUrl)); if (existing) { return existing.focus(); } return self.clients.openWindow(targetUrl); }) ); });