/**
* 生成后端种子静态资源:D:/bsch/cms/upload/seed/
* - SVG 场景插画(校园主题分层构图:天空渐变/山峦/建筑剪影/树木/水面;无占位文字)
* - 最小合法 PDF(附件演示可真实打开)
* 轮播 1920×560 对齐主题包设计尺寸约定(themes 下的 theme.json bannerSize)。
* 用法:node seed-assets.cjs
*/
const fs = require("fs");
const path = require("path");
const OUT = "D:/bsch/cms/upload/seed";
fs.mkdirSync(OUT, { recursive: true });
function hashCode(s) {
let h = 0;
for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0;
return Math.abs(h);
}
/** 确定性伪随机(同名资源每次生成完全一致,保证截图可复现) */
function rng(seed) {
let s = seed || 1;
return () => {
s = (s * 1664525 + 1013904223) | 0;
return ((s >>> 8) & 0xffff) / 0xffff;
};
}
/** 一栋建筑剪影(含窗户点阵) */
function building(x, baseY, w, h, fill, winFill, r) {
const cols = Math.max(2, Math.floor(w / 26));
const rows = Math.max(2, Math.floor(h / 30));
let wins = "";
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
if (r() < 0.25) continue; // 部分窗户熄灭,更像真实夜景/黄昏
wins += ``;
}
}
return `${wins}`;
}
/** 一棵树(圆形树冠 + 矩形树干) */
function tree(x, baseY, scale, leaf, trunk) {
const r = 16 * scale;
return `
`;
}
/** 山峦剪影(两座) */
function mountains(w, baseY, c1, c2, r) {
const x1 = w * (0.05 + r() * 0.2);
const x2 = w * (0.55 + r() * 0.25);
const m1 = `M ${x1} ${baseY} L ${x1 + w * 0.22} ${baseY - 90 - r() * 70} L ${x1 + w * 0.46} ${baseY} Z`;
const m2 = `M ${x2} ${baseY} L ${x2 + w * 0.2} ${baseY - 70 - r() * 60} L ${x2 + w * 0.42} ${baseY} Z`;
return ``;
}
/** 云(椭圆组) */
function cloud(cx, cy, scale, fill) {
return `
`;
}
/** 完整校园场景(黄昏/清晨/白天三种氛围可选) */
function campusScene(name, w, h, palette, opts = {}) {
const seed = hashCode(name);
const r = rng(seed);
const p = {
sky1: palette[0], sky2: palette[1],
sun: palette[2] || "#fff",
far: palette[3] || "rgba(255,255,255,0.28)",
near: palette[4] || "rgba(20,40,70,0.35)",
ground: palette[5] || "#2c4a6e",
water: palette[6] || null,
building: palette[7] || "rgba(16,34,60,0.85)",
win: palette[8] || "rgba(255,224,150,0.9)",
leaf: palette[9] || "rgba(46,94,60,0.9)",
trunk: palette[10] || "rgba(66,48,30,0.9)",
cloud: palette[11] || "rgba(255,255,255,0.5)",
};
const horizon = h * (opts.horizon || 0.72);
let out = ``;
return out;
}
/** 校徽(圆形徽章:书本 + 星 + 环形留白,透明背景适配页头) */
function emblem() {
return ``;
}
/* ---- 轮播(1920×560,对齐 theme.json bannerSize 约定)---- */
fs.writeFileSync(path.join(OUT, "banner-1.svg"),
campusScene("banner-1", 1920, 560, ["#1c3f68", "#7fb2e5", "#fff2c9", "rgba(255,255,255,0.22)", "rgba(20,45,80,0.30)", "#27517f"], { water: true, buildingX: 0.40 }));
fs.writeFileSync(path.join(OUT, "banner-2.svg"),
campusScene("banner-2", 1920, 560, ["#0e4f4a", "#8fd0b0", "#fdeec1", "rgba(255,255,255,0.20)", "rgba(10,60,50,0.32)", "#1f6b52", "#2f8a68", "rgba(8,52,44,0.88)", "rgba(255,240,190,0.92)", "rgba(24,92,64,0.95)", "rgba(52,40,24,0.95)", "rgba(255,255,255,0.42)"], { water: true, buildingX: 0.46 }));
fs.writeFileSync(path.join(OUT, "banner-3.svg"),
campusScene("banner-3", 1920, 560, ["#3a1f5d", "#e08a6d", "#ffd9a0", "rgba(255,214,170,0.30)", "rgba(52,24,70,0.38)", "#4a2a63", "#6b3d84", "rgba(30,14,48,0.9)", "rgba(255,226,170,0.95)", "rgba(96,60,110,0.9)", "rgba(50,32,24,0.9)", "rgba(255,235,210,0.4)"], { horizon: 0.78, buildingX: 0.36 }));
fs.writeFileSync(path.join(OUT, "banner-4.svg"),
campusScene("banner-4", 1920, 560, ["#141c3a", "#4f7fd9", "#cfe0ff", "rgba(190,210,255,0.20)", "rgba(12,20,48,0.42)", "#1b2a52"], { horizon: 0.75, buildingX: 0.44 }));
/* ---- 文章封面(640×360)---- */
fs.writeFileSync(path.join(OUT, "cover-运动会.svg"),
campusScene("cover-sports", 640, 360, ["#0d3b66", "#69a8d8", "#fff3c4", "rgba(255,255,255,0.24)", "rgba(10,40,70,0.34)", "#1c5080", "#2e6da4", "rgba(10,34,62,0.9)", "rgba(255,230,160,0.92)", "rgba(30,90,70,0.95)", "rgba(50,38,22,0.95)", "rgba(255,255,255,0.5)"], { water: true }));
fs.writeFileSync(path.join(OUT, "cover-迎新晚会.svg"),
campusScene("cover-gala", 640, 360, ["#2a0e4e", "#c25b8a", "#ffe3a0", "rgba(255,200,220,0.26)", "rgba(40,12,64,0.4)", "#3c1a5c", "#57297a", "rgba(26,8,44,0.92)", "rgba(255,224,170,0.95)", "rgba(120,60,130,0.9)", "rgba(48,30,20,0.9)", "rgba(255,230,240,0.4)"], { horizon: 0.8, buildingX: 0.42 }));
fs.writeFileSync(path.join(OUT, "cover-创新创业.svg"),
campusScene("cover-inno", 640, 360, ["#03363d", "#4fd1c5", "#eafff9", "rgba(220,255,250,0.22)", "rgba(4,50,56,0.36)", "#065a63", "#0b7d85", "rgba(4,42,48,0.9)", "rgba(210,255,245,0.92)", "rgba(10,92,80,0.9)", "rgba(40,40,22,0.9)", "rgba(235,255,252,0.4)"], { water: true }));
/* ---- 正文配图(640×360)---- */
["art-1", "art-2", "art-3", "art-4", "art-5", "art-6"].forEach((n, i) => {
const palettes = [
["#1c3f68", "#7fb2e5", "#fff2c9", "rgba(255,255,255,0.22)", "rgba(20,45,80,0.30)", "#27517f"],
["#5d2019", "#e8a06a", "#fff0d0", "rgba(255,220,190,0.26)", "rgba(80,28,20,0.34)", "#7a3020", null, "rgba(70,24,16,0.9)", "rgba(255,235,200,0.92)", "rgba(160,70,40,0.9)", "rgba(56,36,20,0.9)", "rgba(255,240,220,0.42)"],
["#23306b", "#9fb4f0", "#f4f6ff", "rgba(230,235,255,0.24)", "rgba(24,32,86,0.32)", "#303f8c", "#4653a3", "rgba(18,24,66,0.9)", "rgba(235,240,255,0.92)", "rgba(50,70,140,0.9)", "rgba(44,36,26,0.9)", "rgba(245,248,255,0.45)"],
["#0e4f4a", "#8fd0b0", "#fdeec1", "rgba(255,255,255,0.20)", "rgba(10,60,50,0.32)", "#1f6b52", "#2f8a68", "rgba(8,52,44,0.88)", "rgba(255,240,190,0.92)", "rgba(24,92,64,0.95)", "rgba(52,40,24,0.95)", "rgba(255,255,255,0.42)"],
["#4a2a63", "#d9a0c8", "#ffe9f4", "rgba(255,230,245,0.24)", "rgba(60,32,82,0.34)", "#5c3675", "#754a8e", "rgba(44,22,60,0.9)", "rgba(255,235,245,0.92)", "rgba(110,70,120,0.9)", "rgba(50,32,22,0.9)", "rgba(255,245,250,0.42)"],
["#3d2b1f", "#d9b38c", "#fff6e3", "rgba(255,240,215,0.24)", "rgba(60,42,28,0.36)", "#5a4030", "#71523c", "rgba(52,36,24,0.9)", "rgba(255,240,210,0.92)", "rgba(120,90,50,0.9)", "rgba(46,32,18,0.9)", "rgba(255,250,235,0.42)"],
];
fs.writeFileSync(path.join(OUT, n + ".svg"), campusScene(n, 640, 360, palettes[i], { water: i % 2 === 0 }));
});
/* ---- 校徽 + 友链 logo ---- */
fs.writeFileSync(path.join(OUT, "logo-univ.svg"), emblem());
for (let i = 1; i <= 8; i++) {
const hues = [210, 160, 20, 270, 120, 340, 40, 190];
const hue = hues[i - 1];
fs.writeFileSync(path.join(OUT, `logo-${i}.svg`), ``);
}
/* ---- 最小 PDF(附件) ---- */
function miniPdf(title) {
const text = `BT /F1 18 Tf 60 700 Td (${title}) Tj ET`;
const content = `1 0 0 1 0 0 cm\n${text}`;
const objs = [
"<< /Type /Catalog /Pages 2 0 R >>",
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>",
`<< /Length ${content.length} >>\nstream\n${content}\nendstream`,
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
];
let pdf = "%PDF-1.4\n";
const offsets = [];
objs.forEach((o, i) => {
offsets.push(pdf.length);
pdf += `${i + 1} 0 obj\n${o}\nendobj\n`;
});
const xref = pdf.length;
pdf += `xref\n0 ${objs.length + 1}\n0000000000 65535 f \n`;
offsets.forEach(off => {
pdf += String(off).padStart(10, "0") + " 00000 n \n";
});
pdf += `trailer\n<< /Size ${objs.length + 1} /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF`;
return Buffer.from(pdf, "latin1");
}
fs.writeFileSync(path.join(OUT, "2026校历.pdf"), miniPdf("2026 QiulixiaoLi (seed)"));
fs.writeFileSync(path.join(OUT, "运动会秩序册.pdf"), miniPdf("YunDongHui ZhiXuCe (seed)"));
fs.writeFileSync(path.join(OUT, "迎新晚会节目单.pdf"), miniPdf("YingXin WanHui (seed)"));
console.log("seed assets written to", OUT, ":", fs.readdirSync(OUT).length, "files");