17 KiB
海报生成尺寸、模板与导出问题修复报告
日期:2026-07-31
范围:海报单图/长图画布、模板体系、AI 背景生成、浏览器合成、服务端保存与下载
结论性质:代码静态审计 + 7 张样图像素核验 + 现有测试核验;本报告不包含代码修改
1. 结论
当前问题不是单个尺寸参数错误,而是三套概念被混成了一个 exportSize:
- 最终成品尺寸:用户实际下载图片的像素宽高;
- HTML 排版画布尺寸:文字、图表和模块布局使用的 DOM 尺寸;
- 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:
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 成品生成
推荐优先级:
- 服务端 Headless Chromium 渲染最终 HTML 海报,确保任务完成后一定存在最终 PNG;
- 浏览器只负责可编辑预览和低分辨率即时预览;
- AI 只生成背景素材,不生成文字、数字、Logo、表格或整张海报;
- 最终文件保存前执行像素、体积和解码校验;
- 下载接口只返回通过校验的最终文件,并明确区分背景素材和最终成品。
6. 分阶段修复计划
第一阶段:止血(P0,预计 1~2 天)
- 删除前端所有后端不支持的伪规格,或补齐严格映射并拒绝未知规格;
- 单图默认改为
1024×1536;长图改为固定宽度 + 自动高度; - 去掉固定
pixelRatio: 2,按目标宽度计算输出 scale; - 导出后校验真实 PNG 宽高和文件体积;
- 任务状态拆成“背景生成完成”和“最终成品完成”,没有
export_url不得显示可下载; - 下载失败时返回明确原因,禁止用“文件不存在”掩盖合成未完成。
第二阶段:版式重构(P1,预计 3~5 天)
- 拆分
PosterSingleCanvas与PosterLongCanvas; - 为模板增加
layout_key、支持模式、比例和章节 schema; - 模板列表按输出模式和比例过滤;
- 建立完整主题 token,移除正文硬编码绿色;
- AI 背景改成独立 asset spec,不再接收最终长图高度。
第三阶段:可靠导出(P1,预计 2~4 天)
- 将最终海报迁移到服务端 Headless Chromium 渲染;
- 增加超长图最大像素和分片策略;
- 保存真实宽高、字节数、哈希、渲染器版本和模板版本;
- 历史记录支持重新渲染,不依赖原浏览器会话。
第四阶段:回归验证(P1/P2,预计 1~2 天)
至少建立以下金丝雀用例:
single_2_3输出严格为1024×1536;single_9_16输出严格为1080×1920;long_auto_1242输出宽度严格为1242,底部空白不超过设计 footer;- 同一份数据在单图中不出现完整收益表,在长图中章节齐全;
- 所有模板只出现在兼容模式中;
- 用户提交后立即关闭页面,任务仍能生成并下载最终海报;
- 超大长图不会产生黑图、空图、截断或超过服务端限制;
- 下载文件实际尺寸、数据库记录和 UI 显示三者完全一致。
7. 验收标准
修复完成必须同时满足:
- 单图和长图具有不同布局组件、不同内容预算和不同模板白名单;
- 任何尺寸都没有静默回退;
- 长图底部不出现由固定
min-height造成的大段空白; - 导出 PNG 的真实像素与规格一致,并被服务端解码校验;
- 后台显示“完成”时,即使原页面已关闭,历史记录也能直接下载最终成品;
- 模板色彩和版式覆盖整张海报,而不是只改变 hero/CTA;
- 自动化测试覆盖尺寸注册表、真实像素、超长图和无浏览器会话下载。
8. 正向发现
- 已经把 AI 图片定位为“无文字背景”,方向是正确的;
PosterHtmlCanvas已按章节组件化,拆分单图/长图时可以复用数据和部分章节;- 预览缩放已经使用独立 shell + transform,思路合理;
- 导出前已经等待字体和背景资源加载,比直接截图稳定;
- 字段画像已区分单图/长图的信息预算,后续可直接用于布局白名单。
这些基础可以保留,但必须先补齐尺寸契约、独立布局和服务端最终渲染三个核心环节。