mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2026-08-12 17:40:32 +00:00
Compare commits
4 Commits
28a3e878b7
...
b63f453aaf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b63f453aaf | ||
|
|
2f8bc65bbb | ||
|
|
664157587e | ||
|
|
c66a265935 |
94
.github/workflows/store-pwa.yml
vendored
Normal file
94
.github/workflows/store-pwa.yml
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
name: PWA Store Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/store-pwa.yml"
|
||||||
|
- "index.html"
|
||||||
|
- "package.json"
|
||||||
|
- "pnpm-lock.yaml"
|
||||||
|
- "public/**"
|
||||||
|
- "scripts/**"
|
||||||
|
- "src/**"
|
||||||
|
- "vite.config.mjs"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
production_url:
|
||||||
|
description: "Public HTTPS URL used for Microsoft Store/PWABuilder submission"
|
||||||
|
required: true
|
||||||
|
default: "https://cs.houlang.cloud/"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: pwa-store-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-validate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
VITE_APP_ID: d158067f53627d2b98babe8bffd2fd7d
|
||||||
|
VITE_DEFAULT_KV_SERVER: https://kv-service.houlang.cloud
|
||||||
|
VITE_DEFAULT_AUTH_SERVER: https://kv.houlang.cloud
|
||||||
|
STORE_PRODUCTION_URL: ${{ inputs.production_url || 'https://cs.houlang.cloud/' }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Enable Corepack
|
||||||
|
run: corepack enable
|
||||||
|
|
||||||
|
- name: Use Node.js 24
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "24.x"
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build and validate PWA
|
||||||
|
run: pnpm run build:store
|
||||||
|
|
||||||
|
- name: Create store handoff metadata
|
||||||
|
run: |
|
||||||
|
node <<'NODE'
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
const url = process.env.STORE_PRODUCTION_URL;
|
||||||
|
const handoffDir = path.resolve('store-handoff');
|
||||||
|
fs.mkdirSync(handoffDir, { recursive: true });
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(handoffDir, 'microsoft-store-pwa.json'),
|
||||||
|
JSON.stringify({
|
||||||
|
productionUrl: url,
|
||||||
|
manifestUrl: new URL('manifest.webmanifest', url).toString(),
|
||||||
|
packageSource: 'Use PWABuilder or Microsoft Partner Center with the production URL above.',
|
||||||
|
validationArtifact: 'dist/',
|
||||||
|
notes: [
|
||||||
|
'Serve the app over HTTPS before packaging.',
|
||||||
|
'Run this workflow after changing manifest, icons, service worker, or public assets.',
|
||||||
|
'Partner Center listing screenshots, age rating, privacy policy, and publisher identity are handled outside this repository.'
|
||||||
|
]
|
||||||
|
}, null, 2)
|
||||||
|
);
|
||||||
|
NODE
|
||||||
|
|
||||||
|
- name: Upload validated PWA dist
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: classworks-pwa-dist
|
||||||
|
path: dist
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Microsoft Store handoff
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: microsoft-store-handoff
|
||||||
|
path: store-handoff
|
||||||
|
if-no-files-found: error
|
||||||
23
docs/microsoft-store-pwa.md
Normal file
23
docs/microsoft-store-pwa.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Microsoft Store PWA 上架流程
|
||||||
|
|
||||||
|
## 仓库内自动化
|
||||||
|
|
||||||
|
1. 在 GitHub Actions 手动运行 `PWA Store Build`。
|
||||||
|
2. 确认 `production_url` 是最终公开 HTTPS 地址,例如 `https://cs.houlang.cloud/`。
|
||||||
|
3. workflow 会执行 `pnpm run build:store`,生成并校验 `dist/manifest.webmanifest`、`dist/sw.js`、图标、截图和 HTML PWA hint。
|
||||||
|
4. 下载 `classworks-pwa-dist` 和 `microsoft-store-handoff` artifacts,作为 PWABuilder 或 Partner Center 提交前的构建记录。
|
||||||
|
|
||||||
|
## 提交到 Microsoft Store
|
||||||
|
|
||||||
|
1. 确保生产站点已部署最新构建,并可通过 HTTPS 访问。
|
||||||
|
2. 使用 PWABuilder 或 Microsoft Partner Center 指向生产 URL。
|
||||||
|
3. 填写商店资料:应用分类、年龄分级、隐私政策、支持链接、截图和发布者信息。
|
||||||
|
4. 如果 manifest、图标、service worker 或公开资源有变更,重新运行 `PWA Store Build`。
|
||||||
|
|
||||||
|
## 本地校验
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run build:store
|
||||||
|
```
|
||||||
|
|
||||||
|
该命令会先刷新音频资源清单,再构建 Vite 应用,最后执行 PWA 构建校验。
|
||||||
BIN
images/1.jpeg
Normal file
BIN
images/1.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 208 KiB |
BIN
images/2.jpeg
Normal file
BIN
images/2.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 249 KiB |
13
index.html
13
index.html
@ -11,6 +11,7 @@
|
|||||||
<meta name="author" content="Sunwuyuan" />
|
<meta name="author" content="Sunwuyuan" />
|
||||||
<meta name="robots" content="index, follow" />
|
<meta name="robots" content="index, follow" />
|
||||||
<link rel="canonical" href="https://cs.houlang.cloud/" />
|
<link rel="canonical" href="https://cs.houlang.cloud/" />
|
||||||
|
<link rel="manifest" href="./manifest.webmanifest" />
|
||||||
|
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:title" content="Classworks 作业板" />
|
<meta property="og:title" content="Classworks 作业板" />
|
||||||
@ -27,9 +28,17 @@
|
|||||||
<meta name="twitter:description" content="适用于班级大屏的作业板小工具,支持记录、查看并同步作业,开源免费。" />
|
<meta name="twitter:description" content="适用于班级大屏的作业板小工具,支持记录、查看并同步作业,开源免费。" />
|
||||||
<meta name="twitter:image" content="https://cs.houlang.cloud/banner.png" />
|
<meta name="twitter:image" content="https://cs.houlang.cloud/banner.png" />
|
||||||
|
|
||||||
<link rel="apple-touch-icon" href="/image/apple-touch-icon.png" sizes="180x180" />
|
<link rel="apple-touch-icon" href="./pwa/image/apple-touch-icon-180x180.png" sizes="180x180" />
|
||||||
<link rel="mask-icon" href="/image/mask-icon.svg" color="#212121" />
|
<link rel="mask-icon" href="./pwa/image/logo.svg" color="#212121" />
|
||||||
<meta name="theme-color" content="#212121" />
|
<meta name="theme-color" content="#212121" />
|
||||||
|
<meta name="application-name" content="Classworks" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Classworks" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="msapplication-TileColor" content="#212121" />
|
||||||
|
<meta name="msapplication-TileImage" content="./pwa/image/pwa-192x192.png" />
|
||||||
|
<meta name="msapplication-starturl" content="./" />
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -6,8 +6,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"build:store": "node scripts/generate-sound-list.js && vite build && node scripts/validate-pwa-build.js",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint . --fix",
|
"lint": "eslint . --fix",
|
||||||
|
"pwa:validate": "node scripts/validate-pwa-build.js",
|
||||||
"prebuild": "node scripts/generate-sound-list.js"
|
"prebuild": "node scripts/generate-sound-list.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
BIN
public/images/1.jpeg
Normal file
BIN
public/images/1.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 208 KiB |
BIN
public/images/2.jpeg
Normal file
BIN
public/images/2.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 249 KiB |
134
scripts/validate-pwa-build.js
Normal file
134
scripts/validate-pwa-build.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
const distDir = path.resolve('dist');
|
||||||
|
const manifestPath = path.join(distDir, 'manifest.webmanifest');
|
||||||
|
const indexPath = path.join(distDir, 'index.html');
|
||||||
|
const serviceWorkerPath = path.join(distDir, 'sw.js');
|
||||||
|
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
function fail(message) {
|
||||||
|
errors.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readJson(filePath) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
} catch (error) {
|
||||||
|
fail(`无法读取或解析 ${path.relative(process.cwd(), filePath)}: ${error.message}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileExistsFromManifest(src) {
|
||||||
|
const normalized = src.replace(/^\.?\//, '').split('?')[0];
|
||||||
|
return fs.existsSync(path.join(distDir, normalized));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(distDir)) {
|
||||||
|
fail('dist 目录不存在,请先运行构建。');
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifest = fs.existsSync(manifestPath) ? readJson(manifestPath) : null;
|
||||||
|
if (!manifest) {
|
||||||
|
fail('dist/manifest.webmanifest 不存在。');
|
||||||
|
} else {
|
||||||
|
for (const field of ['id', 'name', 'short_name', 'description', 'start_url', 'scope', 'display', 'theme_color', 'background_color']) {
|
||||||
|
if (!manifest[field]) {
|
||||||
|
fail(`manifest 缺少 ${field}。`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest.id !== '7C24F2B3.ClassworksPWA') {
|
||||||
|
fail('manifest.id 必须是 7C24F2B3.ClassworksPWA。');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!['standalone', 'fullscreen', 'minimal-ui'].includes(manifest.display)) {
|
||||||
|
fail('manifest.display 应为 standalone、fullscreen 或 minimal-ui。');
|
||||||
|
}
|
||||||
|
|
||||||
|
const icons = Array.isArray(manifest.icons) ? manifest.icons : [];
|
||||||
|
const has192 = icons.some((icon) => icon.sizes?.includes('192x192') && fileExistsFromManifest(icon.src));
|
||||||
|
const has512 = icons.some((icon) => icon.sizes?.includes('512x512') && fileExistsFromManifest(icon.src));
|
||||||
|
const hasMaskable = icons.some((icon) => icon.sizes?.includes('512x512') && icon.purpose?.includes('maskable') && fileExistsFromManifest(icon.src));
|
||||||
|
|
||||||
|
if (!has192) fail('manifest 缺少可访问的 192x192 图标。');
|
||||||
|
if (!has512) fail('manifest 缺少可访问的 512x512 图标。');
|
||||||
|
if (!hasMaskable) fail('manifest 缺少可访问的 512x512 maskable 图标。');
|
||||||
|
|
||||||
|
if (!Array.isArray(manifest.categories) || manifest.categories.length === 0) {
|
||||||
|
fail('manifest 缺少 categories,Microsoft Store/PWABuilder 会降低质量评分。');
|
||||||
|
}
|
||||||
|
|
||||||
|
const screenshots = Array.isArray(manifest.screenshots) ? manifest.screenshots : [];
|
||||||
|
if (screenshots.length === 0) {
|
||||||
|
fail('manifest 缺少 screenshots。');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const screenshot of screenshots) {
|
||||||
|
if (!screenshot.src || !screenshot.sizes || !screenshot.type) {
|
||||||
|
fail(`截图条目不完整: ${JSON.stringify(screenshot)}`);
|
||||||
|
} else if (!fileExistsFromManifest(screenshot.src)) {
|
||||||
|
fail(`截图文件不存在: ${screenshot.src}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileHandlers = Array.isArray(manifest.file_handlers) ? manifest.file_handlers : [];
|
||||||
|
const fileExtensions = new Set(
|
||||||
|
fileHandlers.flatMap((handler) => Object.values(handler.accept || {}).flat())
|
||||||
|
);
|
||||||
|
if (!fileExtensions.has('.csb') || !fileExtensions.has('.csi')) {
|
||||||
|
fail('manifest.file_handlers 必须允许 .csb 和 .csi。');
|
||||||
|
}
|
||||||
|
|
||||||
|
const protocolHandlers = Array.isArray(manifest.protocol_handlers) ? manifest.protocol_handlers : [];
|
||||||
|
if (!protocolHandlers.some((handler) => handler.protocol === 'cs' && handler.url?.includes('%s'))) {
|
||||||
|
fail('manifest.protocol_handlers 必须声明 cs:// 协议处理。');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest.edge_side_panel) {
|
||||||
|
fail('manifest 不应声明 edge_side_panel。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(indexPath)) {
|
||||||
|
fail('dist/index.html 不存在。');
|
||||||
|
} else {
|
||||||
|
const html = fs.readFileSync(indexPath, 'utf8');
|
||||||
|
if (!html.includes('rel="manifest"')) {
|
||||||
|
fail('index.html 缺少 manifest 链接。');
|
||||||
|
}
|
||||||
|
if (!html.includes('apple-touch-icon')) {
|
||||||
|
fail('index.html 缺少 apple-touch-icon。');
|
||||||
|
}
|
||||||
|
if (!html.includes('msapplication-TileImage')) {
|
||||||
|
fail('index.html 缺少 Windows tile 图标声明。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(serviceWorkerPath)) {
|
||||||
|
fail('dist/sw.js 不存在,PWA 离线能力未生成。');
|
||||||
|
} else {
|
||||||
|
const sw = fs.readFileSync(serviceWorkerPath, 'utf8');
|
||||||
|
if (!sw.includes('sw-cache-manager.js')) {
|
||||||
|
fail('sw.js 未导入 sw-cache-manager.js。');
|
||||||
|
}
|
||||||
|
if (!sw.includes('index.html')) {
|
||||||
|
fail('sw.js 未包含导航回退,离线打开应用可能失败。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(path.join(distDir, 'sw-cache-manager.js'))) {
|
||||||
|
fail('dist/sw-cache-manager.js 不存在。');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.error('PWA 构建校验失败:');
|
||||||
|
for (const error of errors) {
|
||||||
|
console.error(`- ${error}`);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('PWA 构建校验通过。');
|
||||||
20
src/App.vue
20
src/App.vue
@ -72,6 +72,17 @@ const bgOverlayStyle = computed(() => ({
|
|||||||
|
|
||||||
let unwatchSettings = null;
|
let unwatchSettings = null;
|
||||||
|
|
||||||
|
const onBeforeInstallPrompt = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
window.deferredPwaPrompt = e;
|
||||||
|
window.dispatchEvent(new Event('pwa-prompt-ready'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAppInstalled = () => {
|
||||||
|
window.deferredPwaPrompt = null;
|
||||||
|
window.dispatchEvent(new Event('pwa-installed'));
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 应用保存的主题设置
|
// 应用保存的主题设置
|
||||||
const savedTheme = getSetting("theme.mode");
|
const savedTheme = getSetting("theme.mode");
|
||||||
@ -88,15 +99,14 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('beforeinstallprompt', (e) => {
|
window.addEventListener('beforeinstallprompt', onBeforeInstallPrompt);
|
||||||
e.preventDefault();
|
window.addEventListener('appinstalled', onAppInstalled);
|
||||||
window.deferredPwaPrompt = e;
|
|
||||||
window.dispatchEvent(new Event('pwa-prompt-ready'));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (unwatchSettings) unwatchSettings();
|
if (unwatchSettings) unwatchSettings();
|
||||||
|
window.removeEventListener('beforeinstallprompt', onBeforeInstallPrompt);
|
||||||
|
window.removeEventListener('appinstalled', onAppInstalled);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@ -361,15 +361,22 @@ const onDisplayModeChange = () => {
|
|||||||
refreshStates();
|
refreshStates();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onPwaInstalled = () => {
|
||||||
|
permissionStates.pwa.status = "granted";
|
||||||
|
refreshStates();
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
refreshStates();
|
refreshStates();
|
||||||
window.addEventListener('pwa-prompt-ready', onPromptReady);
|
window.addEventListener('pwa-prompt-ready', onPromptReady);
|
||||||
|
window.addEventListener('pwa-installed', onPwaInstalled);
|
||||||
displayModeMedia = window.matchMedia('(display-mode: standalone)');
|
displayModeMedia = window.matchMedia('(display-mode: standalone)');
|
||||||
displayModeMedia.addEventListener('change', onDisplayModeChange);
|
displayModeMedia.addEventListener('change', onDisplayModeChange);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('pwa-prompt-ready', onPromptReady);
|
window.removeEventListener('pwa-prompt-ready', onPromptReady);
|
||||||
|
window.removeEventListener('pwa-installed', onPwaInstalled);
|
||||||
if (displayModeMedia) {
|
if (displayModeMedia) {
|
||||||
displayModeMedia.removeEventListener('change', onDisplayModeChange);
|
displayModeMedia.removeEventListener('change', onDisplayModeChange);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,12 +38,18 @@ export default defineConfig({
|
|||||||
|
|
||||||
|
|
||||||
workbox: {
|
workbox: {
|
||||||
globPatterns: ['*'],
|
globPatterns: [
|
||||||
|
'**/*.{js,css,html,ico,png,svg,webmanifest,txt,json,woff2,ttf,mp3}',
|
||||||
|
],
|
||||||
navigateFallback: 'index.html',
|
navigateFallback: 'index.html',
|
||||||
|
navigateFallbackDenylist: [
|
||||||
|
/^\/api\//,
|
||||||
|
/^\/socket\.io\//,
|
||||||
|
],
|
||||||
runtimeCaching: [
|
runtimeCaching: [
|
||||||
{
|
{
|
||||||
urlPattern: ({ url, sameOrigin }) => {
|
urlPattern: ({ url, sameOrigin }) => {
|
||||||
return sameOrigin && url.pathname.endsWith('/assets/');
|
return sameOrigin && url.pathname.startsWith('/assets/');
|
||||||
},
|
},
|
||||||
handler: 'CacheFirst',
|
handler: 'CacheFirst',
|
||||||
options: {
|
options: {
|
||||||
@ -57,6 +63,22 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
urlPattern: ({ url, sameOrigin }) => {
|
||||||
|
return sameOrigin && url.pathname.startsWith('/sounds/');
|
||||||
|
},
|
||||||
|
handler: 'CacheFirst',
|
||||||
|
options: {
|
||||||
|
cacheName: 'sound-cache',
|
||||||
|
expiration: {
|
||||||
|
maxEntries: 80,
|
||||||
|
maxAgeSeconds: 60 * 60 * 24 * 30 // 30 天
|
||||||
|
},
|
||||||
|
cacheableResponse: {
|
||||||
|
statuses: [0, 200]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
urlPattern: ({ url, sameOrigin }) => {
|
urlPattern: ({ url, sameOrigin }) => {
|
||||||
return sameOrigin && url.pathname.startsWith('/pwa/');
|
return sameOrigin && url.pathname.startsWith('/pwa/');
|
||||||
@ -79,7 +101,7 @@ export default defineConfig({
|
|||||||
if (!sameOrigin) return false;
|
if (!sameOrigin) return false;
|
||||||
const path = url.pathname;
|
const path = url.pathname;
|
||||||
// 排除已经由其他规则处理的路径
|
// 排除已经由其他规则处理的路径
|
||||||
return !(path.includes('/assets/') || path.includes('/pwa/'));
|
return !(path.includes('/assets/') || path.includes('/pwa/') || path.includes('/sounds/'));
|
||||||
},
|
},
|
||||||
handler: 'NetworkFirst',
|
handler: 'NetworkFirst',
|
||||||
options: {
|
options: {
|
||||||
@ -98,19 +120,59 @@ export default defineConfig({
|
|||||||
additionalManifestEntries: [],
|
additionalManifestEntries: [],
|
||||||
clientsClaim: true,
|
clientsClaim: true,
|
||||||
skipWaiting: true,
|
skipWaiting: true,
|
||||||
importScripts: ['/sw-cache-manager.js']
|
importScripts: ['sw-cache-manager.js']
|
||||||
},
|
},
|
||||||
manifest: {
|
manifest: {
|
||||||
name: 'Classworks作业板',
|
id: '7C24F2B3.ClassworksPWA',
|
||||||
short_name: 'Classworks',
|
name: 'Classworks PWA',
|
||||||
description: '记录,查看并同步作业',
|
short_name: 'Classworks PWA',
|
||||||
|
description: '适用于班级大屏的作业板小工具,支持记录、查看并同步作业。',
|
||||||
theme_color: '#212121',
|
theme_color: '#212121',
|
||||||
background_color: '#212121',
|
background_color: '#212121',
|
||||||
|
lang: 'zh-CN',
|
||||||
|
dir: 'ltr',
|
||||||
display: 'standalone',
|
display: 'standalone',
|
||||||
|
display_override: ['window-controls-overlay', 'standalone', 'minimal-ui', 'fullscreen'],
|
||||||
start_url: './',
|
start_url: './',
|
||||||
edge_side_panel: {
|
scope: './',
|
||||||
default_path: './',
|
orientation: 'any',
|
||||||
|
categories: ['education', 'productivity', 'utilities'],
|
||||||
|
prefer_related_applications: false,
|
||||||
|
launch_handler: {
|
||||||
|
client_mode: 'navigate-existing',
|
||||||
},
|
},
|
||||||
|
screenshots: [
|
||||||
|
{
|
||||||
|
src: './images/1.jpeg',
|
||||||
|
sizes: '1901x1080',
|
||||||
|
type: 'image/jpeg',
|
||||||
|
form_factor: 'wide',
|
||||||
|
label: 'Classworks 作业板主界面',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: './images/2.jpeg',
|
||||||
|
sizes: '1901x1080',
|
||||||
|
type: 'image/jpeg',
|
||||||
|
form_factor: 'wide',
|
||||||
|
label: 'Classworks 设置与管理界面',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
file_handlers: [
|
||||||
|
{
|
||||||
|
action: './?file-handler=true',
|
||||||
|
accept: {
|
||||||
|
'application/octet-stream': ['.csb', '.csi'],
|
||||||
|
'application/x-classworks-backup': ['.csb'],
|
||||||
|
'application/x-classworks-install': ['.csi'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
protocol_handlers: [
|
||||||
|
{
|
||||||
|
protocol: 'cs',
|
||||||
|
url: './?protocol=%s',
|
||||||
|
},
|
||||||
|
],
|
||||||
icons: [
|
icons: [
|
||||||
{
|
{
|
||||||
src: './pwa/image/pwa-64x64.png',
|
src: './pwa/image/pwa-64x64.png',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user