Files
ulthon_information/docs/phone-image-architecture.md
augushong 30291a9dca docs(phone-image): 全面更新架构文档反映wangeditor方案
重写渲染管线、新增wangeditor集成章节、更新数据流/存储模型/配置体系、
新增UI布局/已删除函数/已知限制章节(373行新增,171行删除)
2026-05-11 23:40:20 +08:00

599 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 手机图片排版功能 - 架构文档
> 基于源代码实际阅读编写,记录排版引擎的设计原理与数据流。
## 1. 渲染管线
排版引擎的核心是 `PhoneImageEngine`IIFE 闭包,`public/static/js/phone-image.js`),纯前端运行,后端不参与渲染计算。
### 1.1 管线总览
```
用户触发 doRender() 或编辑器 onChange 自动触发
|
v
PhoneImageEngine.render() -- L82: 管线入口,带并发锁
|
+---> editorHtml = window.phoneImageEditor.getHtml() -- L99: 从 wangeditor 读取HTML
+---> preprocessContent(editorHtml) -- L154: 清洗HTML去script/style/iframe规范化img
+---> parseHtmlToBlocks(cleanHtml) -- L201: HTML -> 块级元素数组,<hr> 识别为 page-break
|
+---> generateCoverPage(sizeConfig) -- L619: 生成封面页HTML
|
+---> captureEditorBlocks(editorHtml, blocks, areaHeight, sizeConfig) -- L345: DOM测高核心
| |
| +---> 按 page-break(<hr>) 将 blocks 分组 -- L349-365
| +---> getEditorChildren() -- L323: 穿透 div 包装层获取编辑器DOM子元素
| +---> 创建 500px 测量容器fixed, visibility:hidden -- L376-387
| +---> 串行测高每组DOM克隆到测量容器 -> getBoundingClientRect -- L392-433
| +---> 按比例分配实际高度给组内各 block -- L421-428
| +---> paginateContent(blocks, contentAreaHeight, sizeConfig) -- L445: 按高度累加分页
|
+---> generateSummaryPage(sizeConfig, totalPages) -- L731: 生成尾页HTML
+---> 页码补全 N/M -- L122-129: 二次遍历替换占位页码
+---> renderThumbnails(sizeConfig) -- L968: 截图 -> 显示右侧缩略图
|
+---> doCapturePages({scale:1}) -- L841: staging隐藏区域渲染 + html2canvas串行截图
|
+---> 纯图片页优化: 直接用 Image + Canvas跳过 html2canvas -- L805-830
```
### 1.2 并发控制
`render()` 函数使用 `_locked` 标志防止并发渲染(`phone-image.js:86-91`)。渲染期间如果有新请求,设置 `_pending = true`,当前渲染完成后自动重试(`phone-image.js:134-137`)。
### 1.3 DOM 测高机制
`captureEditorBlocks``phone-image.js:345-437`)是管线中替代旧版 `renderContentFlow` + `measureBlockHeights` 的核心函数。工作流程:
1.`page-break` 类型将 blocks 分为若干组
2. 通过 `getEditorChildren()` 获取 wangeditor 编辑区的真实 DOM 子元素
3. 创建 500px 宽的隐藏测量容器fixed 定位,`visibility:hidden`与内容区宽度540 - 20*2 padding一致
4. 对每组:将对应的 DOM 子元素克隆到测量容器,通过 `requestAnimationFrame` 后读取 `getBoundingClientRect().height` 获取实际高度
5. 按各 block 估算高度的比例,将测量的实际高度分配给组内各 block
6. 所有组测高完成后,调用 `paginateContent` 进行分页
### 1.4 截图机制
截图在 `#render-staging` 隐藏区域进行(`phone-image.html:597`)。关键步骤:
1. 将所有页面HTML append到 staging 区域的 `.phone-image-container` 容器中
2. 临时将 `visibility:hidden` 切为 `visible`html2canvas 会跳过隐藏元素)
3. 串行逐页调用 html2canvas 截图(`runCaptureLoop`, L881
4. 纯图片页优化:直接用 `Image + Canvas` 绘制,跳过 html2canvas`phone-image.js:805-830`
5. 截图完成后恢复 `visibility:hidden`
缩略图用 `scale:1`,保存用 `scale:2`输出1080px宽度
### 1.5 getEditorChildren 穿透逻辑
`getEditorChildren()``phone-image.js:323-334`)处理 wangeditor v5 的 DOM 包装层。wangeditor 的 `[data-slate-editor]` 下可能有一层 `<div>` 包装,该函数检测到这种情况时穿透到内层子元素,返回实际的内容节点数组。这对 `captureEditorBlocks` 的 DOM 克隆测高和 `exportLongImage` 的长图导出都至关重要。
## 2. wangeditor 集成
### 2.1 编辑器初始化
编辑器在 `phone_image.html` 模板脚本中初始化L192-288
```javascript
var E = window.wangEditor;
var phoneImageEditor = E.createEditor({
selector: '#editor-text-area',
html: postData.contentHtml, // 初始内容,来自服务端渲染
config: editorConfig
});
var toolbar = E.createToolbar({
editor: phoneImageEditor,
selector: '#editor-toolbar',
config: {
excludeKeys: ['fullScreen'],
insertKeys: {
index: 24,
keys: ['divider'] // 分割线按钮,插入位置 index 24
}
}
});
window.phoneImageEditor = phoneImageEditor; // 全局引用
```
### 2.2 工具栏配置
- 排除 `fullScreen` 按钮
- 在 index 24 位置插入 `divider`(分割线)按钮
- 其余使用 wangeditor 默认按钮集
### 2.3 分割线与分页的关系
wangeditor 的 `divider` 按钮在编辑器中插入 `<hr>` 元素(带 `data-w-e-type="divider"` 属性)。渲染管线中 `parseHtmlToBlocks``<hr>` 识别为 `page-break` 类型的块(`phone-image.js:259-263``paginateContent` 遇到 `page-break` 时强制换页(`phone-image.js:455-466`)。`captureEditorBlocks` 也按 `page-break` 对 blocks 分组测高。
### 2.4 粘贴处理
`editorConfig.customPaste``phone_image.html:220-268`)拦截粘贴操作,将外部图片(`http` 开头的URL图片和 `data:` 开头的 base64 图片上传到服务器后替换为本地URL确保编辑器中的图片资源可控。
### 2.5 DOM 结构
```
div.content-flow-area (540px, overflow-y:auto)
+-- div#editor-toolbar (wangeditor 工具栏)
+-- div#editor-text-area (wangeditor 编辑区)
+-- div[data-slate-editor] (wangeditor 内部)
+-- div (可能的包装层)
+-- p / h1 / img / hr(divider) / ... (实际内容节点)
```
## 3. 数据流
### 3.1 数据源模型
重构后wangeditor 编辑器成为运行时的权威内容源。引擎闭包变量 `postData.content_html` 仅作为初始化回退。
**权威数据源**`window.phoneImageEditor.getHtml()`,通过 `getContentHtml()` API 统一访问(`phone-image.js:1280-1285`
```javascript
getContentHtml: function () {
if (window.phoneImageEditor) {
return window.phoneImageEditor.getHtml();
}
return postData.content_html; // 回退
}
```
**引擎公开 API**
| API | 位置 | 用途 |
|-----|------|------|
| `getContentHtml()` | `phone-image.js:1280-1285` | 优先从 wangeditor 读取HTML回退到闭包变量 |
| `updateConfig(newConfig)` | `phone-image.js:1286-1290` | 使用 `$.extend(config, newConfig)` 更新配置,不重置内容 |
| `saveConfig(postId, saveConfig, url)` | `phone-image.js:1138-1170` | 保存配置到服务端(不生成图片),返回 `{output_id}` |
| `saveImages(postId, saveConfig)` | `phone-image.js:1079-1129` | 截图并保存所有页面图片到服务端 |
| `exportLongImage()` | `phone-image.js:1192-1257` | 从编辑器DOM导出完整长图 |
**DOM `#post-content-html` 的角色**:仅作为初始化数据源(`phone_image.html:105`)和保存后的显示同步层(`phone_image.html:434,459`),保存操作不从中读取内容。
### 3.2 各操作的数据流
```
保存配置 (#btn-save):
content_html 取自 PhoneImageEngine.getContentHtml() -- L431
调用 PhoneImageEngine.saveConfig() API -- L428-432
保存后同步: $('#post-content-html').html(getContentHtml()) -- L434
生成并保存 (#btn-generate):
content_html 取自 PhoneImageEngine.getContentHtml() -- L454
调用 PhoneImageEngine.saveImages() API -- L451-455
保存后同步: $('#post-content-html').html(getContentHtml()) -- L459
重新排版 (doRender):
用 PhoneImageEngine.updateConfig(newConfig) -- L344
再调 PhoneImageEngine.render() -- L346
自动保存 (onChange):
wangeditor onChange -> 2.6s 防抖 -> doAutoSave() -- phone_image.html:209-216
doAutoSave 调用 PhoneImageEngine.saveConfig() -- phone_image.html:403-413
加载历史 (loadFromHistory):
window.phoneImageEditor.setHtml(res.data.content_html) -- phone_image.html:561 (加载到编辑器)
PhoneImageEngine.updateConfig(historyConfig) -- phone_image.html:578
PhoneImageEngine.render() -- phone_image.html:580
导出长图 (更多菜单 -> exportLong):
PhoneImageEngine.exportLongImage() -- phone_image.html:484
内部: getEditorChildren() -> 克隆非divider元素 -> html2canvas scale=2 -> 下载PNG
```
### 3.3 自动保存机制
编辑器内容变更时触发自动保存(`phone_image.html:206-413`
1. `editorConfig.onChange` 触发,设置 `autoSaveLock = true`,显示"等待保存..."状态
2. 清除之前的防抖定时器,设置新的 2.6 秒定时器
3. 定时器到期后调用 `doAutoSave()`
4. `doAutoSave()` 检查 `autoSaveLock`,通过 `PhoneImageEngine.saveConfig()` 发送保存请求
5. 保存状态通过 `#save-state` 指示器反馈waiting/saving/saved/error`phone_image.html:388-397`
手动保存按钮点击时会清除自动保存定时器,避免冲突。
### 3.4 完整数据流图
```
[数据库 Post.content_html]
|
v
Post.php::phoneImage() (L355)
查询 PostOutput 取已保存的 content_html 副本
|
v
phone_image.html 模板渲染
- $layoutContentHtml (优先用已保存副本)
- $layoutConfig (已保存的配置)
|
v
前端初始化 (phone_image.html:152-294)
1. 构造 postData 局部对象contentHtml 取自 #post-content-html (L173)
2. 恢复 savedConfig (L181-186)
3. wangeditor 初始化html=postData.contentHtml (L270-274)
4. window.phoneImageEditor = phoneImageEditor (L288)
5. PhoneImageEngine.init(postData, initConfig) (L290)
6. doRender() 触发初始渲染 (L594)
|
v
渲染管线 (phone-image.js render())
- 读取 window.phoneImageEditor.getHtml() (唯一权威源)
- parseHtmlToBlocks + captureEditorBlocks(测高) + paginateContent(分页)
- staging 渲染 + html2canvas 截图
- 显示缩略图
|
v
用户交互
- 编辑内容 -> wangeditor -> onChange 自动保存(2.6s防抖)
- 分割线 -> wangeditor divider -> <hr> -> page-break 分页
- 保存配置 -> PhoneImageEngine.saveConfig() (content_html 取自 getContentHtml())
- 生成保存 -> PhoneImageEngine.saveImages() (content_html 取自 getContentHtml())
- 重新排版 -> updateConfig() + render()
- 加载历史 -> editor.setHtml() + updateConfig() + render()
- 导出长图 -> exportLongImage() (从编辑器DOM直接截图)
|
v
后端存储
- PostOutput.config (JSON字段含 content_html 副本)
- PostOutputFile (每页图片文件记录)
```
## 4. 存储模型
### 4.1 数据库表
**`ul_post_output`** (模型: `app/model/PostOutput.php`)
| 字段 | 说明 |
|------|------|
| `id` | 主键 |
| `post_id` | 关联文章ID |
| `output_type` | 输出类型,固定为 `phone_image` |
| `config` | JSON字段存储完整配置 + content_html 副本 |
| `status` | 0=生成中, 1=已完成, 2=失败 (常量定义 L20-22) |
| `page_count` | 页数 |
| `admin_id` | 操作管理员ID |
| `create_time` | 创建时间 (int) |
| `delete_time` | 软删除标记 (默认0trait: SoftDelete) |
模型使用 `$json = ['config']` 自动序列化/反序列化配置(`PostOutput.php:18`)。
**`ul_post_output_file`** (模型: `app/model/PostOutputFile.php`)
| 字段 | 说明 |
|------|------|
| `id` | 主键 |
| `output_id` | 关联 post_output.id |
| `page` | 页码 (从1开始) |
| `file_path` | 相对路径,如 `/upload/post_output/20260511/5_1.jpg` |
| `file_url` | 访问URL (同 file_path) |
| `file_size` | 文件字节数 |
| `width` | 图片宽度 (px) |
| `height` | 图片高度 (px) |
关系: `PostOutput hasMany PostOutputFile``PostOutput belongsTo Post`
### 4.2 config JSON 结构
```json
{
"size": "xiaohongshu",
"watermark": "",
"pageAlignments": { "1": "top", "2": "center" },
"content_html": "<p>文章HTML副本</p><hr><p>第二页内容</p>"
}
```
`content_html` 作为配置的一部分保存,使每次排版记录都携带当时的内容快照。含 `<hr>` 分页标记的 HTML 被完整保存,`parseHtmlToBlocks` 可以正确解析。
**向后兼容**:旧版 PostOutput 记录中保存的 `<hr>` 分页标记仍可被 `parseHtmlToBlocks` 正确识别为 `page-break``phone-image.js:259-263`),不影响历史记录加载和重新排版。
### 4.3 loadFromHistory 加载机制
```
loadFromHistory(outputId)
-> GET loadPostOutputConfig?id=XXX
-> window.phoneImageEditor.setHtml(res.data.content_html) -- 加载到编辑器
-> PhoneImageEngine.updateConfig(historyConfig) -- 更新配置
-> PhoneImageEngine.render() -- 重新渲染
```
不再使用 `init()` 全量重置,而是通过 `editor.setHtml()` 直接将历史内容加载到 wangeditor然后 `updateConfig()` 更新配置后重新渲染。
### 4.4 文件存储
图片保存到 `public/upload/post_output/{Ymd}/{outputId}_{page}.jpg``PhoneImage.php:87-92`。ZIP 临时文件生成到 `runtime/temp/` 目录,请求结束后通过 `register_shutdown_function` 清理(`Post.php:570-575`)。
## 5. wangeditor 配置与工具栏
### 5.1 编辑器配置
```javascript
editorConfig = {
MENU_CONF: {},
placeholder: '请输入排版内容,使用分割线标记分页位置',
scroll: false,
MENU_CONF: {
'uploadImage': {
server: '{:url("File/wangEditorSave")}',
fieldName: 'file',
meta: { type: 'editor' }
}
},
onChange: function(editor) { /* 自动保存 2.6s 防抖 */ },
customPaste: function(editor, event) { /* 外部图片上传处理 */ }
}
```
### 5.2 工具栏配置
```javascript
toolbar config = {
excludeKeys: ['fullScreen'],
insertKeys: {
index: 24, // 在第24个按钮位置后插入
keys: ['divider'] // 分割线按钮
}
}
```
保留 wangeditor 所有默认按钮(粗体、斜体、标题、列表、引用、图片、链接等),额外添加分割线按钮用于手动标记分页位置。
## 6. 配置体系
### 6.1 PHP 端配置字段定义
`PhoneImage::getConfigFields()` 定义了4个字段
| 字段 | 类型 | 选项/范围 | 默认值 |
|------|------|-----------|--------|
| `size` | select | xiaohongshu/douyin | xiaohongshu |
| `fontSize` | number | 10-24 | 14 |
| `watermark` | text | - | '' |
| `pageAlignments` | json | - | '{}' |
### 6.2 JS 端实际使用的配置
`phone-image.js` 闭包内的 `config` 对象 (L14-23)
```javascript
config = {
size: 'xiaohongshu',
watermark: '',
pageAlignments: {}, // 逐页对齐
sizes: { ... }, // 尺寸预设
contentPadding: 20
}
```
### 6.3 设置弹框
通过 layui layer 弹框实现(`phone_image.html:293-330`),提供:
- 尺寸选择:小红书 (1080x1440) / 抖音 (1080x1920)
- 水印输入:可选水印文字
确认后自动触发 `doRender()` 重新排版。
### 6.4 自动保存状态指示器
`<span id="save-state">` 元素(`phone_image.html:417`)动态显示保存状态:
- `waiting`:黄色,"等待保存..."
- `saving`:蓝色,"保存中..."
- `saved`:绿色,"已保存"
- `error`:红色,"保存失败"
## 7. UI 布局
### 7.1 整体结构
两栏布局,适配宽屏操作:
```
+----------------------------------------------------------+
| page-header (顶部操作栏) |
| [返回列表] 标题 [设置] [保存] [生成并保存] [更多v] 已保存 |
+------------------------------+---------------------------+
| content-flow-area (540px) | paginated-preview-area |
| +--------------------------+ | (flex:1, 横向滚动) |
| | #editor-toolbar | | |
| | (wangeditor工具栏) | | [缩略图1] [缩略图2] ... |
| +--------------------------+ | 每张带页码和对齐按钮 |
| | #editor-text-area | | |
| | (wangeditor编辑区) | | |
| | min-height:300px | | |
| +--------------------------+ | |
+------------------------------+---------------------------+
```
### 7.2 顶部操作栏
按钮直接显示:设置、保存、生成并保存。"更多"下拉菜单(纯 JS 实现,`phone_image.html:358-385`)包含:历史记录、重新生成、打包下载、导出长图。
layui 2.x 没有 dropdown 模块,更多菜单用纯 JS 实现的 `toggle` + `stopPropagation` + 外部点击关闭。
### 7.3 缩略图区域
右侧缩略图横向排列,每个缩略图带页码标签和对齐切换按钮(仅内容页显示,`phone-image.js:1017-1025`)。对齐按钮切换 `top`/`center` 垂直对齐。
## 8. 已删除的函数/组件
wangeditor 重构中移除的旧版函数和组件:
### 8.1 渲染管线相关
| 已删除函数 | 说明 |
|-----------|------|
| `renderContentFlow` | 旧版将块渲染到中间栏 `#content-flow`,已由 wangeditor 编辑器替代 |
| `convertFlowBlocksToImages` | 旧版表格/代码块预转换html2canvas截图替换为img新版不再需要 |
| `measureBlockHeights` | 旧版从中间栏DOM读取实测高度已由 `captureEditorBlocks` 的测量容器方案替代 |
| `insertPageBreak` | 旧版中间栏"+"按钮插入分页标记,已由 wangeditor divider 替代 |
| `removePageBreak` | 旧版中间栏"-"按钮移除分页标记,已由编辑器直接删除 divider 替代 |
### 8.2 高度估算相关
| 已删除函数 | 说明 |
|-----------|------|
| `estimateTextHeight` | 旧版纯计算估算段落高度,已由 DOM 测量替代 |
| `estimateHeadingHeight` | 旧版估算标题高度 |
| `estimateListHeight` | 旧版估算列表高度 |
| `estimateBlockquoteHeight` | 旧版估算引用块高度 |
### 8.3 工具函数
| 已删除函数 | 说明 |
|-----------|------|
| `simpleHash` | 旧版 djb2 哈希,用于 `convertedBlockCache` 缓存key |
| `getContentWidth` | 旧版获取内容区宽度 |
### 8.4 UI 组件
| 已删除组件 | 说明 |
|-----------|------|
| `#content-flow` 中间栏 | 旧版内容渲染和交互区域,已由 wangeditor 编辑器替代 |
| 左侧工具栏 | 旧版编辑操作面板(分页标记按钮等) |
| `convertedBlockCache` | 旧版表格/代码块截图缓存,新版不再预转换 |
## 9. 已知限制
### 9.1 html2canvas 兼容性
html2canvas 截图不支持以下 CSS 特性:
- `transform`
- `filter`
- `clip-path`
- `backdrop-filter`
- CSS grid
- `object-fit`
在 wangeditor 编辑器和自定义样式中应避免使用这些属性,否则截图中可能显示异常。
### 9.2 wangeditor divider 特性
wangeditor 的 divider 是 void 元素,`::after` 伪元素可能不生效。编辑器中 divider 的视觉样式依赖 wangeditor 内部 CSS。
### 9.3 layui dropdown 缺失
layui 2.x 没有 dropdown 模块,"更多"菜单用纯 JS 实现(`phone_image.html:358-385`),包括 `toggle` 显示/隐藏、`stopPropagation` 阻止冒泡、全局 `click` 事件关闭。
### 9.4 保存数据量限制
`saveImages()` 有16MB的客户端检查`phone-image.js:1096-1100`但服务端没有对应的分块接收机制。大文章超过约30页的base64数据可能超出 PHP `post_max_size` 配置。
### 9.5 目录权限 0777
`PhoneImage.php:95``:140``mkdir` 使用 0777 权限,与项目其他位置的 anti-pattern 一致。
### 9.6 接口 `PostOutputManagerInterface` 未被充分利用
`PhoneImage` 实现了 `process()``getPreview()` 方法,但两者都返回空值(`PhoneImage.php:56-67`)。实际业务完全在控制器中直接调用 `PhoneImage` 的其他方法,绕过了接口定义的流程。接口仅作为类型约束存在。
### 9.7 `saveConfigOnly` 是静态方法但 `createOutput` 是实例方法
`PhoneImage::saveConfigOnly()` 是 static 方法L223而同类的 `createOutput()` 是实例方法L193。两者的实现几乎相同都是 `PostOutput::create()`),但调用方式不同。控制器中 `savePostOutput` 通过实例调用 `createOutput``savePostOutputConfig` 通过静态调用 `saveConfigOnly`,风格不统一。
## 10. API 端点清单
所有端点位于 `app/admin/controller/Post.php`,通过 ThinkPHP 自动路由访问URL前缀为 `/index.php/admin/post/`
| 端点 | 方法 | 说明 | 前端调用方 |
|------|------|------|-----------|
| `phoneImage/{id}` | GET | 排版操作页(渲染模板) | 页面跳转 |
| `savePostOutput` | POST (JSON) | 保存输出记录+图片 | `PhoneImageEngine.saveImages()` (phone-image.js:1103) |
| `savePostOutputConfig` | POST (JSON) | 仅保存配置(不生成图片) | `PhoneImageEngine.saveConfig()` (phone-image.js:1152) |
| `loadPostOutputConfig` | GET | 加载历史记录配置 | `loadFromHistory()` (phone_image.html:550) |
| `getOutputListJson` | GET | 获取输出记录列表 | 历史弹窗 (phone_image.html:504) |
| `deletePostOutput` | POST/GET | 删除输出记录及文件 | 未在前端模板中直接调用 |
| `regeneratePostOutput` | POST/GET | 删除旧记录,返回配置 | 未在前端模板中直接调用 |
| `downloadPostOutputZip/{id}` | GET | 打包下载ZIP | 更多菜单 -> 打包下载 |
| `postOutputList/{id}` | GET | 输出管理列表页 | 独立页面入口 |
### 请求/响应格式
**savePostOutput** 请求体:
```json
{
"post_id": 123,
"output_type": "phone_image",
"config": { "size": "...", "watermark": "...", ... },
"content_html": "<p>...</p>",
"pages": ["data:image/jpeg;base64,..."]
}
```
**savePostOutputConfig** 请求体:
```json
{
"post_id": 123,
"output_type": "phone_image",
"config": { "size": "...", "watermark": "...", "pageAlignments": {} },
"content_html": "<p>...</p>"
}
```
**通用响应格式**:
```json
{ "code": 0, "msg": "", "data": { "output_id": 456 } }
```
失败时 `code` 为 500。
## 11. 修复记录
### 11.1 数据源统一修复wangeditor 重构前)
**修复日期**2026-05-11
**核心原理**将三个独立的数据源DOM、闭包变量、模板局部变量统一为单一权威数据源通过新增的引擎 API 对外暴露。
**变更清单**
| 变更 | 文件 | 说明 |
|------|------|------|
| 新增 `getContentHtml()` | `phone-image.js:1280-1285` | 优先从 wangeditor 读取,回退到闭包变量 |
| 新增 `updateConfig(newConfig)` | `phone-image.js:1286-1290` | 使用 `$.extend` 更新配置,不重置内容 |
| 新增 `saveConfig()` API | `phone-image.js:1138-1170` | 引擎级保存配置方法 |
| 新增 `exportLongImage()` | `phone-image.js:1192-1257` | 从编辑器DOM导出长图 |
| 保存按钮改用 `getContentHtml()` | `phone_image.html:431` | content_html 取自引擎 API |
| 生成按钮改用 `getContentHtml()` | `phone_image.html:454` | content_html 取自引擎 API |
| 自动保存机制 | `phone_image.html:206-413` | onChange 2.6s 防抖自动保存 |
| `doRender` 改用 `updateConfig()` | `phone_image.html:344` | 替代 `init()` 调用 |
| `loadFromHistory``editor.setHtml()` | `phone_image.html:561` | 加载到编辑器而非重置引擎 |
### 11.2 wangeditor 重构
**重构日期**2026-05-11
**核心变更**:将内容编辑从旧版中间栏(`#content-flow` + 手动分页标记)迁移到 wangeditor 富文本编辑器。
**主要变更**
| 变更 | 说明 |
|------|------|
| 引入 wangeditor v5 | 替代旧版中间栏内容展示和编辑 |
| 新增 `captureEditorBlocks` | DOM 测量方案替代旧版高度估算 |
| 新增 `getEditorChildren` | 穿透 wangeditor DOM 包装层 |
| 新增 `exportLongImage` | 从编辑器DOM导出完整长图 |
| 分割线替代分页标记按钮 | wangeditor divider (`<hr>`) = page-break |
| 自动保存 | 编辑器 onChange 触发 2.6s 防抖保存 |
| 更多菜单纯 JS 实现 | 替代 layui dropdown不存在 |
| 删除旧版渲染管线 | renderContentFlow / convertFlowBlocksToImages / measureBlockHeights 等 |
| 删除旧版高度估算 | estimateTextHeight 等,由 DOM 测量替代 |
| 删除旧版 UI | 左侧工具栏、中间栏交互 |
**重构效果**
- 内容编辑体验从"预览+标记"模式变为"所见即所得"编辑模式
- 分页标记通过分割线直观可见和可编辑
- 自动保存减少手动操作
- DOM 测量替代估算,分页精度提升
---
*文档基于以下源文件的完整阅读:*
- `public/static/js/phone-image.js` (1292行)
- `view/admin/post/phone_image.html` (600行)
- `app/admin/controller/Post.php` (L350-579)
- `app/common/tools/PhoneImage.php` (229行)
- `app/common/tools/PostOutputManagerInterface.php` (28行)
- `app/model/PostOutput.php` (62行)
- `app/model/PostOutputFile.php` (26行)