// 将"方案计划文档管理系统技术方案.md"输出为 docx // 正文:仿宋_GB2312、小四(12pt)、固定值28磅行距、首行缩进2字符 // 标题:黑体、小四(12pt)、加粗 // 列表:仿宋小四、悬挂缩进、与正文同字号行距 const fs = require("fs"); const path = require("path"); const docx = require(path.join("C:/nvm4w/nodejs/node_modules/docx")); const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, HeadingLevel, AlignmentType, BorderStyle, WidthType, ShadingType, PageBreak, LevelFormat, LineRuleType, } = docx; // ===== 格式常量 ===== const FONT_BODY = "仿宋_GB2312"; const FONT_HEAD = "黑体"; const SIZE = 24; // 小四 = 12pt = 24 half-points const LINE = 560; // 固定值 28磅 = 28*20 = 560 twips const INDENT_FIRST = 480; // 首行缩进 2字符 ≈ 12pt*2*20 = 480 // ===== 行内格式解析(**bold** 与 `code`)===== function parseInline(text) { const runs = []; const regex = /(\*\*[^*]+\*\*|`[^`]+`)/g; let last = 0, m; while ((m = regex.exec(text)) !== null) { if (m.index > last) runs.push({ text: text.slice(last, m.index) }); const tok = m[0]; if (tok.startsWith("**")) runs.push({ text: tok.slice(2, -2), bold: true }); else if (tok.startsWith("`")) runs.push({ text: tok.slice(1, -1), code: true }); last = m.index + tok.length; } if (last < text.length) runs.push({ text: text.slice(last) }); return runs; } function makeRuns(text, { heading = false } = {}) { const font = heading ? FONT_HEAD : FONT_BODY; const items = parseInline(text); return items.map(r => new TextRun({ text: r.text, bold: !!r.bold || heading, font: { name: r.code ? "Consolas" : font, eastAsia: font }, size: SIZE, color: "000000", })); } // ===== 段落构造 ===== function bodyPara(text) { return new Paragraph({ spacing: { line: LINE, lineRule: LineRuleType.EXACT, before: 0, after: 0 }, indent: { firstLine: INDENT_FIRST }, alignment: AlignmentType.JUSTIFIED, children: makeRuns(text), }); } function headingPara(text) { return new Paragraph({ spacing: { line: LINE, lineRule: LineRuleType.EXACT, before: 120, after: 60 }, indent: { firstLine: 0 }, children: makeRuns(text, { heading: true }), }); } function quotePara(text) { return new Paragraph({ spacing: { line: LINE, lineRule: LineRuleType.EXACT, before: 0, after: 0 }, indent: { left: 480, firstLine: 0 }, alignment: AlignmentType.LEFT, children: makeRuns(text), }); } function bulletItem(text) { // 无序列表项按用户要求:不加黑点、与普通正文段落一致(首行缩进2字符) return new Paragraph({ spacing: { line: LINE, lineRule: LineRuleType.EXACT, before: 0, after: 0 }, indent: { firstLine: INDENT_FIRST }, alignment: AlignmentType.JUSTIFIED, children: makeRuns(text), }); } function orderedItem(text, ref) { return new Paragraph({ numbering: { reference: ref, level: 0 }, spacing: { line: LINE, lineRule: LineRuleType.EXACT, before: 0, after: 0 }, alignment: AlignmentType.JUSTIFIED, children: makeRuns(text), }); } function separator() { // 横线:底边框的空段 return new Paragraph({ spacing: { line: LINE, lineRule: LineRuleType.EXACT, before: 0, after: 0 }, border: { bottom: { style: BorderStyle.SINGLE, size: 6, color: "888888", space: 1 } }, children: [new TextRun({ text: "" })], }); } // ===== Markdown 解析为段落对象 ===== const SRC = path.join(__dirname, "方案计划文档管理系统技术方案.md"); const OUT = path.join(__dirname, "方案计划文档管理系统技术方案.docx"); const md = fs.readFileSync(SRC, "utf-8"); const rawLines = md.split(/\r?\n/); const blocks = []; let orderCounter = 0; for (let i = 0; i < rawLines.length; i++) { const line = rawLines[i]; if (!line.trim()) continue; if (/^-{3,}\s*$/.test(line)) { blocks.push({ type: "hr" }); continue; } const hm = /^(#{1,6})\s+(.+)$/.exec(line); if (hm) { blocks.push({ type: "heading", text: hm[2].trim() }); continue; } if (/^>\s?/.test(line)) { blocks.push({ type: "quote", text: line.replace(/^>\s?/, "") }); continue; } if (/^[-*+]\s+/.test(line)) { blocks.push({ type: "bullet", text: line.replace(/^[-*+]\s+/, "") }); continue; } const om = /^(\d+)\.\s+(.*)$/.exec(line); if (om) { const prev = rawLines[i - 1] || ""; if (!/^\d+\.\s+/.test(prev)) orderCounter += 1; blocks.push({ type: "ordered", text: om[2], group: orderCounter }); continue; } blocks.push({ type: "body", text: line }); } // ===== 生成 docx children ===== const children = []; for (const b of blocks) { switch (b.type) { case "heading": children.push(headingPara(b.text)); break; case "quote": children.push(quotePara(b.text)); break; case "body": children.push(bodyPara(b.text)); break; case "bullet": children.push(bulletItem(b.text)); break; case "ordered": children.push(orderedItem(b.text, `order-${b.group}`)); break; case "hr": children.push(separator()); break; } } // ===== 构造 numbering(仅用于有序列表;无序列表已改为普通段落样式)===== const numbering = { config: [ ...Array.from({ length: orderCounter }, (_, k) => ({ reference: `order-${k + 1}`, levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT, style: { paragraph: { indent: { left: 720, hanging: 360 } } }, }], })), ], }; const doc = new Document({ creator: "DMS", title: "方案计划文档管理系统技术方案", styles: { default: { document: { run: { font: { name: FONT_BODY, eastAsia: FONT_BODY }, size: SIZE, color: "000000" }, paragraph: { spacing: { line: LINE, lineRule: LineRuleType.EXACT } }, }, }, }, numbering, sections: [{ properties: { page: { size: { width: 11906, height: 16838 }, // A4 margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }, }, }, children, }], }); Packer.toBuffer(doc).then((buf) => { fs.writeFileSync(OUT, buf); console.log(`Generated: ${OUT}`); console.log(`Size: ${buf.length} bytes`); console.log(`Blocks: ${blocks.length}, ordered groups: ${orderCounter}`); }).catch((e) => { console.error("ERROR:", e); process.exit(1); });