1. 新增软著登记所需的源程序生成脚本、docx生成脚本与依赖配置 2. 新增像素大勇者游戏软件V1.0设计说明书与填表指引文档 3. 更新技能配置相关的prefab与meta文件,清理冗余的up状态预制件 4. 修复部分技能buff的颜色与精灵帧引用,更新动画资源引用 5. 新增两个技能图标资源文件
234 lines
8.0 KiB
JavaScript
234 lines
8.0 KiB
JavaScript
/**
|
||
* 软著材料 docx 生成脚本
|
||
* 1) 源程序:解析已生成的 txt,按显示宽度折行(每行 ≤84 列),每页 60 行,
|
||
* 页眉左侧"软件全称 版本号"、右侧 Word 自动页码域"第 X 页 共 Y 页"。
|
||
* 2) 设计说明书:解析 md,生成封面 + 章节正文 docx。
|
||
* 用法:node gen_docx.mjs
|
||
*/
|
||
import fs from 'fs';
|
||
import path from 'path';
|
||
import {
|
||
Document, Packer, Paragraph, TextRun, Header, AlignmentType,
|
||
PageNumber, TabStopType, HeadingLevel,
|
||
} from 'docx';
|
||
|
||
const DIR = import.meta.dirname;
|
||
const SRC_TXT = path.join(DIR, '像素大勇者游戏软件_V1.0_源程序.txt');
|
||
const MANUAL_MD = path.join(DIR, '像素大勇者游戏软件_V1.0_设计说明书.md');
|
||
const OUT_SRC = path.join(DIR, '像素大勇者游戏软件_V1.0_源程序.docx');
|
||
const OUT_MANUAL = path.join(DIR, '像素大勇者游戏软件_V1.0_设计说明书.docx');
|
||
|
||
const SOFTWARE = '像素大勇者游戏软件 V1.0';
|
||
const LINES_PER_PAGE = 60;
|
||
const FOLD_WIDTH = 84; // A4 折行限制:显示列宽(ASCII=1 列,中文=2 列)
|
||
|
||
// A4 纵向,边距:上下 1.27cm,左右 1.5cm
|
||
const PAGE = {
|
||
size: { width: 11906, height: 16838 },
|
||
margin: { top: 720, bottom: 720, left: 850, right: 850, header: 400 },
|
||
};
|
||
const TEXT_WIDTH = PAGE.size.width - PAGE.margin.left - PAGE.margin.right;
|
||
|
||
/** 统一页眉:左侧软件名+版本号,右侧自动页码域 */
|
||
function makeHeader() {
|
||
return new Header({
|
||
children: [
|
||
new Paragraph({
|
||
tabStops: [{ type: TabStopType.RIGHT, position: TEXT_WIDTH }],
|
||
children: [
|
||
new TextRun({ text: SOFTWARE, size: 18, font: { ascii: 'Consolas', eastAsia: 'SimSun' } }),
|
||
new TextRun({
|
||
children: ['\t第 ', PageNumber.CURRENT, ' 页 共 ', PageNumber.TOTAL_PAGES, ' 页'],
|
||
size: 18, font: { ascii: 'Consolas', eastAsia: 'SimSun' },
|
||
}),
|
||
],
|
||
}),
|
||
],
|
||
});
|
||
}
|
||
|
||
// ============================================================
|
||
// 一、源程序 docx
|
||
// ============================================================
|
||
|
||
/** 字符显示宽度(ASCII 记 1 列,全角/中文记 2 列) */
|
||
const charWidth = (ch) => (ch.charCodeAt(0) > 0xff ? 2 : 1);
|
||
|
||
/** 超宽行按显示列宽折行,续行缩进 4 空格 */
|
||
function foldLine(line, width = FOLD_WIDTH) {
|
||
if ([...line].reduce((w, c) => w + charWidth(c), 0) <= width) return [line];
|
||
const out = [];
|
||
let cur = '', curW = 0;
|
||
for (const ch of line) {
|
||
const cw = charWidth(ch);
|
||
if (curW + cw > width) {
|
||
out.push(cur);
|
||
cur = ' ' + ch; // 续行缩进
|
||
curW = 4 + cw;
|
||
} else {
|
||
cur += ch;
|
||
curW += cw;
|
||
}
|
||
}
|
||
if (cur) out.push(cur);
|
||
return out;
|
||
}
|
||
|
||
/** 解析 txt:剔除页眉/分隔线/页间空行,还原连续代码行 */
|
||
function parseSourceTxt() {
|
||
const raw = fs.readFileSync(SRC_TXT, 'utf8').split('\n');
|
||
const codeLines = [];
|
||
let count = 0;
|
||
for (const line of raw) {
|
||
if (line.startsWith('像素大勇者游戏软件')) continue; // 页眉行
|
||
if (/^=+$/.test(line.trim()) && line.trim() !== '') continue; // 分隔线
|
||
if (count > 0 && count % LINES_PER_PAGE === 0 && line.trim() === '') continue; // 页间空行
|
||
codeLines.push(line);
|
||
count++;
|
||
}
|
||
return codeLines;
|
||
}
|
||
|
||
async function buildSourceDocx() {
|
||
const codeLines = parseSourceTxt();
|
||
|
||
// 折行后按每页 50 个显示行分页
|
||
const folded = codeLines.flatMap((l) => foldLine(l.replace(/\t/g, ' ')));
|
||
const totalPages = Math.ceil(folded.length / LINES_PER_PAGE);
|
||
const foldedPages = Math.ceil(codeLines.length / LINES_PER_PAGE); // 折行前理论页数(对照 txt)
|
||
|
||
const children = folded.map((line, i) =>
|
||
new Paragraph({
|
||
pageBreakBefore: i > 0 && i % LINES_PER_PAGE === 0,
|
||
spacing: { after: 0, line: 230, lineRule: 'auto' },
|
||
children: [
|
||
new TextRun({
|
||
text: line,
|
||
size: 19, // 9.5pt
|
||
font: { ascii: 'Consolas', eastAsia: 'SimSun' },
|
||
}),
|
||
],
|
||
})
|
||
);
|
||
|
||
const doc = new Document({
|
||
sections: [{
|
||
properties: { page: PAGE },
|
||
headers: { default: makeHeader() },
|
||
children,
|
||
}],
|
||
});
|
||
|
||
await Packer.toBuffer(doc).then((buf) => fs.writeFileSync(OUT_SRC, buf));
|
||
|
||
console.log('=== 源程序 docx 生成完成 ===');
|
||
console.log(`代码行: ${codeLines.length},折行后显示行: ${folded.length}`);
|
||
console.log(`折行前页数(对照txt): ${foldedPages},docx 实际页数: ${totalPages}`);
|
||
console.log(`输出: ${OUT_SRC}`);
|
||
}
|
||
|
||
// ============================================================
|
||
// 二、设计说明书 docx
|
||
// ============================================================
|
||
|
||
const BODY_FONT = { ascii: 'Times New Roman', eastAsia: 'SimSun' };
|
||
const HEAD_FONT = { ascii: 'Arial', eastAsia: 'SimHei' };
|
||
|
||
/** 解析 **粗体** 行内标记 */
|
||
function parseRuns(text, base = {}) {
|
||
return text
|
||
.split(/(\*\*[^*]+\*\*)/g)
|
||
.filter(Boolean)
|
||
.map((p) =>
|
||
p.startsWith('**')
|
||
? new TextRun({ text: p.slice(2, -2), bold: true, font: BODY_FONT, ...base })
|
||
: new TextRun({ text: p, font: BODY_FONT, ...base })
|
||
);
|
||
}
|
||
|
||
async function buildManualDocx() {
|
||
const md = fs.readFileSync(MANUAL_MD, 'utf8').split('\n');
|
||
const children = [];
|
||
let coverDone = false;
|
||
|
||
for (const rawLine of md) {
|
||
const line = rawLine.trimEnd();
|
||
|
||
if (!coverDone) {
|
||
// 封面区:软件名 / 文档名 / 著作权人 / 日期,居中排布,遇 --- 结束封面
|
||
if (line.startsWith('# ')) {
|
||
children.push(new Paragraph({
|
||
alignment: AlignmentType.CENTER,
|
||
spacing: { before: 240, after: 240 },
|
||
children: [new TextRun({ text: line.slice(2), bold: true, size: 52, font: HEAD_FONT })],
|
||
}));
|
||
} else if (/^(著作权人|开发完成日期):/.test(line)) {
|
||
children.push(new Paragraph({
|
||
alignment: AlignmentType.CENTER,
|
||
spacing: { before: 120, after: 120 },
|
||
children: [new TextRun({ text: line, size: 24, font: BODY_FONT })],
|
||
}));
|
||
} else if (line.trim() === '---') {
|
||
coverDone = true;
|
||
// 封面独立成页
|
||
children.push(new Paragraph({ children: [new TextRun({ text: '', size: 2 })], pageBreakBefore: false }));
|
||
children.push(new Paragraph({ pageBreakBefore: true, children: [] }));
|
||
}
|
||
continue;
|
||
}
|
||
|
||
// 正文区
|
||
if (line.trim() === '' || line.trim() === '---') continue;
|
||
if (line.startsWith('## ')) {
|
||
children.push(new Paragraph({
|
||
heading: HeadingLevel.HEADING_1,
|
||
spacing: { before: 240, after: 120 },
|
||
children: [new TextRun({ text: line.slice(3), bold: true, size: 30, font: HEAD_FONT, color: '000000' })],
|
||
}));
|
||
} else if (line.startsWith('### ')) {
|
||
children.push(new Paragraph({
|
||
heading: HeadingLevel.HEADING_2,
|
||
spacing: { before: 180, after: 100 },
|
||
children: [new TextRun({ text: line.slice(4), bold: true, size: 26, font: HEAD_FONT, color: '000000' })],
|
||
}));
|
||
} else if (line.startsWith('【建议插入截图')) {
|
||
children.push(new Paragraph({
|
||
spacing: { before: 60, after: 60 },
|
||
indent: { left: 420 },
|
||
children: [new TextRun({ text: line, italics: true, color: '808080', size: 21, font: BODY_FONT })],
|
||
}));
|
||
} else if (/^\d+\.\s/.test(line)) {
|
||
children.push(new Paragraph({
|
||
spacing: { after: 100 },
|
||
indent: { left: 420 },
|
||
children: parseRuns(line, { size: 21 }),
|
||
}));
|
||
} else {
|
||
children.push(new Paragraph({
|
||
spacing: { after: 100, line: 300 },
|
||
children: parseRuns(line, { size: 21 }),
|
||
}));
|
||
}
|
||
}
|
||
|
||
const doc = new Document({
|
||
styles: {
|
||
default: {
|
||
document: { run: { font: BODY_FONT, size: 21 } },
|
||
},
|
||
},
|
||
sections: [{
|
||
properties: { page: PAGE },
|
||
headers: { default: makeHeader() },
|
||
children,
|
||
}],
|
||
});
|
||
|
||
await Packer.toBuffer(doc).then((buf) => fs.writeFileSync(OUT_MANUAL, buf));
|
||
console.log('=== 设计说明书 docx 生成完成 ===');
|
||
console.log(`输出: ${OUT_MANUAL}`);
|
||
}
|
||
|
||
await buildSourceDocx();
|
||
await buildManualDocx();
|