290 lines
17 KiB
Markdown
290 lines
17 KiB
Markdown
# 海报生成尺寸、模板与导出问题修复报告
|
||
|
||
日期:2026-07-31
|
||
范围:海报单图/长图画布、模板体系、AI 背景生成、浏览器合成、服务端保存与下载
|
||
结论性质:代码静态审计 + 7 张样图像素核验 + 现有测试核验;本报告不包含代码修改
|
||
|
||
## 1. 结论
|
||
|
||
当前问题不是单个尺寸参数错误,而是三套概念被混成了一个 `exportSize`:
|
||
|
||
1. **最终成品尺寸**:用户实际下载图片的像素宽高;
|
||
2. **HTML 排版画布尺寸**:文字、图表和模块布局使用的 DOM 尺寸;
|
||
3. **AI 背景素材尺寸**:图片模型实际支持并返回的尺寸。
|
||
|
||
这三层目前没有统一、可验证的尺寸契约,导致:
|
||
|
||
- 前端展示了后端不支持的尺寸,后端静默回退成 `1024x1792`;
|
||
- 长图声明固定高度,但正文又按内容自适应,容易产生大段空白或尺寸失真;
|
||
- 单图并非独立模板,而是把长图的六段内容放进固定比例容器后隐藏溢出;
|
||
- 模板只定义 AI prompt、配色和参考图,没有定义真正的版式骨架;
|
||
- 最终 PNG 依赖用户浏览器再次合成并上传,用户离开页面、浏览器内存不足或上传超限时,后台任务虽然显示完成,下载文件仍可能不存在。
|
||
|
||
综合判断:当前海报生成链路处于“可演示,但不可稳定交付”的状态。需要先修正尺寸和成品生成架构,再继续增加模板。
|
||
|
||
## 2. 样图尺寸核验
|
||
|
||
| 样图 | 实际像素 | 宽高比 | 判断 |
|
||
|---|---:|---:|---|
|
||
| 图 1 | 1242×9530 | 1:7.67 | 典型多段内容长图,宽度固定、高度随内容增长 |
|
||
| 图 2 | 1024×1536 | 2:3 | 文件本身不是长图,而是多个页面被排成 2 列拼版;可作为多章节风格参考,不能作为长图尺寸参考 |
|
||
| 图 3 | 1242×9449 | 1:7.61 | 典型多段内容长图 |
|
||
| 图 4 | 1242×12440 | 1:10.02 | 内容结束后存在明显空白尾部,是错误高度/错误截取范围的直接表现,不应作为目标尺寸 |
|
||
| 图 5 | 900×2209 | 约 9:22 | 超高竖版单图 |
|
||
| 图 6 | 992×1586 | 约 5:8 | 竖版单图 |
|
||
| 图 7 | 1024×1536 | 2:3 | 标准竖版单图 |
|
||
|
||
因此,“长图”和“单图”不能只按一个固定比例区分:
|
||
|
||
- **长图**的核心定义是固定宽度、内容驱动高度,建议标准成品宽度为 `1242` 或 `1080`,高度取真实内容高度;
|
||
- **单图**的核心定义是固定画幅和单屏信息预算,建议主规格为 `1024×1536(2:3)`,辅规格为 `1080×1920(9:16)`;如确需图 5 风格,可单独增加“超高单页”规格,不能混入普通单图。
|
||
|
||
## 3. 审计健康度
|
||
|
||
| 维度 | 分数 | 核心问题 |
|
||
|---|---:|---|
|
||
| 可访问性 | 2/4 | 可编辑区域依赖 `contenteditable`,缺少明确标签和键盘状态提示 |
|
||
| 性能 | 1/4 | 超长 DOM 使用固定 `pixelRatio: 2` 转 base64 PNG,内存和文件体积不可控 |
|
||
| 响应式/尺寸适配 | 1/4 | 预览缩放有所改善,但成品尺寸、内容高度和 AI 素材尺寸没有统一 |
|
||
| 主题系统 | 1/4 | 模板色只影响少数组件,正文大量写死白底和绿色 |
|
||
| 实现完整性 | 1/4 | 单图/长图共用骨架,模板没有版式能力,最终成品依赖前端在线合成 |
|
||
| **总分** | **6/20(Poor)** | **发布前需要结构性修复** |
|
||
|
||
## 4. 详细问题
|
||
|
||
### P0-1:前端尺寸预设与图片模型尺寸映射完全不一致
|
||
|
||
证据:
|
||
|
||
- 前端长图提供 `1080x2160`、`1080x3240`、`1080x4320`;单图还提供 `1792x1024`、`1080x1440`、`2480x3508` 等规格:`frontend/src/components/poster/workspace/PosterCreativePanel.vue:215-231`。
|
||
- 后端 `SIZE_MAP` 只识别 `1080x1920`、`900x500`、`1080x1080`、`800x1200`:`api/insurance/poster/image_generator.py:9-15`。
|
||
- 所有未命中的规格都会静默回退到 `1024x1792`:`api/insurance/poster/image_generator.py:146-160`。
|
||
|
||
影响:用户选择“超长图”“A4”“横版”等规格时,AI 实际仍可能生成 `1024x1792` 背景。数据库却继续记录用户原始选择,形成“记录尺寸”和“真实文件尺寸”不一致。
|
||
|
||
修复要求:建立唯一尺寸注册表,前后端共享同一组规格;对不支持尺寸必须返回参数错误,禁止静默回退。
|
||
|
||
### P0-2:最终成品依赖浏览器合成,后台任务完成不等于可下载
|
||
|
||
证据:
|
||
|
||
- Celery 任务只保存 AI 背景到 `background_file_url`,不会生成带文案和图表的最终海报:`api/insurance/generation/celery_tasks.py:1178-1200`。
|
||
- 最终海报由浏览器执行 `html-to-image`,随后再上传到 `/rendered`:`frontend/src/pages/PosterPage.vue:438-467`。
|
||
- `/download/<id>` 只接受 `export_url`;如果浏览器没有成功上传最终图,即使 `background_file_url` 存在,下载仍返回“文件不存在”:`api/insurance/poster/routes.py:309-327`。
|
||
- 服务端限制最终 PNG 不超过 30 MB:`api/insurance/poster/service.py:422-452`。超长图或高像素图很容易触发此限制。
|
||
|
||
影响:用户关闭页面、切换任务、浏览器崩溃、合成失败或上传超限后,任务仍可能显示完成,但历史记录无法下载。这与目前反馈的“生成出来但导出有问题”高度吻合。
|
||
|
||
修复要求:最终成品必须由服务端任务确定性生成并落盘。浏览器预览可保留,但不应承担唯一成品生成职责。短期内至少应增加独立的 `render_status`,只有最终 PNG 已落盘才显示“完成/可下载”。
|
||
|
||
### P1-1:单图不是独立版式,只是长图容器的裁剪版本
|
||
|
||
证据:
|
||
|
||
- 单图和长图均渲染同一个 `PosterHtmlCanvas`,同一组 hero、summary、benefits、features、CTA、disclaimer:`frontend/src/components/poster/long/PosterHtmlCanvas.vue:1-46`。
|
||
- 单图仅设置 `aspect-ratio`,根节点同时设置 `overflow: hidden`:`frontend/src/components/poster/long/PosterHtmlCanvas.vue:92-101,126-140`。
|
||
|
||
影响:内容多时会被裁掉或挤压;内容少时会留下空白。图 5~7 那种“一张图内完成主视觉、核心利益和数据条”的单图构图,当前骨架无法稳定实现。
|
||
|
||
修复要求:拆分为至少两个成品组件:
|
||
|
||
- `PosterSingleCanvas`:固定画幅,严格信息预算,主视觉与数据一体化;
|
||
- `PosterLongCanvas`:固定宽度,章节按内容自然增长。
|
||
|
||
### P1-2:长图同时使用“预设固定高度”和“内容自适应高度”
|
||
|
||
证据:
|
||
|
||
- 长图画布设置 `minHeight: h`,实际高度又由所有章节内容撑开:`frontend/src/components/poster/long/PosterHtmlCanvas.vue:92-98`。
|
||
- 长图尺寸预设给出 2160、3240、4320 三个固定高度:`PosterCreativePanel.vue:215-222`。
|
||
|
||
影响:当内容不足时产生空白尾部,图 4 已经直观展示这一问题;当内容超过预设时,最终高度又不再等于选择值。`exportSize` 因而既不是目标尺寸,也不是真实尺寸。
|
||
|
||
修复要求:长图取消固定高度预设,改为“成品宽度 + 自动高度”。如果业务确实需要高度档位,应把它定义为内容预算或最大高度,并在超出时分片,而不是 `min-height`。
|
||
|
||
### P1-3:模板只是“背景提示词”,不是版式模板
|
||
|
||
证据:
|
||
|
||
- `PosterTemplate` 只有名称、场景标签、风格描述、配色、参考图和预览图,没有 `layout_key`、支持模式、支持比例或章节规则:`api/insurance/models/poster_template_model.py:6-39`。
|
||
- 模板列表不按 `single/long` 过滤,同一模板可直接用于所有输出模式:`frontend/src/components/poster/workspace/PosterCreativePanel.vue:256-315`。
|
||
- HTML 正文布局始终是同一套组件。
|
||
|
||
影响:选择不同模板时,主要变化只是 AI 背景和少量颜色,无法产生样例中真正不同的章节节奏、数据板式、主视觉占比和信息密度。
|
||
|
||
修复要求:模板模型至少新增:
|
||
|
||
- `layout_key`:对应真实前端/服务端布局实现;
|
||
- `supported_modes`:`single`、`long` 或两者;
|
||
- `supported_aspect_ratios`;
|
||
- `section_schema`:允许的章节、顺序、是否必选、内容上限;
|
||
- `background_strategy`:hero、full-bleed、section、none;
|
||
- `version`:保证旧海报可重复渲染。
|
||
|
||
### P1-4:模板配色没有贯穿整张海报
|
||
|
||
证据:
|
||
|
||
- hero 和 CTA 会读取模板主色/强调色;
|
||
- summary、benefit chart、feature cards 和 disclaimer 大量写死 `#1a3a2a`、`#2d6a4f`、白色和固定灰色:`frontend/src/components/poster/long/PosterSummaryCards.vue:57-93`、`PosterBenefitChart.vue:122-169`、`PosterFeatureCards.vue:35-108`、`PosterDisclaimer.vue:27-52`。
|
||
|
||
影响:暖色、深蓝、深绿等模板最终正文仍呈现同一套白底绿色组件,无法还原图 1~4 的整页主题一致性。
|
||
|
||
修复要求:用画布级 CSS variables/tokens 统一控制背景、正文、弱文本、卡片、边框、主色和强调色;每个布局只消费 token,不再写死品牌颜色。
|
||
|
||
### P1-5:AI 背景尺寸与最终画布职责混乱
|
||
|
||
证据:
|
||
|
||
- prompt 把最终 `size` 直接作为 AI 图片尺寸要求:`api/insurance/poster/image_generator.py:117-123`。
|
||
- 图片 API 实际只接受有限尺寸,长图尺寸无法原生生成;返回图随后仅作为 hero 的 `background-size: cover` 背景:`frontend/src/components/poster/long/PosterHero.vue:30-39,49-56`。
|
||
|
||
影响:系统一方面要求模型生成整张长图尺寸,另一方面只把结果裁进顶部 360px hero。长图模板信息没有真正作用于正文,参考图若含完整排版,还可能诱导模型生成拼版、伪文字或多页面画面。图 2 的“多页拼成一张”正是需要防止的结果类型。
|
||
|
||
修复要求:AI 只生成明确用途的素材,例如 `hero_background_1536x1024`;最终海报尺寸完全由布局渲染器负责。prompt 必须明确安全文字区、人物位置、焦点和裁切策略,而不是传最终长图高度。
|
||
|
||
### P1-6:导出像素比固定为 2,真实尺寸与选择值无关
|
||
|
||
证据:
|
||
|
||
- `html-to-image` 固定使用 `pixelRatio: 2`:`frontend/src/pages/PosterPage.vue:438-449`。
|
||
- HTML 画布宽度又被限制为最多 1080px:`frontend/src/components/poster/long/PosterHtmlCanvas.vue:92-100`。
|
||
|
||
影响:选择 1024 宽时实际导出可能为 2048 宽;选择 2480 宽时 DOM 仍只有 1080 宽,实际导出约 2160 宽;数据库记录的 `exportSize` 均不能代表真实输出。长图还会产生数千万像素的中间 canvas 和 base64 字符串,明显增加浏览器崩溃、黑图、空图或上传超限概率。
|
||
|
||
修复要求:以目标成品宽度反推导出 scale,导出后解码 PNG 校验真实宽高。长图使用 Blob 流程,避免 data URL;设置最大像素总量,超过阈值自动降低 scale 或按章节分片。
|
||
|
||
### P2-1:服务端只检查 PNG 文件头,不校验真实尺寸和完整性
|
||
|
||
`save_rendered` 只检查 30 MB 上限和 PNG signature,没有用 Pillow 解码,也没有对比期望尺寸:`api/insurance/poster/service.py:422-452`。
|
||
|
||
修复要求:解码并验证图片完整性,记录 `actual_width`、`actual_height`、`byte_size`、`sha256`;单图必须符合比例容差,长图必须符合固定宽度和最大像素限制。
|
||
|
||
### P2-2:尺寸变更没有进入文档自动保存监听
|
||
|
||
`buildPosterDocument()` 包含 `exportSize`,但自动保存 watcher 没有监听 `exportSize`:`frontend/src/pages/PosterPage.vue:366-400`。
|
||
|
||
影响:用户生成后修改尺寸但没有再次下载时,服务端文档可能保持旧尺寸。
|
||
|
||
### P2-3:现有测试没有覆盖尺寸和导出链路
|
||
|
||
本次执行 `tests/ppt_poster_optimization_test.py`,13 项全部通过;但现有测试只覆盖字段画像、内容裁剪、合规和基础生成前置条件,没有覆盖:
|
||
|
||
- 所有前端规格能否被后端识别;
|
||
- AI 返回尺寸与请求尺寸是否一致;
|
||
- 单图是否裁切内容;
|
||
- 长图是否存在空白尾部;
|
||
- 最终 PNG 的真实像素;
|
||
- 关闭页面后历史任务是否仍可下载;
|
||
- 30 MB、超长 canvas、图片加载失败和图表未完成渲染。
|
||
|
||
### P2-4:当前工作区的 `PosterStage.vue` 尾部存在游离 CSS
|
||
|
||
`</style>` 后仍残留一段没有选择器的 CSS 声明。`vue-tsc --noEmit` 当前可以通过,但该内容属于无效/无归属实现,说明最近预览缩放调整尚未完成清理。它不是本次尺寸问题的主因,但修复时应一并做构建级校验。
|
||
|
||
## 5. 推荐目标架构
|
||
|
||
### 5.1 尺寸契约
|
||
|
||
建议定义统一的 `PosterFormatSpec`:
|
||
|
||
```text
|
||
id single_2_3 | single_9_16 | long_auto_1242
|
||
mode single | long
|
||
canvasWidth 1024 | 1080 | 1242
|
||
canvasHeight 固定值或 auto
|
||
aspectRatio 单图必填,长图为空
|
||
maxPixelCount 浏览器/服务端安全阈值
|
||
assetSpecs hero 背景等 AI 素材的独立尺寸
|
||
allowedLayouts 可使用的 layout_key
|
||
```
|
||
|
||
前端选项、后端校验、任务快照和导出器必须读取同一份规格。数据库同时保存请求规格 ID 和最终真实像素。
|
||
|
||
### 5.2 两类独立布局
|
||
|
||
**单图模板**:
|
||
|
||
- 主视觉占 50%~70%;
|
||
- 标题、3~5 个核心数据、最多 3 个卖点、免责声明;
|
||
- 禁止完整收益图表和六段长文;
|
||
- 默认 `1024×1536`,可选 `1080×1920`。
|
||
|
||
**长图模板**:
|
||
|
||
- 固定宽度 `1242` 或 `1080`;
|
||
- hero、方案摘要、收益趋势、里程碑卡片、优势、适配人群、CTA、免责声明分章节;
|
||
- 高度按真实内容计算;
|
||
- 超过最大像素总量时输出多张连续图片或服务端分片后再拼接。
|
||
|
||
### 5.3 成品生成
|
||
|
||
推荐优先级:
|
||
|
||
1. **服务端 Headless Chromium 渲染最终 HTML 海报**,确保任务完成后一定存在最终 PNG;
|
||
2. 浏览器只负责可编辑预览和低分辨率即时预览;
|
||
3. AI 只生成背景素材,不生成文字、数字、Logo、表格或整张海报;
|
||
4. 最终文件保存前执行像素、体积和解码校验;
|
||
5. 下载接口只返回通过校验的最终文件,并明确区分背景素材和最终成品。
|
||
|
||
## 6. 分阶段修复计划
|
||
|
||
### 第一阶段:止血(P0,预计 1~2 天)
|
||
|
||
1. 删除前端所有后端不支持的伪规格,或补齐严格映射并拒绝未知规格;
|
||
2. 单图默认改为 `1024×1536`;长图改为固定宽度 + 自动高度;
|
||
3. 去掉固定 `pixelRatio: 2`,按目标宽度计算输出 scale;
|
||
4. 导出后校验真实 PNG 宽高和文件体积;
|
||
5. 任务状态拆成“背景生成完成”和“最终成品完成”,没有 `export_url` 不得显示可下载;
|
||
6. 下载失败时返回明确原因,禁止用“文件不存在”掩盖合成未完成。
|
||
|
||
### 第二阶段:版式重构(P1,预计 3~5 天)
|
||
|
||
1. 拆分 `PosterSingleCanvas` 与 `PosterLongCanvas`;
|
||
2. 为模板增加 `layout_key`、支持模式、比例和章节 schema;
|
||
3. 模板列表按输出模式和比例过滤;
|
||
4. 建立完整主题 token,移除正文硬编码绿色;
|
||
5. AI 背景改成独立 asset spec,不再接收最终长图高度。
|
||
|
||
### 第三阶段:可靠导出(P1,预计 2~4 天)
|
||
|
||
1. 将最终海报迁移到服务端 Headless Chromium 渲染;
|
||
2. 增加超长图最大像素和分片策略;
|
||
3. 保存真实宽高、字节数、哈希、渲染器版本和模板版本;
|
||
4. 历史记录支持重新渲染,不依赖原浏览器会话。
|
||
|
||
### 第四阶段:回归验证(P1/P2,预计 1~2 天)
|
||
|
||
至少建立以下金丝雀用例:
|
||
|
||
1. `single_2_3` 输出严格为 `1024×1536`;
|
||
2. `single_9_16` 输出严格为 `1080×1920`;
|
||
3. `long_auto_1242` 输出宽度严格为 `1242`,底部空白不超过设计 footer;
|
||
4. 同一份数据在单图中不出现完整收益表,在长图中章节齐全;
|
||
5. 所有模板只出现在兼容模式中;
|
||
6. 用户提交后立即关闭页面,任务仍能生成并下载最终海报;
|
||
7. 超大长图不会产生黑图、空图、截断或超过服务端限制;
|
||
8. 下载文件实际尺寸、数据库记录和 UI 显示三者完全一致。
|
||
|
||
## 7. 验收标准
|
||
|
||
修复完成必须同时满足:
|
||
|
||
- 单图和长图具有不同布局组件、不同内容预算和不同模板白名单;
|
||
- 任何尺寸都没有静默回退;
|
||
- 长图底部不出现由固定 `min-height` 造成的大段空白;
|
||
- 导出 PNG 的真实像素与规格一致,并被服务端解码校验;
|
||
- 后台显示“完成”时,即使原页面已关闭,历史记录也能直接下载最终成品;
|
||
- 模板色彩和版式覆盖整张海报,而不是只改变 hero/CTA;
|
||
- 自动化测试覆盖尺寸注册表、真实像素、超长图和无浏览器会话下载。
|
||
|
||
## 8. 正向发现
|
||
|
||
- 已经把 AI 图片定位为“无文字背景”,方向是正确的;
|
||
- `PosterHtmlCanvas` 已按章节组件化,拆分单图/长图时可以复用数据和部分章节;
|
||
- 预览缩放已经使用独立 shell + transform,思路合理;
|
||
- 导出前已经等待字体和背景资源加载,比直接截图稳定;
|
||
- 字段画像已区分单图/长图的信息预算,后续可直接用于布局白名单。
|
||
|
||
这些基础可以保留,但必须先补齐尺寸契约、独立布局和服务端最终渲染三个核心环节。
|