diff --git a/.gitignore b/.gitignore index 997223d..17ab75f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store node_modules /dist +/dev-dist # local env files .env.local @@ -21,3 +22,4 @@ pnpm-debug.log* *.njsproj *.sln *.sw? + diff --git a/dev-dist/sw.js b/dev-dist/sw.js index ebc5fa9..48d0c19 100644 --- a/dev-dist/sw.js +++ b/dev-dist/sw.js @@ -67,8 +67,9 @@ if (!self.define) { }); }; } -define(['./workbox-86c9b217'], (function (workbox) { 'use strict'; +define(['./workbox-5ea419d9'], (function (workbox) { 'use strict'; + importScripts("sw-cache-manager.js"); self.skipWaiting(); workbox.clientsClaim(); @@ -81,12 +82,62 @@ define(['./workbox-86c9b217'], (function (workbox) { 'use strict'; "url": "suppress-warnings.js", "revision": "d41d8cd98f00b204e9800998ecf8427e" }, { - "url": "index.html", - "revision": "0.6m682f8mvag" + "url": "/", + "revision": "0.0lkakoc2in8" }], {}); workbox.cleanupOutdatedCaches(); - workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { + workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("/"), { allowlist: [/^\/$/] })); + workbox.registerRoute(/\.(?:js)$/i, new workbox.StaleWhileRevalidate({ + "cacheName": "js-cache", + plugins: [new workbox.ExpirationPlugin({ + maxEntries: 100, + maxAgeSeconds: 604800 + })] + }), 'GET'); + workbox.registerRoute(/\.(?:css)$/i, new workbox.StaleWhileRevalidate({ + "cacheName": "css-cache", + plugins: [new workbox.ExpirationPlugin({ + maxEntries: 50, + maxAgeSeconds: 604800 + })] + }), 'GET'); + workbox.registerRoute(/\.(?:html)$/i, new workbox.NetworkFirst({ + "cacheName": "html-cache", + plugins: [new workbox.ExpirationPlugin({ + maxEntries: 20, + maxAgeSeconds: 86400 + })] + }), 'GET'); + workbox.registerRoute(/\.(?:png|jpg|jpeg|svg|gif)$/i, new workbox.StaleWhileRevalidate({ + "cacheName": "images-cache", + plugins: [new workbox.ExpirationPlugin({ + maxEntries: 50, + maxAgeSeconds: 2592000 + })] + }), 'GET'); + workbox.registerRoute(/\/cdn-cgi\/.*/i, new workbox.NetworkFirst({ + "cacheName": "cdn-cgi-cache", + "networkTimeoutSeconds": 10, + plugins: [new workbox.ExpirationPlugin({ + maxEntries: 50, + maxAgeSeconds: 86400 + })] + }), 'GET'); + workbox.registerRoute(({ + url + }) => { + return url.origin !== self.location.origin; + }, new workbox.NetworkFirst({ + "cacheName": "external-resources", + "networkTimeoutSeconds": 10, + plugins: [new workbox.ExpirationPlugin({ + maxEntries: 100, + maxAgeSeconds: 86400 + }), new workbox.CacheableResponsePlugin({ + statuses: [0, 200] + })] + }), 'GET'); })); diff --git a/public/image/apple-touch-icon-180x180.png b/public/image/apple-touch-icon-180x180.png index d3d92c0..f70f541 100644 Binary files a/public/image/apple-touch-icon-180x180.png and b/public/image/apple-touch-icon-180x180.png differ diff --git a/public/image/favicon.ico b/public/image/favicon.ico index 8fe5a93..e0db255 100644 Binary files a/public/image/favicon.ico and b/public/image/favicon.ico differ diff --git a/public/image/logo.svg b/public/image/logo.svg index a33474c..f11a8e5 100644 --- a/public/image/logo.svg +++ b/public/image/logo.svg @@ -1,23 +1,13 @@ + - - + - + - - - - - - - - - - diff --git a/public/image/maskable-icon-512x512.png b/public/image/maskable-icon-512x512.png index e1d0c88..049d814 100644 Binary files a/public/image/maskable-icon-512x512.png and b/public/image/maskable-icon-512x512.png differ diff --git a/public/image/pwa-192x192.png b/public/image/pwa-192x192.png index a5a5fc3..c858a69 100644 Binary files a/public/image/pwa-192x192.png and b/public/image/pwa-192x192.png differ diff --git a/public/image/pwa-512x512.png b/public/image/pwa-512x512.png index 73730af..5452d3a 100644 Binary files a/public/image/pwa-512x512.png and b/public/image/pwa-512x512.png differ diff --git a/public/image/pwa-64x64.png b/public/image/pwa-64x64.png index 9e2f0d5..fcd940c 100644 Binary files a/public/image/pwa-64x64.png and b/public/image/pwa-64x64.png differ diff --git a/public/sw-cache-manager.js b/public/sw-cache-manager.js new file mode 100644 index 0000000..8d67967 --- /dev/null +++ b/public/sw-cache-manager.js @@ -0,0 +1,44 @@ +// 添加缓存管理消息处理 +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'CACHE_KEYS') { + // 获取所有缓存键 + caches.keys().then((cacheNames) => { + event.ports[0].postMessage({ cacheNames }); + }); + } else if (event.data && event.data.type === 'CACHE_CONTENT') { + // 获取特定缓存的内容 + const cacheName = event.data.cacheName; + caches.open(cacheName).then((cache) => { + cache.keys().then((requests) => { + const urls = requests.map(request => request.url); + event.ports[0].postMessage({ cacheName, urls }); + }); + }); + } else if (event.data && event.data.type === 'CLEAR_CACHE') { + // 清除特定缓存 + const cacheName = event.data.cacheName; + caches.delete(cacheName).then((success) => { + event.ports[0].postMessage({ success, cacheName }); + }); + } else if (event.data && event.data.type === 'CLEAR_URL') { + // 清除特定URL的缓存 + const cacheName = event.data.cacheName; + const url = event.data.url; + caches.open(cacheName).then((cache) => { + cache.delete(url).then((success) => { + event.ports[0].postMessage({ success, cacheName, url }); + }); + }); + } else if (event.data && event.data.type === 'CLEAR_ALL_CACHES') { + // 清除所有缓存 + caches.keys().then((cacheNames) => { + Promise.all( + cacheNames.map(name => caches.delete(name)) + ).then(() => { + event.ports[0].postMessage({ success: true }); + }); + }); + } +}); + +console.log('Cache Manager extension loaded'); \ No newline at end of file diff --git a/src/components/CacheManager.vue b/src/components/CacheManager.vue new file mode 100644 index 0000000..2143d78 --- /dev/null +++ b/src/components/CacheManager.vue @@ -0,0 +1,238 @@ + + + \ No newline at end of file diff --git a/src/components/settings/cards/DataProviderSettingsCard.vue b/src/components/settings/cards/DataProviderSettingsCard.vue index 7a2029a..d05c433 100644 --- a/src/components/settings/cards/DataProviderSettingsCard.vue +++ b/src/components/settings/cards/DataProviderSettingsCard.vue @@ -9,11 +9,7 @@ 检查服务器连接 @@ -27,9 +23,7 @@ 清除数据库缓存 - 这将清除所有IndexedDB中的数据 + 这将清除所有IndexedDB中的数据 + + + 查看本地缓存 + + @@ -55,12 +60,8 @@ {{ confirmMessage }} - 取消 - 确认 + 取消 + 确认 diff --git a/src/pages/CacheManagement.vue b/src/pages/CacheManagement.vue new file mode 100644 index 0000000..132d0af --- /dev/null +++ b/src/pages/CacheManagement.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/src/sw.js b/src/sw.js new file mode 100644 index 0000000..d1fddb4 --- /dev/null +++ b/src/sw.js @@ -0,0 +1,141 @@ +import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching' +import { registerRoute, setCatchHandler } from 'workbox-routing' +import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies' +import { ExpirationPlugin } from 'workbox-expiration' +import { CacheableResponsePlugin } from 'workbox-cacheable-response' + +// 使用 self.__WB_MANIFEST 是 workbox 的一个特殊变量,会被实际的预缓存清单替换 +precacheAndRoute(self.__WB_MANIFEST) +cleanupOutdatedCaches() + +// JS 文件缓存 +registerRoute( + /\.(?:js)$/i, + new StaleWhileRevalidate({ + cacheName: 'js-cache', + plugins: [ + new ExpirationPlugin({ + maxEntries: 100, + maxAgeSeconds: 60 * 60 * 24 * 7 // 7 天 + }) + ] + }) +) + +// CSS 文件缓存 +registerRoute( + /\.(?:css)$/i, + new StaleWhileRevalidate({ + cacheName: 'css-cache', + plugins: [ + new ExpirationPlugin({ + maxEntries: 50, + maxAgeSeconds: 60 * 60 * 24 * 7 // 7 天 + }) + ] + }) +) + +// HTML 文件缓存 +registerRoute( + /\.(?:html)$/i, + new NetworkFirst({ + cacheName: 'html-cache', + plugins: [ + new ExpirationPlugin({ + maxEntries: 20, + maxAgeSeconds: 60 * 60 * 24 // 1 天 + }) + ] + }) +) + +// 图片缓存 +registerRoute( + /\.(?:png|jpg|jpeg|svg|gif)$/i, + new StaleWhileRevalidate({ + cacheName: 'images-cache', + plugins: [ + new ExpirationPlugin({ + maxEntries: 50, + maxAgeSeconds: 60 * 60 * 24 * 30 // 30 天 + }) + ] + }) +) + +// CDN 缓存 +registerRoute( + /\/cdn-cgi\/.*/i, + new NetworkFirst({ + cacheName: 'cdn-cgi-cache', + plugins: [ + new ExpirationPlugin({ + maxEntries: 50, + maxAgeSeconds: 60 * 60 * 24 // 1 天 + }) + ], + networkTimeoutSeconds: 10 + }) +) + +// 外部资源缓存 +registerRoute( + ({ url }) => url.origin !== self.location.origin, + new NetworkFirst({ + cacheName: 'external-resources', + plugins: [ + new ExpirationPlugin({ + maxEntries: 100, + maxAgeSeconds: 60 * 60 * 24 // 1 天 + }), + new CacheableResponsePlugin({ + statuses: [0, 200] + }) + ], + networkTimeoutSeconds: 10 + }) +) + +// 添加缓存管理消息处理 +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'CACHE_KEYS') { + // 获取所有缓存键 + caches.keys().then((cacheNames) => { + event.ports[0].postMessage({ cacheNames }); + }); + } else if (event.data && event.data.type === 'CACHE_CONTENT') { + // 获取特定缓存的内容 + const cacheName = event.data.cacheName; + caches.open(cacheName).then((cache) => { + cache.keys().then((requests) => { + const urls = requests.map(request => request.url); + event.ports[0].postMessage({ cacheName, urls }); + }); + }); + } else if (event.data && event.data.type === 'CLEAR_CACHE') { + // 清除特定缓存 + const cacheName = event.data.cacheName; + caches.delete(cacheName).then((success) => { + event.ports[0].postMessage({ success, cacheName }); + }); + } else if (event.data && event.data.type === 'CLEAR_URL') { + // 清除特定URL的缓存 + const cacheName = event.data.cacheName; + const url = event.data.url; + caches.open(cacheName).then((cache) => { + cache.delete(url).then((success) => { + event.ports[0].postMessage({ success, cacheName, url }); + }); + }); + } else if (event.data && event.data.type === 'CLEAR_ALL_CACHES') { + // 清除所有缓存 + caches.keys().then((cacheNames) => { + Promise.all( + cacheNames.map(name => caches.delete(name)) + ).then(() => { + event.ports[0].postMessage({ success: true }); + }); + }); + } +}); \ No newline at end of file diff --git a/vite.config.mjs b/vite.config.mjs index 30c5d86..2570a60 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -19,15 +19,102 @@ export default defineConfig({ Layouts(), Vue({ template: { transformAssetUrls } - }), VitePWA({ + }), + VitePWA({ registerType: 'autoUpdate', devOptions: { - navigateFallback: '/index.html', // 离线支持(navigateFallback) + navigateFallback: '/', enabled: true, - suppressWarnings: true, // 是否抑制 Workbox 的警告 + suppressWarnings: true, + }, + + lang: 'zh-CN', + injectRegister: 'auto', + strategies: 'generateSW', + workbox: { + globPatterns: ['**/*.{js,css,html,png,svg,jpg,jpeg,gif,ico,woff,woff2,ttf,eot}'], + navigateFallback: '/', + runtimeCaching: [ + { + urlPattern: /\.(?:js)$/i, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'js-cache', + expiration: { + maxEntries: 100, + maxAgeSeconds: 60 * 60 * 24 * 7 // 7 天 + } + } + }, + { + urlPattern: /\.(?:css)$/i, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'css-cache', + expiration: { + maxEntries: 50, + maxAgeSeconds: 60 * 60 * 24 * 7 // 7 天 + } + } + }, + { + urlPattern: /\.(?:html)$/i, + handler: 'NetworkFirst', + options: { + cacheName: 'html-cache', + expiration: { + maxEntries: 20, + maxAgeSeconds: 60 * 60 * 24 // 1 天 + } + } + }, + { + urlPattern: /\.(?:png|jpg|jpeg|svg|gif)$/i, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'images-cache', + expiration: { + maxEntries: 50, + maxAgeSeconds: 60 * 60 * 24 * 30 // 30 天 + } + } + }, + { + urlPattern: /\/cdn-cgi\/.*/i, + handler: 'NetworkFirst', + options: { + cacheName: 'cdn-cgi-cache', + expiration: { + maxEntries: 50, + maxAgeSeconds: 60 * 60 * 24 // 1 天 + }, + networkTimeoutSeconds: 10 + } + }, + { + // 匹配除了当前域名以外的所有请求 + urlPattern: ({ url }) => { + return url.origin !== self.location.origin; + }, + handler: 'NetworkFirst', + options: { + cacheName: 'external-resources', + expiration: { + maxEntries: 100, + maxAgeSeconds: 60 * 60 * 24 // 1 天 + }, + networkTimeoutSeconds: 10, + cacheableResponse: { + statuses: [0, 200] + } + } + } + ], + additionalManifestEntries: [], + clientsClaim: true, + skipWaiting: true, + importScripts: ['sw-cache-manager.js'] }, - - lang: 'zh-CN', manifest: { name: 'Classworks作业板', short_name: 'Classworks', @@ -39,18 +126,6 @@ export default defineConfig({ edge_side_panel: { default_path: '/', }, - workbox: { - globPatterns: ['**/*.{js,css,html,png,svg}'], - navigateFallback: '/index.html', // 离线支持(navigateFallback) - runtimeCaching: [ - //所有资源都使用网络优先 - { - urlPattern: /./, - handler: 'NetworkFirst', - }, - ], - }, - icons: [ { src: '/image/pwa-64x64.png', @@ -74,7 +149,6 @@ export default defineConfig({ purpose: 'maskable' } ], - shortcuts: [ { name: '随机点名',