本地缓存文件
2
.gitignore
vendored
@ -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?
|
||||
|
||||
|
@ -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');
|
||||
|
||||
}));
|
||||
|
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 526 B |
Before Width: | Height: | Size: 367 B After Width: | Height: | Size: 368 B |
@ -1,23 +1,13 @@
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256" viewBox="0 0 256 256" fill="none">
|
||||
<g clip-path="url(#clip-path-74_1)">
|
||||
<path fill="#FFFFFF" d="M0 256L256 256L256 0L0 0L0 256Z">
|
||||
</path>
|
||||
<rect x="0" y="0" width="256" height="128" fill="#D8C4A0" >
|
||||
</rect>
|
||||
<rect x="0" y="128" width="256" height="128" fill="#F5E0BB" >
|
||||
</rect>
|
||||
<path d="M28 228L128 128L228 128L128 228L28 228Z" fill-rule="evenodd" fill="#241A04" >
|
||||
<path d="M48 208L128 128L208 128L128 208L48 208Z" fill-rule="evenodd" fill="#241A04" >
|
||||
</path>
|
||||
<path d="M28 128L128 28L228 28L128 128L28 128Z" fill-rule="evenodd" fill="#52452A" >
|
||||
<path d="M48 128L128 48L208 48L128 128L48 128Z" fill-rule="evenodd" fill="#52452A" >
|
||||
</path>
|
||||
<g >
|
||||
<path fill="#000000" d="M-3049.01 2467.94L-3043.48 2467.94L-3043.48 2466.99L-3045.92 2466.99C-3046.36 2466.99 -3046.9 2467.04 -3047.36 2467.08C-3045.29 2465.12 -3043.9 2463.33 -3043.9 2461.57C-3043.9 2460.01 -3044.9 2458.99 -3046.47 2458.99C-3047.58 2458.99 -3048.35 2459.49 -3049.06 2460.27L-3048.43 2460.9C-3047.93 2460.31 -3047.32 2459.88 -3046.6 2459.88C-3045.51 2459.88 -3044.98 2460.61 -3044.98 2461.62C-3044.98 2463.13 -3046.25 2464.88 -3049.01 2467.29L-3049.01 2467.94ZM-3039.27 2468.1C-3037.9 2468.1 -3036.74 2466.95 -3036.74 2465.24C-3036.74 2463.39 -3037.7 2462.48 -3039.19 2462.48C-3039.87 2462.48 -3040.64 2462.88 -3041.18 2463.54C-3041.13 2460.81 -3040.13 2459.89 -3038.91 2459.89C-3038.38 2459.89 -3037.85 2460.15 -3037.52 2460.56L-3036.89 2459.89C-3037.39 2459.36 -3038.04 2458.99 -3038.96 2458.99C-3040.66 2458.99 -3042.21 2460.3 -3042.21 2463.74C-3042.21 2466.65 -3040.95 2468.1 -3039.27 2468.1ZM-3041.15 2464.41C-3040.58 2463.6 -3039.91 2463.3 -3039.36 2463.3C-3038.3 2463.3 -3037.78 2464.05 -3037.78 2465.24C-3037.78 2466.44 -3038.43 2467.23 -3039.27 2467.23C-3040.37 2467.23 -3041.03 2466.24 -3041.15 2464.41ZM-3035.17 2467.94L-3030.34 2467.94L-3030.34 2467.03L-3032.1 2467.03L-3032.1 2459.15L-3032.95 2459.15C-3033.43 2459.42 -3033.99 2459.62 -3034.77 2459.77L-3034.77 2460.47L-3033.2 2460.47L-3033.2 2467.03L-3035.17 2467.03L-3035.17 2467.94ZM-3029.51 2467.94L-3028.4 2467.94L-3027.54 2465.25L-3024.33 2465.25L-3023.49 2467.94L-3022.31 2467.94L-3025.3 2459.15L-3026.54 2459.15L-3029.51 2467.94ZM-3027.27 2464.38L-3026.84 2463.02C-3026.52 2462.02 -3026.24 2461.08 -3025.96 2460.04L-3025.91 2460.04C-3025.62 2461.06 -3025.35 2462.02 -3025.02 2463.02L-3024.6 2464.38L-3027.27 2464.38ZM-3018.93 2468.1C-3017.26 2468.1 -3016.19 2466.58 -3016.19 2463.51C-3016.19 2460.47 -3017.26 2458.99 -3018.93 2458.99C-3020.61 2458.99 -3021.67 2460.47 -3021.67 2463.51C-3021.67 2466.58 -3020.61 2468.1 -3018.93 2468.1ZM-3018.93 2467.21C-3019.93 2467.21 -3020.61 2466.09 -3020.61 2463.51C-3020.61 2460.95 -3019.93 2459.85 -3018.93 2459.85C-3017.93 2459.85 -3017.25 2460.95 -3017.25 2463.51C-3017.25 2466.09 -3017.93 2467.21 -3018.93 2467.21ZM-3012.27 2468.1C-3010.6 2468.1 -3009.53 2466.58 -3009.53 2463.51C-3009.53 2460.47 -3010.6 2458.99 -3012.27 2458.99C-3013.95 2458.99 -3015.01 2460.47 -3015.01 2463.51C-3015.01 2466.58 -3013.95 2468.1 -3012.27 2468.1ZM-3012.27 2467.21C-3013.27 2467.21 -3013.95 2466.09 -3013.95 2463.51C-3013.95 2460.95 -3013.27 2459.85 -3012.27 2459.85C-3011.27 2459.85 -3010.59 2460.95 -3010.59 2463.51C-3010.59 2466.09 -3011.27 2467.21 -3012.27 2467.21Z">
|
||||
</path>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip-path-74_1">
|
||||
<path d="M0 256L256 256L256 0L0 0L0 256Z" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 554 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 746 B After Width: | Height: | Size: 670 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 297 B |
44
public/sw-cache-manager.js
Normal file
@ -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');
|
238
src/components/CacheManager.vue
Normal file
@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center">
|
||||
<span>缓存管理</span>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="error" @click="clearAllCaches" :loading="loading">
|
||||
清除所有缓存
|
||||
</v-btn>
|
||||
<v-btn icon class="ml-2" @click="refreshCaches">
|
||||
<v-icon>mdi-refresh</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-alert v-if="!serviceWorkerActive" type="warning" class="mb-4">
|
||||
Service Worker 未激活,缓存管理功能不可用。
|
||||
</v-alert>
|
||||
|
||||
<v-alert v-if="message" :type="messageType" class="mb-4">
|
||||
{{ message }}
|
||||
</v-alert>
|
||||
|
||||
<v-expansion-panels v-if="caches.length > 0">
|
||||
<v-expansion-panel v-for="cache in caches" :key="cache.name">
|
||||
<v-expansion-panel-title>
|
||||
<div class="d-flex align-center">
|
||||
<span>{{ formatCacheName(cache.name) }}</span>
|
||||
<v-chip class="ml-2" size="small">{{ cache.urls.length }} 个文件</v-chip>
|
||||
</div>
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<div class="d-flex justify-end mb-2">
|
||||
<v-btn color="error" size="small" @click="clearCache(cache.name)" :loading="loading">
|
||||
清除此缓存
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-list lines="two">
|
||||
<v-list-item v-for="(url, index) in cache.urls" :key="index">
|
||||
<v-list-item-title class="text-truncate">
|
||||
{{ getFileName(url) }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-truncate">
|
||||
{{ url }}
|
||||
</v-list-item-subtitle>
|
||||
<template v-slot:append>
|
||||
<v-btn icon size="small" color="error" @click="clearUrl(cache.name, url)">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
|
||||
<v-skeleton-loader v-else-if="loading" type="article" />
|
||||
|
||||
<v-alert v-else type="info">
|
||||
没有找到缓存数据。
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CacheManager',
|
||||
data() {
|
||||
return {
|
||||
caches: [],
|
||||
loading: false,
|
||||
serviceWorkerActive: false,
|
||||
message: '',
|
||||
messageType: 'info',
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.checkServiceWorker();
|
||||
},
|
||||
methods: {
|
||||
checkServiceWorker() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
this.serviceWorkerActive = true;
|
||||
this.refreshCaches();
|
||||
}).catch(() => {
|
||||
this.serviceWorkerActive = false;
|
||||
});
|
||||
} else {
|
||||
this.serviceWorkerActive = false;
|
||||
}
|
||||
},
|
||||
async refreshCaches() {
|
||||
if (!this.serviceWorkerActive) return;
|
||||
|
||||
this.loading = true;
|
||||
this.message = '';
|
||||
this.caches = [];
|
||||
|
||||
try {
|
||||
// 获取所有缓存名称
|
||||
const cacheNames = await this.sendMessageToSW({ type: 'CACHE_KEYS' });
|
||||
|
||||
// 获取每个缓存的内容
|
||||
for (const cacheName of cacheNames.cacheNames) {
|
||||
const cacheContent = await this.sendMessageToSW({
|
||||
type: 'CACHE_CONTENT',
|
||||
cacheName
|
||||
});
|
||||
|
||||
this.caches.push({
|
||||
name: cacheName,
|
||||
urls: cacheContent.urls || []
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.showMessage('获取缓存信息失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async clearCache(cacheName) {
|
||||
this.loading = true;
|
||||
try {
|
||||
const result = await this.sendMessageToSW({
|
||||
type: 'CLEAR_CACHE',
|
||||
cacheName
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
this.showMessage(`已清除缓存: ${this.formatCacheName(cacheName)}`, 'success');
|
||||
await this.refreshCaches();
|
||||
} else {
|
||||
this.showMessage('清除缓存失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showMessage('清除缓存失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async clearUrl(cacheName, url) {
|
||||
this.loading = true;
|
||||
try {
|
||||
const result = await this.sendMessageToSW({
|
||||
type: 'CLEAR_URL',
|
||||
cacheName,
|
||||
url
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
this.showMessage(`已从缓存中删除: ${this.getFileName(url)}`, 'success');
|
||||
await this.refreshCaches();
|
||||
} else {
|
||||
this.showMessage('删除缓存项失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showMessage('删除缓存项失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async clearAllCaches() {
|
||||
if (!confirm('确定要清除所有缓存吗?这可能会导致应用需要重新下载资源。')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
try {
|
||||
const result = await this.sendMessageToSW({ type: 'CLEAR_ALL_CACHES' });
|
||||
|
||||
if (result.success) {
|
||||
this.showMessage('已清除所有缓存', 'success');
|
||||
await this.refreshCaches();
|
||||
} else {
|
||||
this.showMessage('清除所有缓存失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showMessage('清除所有缓存失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
sendMessageToSW(message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!navigator.serviceWorker.controller) {
|
||||
reject(new Error('Service Worker 未控制页面'));
|
||||
return;
|
||||
}
|
||||
|
||||
const messageChannel = new MessageChannel();
|
||||
messageChannel.port1.onmessage = (event) => {
|
||||
resolve(event.data);
|
||||
};
|
||||
|
||||
navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
|
||||
|
||||
// 设置超时
|
||||
setTimeout(() => {
|
||||
reject(new Error('Service Worker 响应超时'));
|
||||
}, 5000);
|
||||
});
|
||||
},
|
||||
formatCacheName(name) {
|
||||
// 格式化缓存名称,使其更易读
|
||||
return name
|
||||
.replace('workbox-precache-', '预缓存-')
|
||||
.replace('-cache', '')
|
||||
.replace('js', 'JS')
|
||||
.replace('css', 'CSS')
|
||||
.replace('html', 'HTML')
|
||||
.replace('images', '图片')
|
||||
.replace('external-resources', '外部资源')
|
||||
.replace('cdn-cgi', 'CDN');
|
||||
},
|
||||
getFileName(url) {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
const pathParts = urlObj.pathname.split('/');
|
||||
return pathParts[pathParts.length - 1] || urlObj.hostname;
|
||||
} catch (e) {
|
||||
return url;
|
||||
}
|
||||
},
|
||||
showMessage(message, type = 'info') {
|
||||
this.message = message;
|
||||
this.messageType = type;
|
||||
|
||||
// 5秒后自动清除消息
|
||||
setTimeout(() => {
|
||||
if (this.message === message) {
|
||||
this.message = '';
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -9,11 +9,7 @@
|
||||
</template>
|
||||
<v-list-item-title>检查服务器连接</v-list-item-title>
|
||||
<template #append>
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
variant="tonal"
|
||||
@click="checkServerConnection"
|
||||
>
|
||||
<v-btn :loading="loading" variant="tonal" @click="checkServerConnection">
|
||||
测试连接
|
||||
</v-btn>
|
||||
</template>
|
||||
@ -27,9 +23,7 @@
|
||||
<v-icon icon="mdi-database" class="mr-3" />
|
||||
</template>
|
||||
<v-list-item-title>清除数据库缓存</v-list-item-title>
|
||||
<v-list-item-subtitle
|
||||
>这将清除所有IndexedDB中的数据</v-list-item-subtitle
|
||||
>
|
||||
<v-list-item-subtitle>这将清除所有IndexedDB中的数据</v-list-item-subtitle>
|
||||
<template #append>
|
||||
<v-btn color="error" variant="tonal" @click="confirmClearIndexedDB">
|
||||
清除
|
||||
@ -46,6 +40,17 @@
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-icon icon="mdi-lan-connect" class="mr-3" />
|
||||
</template>
|
||||
<v-list-item-title>查看本地缓存</v-list-item-title>
|
||||
<template #append>
|
||||
<v-btn variant="tonal" to="/cachemanagement">
|
||||
查看
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<!-- 确认对话框 -->
|
||||
@ -55,12 +60,8 @@
|
||||
<v-card-text>{{ confirmMessage }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" variant="text" @click="confirmDialog = false"
|
||||
>取消</v-btn
|
||||
>
|
||||
<v-btn color="error" variant="tonal" @click="handleConfirm"
|
||||
>确认</v-btn
|
||||
>
|
||||
<v-btn color="grey" variant="text" @click="confirmDialog = false">取消</v-btn>
|
||||
<v-btn color="error" variant="tonal" @click="handleConfirm">确认</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
22
src/pages/CacheManagement.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<h1 class="text-h4 mb-4">缓存管理</h1>
|
||||
<p class="mb-6">在这里您可以查看和管理应用的缓存文件。清除缓存可能会导致应用需要重新下载资源。</p>
|
||||
<CacheManager />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CacheManager from '@/components/CacheManager.vue';
|
||||
|
||||
export default {
|
||||
name: 'CacheManagementPage',
|
||||
components: {
|
||||
CacheManager
|
||||
}
|
||||
}
|
||||
</script>
|
141
src/sw.js
Normal file
@ -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 });
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
110
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: '随机点名',
|
||||
|