# 微信公众号编辑器技术参考

## 编辑器 DOM 结构

| 元素 | 选择器 | 说明 |
|------|--------|------|
| 标题输入框 | `TEXTAREA#title` | 文章标题，textarea 元素 |
| 正文编辑器 | `DIV.ProseMirror` | contenteditable，基于 ProseMirror |
| 保存为草稿按钮 | `SPAN.btn`（文本匹配"保存为草稿"） | 点击触发草稿保存 |
| 继续保存按钮 | `A#js_continue_save` | 违规警告弹窗中的确认按钮 |
| 已保存提示 | `DIV#js_save_success` | display:block 时表示保存成功 |
| 违规警告框 | `DIV#js_key_words` | display:none 时表示无警告 |

## 标题赋值方式

直接对 `textarea.value` 赋值不会触发 Vue/React 的响应式绑定，必须用原生 setter：

```javascript
var ta = document.querySelector('#title');
var nativeSet = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
nativeSet.call(ta, '文章标题');
ta.dispatchEvent(new Event('input', {bubbles: true}));
ta.dispatchEvent(new Event('change', {bubbles: true}));
```

## 正文插入方式

```javascript
// 聚焦并清空
var ed = document.querySelector('.ProseMirror');
ed.focus();
document.execCommand('selectAll', false, null);
document.execCommand('delete', false, null);

// 插入一段文字
document.execCommand('insertText', false, '段落内容');
// 插入换行（新段落）
document.execCommand('insertParagraph', false, null);
```

## 排版样式约定

| 类型 | 前缀/格式 | 示例 |
|------|-----------|------|
| 普通正文 | `　　`（两个全角空格，首行缩进） | `　　这是正文内容。` |
| 章节标题 | `【标题】` | `【关键数据】` |
| 分割线 | `————————————————————` | — |
| 重点数据 | `▶ ` 前缀 | `▶ 📊 春招岗位占比：全年招聘量的40%` |
| 列表项 | `• ` 前缀 | `• 节点一（3月）：企业春招启动` |

## URL 规律

| 页面 | URL 格式 |
|------|----------|
| 公众号后台首页 | `https://mp.weixin.qq.com` |
| 新建文章编辑页 | `https://mp.weixin.qq.com/cgi-bin/appmsg?action=edit&type=10&token=<TOKEN>&lang=zh_CN` |
| 编辑已有文章 | 同上，额外带 `appmsgid=<ID>` |

> **注意**：token 从当前已登录页面的 URL 中提取，会定期过期，需重新登录。

## Chrome 调试模式启动

```
"C:\Program Files\Google\Chrome\Application\chrome.exe" ^
  --remote-debugging-port=9222 ^
  --user-data-dir=C:\ChromeDebug
```

**必须指定 `--user-data-dir`，否则调试端口可能无法绑定（已有实例冲突）。**

验证方式：浏览器访问 `http://localhost:9222/json`，返回 JSON 数组即表示成功。

## 封面设置 DOM 结构

| 元素 | 选择器 | 说明 |
|------|--------|------|
| 封面区域 | `#js_cover_area` | 点击打开封面选择入口 |
| 从正文选择链接 | `a.js_selectCoverFromContent` | 弹窗中"从正文选择"按钮 |
| 图片列表项 | `li.appmsg_content_img_item` | 弹窗中可供选择的图片（每张一个 li） |
| 下一步按钮 | `button.weui-desktop-btn`（文本匹配"下一步"） | 选中图片后变为可点击（深绿色） |
| 确认按钮 | `button`（文本匹配"确认"，可见） | 裁剪界面的确认按钮 |

### 封面设置完整流程（已验证）

```javascript
// 1. 点击封面区域
document.querySelector('#js_cover_area').click();

// 等 1 秒

// 2. 点击"从正文选择"
document.querySelector('a.js_selectCoverFromContent').click();

// 等 3 秒（弹窗加载 + 图片列表渲染）

// 3. 点击第一张图片
document.querySelector('li.appmsg_content_img_item').click();

// 等 1.5 秒（图片选中，按钮变绿）

// 4. 点击"下一步"
var btns = Array.from(document.querySelectorAll('button'));
var next = btns.find(b => b.innerText.trim() === '下一步' &&
    window.getComputedStyle(b).display !== 'none');
next.click();

// 等 2 秒（裁剪界面加载）

// 5. 点击"确认"
var confirm = btns.find(b => b.innerText.trim() === '确认' &&
    window.getComputedStyle(b).display !== 'none');
confirm.click();

// 等 3 秒（封面渲染，有约 1 秒延迟，JS 检测不到不代表失败）
```

> **注意**：`li.appmsg_content_img_item` 只在弹窗完全加载后才出现，需等待至少 3 秒再查找。
> "下一步"按钮在图片未选中时为禁用态（class 含 `weui-desktop-btn_d`），选中后变为可点击。

---

## 常见问题

| 问题 | 原因 | 解决方法 |
|------|------|----------|
| CDP 连接被拒绝 | Chrome 未以调试模式启动，或被旧实例占用 | 彻底关闭 Chrome，重新用 `--user-data-dir` 启动 |
| localhost:9222 拒绝连接 | 缺少 `--user-data-dir` 参数 | 补充 `--user-data-dir=C:\ChromeDebug` |
| 标题填写后没反应 | 直接赋值 value 不触发框架响应 | 改用 HTMLTextAreaElement.prototype 原生 setter |
| 草稿保存失败 | 违规警告弹窗阻断 | 先点击 `#js_continue_save` 再点保存 |
| token 错误/过期 | 登录 session 过期 | 重新扫码登录公众号后台 |
| 封面弹窗图片找不到 | 弹窗未完全加载 | 增加等待时间到 3~5 秒后再查找 `li.appmsg_content_img_item` |
| 点"下一步"没反应 | 图片未选中，按钮仍为禁用态 | 确保先点击图片（出现对号）再点下一步 |
| 封面设置后 JS 检测不到 img | 封面渲染有延迟（约 1 秒） | 等 3 秒后再验证，不影响实际效果 |
| 保存草稿太慢 | 文章较长或图片较多 | `cdp_save_draft.py` 中等待已设为 20 秒，可按需增加 |
