feat(publish): B站-智能工厂四级补贴首发归档

- drafts/ 按日期+名称分类重整
- 2026-05-09_B站首发归档至published/
- 配图6张永久存档
This commit is contained in:
小橙
2026-05-09 13:20:00 +00:00
parent 8104e1ccf2
commit 7edb53c43c
90 changed files with 5002 additions and 101 deletions

View File

@@ -0,0 +1,131 @@
#!/usr/bin/env node
/**
* gen_bilibili_docx.js
* 生成B站视频发布版docx文档
*
* 用法:
* node gen_bilibili_docx.js \
* --title "视频标题" \
* --subtitle "封面副标题" \
* --intro "视频简介和标签" \
* --output "output.docx" \
* --cover "cover.png" \
* --opening "opening.png" \
* --policy "policy.png" \
* --subsidy "subsidy.png" \
* --indicators "indicators.png" \
* --case "case.png" \
* --content-json '<JSON_STRING>'
*
* JSON_STRING 格式:
* {
* "sections": [
* { "heading": "小标题", "img": "对应图片key(cover/opening/policy/subsidy/indicators/case)", "paragraphs": ["正文1", "正文2"] }
* ]
* }
*/
const { Document, Packer, Paragraph, TextRun, ImageRun } = require('/home/node/.openclaw/node_modules/docx');
const fs = require('fs');
const args = process.argv.slice(2);
let title = '', subtitle = '', intro = '', output = '';
let coverImg = '', openingImg = '', policyImg = '', subsidyImg = '', indicatorsImg = '', caseImg = '';
let contentJson = '';
for (let i = 0; i < args.length; i++) {
if (args[i] === '--title' && args[i+1]) title = args[++i];
else if (args[i] === '--subtitle' && args[i+1]) subtitle = args[++i];
else if (args[i] === '--intro' && args[i+1]) intro = args[++i];
else if (args[i] === '--output' && args[i+1]) output = args[++i];
else if (args[i] === '--cover' && args[i+1]) coverImg = args[++i];
else if (args[i] === '--opening' && args[i+1]) openingImg = args[++i];
else if (args[i] === '--policy' && args[i+1]) policyImg = args[++i];
else if (args[i] === '--subsidy' && args[i+1]) subsidyImg = args[++i];
else if (args[i] === '--indicators' && args[i+1]) indicatorsImg = args[++i];
else if (args[i] === '--case' && args[i+1]) caseImg = args[++i];
else if (args[i] === '--content-json' && args[i+1]) contentJson = args[++i];
}
if (!output) { console.error('Error: --output required'); process.exit(1); }
const imgMap = { cover: coverImg, opening: openingImg, policy: policyImg, subsidy: subsidyImg, indicators: indicatorsImg, case: caseImg };
const loadImg = (k) => { const p = imgMap[k]; return (p && fs.existsSync(p)) ? fs.readFileSync(p) : null; };
const imgs = { cover: loadImg('cover'), opening: loadImg('opening'), policy: loadImg('policy'), subsidy: loadImg('subsidy'), indicators: loadImg('indicators'), case: loadImg('case') };
const E = () => new Paragraph({ text: '' });
const TITLE = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 56 })], alignment: 'center', spacing: { before: 0, after: 160 } });
const SUB = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 28, color: '666666' })], alignment: 'center', spacing: { before: 0, after: 200 } });
const H2 = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 36 })], spacing: { before: 240, after: 120 } });
const BODY = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 28 })], spacing: { before: 60, after: 60 } });
const HL = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 28, color: '1A1A1A' })], spacing: { before: 120, after: 80 } });
const EMP = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 28 })], spacing: { before: 160, after: 160 }, border: { bottom: { color: 'CCCCCC', space: 1, style: 'single', size: 4 } } });
const END = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 28, italics: true, color: '555555' })], spacing: { before: 80, after: 80 } });
const IMGP = (d, w, h) => d ? new Paragraph({ children: [new ImageRun({ data: d, transformation: { width: w, height: h }, type: 'png' })], alignment: 'center', spacing: { before: 80, after: 80 } }) : E();
let sections = [];
try { sections = contentJson ? JSON.parse(contentJson) : {}; } catch(e) { console.error('JSON parse error:', e.message); }
// Auto-detect which image matches a heading keyword
const headingImgMap = {
'开场': 'opening', '悬念': 'opening', '引入': 'opening',
'政策': 'policy', '全景': 'policy', '四级': 'policy',
'补贴': 'subsidy', '金额': 'subsidy', '数字': 'subsidy',
'指标': 'indicators', '门槛': 'indicators', '硬指标': 'indicators',
'案例': 'case', '真实': 'case', 'SCADA': 'case',
};
const detectImg = (heading) => {
if (!heading) return null;
for (const [kw, imgKey] of Object.entries(headingImgMap)) {
if (heading.includes(kw)) return imgs[imgKey];
}
return null;
};
const children = [];
// Cover
children.push(IMGP(imgs.cover, 560, 315));
children.push(E());
children.push(TITLE(title));
if (subtitle) children.push(SUB(subtitle));
if (intro) { const introPara = new Paragraph({ children: [new TextRun({ text: intro, size: 22, color: '0077CC' })], alignment: 'center', spacing: { before: 0, after: 200 } }); children.push(introPara); }
children.push(E());
// Sections
if (sections.sections && Array.isArray(sections.sections)) {
for (const sec of sections.sections) {
if (sec.heading) children.push(H2(sec.heading));
const secImg = sec.img ? imgs[sec.img] : detectImg(sec.heading);
if (secImg) { children.push(IMGP(secImg, 480, 270)); children.push(E()); }
if (sec.paragraphs && Array.isArray(sec.paragraphs)) {
for (const p of sec.paragraphs) {
if (!p || p.trim() === '') { children.push(E()); continue; }
const clean = p.trim();
if (clean.length < 60 && (clean.includes('——') || clean.match(/^[①②③④]/))) {
children.push(BODY(clean));
} else if (clean.length < 80 && !clean.includes('。')) {
children.push(BODY(clean));
} else if (clean.startsWith('') || clean.startsWith('')) {
children.push(HL(clean));
} else {
children.push(BODY(clean));
}
}
}
children.push(E());
}
}
// Source
children.push(new Paragraph({
children: [new TextRun({ text: '内容来源上海橙轩智能Orpaon· 制造业数字化解决方案 | 官网www.orpaon.com', size: 20, color: '999999' })],
alignment: 'center', spacing: { before: 200, after: 0 },
}));
const doc = new Document({ sections: [{ children }] });
Packer.toBuffer(doc).then(buf => {
fs.writeFileSync(output, buf);
console.log('done');
}).catch(e => { console.error(e); process.exit(1); });