baodan/docs/海报生成尺寸模板与导出问题修复报告_20260731.md

17 KiB
Raw Blame History

海报生成尺寸、模板与导出问题修复报告

日期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 标准竖版单图

因此,“长图”和“单图”不能只按一个固定比例区分:

  • 长图的核心定义是固定宽度、内容驱动高度,建议标准成品宽度为 12421080,高度取真实内容高度;
  • 单图的核心定义是固定画幅和单屏信息预算,建议主规格为 1024×15362:3,辅规格为 1080×19209:16;如确需图 5 风格,可单独增加“超高单页”规格,不能混入普通单图。

3. 审计健康度

维度 分数 核心问题
可访问性 2/4 可编辑区域依赖 contenteditable,缺少明确标签和键盘状态提示
性能 1/4 超长 DOM 使用固定 pixelRatio: 2 转 base64 PNG内存和文件体积不可控
响应式/尺寸适配 1/4 预览缩放有所改善,但成品尺寸、内容高度和 AI 素材尺寸没有统一
主题系统 1/4 模板色只影响少数组件,正文大量写死白底和绿色
实现完整性 1/4 单图/长图共用骨架,模板没有版式能力,最终成品依赖前端在线合成
总分 6/20Poor 发布前需要结构性修复

4. 详细问题

P0-1前端尺寸预设与图片模型尺寸映射完全不一致

证据:

  • 前端长图提供 1080x21601080x32401080x4320;单图还提供 1792x10241080x14402480x3508 等规格:frontend/src/components/poster/workspace/PosterCreativePanel.vue:215-231
  • 后端 SIZE_MAP 只识别 1080x1920900x5001080x1080800x1200api/insurance/poster/image_generator.py:9-15
  • 所有未命中的规格都会静默回退到 1024x1792api/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,随后再上传到 /renderedfrontend/src/pages/PosterPage.vue:438-467
  • /download/<id> 只接受 export_url;如果浏览器没有成功上传最终图,即使 background_file_url 存在,下载仍返回“文件不存在”:api/insurance/poster/routes.py:309-327
  • 服务端限制最终 PNG 不超过 30 MBapi/insurance/poster/service.py:422-452。超长图或高像素图很容易触发此限制。

影响:用户关闭页面、切换任务、浏览器崩溃、合成失败或上传超限后,任务仍可能显示完成,但历史记录无法下载。这与目前反馈的“生成出来但导出有问题”高度吻合。

修复要求:最终成品必须由服务端任务确定性生成并落盘。浏览器预览可保留,但不应承担唯一成品生成职责。短期内至少应增加独立的 render_status,只有最终 PNG 已落盘才显示“完成/可下载”。

P1-1单图不是独立版式只是长图容器的裁剪版本

证据:

  • 单图和长图均渲染同一个 PosterHtmlCanvas,同一组 hero、summary、benefits、features、CTA、disclaimerfrontend/src/components/poster/long/PosterHtmlCanvas.vue:1-46
  • 单图仅设置 aspect-ratio,根节点同时设置 overflow: hiddenfrontend/src/components/poster/long/PosterHtmlCanvas.vue:92-101,126-140

影响:内容多时会被裁掉或挤压;内容少时会留下空白。图 57 那种“一张图内完成主视觉、核心利益和数据条”的单图构图,当前骨架无法稳定实现。

修复要求:拆分为至少两个成品组件:

  • 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_modessinglelong 或两者;
  • supported_aspect_ratios
  • section_schema:允许的章节、顺序、是否必选、内容上限;
  • background_strategyhero、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-93PosterBenefitChart.vue:122-169PosterFeatureCards.vue:35-108PosterDisclaimer.vue:27-52

影响:暖色、深蓝、深绿等模板最终正文仍呈现同一套白底绿色组件,无法还原图 14 的整页主题一致性。

修复要求:用画布级 CSS variables/tokens 统一控制背景、正文、弱文本、卡片、边框、主色和强调色;每个布局只消费 token不再写死品牌颜色。

P1-5AI 背景尺寸与最终画布职责混乱

证据:

  • 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: 2frontend/src/pages/PosterPage.vue:438-449
  • HTML 画布宽度又被限制为最多 1080pxfrontend/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_widthactual_heightbyte_sizesha256;单图必须符合比例容差,长图必须符合固定宽度和最大像素限制。

P2-2尺寸变更没有进入文档自动保存监听

buildPosterDocument() 包含 exportSize,但自动保存 watcher 没有监听 exportSizefrontend/src/pages/PosterPage.vue:366-400

影响:用户生成后修改尺寸但没有再次下载时,服务端文档可能保持旧尺寸。

P2-3现有测试没有覆盖尺寸和导出链路

本次执行 tests/ppt_poster_optimization_test.py13 项全部通过;但现有测试只覆盖字段画像、内容裁剪、合规和基础生成前置条件,没有覆盖:

  • 所有前端规格能否被后端识别;
  • 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%
  • 标题、35 个核心数据、最多 3 个卖点、免责声明;
  • 禁止完整收益图表和六段长文;
  • 默认 1024×1536,可选 1080×1920

长图模板

  • 固定宽度 12421080
  • hero、方案摘要、收益趋势、里程碑卡片、优势、适配人群、CTA、免责声明分章节
  • 高度按真实内容计算;
  • 超过最大像素总量时输出多张连续图片或服务端分片后再拼接。

5.3 成品生成

推荐优先级:

  1. 服务端 Headless Chromium 渲染最终 HTML 海报,确保任务完成后一定存在最终 PNG
  2. 浏览器只负责可编辑预览和低分辨率即时预览;
  3. AI 只生成背景素材不生成文字、数字、Logo、表格或整张海报
  4. 最终文件保存前执行像素、体积和解码校验;
  5. 下载接口只返回通过校验的最终文件,并明确区分背景素材和最终成品。

6. 分阶段修复计划

第一阶段止血P0预计 12 天)

  1. 删除前端所有后端不支持的伪规格,或补齐严格映射并拒绝未知规格;
  2. 单图默认改为 1024×1536;长图改为固定宽度 + 自动高度;
  3. 去掉固定 pixelRatio: 2,按目标宽度计算输出 scale
  4. 导出后校验真实 PNG 宽高和文件体积;
  5. 任务状态拆成“背景生成完成”和“最终成品完成”,没有 export_url 不得显示可下载;
  6. 下载失败时返回明确原因,禁止用“文件不存在”掩盖合成未完成。

第二阶段版式重构P1预计 35 天)

  1. 拆分 PosterSingleCanvasPosterLongCanvas
  2. 为模板增加 layout_key、支持模式、比例和章节 schema
  3. 模板列表按输出模式和比例过滤;
  4. 建立完整主题 token移除正文硬编码绿色
  5. AI 背景改成独立 asset spec不再接收最终长图高度。

第三阶段可靠导出P1预计 24 天)

  1. 将最终海报迁移到服务端 Headless Chromium 渲染;
  2. 增加超长图最大像素和分片策略;
  3. 保存真实宽高、字节数、哈希、渲染器版本和模板版本;
  4. 历史记录支持重新渲染,不依赖原浏览器会话。

第四阶段回归验证P1/P2预计 12 天)

至少建立以下金丝雀用例:

  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思路合理
  • 导出前已经等待字体和背景资源加载,比直接截图稳定;
  • 字段画像已区分单图/长图的信息预算,后续可直接用于布局白名单。

这些基础可以保留,但必须先补齐尺寸契约、独立布局和服务端最终渲染三个核心环节。