chore: 今日工作总结复盘-发布流程/Skill/内容规范沉淀

This commit is contained in:
小橙
2026-05-09 15:33:28 +00:00
parent 64afc0ec5f
commit d2ff5db8f2
6 changed files with 365 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
---
name: docx-publisher
description: 生成图文平台发布版docx文档支持多平台合规过滤。当用户说"生成发布版"、"生成docx"时触发。工作流读取drafts/对应草稿 → 注入平台合规规则 → 生成配图 → Node.js脚本生成带图docx → 发给用户。
---
# 图文平台发布版生成器
将图文草稿md格式转换为**发布版 docx文档**,图片直接嵌入,可导入各平台后台直接发布。
## 触发词
"生成发布版"、"生成docx"、"发XX平台"
## 平台合规规则
详见 `references/平台合规规则.md`。生成前必须读取对应平台的规则。
## 工作流
### Step 1读取草稿 + 合规规则
1. 读取 `drafts/` 下对应 md 文件(优先读母版 `master` 版,内容更完整)
2. 读取 `references/平台合规规则.md`,确认目标平台的**禁用词列表**和**特殊规则**
3. 将草稿正文中的禁用词替换为合规替代表述
### Step 2生成配图
根据平台类型决定配图数量:
- **图文平台(百家号/搜狐号/公众号等)**:封面图 + 每段落1张共4-6张
- **化工仪器网**:封面图 + 3张段落图政策/技术/案例)
**关键**:每张图生成后**立即用 `cp`** 复制到 `published/<日期>_<主题>/assets/` 永久目录,再生成下一张
### Step 3生成 docx
```bash
node scripts/gen_docx.js \
--title "标题" \
--output "output.docx" \
--cover ./assets/cover.png \
--image2 ./assets/img2.png \
--image3 ./assets/img3.png \
--image4 ./assets/img4.png \
--content-json '<JSON>'
```
### Step 4发给用户
发 MEDIA: 路径给用户,告知可直接导入平台发布。
## docx 格式规范
| 元素 | 样式 |
|------|------|
| 标题 | 24pt+,加粗,居中 |
| 副标题/摘要 | 灰色,斜体,居中 |
| 小标题H2 | 加粗,段落前后间距 |
| 核心观点(加粗句)| 加粗,可带底边线 |
| 正文 | 标准字 |
| 结束语/来源 | 灰色,小字,居中 |
| 图片 | 宽度480-580px居中 |
## 禁止出现在发布版的内容
- 所有平台禁用词(见 `references/平台合规规则.md`
- 联系方式(电话/QQ/微信/网址)
- 绝对化用语(最好/第一/国家级/唯一等)
- 未通过合规扫描的内容
- `[时间] 旁白类型` 格式的分镜指令

View File

@@ -0,0 +1,92 @@
# 平台合规规则
> 生成各平台发布版 docx 前,必须读取本文件并对目标平台应用对应规则。
---
## 化工仪器网 (chem17.com)
**审核严格程度:高**
### 禁用词(直接替换,不得出现)
| 禁用词 | 替代表达 |
|--------|---------|
| 卓越 | 更高级别 |
| 先进 | 高等级 |
| 领航 | 最高级别 |
| 工信部 | 六部门 |
| 首次 | 首次 → "这一次" |
| 第一 | 首位/领先 |
| 国家级 | 全国性 |
| 行业标杆 | 行业示范 |
| 保证/确保/100% | 通常可达到/通常情况下 |
| 国家级 | 全国性标准 |
| 官方 | 权威 |
### 特殊规则
- **禁止插入联系方式**:电话/QQ/微信/网址均不可出现
- **图片中不得含联系方式水印**
- **来源处不得写网址**,只写公司简称
- **数据须注明来源**:补贴金额后加"(以各地政策为准)";案例成效加"(模拟参考值,以实际为准)"
### 替代表达库
```
"详细方案" → "完整方案"
"完美解决" → "有效改善"
"彻底解决" → "大幅改善"
"遥遥领先" → "具备优势"
"绝对可靠" → "稳定可靠"
"唯一" → "少数具备"
"独家" → "自主研发"
```
---
## 百家号
**审核严格程度:中**
- 禁用词参照广告法(参见 `brand/banned-words.md`
- 联系方式:可在文末注明公司名,但不可留具体手机号/微信号
- 不得出现竞品负面表述
---
## 搜狐号
**审核严格程度:中**
- 基本同百家号规则
- 图片中不得含水印联系方式
---
## 微信公众号
**审核严格程度:中**
- 关注自动回复/外部链接需合规
- 不得诱导关注/分享
- 图片须有版权
---
## CSDN / 博客园
**审核严格程度:低**
- 技术文章为主,相对宽松
- 代码块须格式正确
- 可保留技术参考链接
---
## 通用规则(所有平台)
1. **数据必须有来源**:补贴金额注明政策依据;案例数据注明"模拟参考值"
2. **禁用绝对化用语**:最好/最佳/第一/唯一/顶级/完美
3. **竞品中性原则**:不得出现"碾压/完爆/吊打"等贬低竞品词汇
4. **联系方式处理**:原则上不发手机号/微信号,平台要求除外

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env node
/**
* gen_docx.js
* 通用图文平台发布版docx生成器
*
* 用法:
* node gen_docx.js --title "标题" --subtitle "副标题" --output output.docx \
* --cover ./assets/cover.png \
* --content-json '<JSON_STRING>'
*/
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 = '', output = '', coverImg = '';
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] === '--output' && args[i+1]) output = args[++i];
else if (args[i] === '--cover' && args[i+1]) coverImg = args[++i];
else if (args[i] === '--content-json' && args[i+1]) contentJson = args[++i];
}
if (!output) { console.error('Usage: --output required'); process.exit(1); }
const loadImg = (p) => (p && fs.existsSync(p)) ? fs.readFileSync(p) : null;
const cover = coverImg ? loadImg(coverImg) : null;
const E = () => new Paragraph({ text: '' });
const TITLE = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 48 })], alignment: 'center', spacing: { before: 0, after: 160 } });
const SUB = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 24, color: '888888', italics: true })], alignment: 'center', spacing: { before: 0, after: 200 } });
const H2 = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 36 })], spacing: { before: 320, after: 160 } });
const H3 = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 28 })], spacing: { before: 200, after: 80 } });
const BODY = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 28 })], spacing: { before: 60, after: 80 } });
const EMP = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 30 })], spacing: { before: 120, after: 100 } });
const END = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 22, color: 'AAAAAA', italics: true })], alignment: 'center', spacing: { before: 200, after: 0 } });
const IMGP = (d, w, h) => d ? new Paragraph({ children: [new ImageRun({ data: d, transformation: { width: w, height: h }, type: 'png' })], alignment: 'center', spacing: { before: 100, after: 100 } }) : E();
let config = {};
try { config = contentJson ? JSON.parse(contentJson) : {}; } catch(e) { console.error('JSON parse error:', e.message); }
const children = [];
// Cover
if (cover) { children.push(IMGP(cover, 580, 326)); children.push(E()); }
children.push(TITLE(title));
if (subtitle) children.push(SUB(subtitle));
children.push(E());
// Sections
const sections = config.sections || [];
for (const sec of sections) {
if (sec.heading) children.push(H2(sec.heading));
if (sec.image) 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.match(/^[①②③④]/)) {
children.push(BODY(clean));
} else if (clean.length < 80 && !clean.includes('。')) {
children.push(BODY(clean));
} else {
children.push(BODY(clean));
}
}
}
children.push(E());
}
// Source
if (config.source) children.push(END(config.source));
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); });