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/cache/
.vitepress/public/ .vitepress/public/
server/auth.config.json 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, cleanUrls: true,
ignoreDeadLinks: true, ignoreDeadLinks: true,
head: [ 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.svg', type: 'image/svg+xml' }],
['link', { rel: 'icon', href: '/favicon.png', type: 'image/png', sizes: '512x512' }], ['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: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' }],
['link', { rel: 'manifest', href: '/site.webmanifest' }], ['link', { rel: 'manifest', href: '/site.webmanifest' }],
['meta', { name: 'theme-color', content: '#0f3460' }], ['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: 'apple-mobile-web-app-title', content: '道德经' }],
['meta', { name: 'application-name', content: 'DAO DE JING' }], ['meta', { name: 'application-name', content: '道德经' }],
], ],
themeConfig: { themeConfig: {
logo: { src: '/favicon.png', width: 24, height: 24 }, logo: { src: '/favicon.ico', width: 24, height: 24 },
nav: [ nav: [
{ text: '首页', link: '/' }, { text: '首页', link: '/' },
{ text: '五行', link: '/金、木、水、火、土 - 五行/' }, { text: '五行', link: '/金、木、水、火、土 - 五行/' },
BIN
View File
Binary file not shown.
+19 -6
View File
@@ -3,20 +3,33 @@
"short_name": "道德经", "short_name": "道德经",
"description": "传统文化典籍资料库", "description": "传统文化典籍资料库",
"start_url": "/", "start_url": "/",
"scope": "/",
"display": "standalone", "display": "standalone",
"background_color": "#1a1a2e", "background_color": "#1a1a2e",
"theme_color": "#0f3460", "theme_color": "#0f3460",
"icons": [ "icons": [
{ {
"src": "/apple-touch-icon.png", "src": "/favicon.ico",
"sizes": "512x512", "sizes": "48x48",
"type": "image/png", "type": "image/x-icon"
"purpose": "any maskable"
}, },
{ {
"src": "/favicon.png", "src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icon-512.png",
"sizes": "512x512", "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" "serve": "npm run build && npm run start"
}, },
"devDependencies": { "devDependencies": {
"sharp": "^0.33.5",
"to-ico": "^1.1.5",
"vitepress": "^1.6.3" "vitepress": "^1.6.3"
}, },
"dependencies": { "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() resetPublicDir()
await import('./generate-icons.mjs')
copyDir(path.join(root, 'assets'), path.join(publicDir, 'assets')) copyDir(path.join(root, 'assets'), path.join(publicDir, 'assets'))
copyDir(path.join(root, 'images'), path.join(publicDir, 'images')) copyDir(path.join(root, 'images'), path.join(publicDir, 'images'))
const siteDir = path.join(root, 'assets', 'site') const siteDir = path.join(root, 'assets', 'site')
if (fs.existsSync(siteDir)) { if (fs.existsSync(siteDir)) {
const skipInPublicRoot = new Set(['apple-touch-icon.png'])
for (const file of fs.readdirSync(siteDir)) { 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, 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') console.log('[prepare-public] assets、images 与站点图标已同步到 .vitepress/public')
+22
View File
@@ -89,11 +89,33 @@ function isAuthenticated(req) {
return Boolean(req.session?.user) 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) { function authGuard(req, res, next) {
if (req.path === '/login' || req.path.startsWith('/api/login')) { if (req.path === '/login' || req.path.startsWith('/api/login')) {
return next() return next()
} }
if (isPublicPath(req.path)) {
return next()
}
if (isAuthenticated(req)) { if (isAuthenticated(req)) {
return next() return next()
} }
+4
View File
@@ -5,10 +5,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0f3460" /> <meta name="theme-color" content="#0f3460" />
<meta name="apple-mobile-web-app-title" content="道德经" /> <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.svg" type="image/svg+xml" />
<link rel="icon" href="/favicon.png" type="image/png" sizes="512x512" /> <link rel="icon" href="/favicon.png" type="image/png" sizes="512x512" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" /> <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> <title>登录 · DAO DE JING</title>
<style> <style>
* { box-sizing: border-box; } * { box-sizing: border-box; }