diff --git a/app/common/tools/PhoneImage.php b/app/common/tools/PhoneImage.php index a4ffe2c..564b526 100644 --- a/app/common/tools/PhoneImage.php +++ b/app/common/tools/PhoneImage.php @@ -15,11 +15,10 @@ class PhoneImage implements PostOutputManagerInterface public function getConfigFields(): array { return [ - 'template' => ['type' => 'select', 'options' => ['minimal', 'magazine', 'mixed'], 'default' => 'minimal'], 'size' => ['type' => 'select', 'options' => ['xiaohongshu', 'douyin'], 'default' => 'xiaohongshu'], - 'font' => ['type' => 'select', 'options' => ['source-han-sans', 'alibaba-puhuiti', 'lxgw-wenkai'], 'default' => 'source-han-sans'], 'fontSize' => ['type' => 'number', 'default' => 14, 'min' => 10, 'max' => 24], 'watermark' => ['type' => 'text', 'default' => ''], + 'pageAlignments' => ['type' => 'json', 'default' => '{}'], ]; } @@ -30,21 +29,17 @@ class PhoneImage implements PostOutputManagerInterface { $fields = $this->getConfigFields(); - if (isset($config['template']) && !in_array($config['template'], $fields['template']['options'])) { - return false; - } if (isset($config['size']) && !in_array($config['size'], $fields['size']['options'])) { return false; } - if (isset($config['font']) && !in_array($config['font'], $fields['font']['options'])) { - return false; - } if (isset($config['fontSize'])) { $size = intval($config['fontSize']); if ($size < $fields['fontSize']['min'] || $size > $fields['fontSize']['max']) { return false; } } + // watermark 是文本类型,无需特殊验证 + // pageAlignments 是 JSON 类型,存储时由框架自动序列化 return true; } diff --git a/docs/phone-image-architecture.md b/docs/phone-image-architecture.md index 7b27b45..79b8f17 100644 --- a/docs/phone-image-architecture.md +++ b/docs/phone-image-architecture.md @@ -53,7 +53,44 @@ PhoneImageEngine.render() -- L129: 管线入口,带并发 ## 2. 数据流 -### 2.1 HTML内容的三个来源(三源问题) +### 2.1 修复后的单一数据源模型(当前状态) + +修复后,引擎闭包变量 `postData.content_html` 成为唯一的权威数据源,所有读写操作统一通过引擎 API 进行。 + +**权威数据源**:闭包 `postData.content_html`(`phone-image.js:32`),通过以下两个 API 对外暴露: + +| API | 位置 | 用途 | +|-----|------|------| +| `getContentHtml()` | `phone-image.js:1485-1487` | 返回 `postData.content_html`,外部获取内容的唯一标准接口 | +| `updateConfig(newConfig)` | `phone-image.js:1488-1492` | 使用 `$.extend(config, newConfig)` 更新配置,**不重置** `postData` | + +**DOM `#post-content-html` 的角色降级**:仅作为初始化数据源和显示层,不再被保存操作读取。保存后主动同步 `$('#post-content-html').html(PhoneImageEngine.getContentHtml())`(`phone_image.html:271`, `phone_image.html:295`),保持显示层与权威源一致。 + +**各操作的数据流**: + +``` +保存配置 (#btn-save): + content_html 取自 PhoneImageEngine.getContentHtml() -- L268 + 保存后同步: $('#post-content-html').html(getContentHtml()) -- L271 + +生成并保存 (#btn-generate): + content_html 取自 PhoneImageEngine.getContentHtml() -- L290 + 保存后同步: $('#post-content-html').html(getContentHtml()) -- L295 + +重新排版 (doRender): + 用 PhoneImageEngine.updateConfig(newConfig) -- L239 + 不再调用 init(),不重置 postData.content_html + +加载历史 (loadFromHistory): + PhoneImageEngine.init(postData, historyConfig) -- L414 (全量初始化) + PhoneImageEngine.render() -- L416 +``` + +**分页标记操作**仍然直接修改闭包内的 `postData.content_html`(`insertPageBreak` L1223, `removePageBreak` L1261),但保存时通过 `getContentHtml()` 读取,确保标记不会丢失。 + +### 2.2 修复前的三源问题(历史记录) + +> 以下记录修复前的问题状态,保留作为设计决策参考。 文章HTML内容在运行时存在于三个独立位置,彼此之间没有同步机制: @@ -74,9 +111,9 @@ PhoneImageEngine.render() -- L129: 管线入口,带并发 -> 闭包 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()`(`phone_image.html:267`, `phone_image.html:288`),这意味着分页标记在保存时会丢失。 +**问题(已修复)**:分页标记操作(`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.2 完整数据流图 +### 2.3 完整数据流图(修复后) ``` [数据库 Post.content_html] @@ -98,15 +135,16 @@ phone_image.html 模板渲染 | v 渲染管线 (phone-image.js render()) - - 读取 postData.content_html + - 读取 postData.content_html (唯一权威源) - 生成 pages 数组 - 截图显示缩略图 | v 用户交互 - 分页标记 -> 修改 postData.content_html (闭包内) - - 保存配置 -> POST savePostOutputConfig (content_html 取自 DOM) - - 生成保存 -> POST savePostOutput (content_html 取自 DOM) + - 保存配置 -> POST savePostOutputConfig (content_html 取自 getContentHtml()) + - 生成保存 -> POST savePostOutput (content_html 取自 getContentHtml()) + - 重新排版 -> updateConfig() + render() (不重置 postData) | v 后端存储 @@ -114,18 +152,18 @@ phone_image.html 模板渲染 - PostOutputFile (每页图片文件记录) ``` -### 2.3 历史记录加载流 +### 2.4 历史记录加载流(修复后) ``` -loadFromHistory(outputId) (phone_image.html:371) +loadFromHistory(outputId) (phone_image.html:374) -> GET loadPostOutputConfig?id=XXX -> 更新表单控件 (size/fontSize/watermark) -> 更新 postData.contentHtml (模板局部变量) - -> 更新 DOM #post-content-html - -> doRender(renderConfig) (含 pageAlignments) + -> PhoneImageEngine.init(postData, historyConfig) (全量初始化, L414) + -> PhoneImageEngine.render() (L416) ``` -注意:`loadFromHistory` 更新的是模板局部的 `postData.contentHtml` 和 DOM,但没有直接更新闭包内的 `postData.content_html`。它依赖 `doRender -> init()` 的间接传递来同步。 +修复后 `loadFromHistory` 使用 `init + render` 全量初始化引擎,直接将历史内容传入闭包,不再依赖 `doRender` 的间接传递。 ## 3. 存储模型 @@ -182,17 +220,20 @@ loadFromHistory(outputId) (phone_image.html:371) ## 4. 配置体系 -### 4.1 PHP 端配置字段定义 +### 4.1 PHP 端配置字段定义(对齐后) -`PhoneImage::getConfigFields()` (L15-23) 定义了5个字段: +`PhoneImage::getConfigFields()` (L15-23) 定义了4个字段,已与 JS 端对齐: | 字段 | 类型 | 选项/范围 | 默认值 | |------|------|-----------|--------| -| `template` | select | minimal/magazine/mixed | minimal | | `size` | select | xiaohongshu/douyin | xiaohongshu | -| `font` | select | source-han-sans/alibaba-puhuiti/lxgw-wenkai | source-han-sans | | `fontSize` | number | 10-24 | 14 | | `watermark` | text | - | '' | +| `pageAlignments` | json | - | '{}' | + +**已移除的字段**:`template`(3个模板选项 minimal/magazine/mixed)和 `font`(3个字体选项 source-han-sans/alibaba-puhuiti/lxgw-wenkai)已从 `getConfigFields()` 中移除,因为 JS 端无对应的模板/字体切换逻辑。 + +**新增的字段**:`pageAlignments` 已新增到 `getConfigFields()`,类型为 `json`,默认值 `'{}'`,与 JS 端的逐页对齐功能对齐。 ### 4.2 JS 端实际使用的配置 @@ -209,52 +250,63 @@ config = { } ``` -### 4.3 PHP/JS 字段不对齐 +### 4.3 PHP/JS 字段对齐状态(修复后) -两端存在明显的字段定义差异: +修复后两端字段已基本对齐: -| 问题 | PHP 定义 | JS 实际使用 | -|------|----------|-------------| -| `template` | PHP有定义 (3个模板选项) | JS完全不使用,无任何模板切换逻辑 | -| `font` | PHP有定义 (3个字体选项) | JS不使用,字体通过CSS引入,无切换机制 | -| `pageAlignments` | PHP未定义 | JS核心功能,保存在config中 | -| `contentPadding` | PHP未定义 | JS硬编码为20 (L23) | +| 字段 | PHP 定义 | JS 使用 | 状态 | +|------|----------|---------|------| +| `size` | select, 2个选项 | 核心功能 | 已对齐 | +| `fontSize` | number, 10-24 | 核心功能 | 已对齐 | +| `watermark` | text | 核心功能 | 已对齐 | +| `pageAlignments` | json | 核心功能 | 已对齐 | +| `contentPadding` | 未定义 | 硬编码为20 (L23) | 仍为 JS 内部常量,无需 PHP 定义 | -`validateConfig()` (L29-50) 验证 `template`/`size`/`font`/`fontSize`,但 `pageAlignments` 不在验证范围内。前端发送的配置比PHP定义的字段多,PHP不做字段白名单过滤,全部存入JSON。 +`validateConfig()` (L28-50) 验证 `size`/`fontSize`,`pageAlignments` 为 JSON 类型不做严格校验。前端发送的配置比 PHP 定义的字段多(如 `content_html`、`sizes`、`contentPadding`),PHP 不做字段白名单过滤,全部存入 JSON config 字段。 + +> **修复前的字段不对齐记录**:`template`(PHP 有定义但 JS 不使用)和 `font`(PHP 有定义但 JS 不使用)已从 PHP 端移除;`pageAlignments`(JS 核心功能但 PHP 未定义)已新增到 PHP 端。 ## 5. 已知问题 -### 5.1 分页标记丢失(根因分析) +### 5.1 已修复 + +#### 5.1.1 分页标记丢失(已修复) **现象**:用户通过中间栏的 "+" 按钮插入分页标记后,点击"保存"或"生成并保存",重新加载页面时分页标记消失。 -**根因**:数据源不一致。 +**根因**:数据源不一致。分页标记写入闭包 `postData.content_html`,但保存时读取的是 DOM `#post-content-html`(不含标记)。 -分页标记的插入流程 (`insertPageBreak`, L1198-1227): -1. 从闭包 `postData.content_html` 读取内容 -2. 在目标位置插入 `