174 lines
11 KiB
JavaScript
174 lines
11 KiB
JavaScript
const fs = require('fs')
|
||
const path = require('path')
|
||
|
||
const projectRoot = path.resolve(__dirname, '..')
|
||
const outputDir = path.join(projectRoot, 'static', 'explain', 'unit-previews')
|
||
|
||
// 业务单元来自当前 H5 讲解页实际渲染的一级 outline,生成结果需与映射表同步。
|
||
const units = [
|
||
{ id: '7467940240901013508', hall: '地球厅', name: '第一单元:宜居地球', theme: 'earth' },
|
||
{ id: '7467940240901013509', hall: '地球厅', name: '第二单元:地史探源', theme: 'geology' },
|
||
{ id: '7467940240901013510', hall: '地球厅', name: '第三单元:华夏山水', theme: 'landscape' },
|
||
{ id: '7467940240901013511', hall: '地球厅', name: '第四单元:矿产资源——大地的馈赠', theme: 'mineral' },
|
||
{ id: '7467940240901013512', hall: '地球厅', name: '专题区:地心穿越+自然灾害', theme: 'disaster' },
|
||
{ id: '7467940240901013530', hall: '家园厅', name: '序厅', theme: 'home' },
|
||
{ id: '7467940240901013531', hall: '家园厅', name: '第一单元:天地共鉴', theme: 'home' },
|
||
{ id: '7467940240901013532', hall: '家园厅', name: '第二单元:万物共生', theme: 'ecology' },
|
||
{ id: '7467940240901013533', hall: '家园厅', name: '第三单元:家园共筑', theme: 'home' },
|
||
{ id: '7467940240901013534', hall: '家园厅', name: '尾厅', theme: 'home' },
|
||
{ id: '7467940240901013513', hall: '恐龙厅', name: '第一单元:初识恐龙', theme: 'dinosaur' },
|
||
{ id: '7467940240901013514', hall: '恐龙厅', name: '第二单元:恐龙解密', theme: 'dinosaur' },
|
||
{ id: '7467940240901013515', hall: '恐龙厅', name: '第三单元:恐龙时代', theme: 'dinosaur' },
|
||
{ id: '7467940240901013516', hall: '恐龙厅', name: '第四单元:粤赣育龙', theme: 'dinosaur' },
|
||
{ id: '7467940240901013517', hall: '恐龙厅', name: '第五单元:终结与新生', theme: 'dinosaur' },
|
||
{ id: '2060939176503275521', hall: '人类厅', name: '第一单元:什么是灵长类', theme: 'human' },
|
||
{ id: '2060939176637493249', hall: '人类厅', name: '第二单元:猿与猴', theme: 'human' },
|
||
{ id: '2060939176767516674', hall: '人类厅', name: '第三单元:丛林远足', theme: 'forest' },
|
||
{ id: '2060939176897540098', hall: '人类厅', name: '第四单元:与巨兽同行', theme: 'human' },
|
||
{ id: '2060939177027563521', hall: '人类厅', name: '第五单元:从燧石到硅基', theme: 'human' },
|
||
{ id: '7467940240901013523', hall: '生态厅', name: '前言', theme: 'ecology' },
|
||
{ id: '7467940240901013524', hall: '生态厅', name: '第二单元:各得其所——世界动物地理区系', theme: 'ecology' },
|
||
{ id: '7467940240901013525', hall: '生态厅', name: '第三单元:地球之窗——全球主要生态系统景观', theme: 'ecology' },
|
||
{ id: '7467940240901013526', hall: '生物厅', name: '序篇:生物的命名与分类', theme: 'biology' },
|
||
{ id: '7467940240901013527', hall: '生物厅', name: '第二单元:中国南方保护动物', theme: 'biology' },
|
||
{ id: '7467940240901013528', hall: '生物厅', name: '第三单元:生物基础知识', theme: 'biology' },
|
||
{ id: '7467940240901013529', hall: '生物厅', name: '第四单元:生物大观', theme: 'biology' },
|
||
{ id: '7467940240901013518', hall: '演化厅', name: '第零单元:基于博物学的进化思想', theme: 'evolution' },
|
||
{ id: '7467940240901013519', hall: '演化厅', name: '第一单元:经典进化学说——影响世界的理论', theme: 'evolution' },
|
||
{ id: '7467940240901013520', hall: '演化厅', name: '第二单元:遗传与变异——演化的内在驱动力', theme: 'evolution' },
|
||
{ id: '7467940240901013521', hall: '演化厅', name: '第三单元:演化方向的选择——适应', theme: 'evolution' },
|
||
{ id: '7467940240901013522', hall: '演化厅', name: '第四单元:三十八亿年的创造——从单细胞到智慧生命', theme: 'evolution' },
|
||
{ id: '7467940240901013505', hall: '宇宙厅', name: '第一单元:仰望苍穹——人类对宇宙认识的历程', theme: 'cosmos' },
|
||
{ id: '7467940240901013506', hall: '宇宙厅', name: '第二单元:拾级而观——等级宇宙结构', theme: 'cosmos' },
|
||
{ id: '7467940240901013507', hall: '宇宙厅', name: '第三单元:采石知天——行星科学与深空探测', theme: 'meteorite' }
|
||
]
|
||
|
||
const themes = {
|
||
earth: ['#d7e9d2', '#fbf6d8', '#2f6f5f', '#8eac50'],
|
||
geology: ['#ebe1d3', '#bfd5dc', '#674c3b', '#c08c45'],
|
||
landscape: ['#dcefd7', '#f4efd0', '#265d4e', '#92b65a'],
|
||
mineral: ['#e8e1d4', '#f1f4df', '#4b4f5a', '#c2a95b'],
|
||
disaster: ['#eee0d1', '#d7e3e0', '#5b3d35', '#d69d45'],
|
||
home: ['#edf2df', '#f5f2d7', '#334c45', '#b2bf47'],
|
||
ecology: ['#dbeedb', '#f4f2d1', '#2a5a49', '#87a946'],
|
||
dinosaur: ['#e7e0cd', '#dce8d5', '#4d4932', '#9a7844'],
|
||
human: ['#e8e0d7', '#eef0d6', '#4b3f36', '#b8884b'],
|
||
forest: ['#d9eddc', '#eff3d2', '#2f5f43', '#76a159'],
|
||
biology: ['#dff0df', '#eef4d4', '#2f604b', '#7bb36c'],
|
||
evolution: ['#e6e6d8', '#dbe9df', '#3f5147', '#9ea34a'],
|
||
cosmos: ['#dce5ef', '#f2f1d4', '#26354e', '#7b8bb7'],
|
||
meteorite: ['#dedfe5', '#f4efd5', '#343746', '#b19b56'],
|
||
fallback: ['#e8eadf', '#f7f5df', '#3f4d43', '#a8b64c']
|
||
}
|
||
|
||
const escapeXml = (value) => String(value)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
|
||
const shortTitle = (name) => name
|
||
.replace(/^第[一二三四五六七八九十零]+单元[::]?/, '')
|
||
.replace(/^专题区[::]?/, '')
|
||
.replace(/^序篇[::]?/, '')
|
||
.replace(/[——+].*$/, '')
|
||
.trim()
|
||
|
||
const motif = (theme, accent, dark) => {
|
||
if (theme === 'cosmos') {
|
||
return `
|
||
<circle cx="222" cy="58" r="24" fill="${accent}" opacity=".55"/>
|
||
<circle cx="278" cy="92" r="8" fill="${dark}" opacity=".8"/>
|
||
<path d="M42 132 C100 86 172 82 322 42" fill="none" stroke="${dark}" stroke-width="7" stroke-linecap="round" opacity=".28"/>
|
||
<path d="M222 58 C242 77 265 88 294 91" fill="none" stroke="${dark}" stroke-width="3" opacity=".65"/>
|
||
`
|
||
}
|
||
if (theme === 'meteorite' || theme === 'mineral' || theme === 'geology') {
|
||
return `
|
||
<path d="M240 34 L330 78 L298 160 L208 150 L178 78 Z" fill="${accent}" opacity=".72"/>
|
||
<path d="M238 52 L292 82 L278 130 L220 126 L204 84 Z" fill="none" stroke="${dark}" stroke-width="5" opacity=".55"/>
|
||
<path d="M36 146 C86 122 132 120 190 146 S284 174 348 134" fill="none" stroke="${dark}" stroke-width="8" opacity=".18"/>
|
||
`
|
||
}
|
||
if (theme === 'dinosaur') {
|
||
return `
|
||
<path d="M206 119 C220 74 258 48 308 60 C327 65 340 80 344 98 C318 88 292 94 276 116 C256 143 228 151 196 143 Z" fill="${accent}" opacity=".78"/>
|
||
<circle cx="312" cy="84" r="5" fill="${dark}" opacity=".85"/>
|
||
<path d="M198 142 C160 142 128 132 94 106" fill="none" stroke="${dark}" stroke-width="12" stroke-linecap="round" opacity=".36"/>
|
||
<path d="M92 106 C70 96 56 76 52 56" fill="none" stroke="${dark}" stroke-width="7" stroke-linecap="round" opacity=".3"/>
|
||
`
|
||
}
|
||
if (theme === 'human') {
|
||
return `
|
||
<circle cx="270" cy="72" r="28" fill="${accent}" opacity=".78"/>
|
||
<path d="M228 150 C232 108 246 93 270 93 C294 93 309 110 314 150" fill="none" stroke="${dark}" stroke-width="12" stroke-linecap="round" opacity=".36"/>
|
||
<path d="M58 142 C92 98 132 78 180 80" fill="none" stroke="${dark}" stroke-width="8" stroke-linecap="round" opacity=".22"/>
|
||
`
|
||
}
|
||
if (theme === 'biology' || theme === 'ecology' || theme === 'forest') {
|
||
return `
|
||
<path d="M248 142 C306 124 336 80 328 36 C284 42 244 78 230 130 Z" fill="${accent}" opacity=".72"/>
|
||
<path d="M230 132 C270 108 294 78 326 38" fill="none" stroke="${dark}" stroke-width="5" opacity=".45"/>
|
||
<path d="M70 154 C110 104 154 82 210 76" fill="none" stroke="${dark}" stroke-width="8" stroke-linecap="round" opacity=".18"/>
|
||
<circle cx="90" cy="70" r="18" fill="${accent}" opacity=".35"/>
|
||
`
|
||
}
|
||
if (theme === 'evolution') {
|
||
return `
|
||
<path d="M64 142 C94 94 124 76 160 98 C190 116 198 70 230 58 C264 44 300 68 326 118" fill="none" stroke="${dark}" stroke-width="9" stroke-linecap="round" opacity=".28"/>
|
||
<circle cx="84" cy="128" r="13" fill="${accent}" opacity=".8"/>
|
||
<circle cx="160" cy="98" r="15" fill="${accent}" opacity=".68"/>
|
||
<circle cx="232" cy="58" r="17" fill="${accent}" opacity=".58"/>
|
||
<circle cx="322" cy="116" r="20" fill="${accent}" opacity=".76"/>
|
||
`
|
||
}
|
||
return `
|
||
<path d="M64 154 C108 114 156 96 208 98 C266 101 300 76 340 42" fill="none" stroke="${dark}" stroke-width="9" stroke-linecap="round" opacity=".22"/>
|
||
<circle cx="272" cy="74" r="34" fill="${accent}" opacity=".65"/>
|
||
<path d="M246 120 C286 109 316 84 334 48" fill="none" stroke="${dark}" stroke-width="5" opacity=".4"/>
|
||
`
|
||
}
|
||
|
||
const svgFor = ({ id, hall, name, theme }) => {
|
||
const [start, end, dark, accent] = themes[theme] || themes.fallback
|
||
const title = shortTitle(name) || name
|
||
|
||
return `<svg xmlns="http://www.w3.org/2000/svg" width="640" height="360" viewBox="0 0 640 360" role="img" aria-label="${escapeXml(hall)} ${escapeXml(title)}">
|
||
<defs>
|
||
<linearGradient id="bg-${id}" x1="0" y1="0" x2="1" y2="1">
|
||
<stop offset="0" stop-color="${start}"/>
|
||
<stop offset="1" stop-color="${end}"/>
|
||
</linearGradient>
|
||
<pattern id="dots-${id}" width="34" height="34" patternUnits="userSpaceOnUse">
|
||
<circle cx="4" cy="4" r="2.4" fill="${dark}" opacity=".13"/>
|
||
</pattern>
|
||
</defs>
|
||
<rect width="640" height="360" rx="28" fill="url(#bg-${id})"/>
|
||
<rect width="640" height="360" fill="url(#dots-${id})" opacity=".75"/>
|
||
<path d="M0 274 C108 226 190 248 288 220 C410 184 518 204 640 146 L640 360 L0 360 Z" fill="#fff" opacity=".36"/>
|
||
<g transform="translate(232 56)">
|
||
${motif(theme, accent, dark)}
|
||
</g>
|
||
<rect x="38" y="42" width="126" height="36" rx="18" fill="#ffffff" opacity=".72"/>
|
||
<text x="101" y="66" text-anchor="middle" font-size="20" font-weight="700" fill="${dark}" font-family="HarmonyOS Sans SC, Microsoft YaHei, sans-serif">${escapeXml(hall)}</text>
|
||
<text x="40" y="166" font-size="30" font-weight="800" fill="${dark}" font-family="HarmonyOS Sans SC, Microsoft YaHei, sans-serif">${escapeXml(title.slice(0, 11))}</text>
|
||
<text x="40" y="207" font-size="20" font-weight="600" fill="${dark}" opacity=".72" font-family="HarmonyOS Sans SC, Microsoft YaHei, sans-serif">自然导览 · 图文讲解</text>
|
||
<path d="M40 252 L156 252" stroke="${accent}" stroke-width="9" stroke-linecap="round"/>
|
||
<path d="M40 276 L108 276" stroke="${dark}" stroke-width="5" stroke-linecap="round" opacity=".32"/>
|
||
</svg>
|
||
`
|
||
}
|
||
|
||
fs.mkdirSync(outputDir, { recursive: true })
|
||
units.forEach((unit) => {
|
||
fs.writeFileSync(path.join(outputDir, `${unit.id}.svg`), svgFor(unit), 'utf8')
|
||
})
|
||
fs.writeFileSync(path.join(outputDir, 'default.svg'), svgFor({
|
||
id: 'default',
|
||
hall: '讲解',
|
||
name: '自然博物馆导览',
|
||
theme: 'fallback'
|
||
}), 'utf8')
|
||
|
||
console.log(`Generated ${units.length + 1} explain unit preview SVG files in ${path.relative(projectRoot, outputDir)}`)
|