mirror of
https://gitee.com/ulthon/ulthon_information.git
synced 2026-07-01 16:32:48 +08:00
docs(phone-image): 全面更新架构文档反映wangeditor方案
重写渲染管线、新增wangeditor集成章节、更新数据流/存储模型/配置体系、 新增UI布局/已删除函数/已知限制章节(373行新增,171行删除)
This commit is contained in:
@@ -9,111 +9,191 @@
|
|||||||
### 1.1 管线总览
|
### 1.1 管线总览
|
||||||
|
|
||||||
```
|
```
|
||||||
用户触发 doRender()
|
用户触发 doRender() 或编辑器 onChange 自动触发
|
||||||
|
|
|
|
||||||
v
|
v
|
||||||
PhoneImageEngine.init(options, userConfig) -- L80: 合并配置、注册事件委托
|
PhoneImageEngine.render() -- L82: 管线入口,带并发锁
|
||||||
|
|
|
|
||||||
v
|
+---> editorHtml = window.phoneImageEditor.getHtml() -- L99: 从 wangeditor 读取HTML
|
||||||
PhoneImageEngine.render() -- L129: 管线入口,带并发锁
|
+---> preprocessContent(editorHtml) -- L154: 清洗HTML(去script/style/iframe,规范化img)
|
||||||
|
+---> parseHtmlToBlocks(cleanHtml) -- L201: HTML -> 块级元素数组,<hr> 识别为 page-break
|
||||||
|
|
|
|
||||||
+---> preprocessContent(content_html) -- L226: 清洗HTML(去script/style/iframe,规范化img)
|
+---> generateCoverPage(sizeConfig) -- L619: 生成封面页HTML
|
||||||
+---> parseHtmlToBlocks(cleanHtml) -- L273: HTML -> 块级元素数组,含高度估算
|
|
||||||
+---> renderContentFlow(blocks) -- L1160: 将块渲染到中间栏 #content-flow
|
|
||||||
+---> convertFlowBlocksToImages(blocks) -- L1051: 表格/代码块 -> html2canvas截图 -> img标签替换
|
|
||||||
+---> measureBlockHeights(blocks) -- L1138: 从中间栏DOM读取实测高度,覆盖估算值
|
|
||||||
+---> generateCoverPage() -- L618: 生成封面页HTML
|
|
||||||
+---> paginateContent(blocks, areaHeight) -- L447: 分页算法,累加高度超限则换页
|
|
||||||
+---> generateSummaryPage() -- L730: 生成尾页HTML
|
|
||||||
+---> 页码补全 N/M -- L188: 二次遍历pages数组,替换占位页码
|
|
||||||
+---> renderThumbnails(sizeConfig) -- L967: 截图 -> 显示右侧缩略图
|
|
||||||
|
|
|
|
||||||
+---> doCapturePages({scale:1}) -- L840: staging隐藏区域渲染 + html2canvas串行截图
|
+---> 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 并发控制
|
### 1.2 并发控制
|
||||||
|
|
||||||
`render()` 函数使用 `_locked` 标志防止并发渲染(`phone-image.js:133-138`)。渲染期间如果有新请求,设置 `_pending = true`,当前渲染完成后自动重试(`phone-image.js:202-207`)。`insertPageBreak` 和 `removePageBreak` 也有类似的 `_retryCount` 轮询机制(`phone-image.js:1198-1209`, `phone-image.js:1233-1244`)。
|
`render()` 函数使用 `_locked` 标志防止并发渲染(`phone-image.js:86-91`)。渲染期间如果有新请求,设置 `_pending = true`,当前渲染完成后自动重试(`phone-image.js:134-137`)。
|
||||||
|
|
||||||
### 1.3 截图机制
|
### 1.3 DOM 测高机制
|
||||||
|
|
||||||
截图在 `#render-staging` 隐藏区域进行(`phone-image.js:64-73`)。关键步骤:
|
`captureEditorBlocks`(`phone-image.js:345-437`)是管线中替代旧版 `renderContentFlow` + `measureBlockHeights` 的核心函数。工作流程:
|
||||||
|
|
||||||
1. 将所有页面HTML append到staging区域
|
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 会跳过隐藏元素)
|
2. 临时将 `visibility:hidden` 切为 `visible`(html2canvas 会跳过隐藏元素)
|
||||||
3. 串行逐页调用 html2canvas 截图(`runCaptureLoop`, L880)
|
3. 串行逐页调用 html2canvas 截图(`runCaptureLoop`, L881)
|
||||||
4. 纯图片页优化:直接用 Image + Canvas 绘制,跳过 html2canvas(`phone-image.js:765-829`)
|
4. 纯图片页优化:直接用 `Image + Canvas` 绘制,跳过 html2canvas(`phone-image.js:805-830`)
|
||||||
5. 截图完成后恢复 `visibility:hidden`
|
5. 截图完成后恢复 `visibility:hidden`
|
||||||
|
|
||||||
缩略图用 `scale:1`,保存用 `scale:2`(输出1080px宽度)。
|
缩略图用 `scale:1`,保存用 `scale:2`(输出1080px宽度)。
|
||||||
|
|
||||||
### 1.4 表格/代码块的预转换
|
### 1.5 getEditorChildren 穿透逻辑
|
||||||
|
|
||||||
在分页之前,中间栏 `#content-flow` 里的 `<table>` 和 `<pre>` 块会被 html2canvas 截图并替换为 `<img data-converted="true">` 标签(`phone-image.js:1051-1132`)。转换结果用 `convertedBlockCache`(djb2哈希做key)缓存,避免重复截图。这是为了保证分页算法拿到准确的高度值,因为 html2canvas 无法在 staging 区域中直接处理复杂元素。
|
`getEditorChildren()`(`phone-image.js:323-334`)处理 wangeditor v5 的 DOM 包装层。wangeditor 的 `[data-slate-editor]` 下可能有一层 `<div>` 包装,该函数检测到这种情况时穿透到内层子元素,返回实际的内容节点数组。这对 `captureEditorBlocks` 的 DOM 克隆测高和 `exportLongImage` 的长图导出都至关重要。
|
||||||
|
|
||||||
## 2. 数据流
|
## 2. wangeditor 集成
|
||||||
|
|
||||||
### 2.1 修复后的单一数据源模型(当前状态)
|
### 2.1 编辑器初始化
|
||||||
|
|
||||||
修复后,引擎闭包变量 `postData.content_html` 成为唯一的权威数据源,所有读写操作统一通过引擎 API 进行。
|
编辑器在 `phone_image.html` 模板脚本中初始化(L192-288):
|
||||||
|
|
||||||
**权威数据源**:闭包 `postData.content_html`(`phone-image.js:32`),通过以下两个 API 对外暴露:
|
```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 | 位置 | 用途 |
|
| API | 位置 | 用途 |
|
||||||
|-----|------|------|
|
|-----|------|------|
|
||||||
| `getContentHtml()` | `phone-image.js:1485-1487` | 返回 `postData.content_html`,外部获取内容的唯一标准接口 |
|
| `getContentHtml()` | `phone-image.js:1280-1285` | 优先从 wangeditor 读取HTML,回退到闭包变量 |
|
||||||
| `updateConfig(newConfig)` | `phone-image.js:1488-1492` | 使用 `$.extend(config, newConfig)` 更新配置,**不重置** `postData` |
|
| `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` 的角色降级**:仅作为初始化数据源和显示层,不再被保存操作读取。保存后主动同步 `$('#post-content-html').html(PhoneImageEngine.getContentHtml())`(`phone_image.html:271`, `phone_image.html:295`),保持显示层与权威源一致。
|
**DOM `#post-content-html` 的角色**:仅作为初始化数据源(`phone_image.html:105`)和保存后的显示同步层(`phone_image.html:434,459`),保存操作不从中读取内容。
|
||||||
|
|
||||||
**各操作的数据流**:
|
### 3.2 各操作的数据流
|
||||||
|
|
||||||
```
|
```
|
||||||
保存配置 (#btn-save):
|
保存配置 (#btn-save):
|
||||||
content_html 取自 PhoneImageEngine.getContentHtml() -- L268
|
content_html 取自 PhoneImageEngine.getContentHtml() -- L431
|
||||||
保存后同步: $('#post-content-html').html(getContentHtml()) -- L271
|
调用 PhoneImageEngine.saveConfig() API -- L428-432
|
||||||
|
保存后同步: $('#post-content-html').html(getContentHtml()) -- L434
|
||||||
|
|
||||||
生成并保存 (#btn-generate):
|
生成并保存 (#btn-generate):
|
||||||
content_html 取自 PhoneImageEngine.getContentHtml() -- L290
|
content_html 取自 PhoneImageEngine.getContentHtml() -- L454
|
||||||
保存后同步: $('#post-content-html').html(getContentHtml()) -- L295
|
调用 PhoneImageEngine.saveImages() API -- L451-455
|
||||||
|
保存后同步: $('#post-content-html').html(getContentHtml()) -- L459
|
||||||
|
|
||||||
重新排版 (doRender):
|
重新排版 (doRender):
|
||||||
用 PhoneImageEngine.updateConfig(newConfig) -- L239
|
用 PhoneImageEngine.updateConfig(newConfig) -- L344
|
||||||
不再调用 init(),不重置 postData.content_html
|
再调 PhoneImageEngine.render() -- L346
|
||||||
|
|
||||||
|
自动保存 (onChange):
|
||||||
|
wangeditor onChange -> 2.6s 防抖 -> doAutoSave() -- phone_image.html:209-216
|
||||||
|
doAutoSave 调用 PhoneImageEngine.saveConfig() -- phone_image.html:403-413
|
||||||
|
|
||||||
加载历史 (loadFromHistory):
|
加载历史 (loadFromHistory):
|
||||||
PhoneImageEngine.init(postData, historyConfig) -- L414 (全量初始化)
|
window.phoneImageEditor.setHtml(res.data.content_html) -- phone_image.html:561 (加载到编辑器)
|
||||||
PhoneImageEngine.render() -- L416
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
**分页标记操作**仍然直接修改闭包内的 `postData.content_html`(`insertPageBreak` L1223, `removePageBreak` L1261),但保存时通过 `getContentHtml()` 读取,确保标记不会丢失。
|
### 3.3 自动保存机制
|
||||||
|
|
||||||
### 2.2 修复前的三源问题(历史记录)
|
编辑器内容变更时触发自动保存(`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`)
|
||||||
|
|
||||||
文章HTML内容在运行时存在于三个独立位置,彼此之间没有同步机制:
|
手动保存按钮点击时会清除自动保存定时器,避免冲突。
|
||||||
|
|
||||||
| 来源 | 位置 | 说明 |
|
### 3.4 完整数据流图
|
||||||
|------|------|------|
|
|
||||||
| **DOM `#post-content-html`** | `phone_image.html:98` | 服务端渲染的隐藏div,`{$layoutContentHtml\|raw}` |
|
|
||||||
| **闭包 `postData.content_html`** | `phone-image.js:32` | IIFE闭包变量,init()时赋值 |
|
|
||||||
| **初始化属性 `postData.contentHtml`** | `phone_image.html:193` | 模板内联脚本中的局部对象属性 |
|
|
||||||
|
|
||||||
具体流向:
|
|
||||||
|
|
||||||
```
|
|
||||||
服务端 ($layoutContentHtml)
|
|
||||||
-> DOM #post-content-html (L98, display:none)
|
|
||||||
-> 模板脚本读取: $('#post-content-html').html() (L193)
|
|
||||||
-> 赋值给 postData.contentHtml (模板局部变量)
|
|
||||||
-> init({contentHtml: ...}) (L208)
|
|
||||||
-> 闭包 postData.content_html = options.contentHtml (phone-image.js:85)
|
|
||||||
```
|
|
||||||
|
|
||||||
**问题(已修复)**:分页标记操作(`insertPageBreak`/`removePageBreak`)只更新闭包内的 `postData.content_html`(`phone-image.js:1223`, `phone-image.js:1261`),不更新 DOM `#post-content-html` 和模板局部的 `postData.contentHtml`。而保存操作(`#btn-save`, `#btn-generate`)原来读取的是 `$('#post-content-html').html()`,这意味着分页标记在保存时会丢失。此问题已通过将保存操作改为读取 `getContentHtml()` 修复。
|
|
||||||
|
|
||||||
### 2.3 完整数据流图(修复后)
|
|
||||||
|
|
||||||
```
|
```
|
||||||
[数据库 Post.content_html]
|
[数据库 Post.content_html]
|
||||||
@@ -128,23 +208,30 @@ phone_image.html 模板渲染
|
|||||||
- $layoutConfig (已保存的配置)
|
- $layoutConfig (已保存的配置)
|
||||||
|
|
|
|
||||||
v
|
v
|
||||||
前端初始化 (phone_image.html:188-208)
|
前端初始化 (phone_image.html:152-294)
|
||||||
1. 构造 postData 局部对象 (L188)
|
1. 构造 postData 局部对象,contentHtml 取自 #post-content-html (L173)
|
||||||
2. 恢复 savedConfig (L201)
|
2. 恢复 savedConfig (L181-186)
|
||||||
3. PhoneImageEngine.init(postData, initConfig) (L208)
|
3. wangeditor 初始化,html=postData.contentHtml (L270-274)
|
||||||
|
4. window.phoneImageEditor = phoneImageEditor (L288)
|
||||||
|
5. PhoneImageEngine.init(postData, initConfig) (L290)
|
||||||
|
6. doRender() 触发初始渲染 (L594)
|
||||||
|
|
|
|
||||||
v
|
v
|
||||||
渲染管线 (phone-image.js render())
|
渲染管线 (phone-image.js render())
|
||||||
- 读取 postData.content_html (唯一权威源)
|
- 读取 window.phoneImageEditor.getHtml() (唯一权威源)
|
||||||
- 生成 pages 数组
|
- parseHtmlToBlocks + captureEditorBlocks(测高) + paginateContent(分页)
|
||||||
- 截图显示缩略图
|
- staging 渲染 + html2canvas 截图
|
||||||
|
- 显示缩略图
|
||||||
|
|
|
|
||||||
v
|
v
|
||||||
用户交互
|
用户交互
|
||||||
- 分页标记 -> 修改 postData.content_html (闭包内)
|
- 编辑内容 -> wangeditor -> onChange 自动保存(2.6s防抖)
|
||||||
- 保存配置 -> POST savePostOutputConfig (content_html 取自 getContentHtml())
|
- 分割线 -> wangeditor divider -> <hr> -> page-break 分页
|
||||||
- 生成保存 -> POST savePostOutput (content_html 取自 getContentHtml())
|
- 保存配置 -> PhoneImageEngine.saveConfig() (content_html 取自 getContentHtml())
|
||||||
- 重新排版 -> updateConfig() + render() (不重置 postData)
|
- 生成保存 -> PhoneImageEngine.saveImages() (content_html 取自 getContentHtml())
|
||||||
|
- 重新排版 -> updateConfig() + render()
|
||||||
|
- 加载历史 -> editor.setHtml() + updateConfig() + render()
|
||||||
|
- 导出长图 -> exportLongImage() (从编辑器DOM直接截图)
|
||||||
|
|
|
|
||||||
v
|
v
|
||||||
后端存储
|
后端存储
|
||||||
@@ -152,22 +239,9 @@ phone_image.html 模板渲染
|
|||||||
- PostOutputFile (每页图片文件记录)
|
- PostOutputFile (每页图片文件记录)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.4 历史记录加载流(修复后)
|
## 4. 存储模型
|
||||||
|
|
||||||
```
|
### 4.1 数据库表
|
||||||
loadFromHistory(outputId) (phone_image.html:374)
|
|
||||||
-> GET loadPostOutputConfig?id=XXX
|
|
||||||
-> 更新表单控件 (size/fontSize/watermark)
|
|
||||||
-> 更新 postData.contentHtml (模板局部变量)
|
|
||||||
-> PhoneImageEngine.init(postData, historyConfig) (全量初始化, L414)
|
|
||||||
-> PhoneImageEngine.render() (L416)
|
|
||||||
```
|
|
||||||
|
|
||||||
修复后 `loadFromHistory` 使用 `init + render` 全量初始化引擎,直接将历史内容传入闭包,不再依赖 `doRender` 的间接传递。
|
|
||||||
|
|
||||||
## 3. 存储模型
|
|
||||||
|
|
||||||
### 3.1 数据库表
|
|
||||||
|
|
||||||
**`ul_post_output`** (模型: `app/model/PostOutput.php`)
|
**`ul_post_output`** (模型: `app/model/PostOutput.php`)
|
||||||
|
|
||||||
@@ -200,29 +274,77 @@ loadFromHistory(outputId) (phone_image.html:374)
|
|||||||
|
|
||||||
关系: `PostOutput hasMany PostOutputFile`,`PostOutput belongsTo Post`。
|
关系: `PostOutput hasMany PostOutputFile`,`PostOutput belongsTo Post`。
|
||||||
|
|
||||||
### 3.2 config JSON 结构
|
### 4.2 config JSON 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"size": "xiaohongshu",
|
"size": "xiaohongshu",
|
||||||
"fontSize": 14,
|
|
||||||
"watermark": "",
|
"watermark": "",
|
||||||
"content_html": "<p>文章HTML副本</p>",
|
"pageAlignments": { "1": "top", "2": "center" },
|
||||||
"pageAlignments": { "1": "top", "2": "center" }
|
"content_html": "<p>文章HTML副本</p><hr><p>第二页内容</p>"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`content_html` 作为配置的一部分保存,使每次排版记录都携带当时的内容快照。这实现了排版内容与文章原文的解耦(`Post.php:369-376` 加载时会优先使用此副本)。
|
`content_html` 作为配置的一部分保存,使每次排版记录都携带当时的内容快照。含 `<hr>` 分页标记的 HTML 被完整保存,`parseHtmlToBlocks` 可以正确解析。
|
||||||
|
|
||||||
### 3.3 文件存储
|
**向后兼容**:旧版 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`)。
|
图片保存到 `public/upload/post_output/{Ymd}/{outputId}_{page}.jpg`(`PhoneImage.php:87-92`)。ZIP 临时文件生成到 `runtime/temp/` 目录,请求结束后通过 `register_shutdown_function` 清理(`Post.php:570-575`)。
|
||||||
|
|
||||||
## 4. 配置体系
|
## 5. wangeditor 配置与工具栏
|
||||||
|
|
||||||
### 4.1 PHP 端配置字段定义(对齐后)
|
### 5.1 编辑器配置
|
||||||
|
|
||||||
`PhoneImage::getConfigFields()` (L15-23) 定义了4个字段,已与 JS 端对齐:
|
```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个字段:
|
||||||
|
|
||||||
| 字段 | 类型 | 选项/范围 | 默认值 |
|
| 字段 | 类型 | 选项/范围 | 默认值 |
|
||||||
|------|------|-----------|--------|
|
|------|------|-----------|--------|
|
||||||
@@ -231,18 +353,13 @@ loadFromHistory(outputId) (phone_image.html:374)
|
|||||||
| `watermark` | text | - | '' |
|
| `watermark` | text | - | '' |
|
||||||
| `pageAlignments` | json | - | '{}' |
|
| `pageAlignments` | json | - | '{}' |
|
||||||
|
|
||||||
**已移除的字段**:`template`(3个模板选项 minimal/magazine/mixed)和 `font`(3个字体选项 source-han-sans/alibaba-puhuiti/lxgw-wenkai)已从 `getConfigFields()` 中移除,因为 JS 端无对应的模板/字体切换逻辑。
|
### 6.2 JS 端实际使用的配置
|
||||||
|
|
||||||
**新增的字段**:`pageAlignments` 已新增到 `getConfigFields()`,类型为 `json`,默认值 `'{}'`,与 JS 端的逐页对齐功能对齐。
|
`phone-image.js` 闭包内的 `config` 对象 (L14-23):
|
||||||
|
|
||||||
### 4.2 JS 端实际使用的配置
|
|
||||||
|
|
||||||
`phone-image.js` 闭包内的 `config` 对象 (L14-24):
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
config = {
|
config = {
|
||||||
size: 'xiaohongshu',
|
size: 'xiaohongshu',
|
||||||
fontSize: 14,
|
|
||||||
watermark: '',
|
watermark: '',
|
||||||
pageAlignments: {}, // 逐页对齐
|
pageAlignments: {}, // 逐页对齐
|
||||||
sizes: { ... }, // 尺寸预设
|
sizes: { ... }, // 尺寸预设
|
||||||
@@ -250,81 +367,146 @@ config = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.3 PHP/JS 字段对齐状态(修复后)
|
### 6.3 设置弹框
|
||||||
|
|
||||||
修复后两端字段已基本对齐:
|
通过 layui layer 弹框实现(`phone_image.html:293-330`),提供:
|
||||||
|
- 尺寸选择:小红书 (1080x1440) / 抖音 (1080x1920)
|
||||||
|
- 水印输入:可选水印文字
|
||||||
|
|
||||||
| 字段 | PHP 定义 | JS 使用 | 状态 |
|
确认后自动触发 `doRender()` 重新排版。
|
||||||
|------|----------|---------|------|
|
|
||||||
| `size` | select, 2个选项 | 核心功能 | 已对齐 |
|
|
||||||
| `fontSize` | number, 10-24 | 核心功能 | 已对齐 |
|
|
||||||
| `watermark` | text | 核心功能 | 已对齐 |
|
|
||||||
| `pageAlignments` | json | 核心功能 | 已对齐 |
|
|
||||||
| `contentPadding` | 未定义 | 硬编码为20 (L23) | 仍为 JS 内部常量,无需 PHP 定义 |
|
|
||||||
|
|
||||||
`validateConfig()` (L28-50) 验证 `size`/`fontSize`,`pageAlignments` 为 JSON 类型不做严格校验。前端发送的配置比 PHP 定义的字段多(如 `content_html`、`sizes`、`contentPadding`),PHP 不做字段白名单过滤,全部存入 JSON config 字段。
|
### 6.4 自动保存状态指示器
|
||||||
|
|
||||||
> **修复前的字段不对齐记录**:`template`(PHP 有定义但 JS 不使用)和 `font`(PHP 有定义但 JS 不使用)已从 PHP 端移除;`pageAlignments`(JS 核心功能但 PHP 未定义)已新增到 PHP 端。
|
`<span id="save-state">` 元素(`phone_image.html:417`)动态显示保存状态:
|
||||||
|
- `waiting`:黄色,"等待保存..."
|
||||||
|
- `saving`:蓝色,"保存中..."
|
||||||
|
- `saved`:绿色,"已保存"
|
||||||
|
- `error`:红色,"保存失败"
|
||||||
|
|
||||||
## 5. 已知问题
|
## 7. UI 布局
|
||||||
|
|
||||||
### 5.1 已修复
|
### 7.1 整体结构
|
||||||
|
|
||||||
#### 5.1.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 | | |
|
||||||
|
| +--------------------------+ | |
|
||||||
|
+------------------------------+---------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
**根因**:数据源不一致。分页标记写入闭包 `postData.content_html`,但保存时读取的是 DOM `#post-content-html`(不含标记)。
|
### 7.2 顶部操作栏
|
||||||
|
|
||||||
**修复方案**:保存操作改用 `PhoneImageEngine.getContentHtml()` 读取闭包内的权威数据源(`phone_image.html:268`, `phone_image.html:290`),保存后主动同步 DOM(`phone_image.html:271`, `phone_image.html:295`)。
|
按钮直接显示:设置、保存、生成并保存。"更多"下拉菜单(纯 JS 实现,`phone_image.html:358-385`)包含:历史记录、重新生成、打包下载、导出长图。
|
||||||
|
|
||||||
#### 5.1.2 双数据源不一致(已修复)
|
layui 2.x 没有 dropdown 模块,更多菜单用纯 JS 实现的 `toggle` + `stopPropagation` + 外部点击关闭。
|
||||||
|
|
||||||
**现象**:`doRender` 调用 `init()` 重置 `postData.content_html`,导致分页标记在重新排版时丢失。
|
### 7.3 缩略图区域
|
||||||
|
|
||||||
**根因**:`doRender` 通过 `init()` 传递配置,`init()` 会用 DOM 内容覆盖闭包变量。
|
右侧缩略图横向排列,每个缩略图带页码标签和对齐切换按钮(仅内容页显示,`phone-image.js:1017-1025`)。对齐按钮切换 `top`/`center` 垂直对齐。
|
||||||
|
|
||||||
**修复方案**:`doRender` 改用 `PhoneImageEngine.updateConfig(newConfig)`(`phone_image.html:239`),仅更新配置不重置 `postData`。
|
## 8. 已删除的函数/组件
|
||||||
|
|
||||||
#### 5.1.3 PHP/JS 字段不对齐(已修复)
|
wangeditor 重构中移除的旧版函数和组件:
|
||||||
|
|
||||||
**现象**:PHP 定义了 JS 不使用的 `template`/`font` 字段,缺少 JS 核心使用的 `pageAlignments` 字段。
|
### 8.1 渲染管线相关
|
||||||
|
|
||||||
**修复方案**:PHP `getConfigFields()` 移除 `template`/`font`,新增 `pageAlignments`(`PhoneImage.php:17-22`)。
|
| 已删除函数 | 说明 |
|
||||||
|
|-----------|------|
|
||||||
|
| `renderContentFlow` | 旧版将块渲染到中间栏 `#content-flow`,已由 wangeditor 编辑器替代 |
|
||||||
|
| `convertFlowBlocksToImages` | 旧版表格/代码块预转换(html2canvas截图替换为img),新版不再需要 |
|
||||||
|
| `measureBlockHeights` | 旧版从中间栏DOM读取实测高度,已由 `captureEditorBlocks` 的测量容器方案替代 |
|
||||||
|
| `insertPageBreak` | 旧版中间栏"+"按钮插入分页标记,已由 wangeditor divider 替代 |
|
||||||
|
| `removePageBreak` | 旧版中间栏"-"按钮移除分页标记,已由编辑器直接删除 divider 替代 |
|
||||||
|
|
||||||
### 5.2 仍存在的问题
|
### 8.2 高度估算相关
|
||||||
|
|
||||||
#### 5.2.1 保存数据量限制
|
| 已删除函数 | 说明 |
|
||||||
|
|-----------|------|
|
||||||
|
| `estimateTextHeight` | 旧版纯计算估算段落高度,已由 DOM 测量替代 |
|
||||||
|
| `estimateHeadingHeight` | 旧版估算标题高度 |
|
||||||
|
| `estimateListHeight` | 旧版估算列表高度 |
|
||||||
|
| `estimateBlockquoteHeight` | 旧版估算引用块高度 |
|
||||||
|
|
||||||
`saveImages()` 有16MB的客户端检查(`phone-image.js:1317-1320`),但服务端没有对应的分块接收机制。大文章(超过约30页)的base64数据可能超出 PHP `post_max_size` 配置。
|
### 8.3 工具函数
|
||||||
|
|
||||||
#### 5.2.2 目录权限 0777
|
| 已删除函数 | 说明 |
|
||||||
|
|-----------|------|
|
||||||
|
| `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 一致。
|
`PhoneImage.php:95` 和 `:140` 中 `mkdir` 使用 0777 权限,与项目其他位置的 anti-pattern 一致。
|
||||||
|
|
||||||
#### 5.2.3 接口 `PostOutputManagerInterface` 未被充分利用
|
### 9.6 接口 `PostOutputManagerInterface` 未被充分利用
|
||||||
|
|
||||||
`PhoneImage` 实现了 `process()` 和 `getPreview()` 方法,但两者都返回空值(`PhoneImage.php:56-67`)。实际业务完全在控制器中直接调用 `PhoneImage` 的其他方法,绕过了接口定义的流程。接口仅作为类型约束存在。
|
`PhoneImage` 实现了 `process()` 和 `getPreview()` 方法,但两者都返回空值(`PhoneImage.php:56-67`)。实际业务完全在控制器中直接调用 `PhoneImage` 的其他方法,绕过了接口定义的流程。接口仅作为类型约束存在。
|
||||||
|
|
||||||
#### 5.2.4 `saveConfigOnly` 是静态方法但 `createOutput` 是实例方法
|
### 9.7 `saveConfigOnly` 是静态方法但 `createOutput` 是实例方法
|
||||||
|
|
||||||
`PhoneImage::saveConfigOnly()` 是 static 方法(L223),而同类的 `createOutput()` 是实例方法(L193)。两者的实现几乎相同(都是 `PostOutput::create()`),但调用方式不同。控制器中 `savePostOutput` 通过实例调用 `createOutput`,`savePostOutputConfig` 通过静态调用 `saveConfigOnly`,风格不统一。
|
`PhoneImage::saveConfigOnly()` 是 static 方法(L223),而同类的 `createOutput()` 是实例方法(L193)。两者的实现几乎相同(都是 `PostOutput::create()`),但调用方式不同。控制器中 `savePostOutput` 通过实例调用 `createOutput`,`savePostOutputConfig` 通过静态调用 `saveConfigOnly`,风格不统一。
|
||||||
|
|
||||||
## 6. API 端点清单
|
## 10. API 端点清单
|
||||||
|
|
||||||
所有端点位于 `app/admin/controller/Post.php`,通过 ThinkPHP 自动路由访问,URL前缀为 `/index.php/admin/post/`。
|
所有端点位于 `app/admin/controller/Post.php`,通过 ThinkPHP 自动路由访问,URL前缀为 `/index.php/admin/post/`。
|
||||||
|
|
||||||
| 端点 | 方法 | 行号 | 说明 | 前端调用方 |
|
| 端点 | 方法 | 说明 | 前端调用方 |
|
||||||
|------|------|------|------|-----------|
|
|------|------|------|-----------|
|
||||||
| `phoneImage/{id}` | GET | L355-383 | 排版操作页(渲染模板) | 页面跳转 |
|
| `phoneImage/{id}` | GET | 排版操作页(渲染模板) | 页面跳转 |
|
||||||
| `savePostOutput` | POST (JSON) | L405-437 | 保存输出记录+图片 | `phone-image.js:1324` |
|
| `savePostOutput` | POST (JSON) | 保存输出记录+图片 | `PhoneImageEngine.saveImages()` (phone-image.js:1103) |
|
||||||
| `savePostOutputConfig` | POST (JSON) | L442-462 | 仅保存配置(不生成图片) | `phone_image.html:184` -> `saveConfig()` |
|
| `savePostOutputConfig` | POST (JSON) | 仅保存配置(不生成图片) | `PhoneImageEngine.saveConfig()` (phone-image.js:1152) |
|
||||||
| `loadPostOutputConfig` | GET | L467-490 | 加载历史记录配置 | `phone_image.html:186` -> `loadFromHistory()` |
|
| `loadPostOutputConfig` | GET | 加载历史记录配置 | `loadFromHistory()` (phone_image.html:550) |
|
||||||
| `getOutputListJson` | GET | L495-515 | 获取输出记录列表 | `phone_image.html:185` -> 历史弹窗 |
|
| `getOutputListJson` | GET | 获取输出记录列表 | 历史弹窗 (phone_image.html:504) |
|
||||||
| `deletePostOutput` | POST/GET | L520-531 | 删除输出记录及文件 | 未在前端模板中直接调用 |
|
| `deletePostOutput` | POST/GET | 删除输出记录及文件 | 未在前端模板中直接调用 |
|
||||||
| `regeneratePostOutput` | POST/GET | L536-552 | 删除旧记录,返回配置 | 未在前端模板中直接调用 |
|
| `regeneratePostOutput` | POST/GET | 删除旧记录,返回配置 | 未在前端模板中直接调用 |
|
||||||
| `downloadPostOutputZip/{id}` | GET | L557-579 | 打包下载ZIP | `phone_image.html:183` -> `#btn-download` |
|
| `downloadPostOutputZip/{id}` | GET | 打包下载ZIP | 更多菜单 -> 打包下载 |
|
||||||
| `postOutputList/{id}` | GET | L388-400 | 输出管理列表页 | 独立页面入口 |
|
| `postOutputList/{id}` | GET | 输出管理列表页 | 独立页面入口 |
|
||||||
|
|
||||||
### 请求/响应格式
|
### 请求/响应格式
|
||||||
|
|
||||||
@@ -333,9 +515,9 @@ config = {
|
|||||||
{
|
{
|
||||||
"post_id": 123,
|
"post_id": 123,
|
||||||
"output_type": "phone_image",
|
"output_type": "phone_image",
|
||||||
"config": { "size": "...", "fontSize": 14, ... },
|
"config": { "size": "...", "watermark": "...", ... },
|
||||||
"content_html": "<p>...</p>",
|
"content_html": "<p>...</p>",
|
||||||
"pages": ["data:image/jpeg;base64,...", ...]
|
"pages": ["data:image/jpeg;base64,..."]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -344,7 +526,7 @@ config = {
|
|||||||
{
|
{
|
||||||
"post_id": 123,
|
"post_id": 123,
|
||||||
"output_type": "phone_image",
|
"output_type": "phone_image",
|
||||||
"config": { "size": "...", "fontSize": 14, ... },
|
"config": { "size": "...", "watermark": "...", "pageAlignments": {} },
|
||||||
"content_html": "<p>...</p>"
|
"content_html": "<p>...</p>"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -355,40 +537,60 @@ config = {
|
|||||||
```
|
```
|
||||||
失败时 `code` 为 500。
|
失败时 `code` 为 500。
|
||||||
|
|
||||||
---
|
## 11. 修复记录
|
||||||
|
|
||||||
## 7. 修复记录
|
### 11.1 数据源统一修复(wangeditor 重构前)
|
||||||
|
|
||||||
### 7.1 数据源统一修复
|
|
||||||
|
|
||||||
**修复日期**:2026-05-11
|
**修复日期**:2026-05-11
|
||||||
|
|
||||||
**核心原理**:将三个独立的数据源(DOM、闭包变量、模板局部变量)统一为单一权威数据源——引擎闭包变量 `postData.content_html`,通过新增的引擎 API 对外暴露。
|
**核心原理**:将三个独立的数据源(DOM、闭包变量、模板局部变量)统一为单一权威数据源,通过新增的引擎 API 对外暴露。
|
||||||
|
|
||||||
**变更清单**:
|
**变更清单**:
|
||||||
|
|
||||||
| 变更 | 文件 | 说明 |
|
| 变更 | 文件 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| 新增 `getContentHtml()` | `phone-image.js:1485-1487` | 返回闭包内 `postData.content_html`,外部获取内容的唯一接口 |
|
| 新增 `getContentHtml()` | `phone-image.js:1280-1285` | 优先从 wangeditor 读取,回退到闭包变量 |
|
||||||
| 新增 `updateConfig(newConfig)` | `phone-image.js:1488-1492` | 使用 `$.extend(config, newConfig)` 更新配置,不重置 `postData` |
|
| 新增 `updateConfig(newConfig)` | `phone-image.js:1286-1290` | 使用 `$.extend` 更新配置,不重置内容 |
|
||||||
| 保存按钮改用 `getContentHtml()` | `phone_image.html:268` | `#btn-save` 的 `content_html` 取自引擎 API 而非 DOM |
|
| 新增 `saveConfig()` API | `phone-image.js:1138-1170` | 引擎级保存配置方法 |
|
||||||
| 生成按钮改用 `getContentHtml()` | `phone_image.html:290` | `#btn-generate` 的 `content_html` 取自引擎 API 而非 DOM |
|
| 新增 `exportLongImage()` | `phone-image.js:1192-1257` | 从编辑器DOM导出长图 |
|
||||||
| 保存后同步 DOM | `phone_image.html:271,295` | 保存成功后 `$('#post-content-html').html(getContentHtml())` |
|
| 保存按钮改用 `getContentHtml()` | `phone_image.html:431` | content_html 取自引擎 API |
|
||||||
| `doRender` 改用 `updateConfig()` | `phone_image.html:239` | 替代 `init()` 调用,避免重置 `postData.content_html` |
|
| 生成按钮改用 `getContentHtml()` | `phone_image.html:454` | content_html 取自引擎 API |
|
||||||
| `loadFromHistory` 改用 `init + render` | `phone_image.html:414-416` | 全量初始化引擎,直接传入历史内容 |
|
| 自动保存机制 | `phone_image.html:206-413` | onChange 2.6s 防抖自动保存 |
|
||||||
| PHP `getConfigFields` 移除 `template`/`font` | `PhoneImage.php:17-22` | 移除 JS 端不使用的字段定义 |
|
| `doRender` 改用 `updateConfig()` | `phone_image.html:344` | 替代 `init()` 调用 |
|
||||||
| PHP `getConfigFields` 新增 `pageAlignments` | `PhoneImage.php:21` | 与 JS 端逐页对齐功能对齐 |
|
| `loadFromHistory` 用 `editor.setHtml()` | `phone_image.html:561` | 加载到编辑器而非重置引擎 |
|
||||||
|
|
||||||
**修复效果**:
|
### 11.2 wangeditor 重构
|
||||||
- 分页标记在保存和重新排版后不再丢失
|
|
||||||
- 历史记录加载正确恢复内容和配置
|
**重构日期**:2026-05-11
|
||||||
- PHP/JS 端配置字段定义一致
|
|
||||||
|
**核心变更**:将内容编辑从旧版中间栏(`#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` (1495行)
|
- `public/static/js/phone-image.js` (1292行)
|
||||||
- `view/admin/post/phone_image.html` (436行)
|
- `view/admin/post/phone_image.html` (600行)
|
||||||
- `app/admin/controller/Post.php` (L350-579)
|
- `app/admin/controller/Post.php` (L350-579)
|
||||||
- `app/common/tools/PhoneImage.php` (229行)
|
- `app/common/tools/PhoneImage.php` (229行)
|
||||||
- `app/common/tools/PostOutputManagerInterface.php` (28行)
|
- `app/common/tools/PostOutputManagerInterface.php` (28行)
|
||||||
|
|||||||
Reference in New Issue
Block a user