feat: 初始化西安旅行记忆互动地图游戏项目
完成项目基础搭建:配置 Vite 开发构建环境,集成 Phaser 3 与虚拟摇杆插件,实现地图漫游、景点触发、拍立得弹窗、进度存档等核心功能,添加占位资源与批量生成脚本,配置项目元数据与忽略规则。
This commit is contained in:
commit
a404e42ca8
42 files changed
+5489
No files matched your search
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @fileoverview 占位照片生成脚本。
|
||||
*
|
||||
* 文件用途:
|
||||
* - 依据 src/data/spots.js 的照片清单,批量生成 4:3 手账风占位 SVG,
|
||||
* 供照片未就绪期的开发与演示使用。
|
||||
*
|
||||
* 核心职责:
|
||||
* 1. 读取景点配置,为每张照片生成 `public/assets/photos/<id>_<序号>.svg`;
|
||||
* 2. 占位视觉:暖色渐变 + 景点图标 + 名称与 caption 水印 + 虚线相框;
|
||||
* 3. 文件已存在时跳过(--force 强制重生成)。
|
||||
*
|
||||
* 主要流程:
|
||||
* node scripts/gen-placeholder-photos.mjs [--force] → 遍历 spots →
|
||||
* 写出 SVG → 控制台汇总生成结果。
|
||||
*
|
||||
* 用法:
|
||||
* node scripts/gen-placeholder-photos.mjs # 仅生成缺失文件
|
||||
* node scripts/gen-placeholder-photos.mjs --force # 强制重新生成全部
|
||||
*/
|
||||
|
||||
import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { SPOTS } from '../src/data/spots.js';
|
||||
|
||||
/** 脚本所在目录。 */
|
||||
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
||||
/** 项目根目录。 */
|
||||
const PROJECT_ROOT = resolve(SCRIPT_DIR, '..');
|
||||
/** 照片输出目录。 */
|
||||
const OUT_DIR = resolve(PROJECT_ROOT, 'public/assets/photos');
|
||||
|
||||
/** 占位卡宽度(像素)。 */
|
||||
const W = 1200;
|
||||
/** 占位卡高度(像素,4:3)。 */
|
||||
const H = 900;
|
||||
|
||||
/**
|
||||
* 按景点 id 生成稳定的色相(0-360)。
|
||||
*
|
||||
* @param {string} id 景点 id。
|
||||
* @returns {number} 色相角度。
|
||||
*/
|
||||
function hueOf(id) {
|
||||
let hash = 0;
|
||||
for (const ch of id) {
|
||||
hash = (hash * 31 + ch.charCodeAt(0)) % 360;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成单张占位照片 SVG 内容。
|
||||
*
|
||||
* @param {Object} spot 景点配置。
|
||||
* @param {Object} photo 照片配置(含 caption)。
|
||||
* @returns {string} SVG 文本。
|
||||
*/
|
||||
function buildSvg(spot, photo) {
|
||||
const hue = hueOf(spot.id);
|
||||
const icon = spot.icon;
|
||||
const escapedCaption = photo.caption
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>');
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="hsl(${hue} 38% 78%)" />
|
||||
<stop offset="1" stop-color="hsl(${(hue + 24) % 360} 30% 64%)" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="${W}" height="${H}" fill="url(#bg)" />
|
||||
<rect x="36" y="36" width="${W - 72}" height="${H - 72}" fill="none"
|
||||
stroke="rgba(255,250,235,0.7)" stroke-width="4" stroke-dasharray="18 12" rx="10" />
|
||||
<text x="${W / 2}" y="${H / 2 - 44}" font-size="150" text-anchor="middle">${icon}</text>
|
||||
<text x="${W / 2}" y="${H / 2 + 92}" font-family="Kaiti SC, KaiTi, serif" font-size="64"
|
||||
fill="rgba(70,50,28,0.82)" text-anchor="middle">${escapedCaption}</text>
|
||||
<text x="${W / 2}" y="${H / 2 + 158}" font-family="Kaiti SC, KaiTi, serif" font-size="36"
|
||||
fill="rgba(70,50,28,0.55)" text-anchor="middle" letter-spacing="8">· 占位照片 ·</text>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 脚本入口:遍历景点生成占位照片。
|
||||
*
|
||||
* @returns {Promise<void>} 完成后输出汇总。
|
||||
*/
|
||||
async function main() {
|
||||
const force = process.argv.includes('--force');
|
||||
mkdirSync(OUT_DIR, { recursive: true });
|
||||
|
||||
let created = 0;
|
||||
let skipped = 0;
|
||||
for (const spot of SPOTS) {
|
||||
spot.photos.forEach((photo, index) => {
|
||||
const fileName = photo.src.split('/').pop();
|
||||
const outPath = resolve(OUT_DIR, fileName);
|
||||
if (!force && existsSync(outPath)) {
|
||||
skipped += 1;
|
||||
return;
|
||||
}
|
||||
writeFileSync(outPath, buildSvg(spot, photo), 'utf8');
|
||||
created += 1;
|
||||
console.log(`[photos] 生成 ${fileName}`);
|
||||
});
|
||||
}
|
||||
console.log(`[photos] 完成:新建 ${created} 张,跳过已存在 ${skipped} 张`);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('[photos] 生成失败:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in new issue
Block a user