Initial commit: RHINE LAB · ANALYSIS OS:三维界面与动效实验(本仓库不含个人归档索引数据)
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import fs from "node:fs/promises";
|
||||
|
||||
const requiredFields = [
|
||||
"id",
|
||||
"title",
|
||||
"en",
|
||||
"department",
|
||||
"category",
|
||||
"date",
|
||||
"lead",
|
||||
"clearance",
|
||||
"abstract",
|
||||
"source",
|
||||
];
|
||||
const isText = (value) => typeof value === "string" && value.trim().length > 0;
|
||||
|
||||
export function validateContent(content) {
|
||||
const errors = [];
|
||||
if (!content || typeof content !== "object" || Array.isArray(content)) {
|
||||
throw new Error("档案数据必须是 JSON 对象。");
|
||||
}
|
||||
for (const key of ["categories", "columns"]) {
|
||||
const names = content[key];
|
||||
if (!Array.isArray(names) || names.length !== 5 || !names.every(isText)) {
|
||||
errors.push(`${key}:必须包含五个非空分类名称`);
|
||||
} else if (new Set(names).size !== 5 || names.includes("全部档案")) {
|
||||
errors.push(`${key}:分类名称不能重复,也不能使用“全部档案”`);
|
||||
}
|
||||
}
|
||||
const categories = Array.isArray(content.categories)
|
||||
? content.categories
|
||||
: [];
|
||||
const columns = Array.isArray(content.columns) ? content.columns : [];
|
||||
if (
|
||||
categories.some((name) => !columns.includes(name)) ||
|
||||
columns.some((name) => !categories.includes(name))
|
||||
) {
|
||||
errors.push("categories 与 columns 必须包含相同的五个分类(顺序可以不同)");
|
||||
}
|
||||
const records = Array.isArray(content.records) ? content.records : [];
|
||||
// 每列条数不设上限:列内不足八条时阵列会循环重复,超过八条就向外扩充。
|
||||
// 唯一的下限是每列至少一条,否则那一列无可显示。
|
||||
if (records.length < 5) errors.push("records:至少需要每个分类一条档案");
|
||||
// 编号宽度随总量自适应(≤999 时仍为三位,与原版一致)。
|
||||
const idWidth = Math.max(3, String(records.length).length);
|
||||
const ids = new Set();
|
||||
records.forEach((record, index) => {
|
||||
const label = `records[${index}]`;
|
||||
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
||||
errors.push(`${label}:必须是档案对象`);
|
||||
return;
|
||||
}
|
||||
for (const key of requiredFields) {
|
||||
if (!isText(record[key])) errors.push(`${label}.${key}:必须是非空文本`);
|
||||
}
|
||||
const expectedId = `X-${String(index + 1).padStart(idWidth, "0")}`;
|
||||
if (record.id !== expectedId)
|
||||
errors.push(`${label}.id:应为 ${expectedId},编号须按顺序保持稳定`);
|
||||
if (ids.has(record.id)) errors.push(`${label}.id:重复编号 ${record.id}`);
|
||||
ids.add(record.id);
|
||||
if (!categories.includes(record.category))
|
||||
errors.push(`${label}.category:未知分类 ${record.category}`);
|
||||
if (
|
||||
!Array.isArray(record.findings) ||
|
||||
record.findings.length === 0 ||
|
||||
!record.findings.every(isText)
|
||||
) {
|
||||
errors.push(`${label}.findings:必须包含至少一条非空研究记录`);
|
||||
}
|
||||
try {
|
||||
const url = new URL(record.source);
|
||||
// 本地归档条目用 file: 指向真实目录;保留 http(s) 以兼容公开设定类条目。
|
||||
if (!["https:", "http:", "file:"].includes(url.protocol)) throw new Error();
|
||||
} catch {
|
||||
errors.push(`${label}.source:必须是有效的 HTTP、HTTPS 或 file 链接`);
|
||||
}
|
||||
});
|
||||
for (const name of columns) {
|
||||
const count = records.filter((record) => record?.category === name).length;
|
||||
if (count < 1) errors.push(`分类“${name}”:至少要有一条档案`);
|
||||
}
|
||||
if (errors.length)
|
||||
throw new Error(`档案数据校验失败:\n- ${errors.join("\n- ")}`);
|
||||
return content;
|
||||
}
|
||||
|
||||
export async function loadContent() {
|
||||
return validateContent(
|
||||
JSON.parse(
|
||||
await fs.readFile(
|
||||
new URL("../content/archives.json", import.meta.url),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function archiveText(r) {
|
||||
return `\uFEFFRHINE LAB · LOCAL ARCHIVE INDEX\nFILE ${r.id} / ${r.title}\n${r.en}\n\n归档位置:${r.department}\n编目范围:${r.date}\n来源:${r.lead}\n访问范围:${r.clearance}\n\n${r.abstract}\n\n研究记录\n${r.findings.map((f, i) => `${i + 1}. ${f}`).join("\n")}\n\n归档参考:${r.source}\n本文件是本地归档的编目摘要,由 scripts/archive-source.mjs 依据实际目录生成。\n`;
|
||||
}
|
||||
Reference in New Issue
Block a user