1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-07-02 09:19:23 +00:00

更新逻辑,缓存

This commit is contained in:
SunWuyuan 2025-04-05 12:19:46 +08:00
parent 8a1e44f1fa
commit eefa858078
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
17 changed files with 292 additions and 279 deletions

144
.gitignore vendored
View File

@ -23,3 +23,147 @@ pnpm-debug.log*
*.sln *.sln
*.sw? *.sw?
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
# End of https://www.toptal.com/developers/gitignore/api/node

View File

@ -67,9 +67,9 @@ if (!self.define) {
}); });
}; };
} }
define(['./workbox-5ea419d9'], (function (workbox) { 'use strict'; define(['./workbox-20a2f87f'], (function (workbox) { 'use strict';
importScripts("sw-cache-manager.js"); importScripts("/sw-cache-manager.js");
self.skipWaiting(); self.skipWaiting();
workbox.clientsClaim(); workbox.clientsClaim();
@ -82,55 +82,42 @@ define(['./workbox-5ea419d9'], (function (workbox) { 'use strict';
"url": "suppress-warnings.js", "url": "suppress-warnings.js",
"revision": "d41d8cd98f00b204e9800998ecf8427e" "revision": "d41d8cd98f00b204e9800998ecf8427e"
}, { }, {
"url": "/", "url": "index.html",
"revision": "0.0lkakoc2in8" "revision": "0.vjgf6ab2p68"
}], {}); }], {});
workbox.cleanupOutdatedCaches(); workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("/"), { workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
allowlist: [/^\/$/] allowlist: [/^\/$/]
})); }));
workbox.registerRoute(/\.(?:js)$/i, new workbox.StaleWhileRevalidate({ workbox.registerRoute(({
"cacheName": "js-cache", url
}) => url.pathname.startsWith("/assets/"), new workbox.CacheFirst({
"cacheName": "assets-cache",
plugins: [new workbox.ExpirationPlugin({ plugins: [new workbox.ExpirationPlugin({
maxEntries: 100, maxEntries: 200,
maxAgeSeconds: 604800 maxAgeSeconds: 5184000
}), new workbox.CacheableResponsePlugin({
statuses: [0, 200]
})] })]
}), 'GET'); }), 'GET');
workbox.registerRoute(/\.(?:css)$/i, new workbox.StaleWhileRevalidate({ workbox.registerRoute(({
"cacheName": "css-cache", url
}) => url.pathname.startsWith("/pwa/"), new workbox.StaleWhileRevalidate({
"cacheName": "pwa-cache",
plugins: [new workbox.ExpirationPlugin({ plugins: [new workbox.ExpirationPlugin({
maxEntries: 50, maxEntries: 50,
maxAgeSeconds: 604800 maxAgeSeconds: 604800
})] }), new workbox.CacheableResponsePlugin({
}), 'GET'); statuses: [0, 200]
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'); }), 'GET');
workbox.registerRoute(({ workbox.registerRoute(({
url url
}) => { }) => {
return url.origin !== self.location.origin; const path = url.pathname;
return !(path.includes("/assets/") || path.includes("/pwa/"));
}, new workbox.NetworkFirst({ }, new workbox.NetworkFirst({
"cacheName": "external-resources", "cacheName": "other-resources",
"networkTimeoutSeconds": 10, "networkTimeoutSeconds": 10,
plugins: [new workbox.ExpirationPlugin({ plugins: [new workbox.ExpirationPlugin({
maxEntries: 100, maxEntries: 100,

View File

@ -1,27 +0,0 @@
{
"studentList": [
"张三",
"李四",
"王五",
"赵六",
"钱七"
],
"homeworkArrange": [
[
"语文",
"数学",
"英语"
],
[
"物理",
"化学",
"生物"
],
[
"政治",
"历史",
"地理"
]
],
"url": "http://localhost:3030"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 368 B

View File

Before

Width:  |  Height:  |  Size: 526 B

After

Width:  |  Height:  |  Size: 526 B

View File

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 368 B

View File

Before

Width:  |  Height:  |  Size: 554 B

After

Width:  |  Height:  |  Size: 554 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 670 B

After

Width:  |  Height:  |  Size: 670 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 297 B

View File

@ -41,4 +41,4 @@ self.addEventListener('message', (event) => {
} }
}); });
console.log('Cache Manager extension loaded'); console.log('Cache Manager extension loaded');

View File

@ -92,22 +92,22 @@ export default {
}, },
async refreshCaches() { async refreshCaches() {
if (!this.serviceWorkerActive) return; if (!this.serviceWorkerActive) return;
this.loading = true; this.loading = true;
this.message = ''; this.message = '';
this.caches = []; this.caches = [];
try { try {
// //
const cacheNames = await this.sendMessageToSW({ type: 'CACHE_KEYS' }); const cacheNames = await this.sendMessageToSW({ type: 'CACHE_KEYS' });
// //
for (const cacheName of cacheNames.cacheNames) { for (const cacheName of cacheNames.cacheNames) {
const cacheContent = await this.sendMessageToSW({ const cacheContent = await this.sendMessageToSW({
type: 'CACHE_CONTENT', type: 'CACHE_CONTENT',
cacheName cacheName
}); });
this.caches.push({ this.caches.push({
name: cacheName, name: cacheName,
urls: cacheContent.urls || [] urls: cacheContent.urls || []
@ -122,11 +122,11 @@ export default {
async clearCache(cacheName) { async clearCache(cacheName) {
this.loading = true; this.loading = true;
try { try {
const result = await this.sendMessageToSW({ const result = await this.sendMessageToSW({
type: 'CLEAR_CACHE', type: 'CLEAR_CACHE',
cacheName cacheName
}); });
if (result.success) { if (result.success) {
this.showMessage(`已清除缓存: ${this.formatCacheName(cacheName)}`, 'success'); this.showMessage(`已清除缓存: ${this.formatCacheName(cacheName)}`, 'success');
await this.refreshCaches(); await this.refreshCaches();
@ -142,12 +142,12 @@ export default {
async clearUrl(cacheName, url) { async clearUrl(cacheName, url) {
this.loading = true; this.loading = true;
try { try {
const result = await this.sendMessageToSW({ const result = await this.sendMessageToSW({
type: 'CLEAR_URL', type: 'CLEAR_URL',
cacheName, cacheName,
url url
}); });
if (result.success) { if (result.success) {
this.showMessage(`已从缓存中删除: ${this.getFileName(url)}`, 'success'); this.showMessage(`已从缓存中删除: ${this.getFileName(url)}`, 'success');
await this.refreshCaches(); await this.refreshCaches();
@ -164,11 +164,11 @@ export default {
if (!confirm('确定要清除所有缓存吗?这可能会导致应用需要重新下载资源。')) { if (!confirm('确定要清除所有缓存吗?这可能会导致应用需要重新下载资源。')) {
return; return;
} }
this.loading = true; this.loading = true;
try { try {
const result = await this.sendMessageToSW({ type: 'CLEAR_ALL_CACHES' }); const result = await this.sendMessageToSW({ type: 'CLEAR_ALL_CACHES' });
if (result.success) { if (result.success) {
this.showMessage('已清除所有缓存', 'success'); this.showMessage('已清除所有缓存', 'success');
await this.refreshCaches(); await this.refreshCaches();
@ -187,14 +187,14 @@ export default {
reject(new Error('Service Worker 未控制页面')); reject(new Error('Service Worker 未控制页面'));
return; return;
} }
const messageChannel = new MessageChannel(); const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = (event) => { messageChannel.port1.onmessage = (event) => {
resolve(event.data); resolve(event.data);
}; };
navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]); navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
// //
setTimeout(() => { setTimeout(() => {
reject(new Error('Service Worker 响应超时')); reject(new Error('Service Worker 响应超时'));
@ -218,14 +218,15 @@ export default {
const urlObj = new URL(url); const urlObj = new URL(url);
const pathParts = urlObj.pathname.split('/'); const pathParts = urlObj.pathname.split('/');
return pathParts[pathParts.length - 1] || urlObj.hostname; return pathParts[pathParts.length - 1] || urlObj.hostname;
} catch (e) { } catch (error) {
console.error('获取文件名失败:', error);
return url; return url;
} }
}, },
showMessage(message, type = 'info') { showMessage(message, type = 'info') {
this.message = message; this.message = message;
this.messageType = type; this.messageType = type;
// 5 // 5
setTimeout(() => { setTimeout(() => {
if (this.message === message) { if (this.message === message) {
@ -235,4 +236,4 @@ export default {
} }
} }
} }
</script> </script>

View File

@ -1,117 +0,0 @@
<template>
<v-card border class="mb-4">
<v-card-title class="d-flex align-center">
<v-icon icon="mdi-account-question" class="mr-2" />
随机点名设置
</v-card-title>
<v-card-text>
<v-switch
v-model="randomPickerEnabled"
label="启用随机点名功能"
color="primary"
hide-details
class="mb-4"
/>
<v-switch
v-model="randomPickerAnimation"
label="启用随机点名动画效果"
color="primary"
hide-details
class="mb-4"
:disabled="!randomPickerEnabled"
/>
<v-slider
v-model="defaultCount"
label="默认抽取人数"
min="1"
max="10"
step="1"
thumb-label
:disabled="!randomPickerEnabled"
class="mb-4"
/>
<v-divider class="my-4" />
<div class="text-subtitle-1 mb-2">学生过滤设置</div>
<v-switch
v-model="excludeAbsent"
label="排除请假学生"
color="primary"
hide-details
class="mb-2"
:disabled="!randomPickerEnabled"
/>
<v-switch
v-model="excludeLate"
label="排除迟到学生"
color="primary"
hide-details
class="mb-2"
:disabled="!randomPickerEnabled"
/>
<v-switch
v-model="excludeExcluded"
label="排除不参与学生"
color="primary"
hide-details
class="mb-2"
:disabled="!randomPickerEnabled"
/>
<v-alert
v-if="randomPickerEnabled"
type="info"
variant="tonal"
class="mt-4"
density="compact"
>
随机点名功能将在主页显示一个按钮点击后可以随机抽取学生
</v-alert>
</v-card-text>
</v-card>
</template>
<script>
import { getSetting, setSetting } from '@/utils/settings';
export default {
name: 'RandomPickerSettingsCard',
data() {
return {
randomPickerEnabled: getSetting('randomPicker.enabled'),
randomPickerAnimation: getSetting('randomPicker.animation'),
defaultCount: getSetting('randomPicker.defaultCount'),
excludeAbsent: getSetting('randomPicker.excludeAbsent'),
excludeLate: getSetting('randomPicker.excludeLate'),
excludeExcluded: getSetting('randomPicker.excludeExcluded')
};
},
watch: {
randomPickerEnabled(newValue) {
setSetting('randomPicker.enabled', newValue);
},
randomPickerAnimation(newValue) {
setSetting('randomPicker.animation', newValue);
},
defaultCount(newValue) {
setSetting('randomPicker.defaultCount', newValue);
},
excludeAbsent(newValue) {
setSetting('randomPicker.excludeAbsent', newValue);
},
excludeLate(newValue) {
setSetting('randomPicker.excludeLate', newValue);
},
excludeExcluded(newValue) {
setSetting('randomPicker.excludeExcluded', newValue);
}
}
};
</script>

View File

@ -2,8 +2,51 @@
<v-container> <v-container>
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<h1 class="text-h4 mb-4">缓存管理</h1> <div class="d-flex align-center mb-6">
<p class="mb-6">在这里您可以查看和管理应用的缓存文件清除缓存可能会导致应用需要重新下载资源</p> <v-icon size="x-large" color="primary" class="mr-3">mdi-database-cog-outline</v-icon>
<div>
<h1 class="text-h4 ">缓存管理</h1>
<div class="text-subtitle-1 text-grey">管理应用的本地缓存资源</div>
</div>
</div>
<v-card class="mb-6" variant="tonal" color="info" density="compact">
<v-card-text class="d-flex align-center">
<v-icon color="info" class="mr-2">mdi-information-outline</v-icon>
<span>在这里您可以查看和管理应用的缓存文件清除缓存可能会导致应用需要重新下载资源但有助于解决某些显示问题</span>
</v-card-text>
</v-card>
<v-row>
<v-col cols="12" md="8">
<v-card class="mb-4" variant="tonal">
<v-card-text>
<div class="d-flex align-center mb-2">
<v-icon color="primary" class="mr-2">mdi-information</v-icon>
<span class="text-h6">什么是缓存</span>
</div>
<p>缓存是浏览器在本地存储的网站资源副本如图片脚本和样式表等这些缓存可以加快页面加载速度减少数据使用并在离线时提供基本功能</p>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="4">
<v-card class="mb-4" variant="tonal">
<v-card-text>
<div class="d-flex align-center mb-2">
<v-icon color="warning" class="mr-2">mdi-lightbulb-outline</v-icon>
<span class="text-h6">何时清除缓存</span>
</div>
<ul class="pl-4">
<li>应用显示过时的内容</li>
<li>界面出现异常</li>
<li>应用功能不正常</li>
</ul>
</v-card-text>
</v-card>
</v-col>
</v-row>
<CacheManager /> <CacheManager />
</v-col> </v-col>
</v-row> </v-row>
@ -17,6 +60,9 @@ export default {
name: 'CacheManagementPage', name: 'CacheManagementPage',
components: { components: {
CacheManager CacheManager
},
metaInfo: {
title: '缓存管理'
} }
} }
</script> </script>

View File

@ -134,7 +134,7 @@
<v-col cols="12"> <v-col cols="12">
<echo-chamber-card border /> <echo-chamber-card border />
</v-col> </v-col>
<!-- 关于卡片 --> <!-- 关于卡片 -->
<v-col cols="12"> <v-col cols="12">
<about-card /> <about-card />
@ -156,12 +156,10 @@
</v-card> </v-card>
</v-col> </v-col>
<v-col cols="12" md="6">
<random-picker-settings-card />
</v-col>
</v-row> </v-row>
</v-container> </v-container>
<!-- 消息记录组件 --> <!-- 消息记录组件 -->
<message-log ref="messageLog" /> <message-log ref="messageLog" />
</div> </div>
@ -189,7 +187,6 @@ import AboutCard from '@/components/settings/AboutCard.vue';
import '../styles/settings.scss'; import '../styles/settings.scss';
import dataProvider from '@/utils/dataProvider'; import dataProvider from '@/utils/dataProvider';
import SettingsExplorer from '@/components/settings/SettingsExplorer.vue'; import SettingsExplorer from '@/components/settings/SettingsExplorer.vue';
import RandomPickerSettingsCard from '@/components/settings/cards/RandomPickerSettingsCard.vue';
export default { export default {
name: 'Settings', name: 'Settings',
@ -205,8 +202,7 @@ export default {
DataProviderSettingsCard, DataProviderSettingsCard,
ThemeSettingsCard, ThemeSettingsCard,
EchoChamberCard, EchoChamberCard,
SettingsExplorer, SettingsExplorer
RandomPickerSettingsCard
}, },
setup() { setup() {
const { mobile } = useDisplay(); const { mobile } = useDisplay();

View File

@ -19,86 +19,62 @@ export default defineConfig({
Layouts(), Layouts(),
Vue({ Vue({
template: { transformAssetUrls } template: { transformAssetUrls }
}), }),
VitePWA({ VitePWA({
registerType: 'autoUpdate', registerType: 'autoUpdate',
devOptions: { devOptions: {
navigateFallback: '/', navigateFallback: 'index.html',
enabled: true, enabled: true,
suppressWarnings: true, suppressWarnings: true,
}, },
lang: 'zh-CN', lang: 'zh-CN',
injectRegister: 'auto', injectRegister: 'auto',
strategies: 'generateSW', strategies: 'generateSW',
workbox: { workbox: {
globPatterns: ['**/*.{js,css,html,png,svg,jpg,jpeg,gif,ico,woff,woff2,ttf,eot}'], globPatterns: ['*'],
navigateFallback: '/', navigateFallback: 'index.html',
runtimeCaching: [ runtimeCaching: [
{ {
urlPattern: /\.(?:js)$/i, urlPattern: ({ url }) => url.pathname.startsWith('/assets/'),
handler: 'StaleWhileRevalidate', handler: 'CacheFirst',
options: { options: {
cacheName: 'js-cache', cacheName: 'assets-cache',
expiration: { expiration: {
maxEntries: 100, maxEntries: 200,
maxAgeSeconds: 60 * 60 * 24 * 7 // 7 天 maxAgeSeconds: 60 * 60 * 24 * 60 // 60 天
}
}
},
{
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 cacheableResponse: {
statuses: [0, 200]
}
} }
}, },
{ {
// 匹配除了当前域名以外的所有请求 urlPattern: ({ url }) => url.pathname.startsWith('/pwa/'),
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'pwa-cache',
expiration: {
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 * 7 // 7 天
},
cacheableResponse: {
statuses: [0, 200]
}
}
},
{
// 匹配除了上述规则外的所有请求
urlPattern: ({ url }) => { urlPattern: ({ url }) => {
return url.origin !== self.location.origin; const path = url.pathname;
// 排除已经由其他规则处理的路径
return !(path.includes('/assets/') || path.includes('/pwa/'));
}, },
handler: 'NetworkFirst', handler: 'NetworkFirst',
options: { options: {
cacheName: 'external-resources', cacheName: 'other-resources',
expiration: { expiration: {
maxEntries: 100, maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24 // 1 天 maxAgeSeconds: 60 * 60 * 24 // 1 天
@ -108,12 +84,13 @@ export default defineConfig({
statuses: [0, 200] statuses: [0, 200]
} }
} }
} },
], ],
additionalManifestEntries: [], additionalManifestEntries: [],
clientsClaim: true, clientsClaim: true,
skipWaiting: true, skipWaiting: true,
importScripts: ['sw-cache-manager.js'] importScripts: ['/sw-cache-manager.js']
}, },
manifest: { manifest: {
name: 'Classworks作业板', name: 'Classworks作业板',
@ -128,22 +105,22 @@ export default defineConfig({
}, },
icons: [ icons: [
{ {
src: '/image/pwa-64x64.png', src: '/pwa/image/pwa-64x64.png',
sizes: '64x64', sizes: '64x64',
type: 'image/png' type: 'image/png'
}, },
{ {
src: '/image/pwa-192x192.png', src: '/pwa/image/pwa-192x192.png',
sizes: '192x192', sizes: '192x192',
type: 'image/png' type: 'image/png'
}, },
{ {
src: '/image/pwa-512x512.png', src: '/pwa/image/pwa-512x512.png',
sizes: '512x512', sizes: '512x512',
type: 'image/png' type: 'image/png'
}, },
{ {
src: '/image/maskable-icon-512x512.png', src: '/pwa/image/maskable-icon-512x512.png',
sizes: '512x512', sizes: '512x512',
type: 'image/png', type: 'image/png',
purpose: 'maskable' purpose: 'maskable'
@ -154,9 +131,15 @@ export default defineConfig({
name: '随机点名', name: '随机点名',
short_name: '随机点名', short_name: '随机点名',
url: '/#random-picker', url: '/#random-picker',
icons: [
{
src: '/pwa/image/pwa-64x64.png',
sizes: '64x64',
type: 'image/png'
}
]
}, },
], ],
} }
}), }),
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme // https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme