Fix PWA shortcut icon on Windows

Allow unauthenticated access to favicon and manifest, generate favicon.ico, and add multi-size manifest icons.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-05 17:33:55 +08:00
parent df619a5d8e
commit a8be586652
10 changed files with 1681 additions and 10 deletions
+4
View File
@@ -38,4 +38,8 @@ node_modules/
.vitepress/cache/
.vitepress/public/
server/auth.config.json
assets/site/favicon.ico
assets/site/favicon.png
assets/site/icon-192.png
assets/site/icon-512.png
+6 -2
View File
@@ -26,16 +26,20 @@ export default defineConfig({
cleanUrls: true,
ignoreDeadLinks: true,
head: [
['link', { rel: 'icon', href: '/favicon.ico', sizes: 'any' }],
['link', { rel: 'shortcut icon', href: '/favicon.ico' }],
['link', { rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' }],
['link', { rel: 'icon', href: '/favicon.png', type: 'image/png', sizes: '512x512' }],
['link', { rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' }],
['link', { rel: 'manifest', href: '/site.webmanifest' }],
['meta', { name: 'theme-color', content: '#0f3460' }],
['meta', { name: 'msapplication-TileColor', content: '#0f3460' }],
['meta', { name: 'msapplication-TileImage', content: '/apple-touch-icon.png' }],
['meta', { name: 'apple-mobile-web-app-title', content: '道德经' }],
['meta', { name: 'application-name', content: 'DAO DE JING' }],
['meta', { name: 'application-name', content: '道德经' }],
],
themeConfig: {
logo: { src: '/favicon.png', width: 24, height: 24 },
logo: { src: '/favicon.ico', width: 24, height: 24 },
nav: [
{ text: '首页', link: '/' },
{ text: '五行', link: '/金、木、水、火、土 - 五行/' },
BIN
View File
Binary file not shown.
+19 -6
View File
@@ -3,20 +3,33 @@
"short_name": "道德经",
"description": "传统文化典籍资料库",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#1a1a2e",
"theme_color": "#0f3460",
"icons": [
{
"src": "/apple-touch-icon.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
"src": "/favicon.ico",
"sizes": "48x48",
"type": "image/x-icon"
},
{
"src": "/favicon.png",
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png"
"type": "image/png",
"purpose": "any"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
+1586
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -11,6 +11,8 @@
"serve": "npm run build && npm run start"
},
"devDependencies": {
"sharp": "^0.33.5",
"to-ico": "^1.1.5",
"vitepress": "^1.6.3"
},
"dependencies": {
+29
View File
@@ -0,0 +1,29 @@
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import sharp from 'sharp'
import toIco from 'to-ico'
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const siteDir = path.join(root, 'assets', 'site')
const sourcePng = path.join(siteDir, 'apple-touch-icon.png')
if (!fs.existsSync(sourcePng)) {
console.warn('[generate-icons] 跳过:未找到 apple-touch-icon.png')
process.exit(0)
}
async function resizePng(size) {
return sharp(sourcePng).resize(size, size).png().toBuffer()
}
const icoSizes = [16, 32, 48, 64, 128, 256]
const icoBuffers = await Promise.all(icoSizes.map((size) => resizePng(size)))
const icoBuffer = await toIco(icoBuffers)
fs.writeFileSync(path.join(siteDir, 'favicon.ico'), icoBuffer)
fs.writeFileSync(path.join(siteDir, 'icon-192.png'), await resizePng(192))
fs.writeFileSync(path.join(siteDir, 'icon-512.png'), await resizePng(512))
fs.writeFileSync(path.join(siteDir, 'favicon.png'), await resizePng(512))
console.log('[generate-icons] 已生成 favicon.ico、icon-192.png、icon-512.png')
+7
View File
@@ -28,14 +28,21 @@ function resetPublicDir() {
}
resetPublicDir()
await import('./generate-icons.mjs')
copyDir(path.join(root, 'assets'), path.join(publicDir, 'assets'))
copyDir(path.join(root, 'images'), path.join(publicDir, 'images'))
const siteDir = path.join(root, 'assets', 'site')
if (fs.existsSync(siteDir)) {
const skipInPublicRoot = new Set(['apple-touch-icon.png'])
for (const file of fs.readdirSync(siteDir)) {
if (skipInPublicRoot.has(file)) continue
fs.copyFileSync(path.join(siteDir, file), path.join(publicDir, file))
}
fs.copyFileSync(
path.join(siteDir, 'icon-512.png'),
path.join(publicDir, 'apple-touch-icon.png'),
)
}
console.log('[prepare-public] assets、images 与站点图标已同步到 .vitepress/public')
+22
View File
@@ -89,11 +89,33 @@ function isAuthenticated(req) {
return Boolean(req.session?.user)
}
const PUBLIC_PATHS = new Set([
'/favicon.ico',
'/favicon.png',
'/favicon.svg',
'/apple-touch-icon.png',
'/icon-192.png',
'/icon-512.png',
'/site.webmanifest',
'/vp-icons.css',
'/hashmap.json',
])
function isPublicPath(reqPath) {
if (PUBLIC_PATHS.has(reqPath)) return true
if (reqPath.startsWith('/assets/')) return true
return false
}
function authGuard(req, res, next) {
if (req.path === '/login' || req.path.startsWith('/api/login')) {
return next()
}
if (isPublicPath(req.path)) {
return next()
}
if (isAuthenticated(req)) {
return next()
}
+4
View File
@@ -5,10 +5,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0f3460" />
<meta name="apple-mobile-web-app-title" content="道德经" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="icon" href="/favicon.png" type="image/png" sizes="512x512" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="msapplication-TileColor" content="#0f3460" />
<meta name="msapplication-TileImage" content="/apple-touch-icon.png" />
<title>登录 · DAO DE JING</title>
<style>
* { box-sizing: border-box; }