YD_TrainingCompetition_Api_Web/scripts/generate-static-manifest.mjs

124 lines
3.5 KiB
JavaScript
Raw Permalink Normal View History

2026-06-10 09:42:57 +08:00
/**
* 扫描 public/static 下各赛事文件夹,生成 manifest.json
* 约定子目录:背景、口令、赛事LOGO、秩序册
*/
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const ROOT = path.join(__dirname, '..', 'public', 'static')
/** 优先使用新目录名,兼容旧名称 */
const SUBDIR_CANDIDATES = {
backgrounds: ['背景', '背景图'],
passwords: ['口令'],
logos: ['赛事LOGO', '赛事logo'],
rosters: ['秩序册'],
}
function resolveSubdir(folderPath, candidates) {
for (const name of candidates) {
if (fs.existsSync(path.join(folderPath, name))) return name
}
return candidates[0]
}
const IMAGE_EXT = new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp', '.svg'])
const ROSTER_EXT = new Set(['.txt', '.csv', '.xlsx', '.xls'])
const PASSWORD_EXT = new Set(['.mp3'])
const LOGO_EXT = IMAGE_EXT
function listFiles(dir, allowedExt) {
if (!fs.existsSync(dir)) return []
return fs
.readdirSync(dir, { withFileTypes: true })
.filter(
(e) =>
e.isFile() &&
!e.name.startsWith('~$') &&
allowedExt.has(path.extname(e.name).toLowerCase()),
)
.map((e) => e.name)
.sort((a, b) => a.localeCompare(b, 'zh-CN'))
}
/** 使用原始路径;由前端 resolveStaticUrl 统一编码 */
function toPublicPath(folder, sub, file) {
return `/static/${folder}/${sub}/${file}`
}
function readEventMeta(folderPath) {
const metaPath = path.join(folderPath, 'event.json')
if (!fs.existsSync(metaPath)) return {}
try {
return JSON.parse(fs.readFileSync(metaPath, 'utf-8'))
} catch {
return {}
}
}
function scan() {
if (!fs.existsSync(ROOT)) {
fs.mkdirSync(ROOT, { recursive: true })
return { generatedAt: new Date().toISOString(), events: [] }
}
const events = []
for (const entry of fs.readdirSync(ROOT, { withFileTypes: true })) {
if (!entry.isDirectory()) continue
if (entry.name.startsWith('.')) continue
const folder = entry.name
const folderPath = path.join(ROOT, folder)
const meta = readEventMeta(folderPath)
const bgDir = resolveSubdir(folderPath, SUBDIR_CANDIDATES.backgrounds)
const pwdDir = resolveSubdir(folderPath, SUBDIR_CANDIDATES.passwords)
const logoDir = resolveSubdir(folderPath, SUBDIR_CANDIDATES.logos)
const rosterDir = resolveSubdir(folderPath, SUBDIR_CANDIDATES.rosters)
const backgrounds = listFiles(path.join(folderPath, bgDir), IMAGE_EXT).map((file) => ({
file,
path: toPublicPath(folder, bgDir, file),
}))
const passwords = listFiles(path.join(folderPath, pwdDir), PASSWORD_EXT).map((file) => ({
file,
path: toPublicPath(folder, pwdDir, file),
}))
const logos = listFiles(path.join(folderPath, logoDir), LOGO_EXT).map((file) => ({
file,
path: toPublicPath(folder, logoDir, file),
}))
const rosters = listFiles(path.join(folderPath, rosterDir), ROSTER_EXT).map((file) => ({
file,
path: toPublicPath(folder, rosterDir, file),
}))
events.push({
id: folder,
name: meta.displayName || folder,
folder,
backgrounds,
passwords,
logos,
rosters,
})
}
events.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'))
return {
generatedAt: new Date().toISOString(),
events,
}
}
const manifest = scan()
const outPath = path.join(ROOT, 'manifest.json')
fs.writeFileSync(outPath, JSON.stringify(manifest, null, 2), 'utf-8')
console.log(`[static-manifest] ${manifest.events.length} 个赛事 -> ${outPath}`)