124 lines
3.5 KiB
JavaScript
124 lines
3.5 KiB
JavaScript
|
|
/**
|
|||
|
|
* 扫描 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}`)
|