754 lines
29 KiB
Markdown
754 lines
29 KiB
Markdown
|
|
# 海报生成问题报告复核与详细修复计划书
|
|||
|
|
|
|||
|
|
日期:2026-07-31
|
|||
|
|
复核对象:`docs/海报生成尺寸模板与导出问题修复报告_20260731.md`
|
|||
|
|
实施范围:`api/insurance/poster/`、`api/insurance/generation/` 中的海报任务、`frontend/src/components/poster/`、`frontend/src/pages/PosterPage.vue` 及对应测试
|
|||
|
|
约束:不修改 BaoDan/Dify 基座文件;所有后端自研改动继续位于 `api/insurance/`;本计划不直接修改业务代码
|
|||
|
|
|
|||
|
|
## 1. 执行摘要
|
|||
|
|
|
|||
|
|
上一份报告对以下主问题判断正确:
|
|||
|
|
|
|||
|
|
- 单图和长图共用一套排版骨架;
|
|||
|
|
- 前端尺寸选项与图片模型支持尺寸不一致;
|
|||
|
|
- 模板只有风格提示能力,没有真正的版式能力;
|
|||
|
|
- `pixelRatio: 2` 使实际导出尺寸失控;
|
|||
|
|
- 最终文件依赖浏览器合成,任务状态与可下载状态存在错位。
|
|||
|
|
|
|||
|
|
但上一份报告不能直接作为开发计划使用,原因是:
|
|||
|
|
|
|||
|
|
1. 它虽然提出了三层尺寸概念,后续方案却再次把 **CSS 逻辑排版宽度** 与 **最终 PNG 像素宽度** 混在一起;
|
|||
|
|
2. 它把服务端 Headless Chromium 当成必选修复,没有先验证较小、较快的浏览器导出修复能否满足当前业务;
|
|||
|
|
3. 部分结论属于推断而不是已复现事实,例如图 4 空白一定来自当前系统、长图很容易超过 30 MB;
|
|||
|
|
4. 它漏掉了两个重要根因:**渲染数据来源分裂**,以及 **fallback 背景仍会绘制文字**;
|
|||
|
|
5. 缺少文件级任务、接口契约、兼容策略、灰度方案、回滚方案和可执行验收用例。
|
|||
|
|
|
|||
|
|
本计划采用“先建立可测基线,再做最小闭环修复,最后决定是否引入服务端浏览器”的顺序。推荐目标工期为 **13~19 人日**;若确认必须支持“用户关闭页面后仍自动产出最终海报”,再增加服务端渲染阶段,预计额外 **4~7 人日**。
|
|||
|
|
|
|||
|
|
## 2. 本计划的工作假设
|
|||
|
|
|
|||
|
|
在没有新的产品确认前,按以下假设推进,实施前第 0 阶段必须由产品负责人确认:
|
|||
|
|
|
|||
|
|
- 图 1~4 表示“完整方案/高信息密度”的设计语言,其中图 2 是多面板拼版参考,不直接作为连续长图的物理尺寸标准;
|
|||
|
|
- 图 5~7 表示单屏竖版海报的设计语言;
|
|||
|
|
- 单图首发只保留 `2:3` 和 `9:16` 两种规格;
|
|||
|
|
- 连续长图使用固定逻辑宽度、内容自动高度,首发只保留一个导出宽度;
|
|||
|
|
- 先保留当前 AI“只生成背景素材”的路线,文字、数字、图表和 Logo 由确定性布局渲染;
|
|||
|
|
- 第一版先修好当前浏览器导出链路,服务端 Headless Chromium 是否上线由可靠性门槛决定;
|
|||
|
|
- 当前工作区已经存在其他未提交修改,实施时不得覆盖或重排无关改动。
|
|||
|
|
|
|||
|
|
## 3. 对上一份报告的问题复核
|
|||
|
|
|
|||
|
|
### 3.1 尺寸分层提出正确,但目标模型仍不够准确
|
|||
|
|
|
|||
|
|
报告区分了最终尺寸、HTML 画布尺寸和 AI 素材尺寸,这是正确的。但它随后建议把长图 `canvasWidth` 直接设为 `1242` 或 `1080`,仍然混淆了两种宽度。
|
|||
|
|
|
|||
|
|
当前海报组件使用 `28px` 标题、`12~18px` 正文、`20~32px` padding,这套字号更像为约 `390~540 CSS px` 的逻辑画布设计。如果直接在 `1080 CSS px` 画布上排版,预览缩小后文字和间距会显得过小;这正是目前视觉密度不足的重要原因之一。
|
|||
|
|
|
|||
|
|
正确模型应为:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
逻辑排版尺寸:决定文字、间距、网格和组件构图,例如 414 CSS px 宽
|
|||
|
|
导出缩放倍率:例如 3
|
|||
|
|
最终成品尺寸:414 × 3 = 1242 px 宽
|
|||
|
|
AI 素材尺寸:由 hero/full-bleed 素材槽位和供应商能力单独决定
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
因此修复中必须同时存在 `layoutWidth` 与 `outputWidth`,禁止继续使用一个 `exportSize` 表达全部含义。
|
|||
|
|
|
|||
|
|
### 3.2 `1242px` 只能作为候选导出宽度,不能直接当成产品标准
|
|||
|
|
|
|||
|
|
图 1、3、4 都是 `1242px` 宽,但这也可能来自手机截图或三倍屏导出。没有渠道规范、文件上传限制和目标设备信息,不能仅凭样图认定所有长图必须输出 `1242px`。
|
|||
|
|
|
|||
|
|
计划中将 `1242px` 作为首选候选,同时保留在第 0 阶段改为 `1080px` 的决策点。两者不应同时首发,避免继续扩大测试矩阵。
|
|||
|
|
|
|||
|
|
### 3.3 图 2 和图 4 的结论过于确定
|
|||
|
|
|
|||
|
|
- 图 2 是 `1024×1536` 的多面板拼版。它可能是设计参考,也可能是模型错误输出;没有生成上下文时不能直接判定为失败。
|
|||
|
|
- 图 4 的空白尾部确实符合错误截取范围,但尚未证明它由当前 `min-height` 直接生成。
|
|||
|
|
|
|||
|
|
修复时应把这两项变成复现用例,而不是把推断当成已确认根因。
|
|||
|
|
|
|||
|
|
### 3.4 30 MB 风险没有数据支撑
|
|||
|
|
|
|||
|
|
7 张样图实际文件大小约为 `0.20~4.60 MB`,均远低于服务端的 30 MB 限制。固定 `pixelRatio: 2` 和 data URL 的确存在浏览器内存风险,但“很容易超过 30 MB”没有被样本证明。
|
|||
|
|
|
|||
|
|
计划应分别记录:
|
|||
|
|
|
|||
|
|
- PNG 压缩后文件大小;
|
|||
|
|
- 解码后的像素总量;
|
|||
|
|
- 浏览器合成峰值内存;
|
|||
|
|
- 上传限制。
|
|||
|
|
|
|||
|
|
文件大小和内存占用不是同一个问题,不能混为一谈。
|
|||
|
|
|
|||
|
|
### 3.5 服务端 Headless Chromium 是可选架构决策,不是第一步
|
|||
|
|
|
|||
|
|
服务端 Chromium 会引入浏览器二进制、字体、worker 内存、Docker 镜像体积、内部渲染鉴权和版本一致性问题。当前项目没有 Playwright/Puppeteer 依赖,直接迁移会扩大改动面。
|
|||
|
|
|
|||
|
|
更稳妥的顺序是:
|
|||
|
|
|
|||
|
|
1. 先修正布局、尺寸契约和浏览器 Blob 导出;
|
|||
|
|
2. 记录真实失败率和关闭页面后的业务要求;
|
|||
|
|
3. 如果必须支持无页面会话生成,或浏览器导出失败率超过门槛,再引入服务端渲染。
|
|||
|
|
|
|||
|
|
### 3.6 审计健康分数对本次决策帮助有限
|
|||
|
|
|
|||
|
|
`6/20` 混入了可访问性、主题和响应式等维度,但本次核心是成品尺寸、版式和导出可靠性。没有运行时页面和用户任务数据,分数存在伪精确感。
|
|||
|
|
|
|||
|
|
本计划改用可验证门槛:真实像素是否一致、内容是否裁切、空白是否超标、关闭页面后是否需要完成、下载成功率和视觉基准是否通过。
|
|||
|
|
|
|||
|
|
### 3.7 报告漏掉了渲染数据来源分裂
|
|||
|
|
|
|||
|
|
当前至少存在三份内容来源:
|
|||
|
|
|
|||
|
|
- 前端画布直接使用 `draft.parsedFields`;
|
|||
|
|
- Celery 的 `build_poster_content()` 使用 `product_rules.get("facts") or product_rules`;
|
|||
|
|
- 最终 `document_json` 又保存 case 的 `confirmed_data`。
|
|||
|
|
|
|||
|
|
而 `poster_content` 只用于 AI prompt,并没有成为 HTML 画布的统一输入。产品小册子的 features、计划书确认数据、字段画像和前端 sections 因而可能不一致。
|
|||
|
|
|
|||
|
|
这会造成“AI 背景理解的是一组卖点,画布展示的是另一组数据”。修复必须先建立唯一 `renderDocument`,不能只改 CSS。
|
|||
|
|
|
|||
|
|
### 3.8 报告漏掉了 fallback 图片会绘制文字
|
|||
|
|
|
|||
|
|
正常 prompt 要求 AI 只生成无文字背景,但 `generate_fallback()` 会在图片中绘制 headline、body 和 CTA。该图片随后又作为 hero 背景,前端 HTML 会再次绘制同样文案,可能产生重复文字、裁切文字或乱码。
|
|||
|
|
|
|||
|
|
fallback 必须改为无文字的纯背景素材,或者在背景生成失败时直接使用模板渐变,不能再生成“半成品海报”。
|
|||
|
|
|
|||
|
|
### 3.9 报告缺少落地约束
|
|||
|
|
|
|||
|
|
原报告没有明确:
|
|||
|
|
|
|||
|
|
- 首发到底支持哪些规格;
|
|||
|
|
- 哪些旧记录继续使用 v1;
|
|||
|
|
- 模板数据如何迁移;
|
|||
|
|
- 模式切换后 sections 和 template 如何处理;
|
|||
|
|
- 如何验证 ECharts 已完成渲染;
|
|||
|
|
- 如何灰度、监控和回滚;
|
|||
|
|
- 每个阶段改哪些文件以及完成定义。
|
|||
|
|
|
|||
|
|
以下计划补齐这些内容。
|
|||
|
|
|
|||
|
|
## 4. 目标产品规格
|
|||
|
|
|
|||
|
|
### 4.1 输出模式与规格
|
|||
|
|
|
|||
|
|
首发建议只支持三种格式,避免继续开放不可验证的“自定义尺寸”:
|
|||
|
|
|
|||
|
|
| formatId | 用途 | 逻辑尺寸 | 最终像素 | 高度策略 |
|
|||
|
|
|---|---|---:|---:|---|
|
|||
|
|
| `single_2_3` | 朋友圈/私聊单图 | 512×768 CSS px | 1024×1536 | 固定 |
|
|||
|
|
| `single_9_16` | 竖屏故事/企微 H5 | 540×960 CSS px | 1080×1920 | 固定 |
|
|||
|
|
| `long_1242_auto` | 完整方案长图 | 414×auto CSS px | 1242×auto | 内容驱动 |
|
|||
|
|
|
|||
|
|
第 0 阶段如果确认渠道统一要求 `1080px` 长图,则把最后一项改为 `long_1080_auto`,逻辑宽度仍保持独立,不改变组件排版原则。
|
|||
|
|
|
|||
|
|
首发明确不支持:横版、方图、A4、自定义宽高、固定 2160/3240/4320 高度。以后增加格式时,每增加一个格式都必须同时增加视觉基准和导出测试。
|
|||
|
|
|
|||
|
|
### 4.2 内容预算
|
|||
|
|
|
|||
|
|
**单图:**
|
|||
|
|
|
|||
|
|
- 1 个品牌区;
|
|||
|
|
- 1 个主标题,建议不超过 24 个中文字符;
|
|||
|
|
- 1 个副标题,最多 2~3 行;
|
|||
|
|
- 3~5 个核心数据;
|
|||
|
|
- 最多 3 个卖点;
|
|||
|
|
- 1 个 CTA;
|
|||
|
|
- 1 条精简免责声明;
|
|||
|
|
- 不展示完整收益折线图和多年度卡片矩阵。
|
|||
|
|
|
|||
|
|
**长图:**
|
|||
|
|
|
|||
|
|
- hero;
|
|||
|
|
- 计划摘要;
|
|||
|
|
- 收益趋势图(有至少 3 个有效节点时);
|
|||
|
|
- 里程碑数据卡;
|
|||
|
|
- 最多 6 个产品卖点;
|
|||
|
|
- 适配/不适配说明;
|
|||
|
|
- CTA;
|
|||
|
|
- 完整免责声明;
|
|||
|
|
- 高度完全由实际可见章节决定,不设置人为 `min-height`。
|
|||
|
|
|
|||
|
|
### 4.3 模板定义
|
|||
|
|
|
|||
|
|
首发不做无限可配置模板系统,只增加解决当前问题所需的最少字段:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
layoutKey single_hero_data | single_story | long_editorial
|
|||
|
|
supportedModes [single] | [long]
|
|||
|
|
supportedFormats [single_2_3, ...]
|
|||
|
|
themeTokens 背景、文字、卡片、边框、主色、强调色
|
|||
|
|
backgroundSlot hero | full_bleed
|
|||
|
|
version 整数
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`section_schema`、任意拖拽布局和管理员可视化模板编辑器不进入本次范围。
|
|||
|
|
|
|||
|
|
## 5. 目标技术模型
|
|||
|
|
|
|||
|
|
### 5.1 格式注册表
|
|||
|
|
|
|||
|
|
在后端建立唯一格式注册表,例如:
|
|||
|
|
|
|||
|
|
`api/insurance/poster/format_registry.py`
|
|||
|
|
|
|||
|
|
职责:
|
|||
|
|
|
|||
|
|
- 返回首发格式列表;
|
|||
|
|
- 校验 `formatId` 与 `outputMode`;
|
|||
|
|
- 提供逻辑尺寸、最终尺寸、最大高度和 AI asset slot;
|
|||
|
|
- 拒绝未知格式,不再静默回退。
|
|||
|
|
|
|||
|
|
新增只读接口:
|
|||
|
|
|
|||
|
|
```http
|
|||
|
|
GET /insurance/poster/formats
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
前端从该接口加载选项,避免 TypeScript 与 Python 各维护一份尺寸清单。接口不可用时只回退到内置的三项安全规格,不开放自定义尺寸。
|
|||
|
|
|
|||
|
|
为了兼容旧记录,保留数据库 `export_size` 字段;新记录同时在 `document_json` 中保存 `formatId`、`requestedOutput` 和 `actualOutput`。第一期不为了实际宽高额外增加数据库列,避免不必要迁移。
|
|||
|
|
|
|||
|
|
### 5.2 统一渲染文档
|
|||
|
|
|
|||
|
|
新增后端构建器,例如:
|
|||
|
|
|
|||
|
|
`api/insurance/poster/render_document_builder.py`
|
|||
|
|
|
|||
|
|
统一输出:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"schemaVersion": 2,
|
|||
|
|
"formatId": "single_2_3",
|
|||
|
|
"outputMode": "single",
|
|||
|
|
"layoutKey": "single_hero_data",
|
|||
|
|
"copy": {},
|
|||
|
|
"facts": {},
|
|||
|
|
"features": [],
|
|||
|
|
"benefits": [],
|
|||
|
|
"theme": {},
|
|||
|
|
"sections": [],
|
|||
|
|
"background": {},
|
|||
|
|
"complianceRevision": ""
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
数据优先级固定为:
|
|||
|
|
|
|||
|
|
1. 人工确认的 case `confirmed_data`;
|
|||
|
|
2. 产品小册子规则中的产品卖点和保障规则;
|
|||
|
|
3. 模板默认值;
|
|||
|
|
4. 安全空状态。
|
|||
|
|
|
|||
|
|
Celery prompt、前端预览、最终导出和历史恢复都读取同一份文档,不再分别拼装。
|
|||
|
|
|
|||
|
|
### 5.3 画布路由
|
|||
|
|
|
|||
|
|
新增轻量路由组件:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
PosterCanvasRouter
|
|||
|
|
├── PosterSingleCanvas
|
|||
|
|
└── PosterLongCanvas
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
章节组件可以复用,但容器、信息预算和排版规则必须独立。`PosterHtmlCanvas` 不再通过 `aspect-ratio + overflow:hidden` 模拟单图。
|
|||
|
|
|
|||
|
|
模式或格式切换时必须执行:
|
|||
|
|
|
|||
|
|
1. 重新计算兼容 layout;
|
|||
|
|
2. 过滤不兼容 template;
|
|||
|
|
3. 按内容预算规范化 sections;
|
|||
|
|
4. 告知用户哪些内容被隐藏,而不是静默裁切;
|
|||
|
|
5. 标记当前最终成品为过期,需要重新导出。
|
|||
|
|
|
|||
|
|
### 5.4 AI 背景素材
|
|||
|
|
|
|||
|
|
图片模型只接收素材槽位规格,不接收最终长图高度:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
最终长图:1242×auto
|
|||
|
|
逻辑 hero:414×360 CSS px
|
|||
|
|
AI 素材:由 provider capability 映射到最接近的竖版或横版背景尺寸
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图片供应商尺寸解析必须严格:
|
|||
|
|
|
|||
|
|
- 已支持规格明确映射;
|
|||
|
|
- 未支持规格返回可诊断错误;
|
|||
|
|
- 不得无提示回退到 `1024x1792`;
|
|||
|
|
- 记录 requested asset size 和 actual asset size;
|
|||
|
|
- fallback 只生成无文字渐变/纹理背景。
|
|||
|
|
|
|||
|
|
### 5.5 浏览器导出 v2
|
|||
|
|
|
|||
|
|
第一版继续使用浏览器导出,但必须改成确定性流程:
|
|||
|
|
|
|||
|
|
1. 等待字体完成;
|
|||
|
|
2. 等待全部图片 decode;
|
|||
|
|
3. 等待 ECharts `finished` 事件,而不是只等待两个 animation frame;
|
|||
|
|
4. 读取未缩放画布的逻辑尺寸;
|
|||
|
|
5. 根据 `outputWidth / layoutWidth` 计算唯一导出倍率;
|
|||
|
|
6. 单图固定输出宽高;长图以真实内容高度计算输出高度;
|
|||
|
|
7. 使用 Blob 导出,不经 base64 data URL 中转;
|
|||
|
|
8. 客户端解码 Blob 核验宽高;
|
|||
|
|
9. 服务端用 Pillow 再次解码并校验;
|
|||
|
|
10. 校验通过后才写入 `export_url` 并显示“可下载”。
|
|||
|
|
|
|||
|
|
长图安全门槛首版建议:
|
|||
|
|
|
|||
|
|
- 最终像素高度超过浏览器验证上限时阻止导出并给出明确提示;
|
|||
|
|
- 不在本次首发中自动降采样,因为静默降低质量会再次造成记录和成品不一致;
|
|||
|
|
- 分片导出作为后续功能,只有真实案例超过上限后再实现。
|
|||
|
|
|
|||
|
|
## 6. 分阶段实施计划
|
|||
|
|
|
|||
|
|
## 阶段 0:冻结基线与复现(1~2 人日)
|
|||
|
|
|
|||
|
|
### 目标
|
|||
|
|
|
|||
|
|
把推断变成可重复证据,确认首发格式。
|
|||
|
|
|
|||
|
|
### 任务
|
|||
|
|
|
|||
|
|
1. 保存当前工作区差异清单,标记海报相关未提交改动的负责人;
|
|||
|
|
2. 固定一份脱敏计划书数据、产品规则、模板和文案作为测试 fixture;
|
|||
|
|
3. 对当前版本分别导出:
|
|||
|
|
- single 默认规格;
|
|||
|
|
- long 2160;
|
|||
|
|
- long 4320;
|
|||
|
|
4. 记录:选择尺寸、DOM 尺寸、PNG 真实尺寸、像素总量、Blob 大小、上传结果和下载结果;
|
|||
|
|
5. 验证图 4 空白是否可由当前代码稳定复现;
|
|||
|
|
6. 验证图 2 是否属于允许的多面板设计方向;
|
|||
|
|
7. 确认长图首发输出宽度为 1242 或 1080;
|
|||
|
|
8. 确认是否存在“用户关闭页面后必须继续完成最终成品”的硬需求。
|
|||
|
|
|
|||
|
|
### 产出
|
|||
|
|
|
|||
|
|
- `tests/fixtures/poster/` 脱敏 fixture;
|
|||
|
|
- 当前版本导出基线表;
|
|||
|
|
- 三个首发 formatId 的最终确认;
|
|||
|
|
- 服务端 Headless 是否必须进入本期的决策记录。
|
|||
|
|
|
|||
|
|
### 完成定义
|
|||
|
|
|
|||
|
|
同一 fixture 在同一浏览器连续导出 3 次,能够稳定复现当前尺寸问题,并获得真实 PNG 元数据。
|
|||
|
|
|
|||
|
|
## 阶段 1:尺寸契约止血(2~3 人日)
|
|||
|
|
|
|||
|
|
### 后端任务
|
|||
|
|
|
|||
|
|
- 新增 `format_registry.py`;
|
|||
|
|
- 新增 `GET /poster/formats`;
|
|||
|
|
- `PosterService.generate_poster()` 改为接收 `formatId`;
|
|||
|
|
- 旧请求中的 `size` 仅用于兼容映射,无法映射时返回参数错误;
|
|||
|
|
- `PosterImageGenerator` 将最终格式与 AI asset size 分离;
|
|||
|
|
- 删除未知尺寸默认回退;
|
|||
|
|
- `generate_fallback()` 改成无文字背景。
|
|||
|
|
|
|||
|
|
### 前端任务
|
|||
|
|
|
|||
|
|
- `PosterDraft` 新增 `formatId`;
|
|||
|
|
- `PosterCreativePanel.vue` 从 formats API 渲染规格;
|
|||
|
|
- 删除 A4、横版、方图和自定义尺寸入口;
|
|||
|
|
- outputMode 切换时自动选择第一个兼容格式;
|
|||
|
|
- `buildPosterDocument()` 和自动保存 watcher 纳入 `formatId`。
|
|||
|
|
|
|||
|
|
### 主要文件
|
|||
|
|
|
|||
|
|
- `api/insurance/poster/format_registry.py`(新增)
|
|||
|
|
- `api/insurance/poster/routes.py`
|
|||
|
|
- `api/insurance/poster/service.py`
|
|||
|
|
- `api/insurance/poster/image_generator.py`
|
|||
|
|
- `frontend/src/utils/poster-api.ts`
|
|||
|
|
- `frontend/src/composables/usePosterWorkspace.ts`
|
|||
|
|
- `frontend/src/components/poster/workspace/PosterCreativePanel.vue`
|
|||
|
|
- `frontend/src/pages/PosterPage.vue`
|
|||
|
|
|
|||
|
|
### 测试
|
|||
|
|
|
|||
|
|
- 每个 formatId 都能返回唯一逻辑/输出规格;
|
|||
|
|
- 未知 formatId 返回 4xx 业务错误;
|
|||
|
|
- 旧 `1024x1792` 记录可映射到安全兼容格式;
|
|||
|
|
- 所有前端可见格式均被后端接受;
|
|||
|
|
- fallback PNG 不包含 headline/body/CTA 绘制逻辑。
|
|||
|
|
|
|||
|
|
### 完成定义
|
|||
|
|
|
|||
|
|
UI 中不存在任何会被后端静默改成其他尺寸的选项。
|
|||
|
|
|
|||
|
|
## 阶段 2:统一渲染文档(2~3 人日)
|
|||
|
|
|
|||
|
|
### 后端任务
|
|||
|
|
|
|||
|
|
- 新增 `render_document_builder.py`;
|
|||
|
|
- case confirmed facts、product rules、copy、template、profile 合并为 schema v2;
|
|||
|
|
- `build_poster_content()` 明确接收 case facts 与 product rules,修复当前来源混用;
|
|||
|
|
- record 创建时立即保存完整 renderDocument;
|
|||
|
|
- Celery 从 renderDocument 构建背景 prompt;
|
|||
|
|
- 历史恢复返回原始 schemaVersion,禁止用最新规则偷偷改变旧成品。
|
|||
|
|
|
|||
|
|
### 前端任务
|
|||
|
|
|
|||
|
|
- `PosterDraft` 增加 `renderDocument` 或等价强类型字段;
|
|||
|
|
- 画布从 renderDocument 读取 facts、features、benefits、theme 和 sections;
|
|||
|
|
- 删除 `defaultFeatures` 从 `parsedFields.key_benefits` 临时拼装的逻辑;
|
|||
|
|
- 编辑操作只修改 renderDocument 对应字段。
|
|||
|
|
|
|||
|
|
### 主要文件
|
|||
|
|
|
|||
|
|
- `api/insurance/poster/render_document_builder.py`(新增)
|
|||
|
|
- `api/insurance/poster/content_builder.py`
|
|||
|
|
- `api/insurance/poster/service.py`
|
|||
|
|
- `api/insurance/generation/celery_tasks.py`
|
|||
|
|
- `frontend/src/composables/usePosterWorkspace.ts`
|
|||
|
|
- `frontend/src/components/poster/workspace/PosterStage.vue`
|
|||
|
|
- `frontend/src/pages/PosterPage.vue`
|
|||
|
|
|
|||
|
|
### 测试
|
|||
|
|
|
|||
|
|
- 人工确认数据覆盖 AI 原始解析数据;
|
|||
|
|
- 产品 features 能进入前端画布;
|
|||
|
|
- 同一 renderDocument 生成的 prompt、预览和导出使用相同事实;
|
|||
|
|
- schema v1 旧记录仍能恢复;
|
|||
|
|
- 缺失收益表时不显示空图表。
|
|||
|
|
|
|||
|
|
### 完成定义
|
|||
|
|
|
|||
|
|
任意一个展示字段都能追溯到 renderDocument 的唯一字段,不再从三个不同对象临时拼接。
|
|||
|
|
|
|||
|
|
## 阶段 3:拆分单图/长图版式与模板(4~6 人日)
|
|||
|
|
|
|||
|
|
### 数据和模板任务
|
|||
|
|
|
|||
|
|
- 为 `poster_templates` 增加最少兼容字段;迁移编号使用实施时的下一个可用编号,避免与当前未提交迁移冲突;
|
|||
|
|
- 为现有模板补齐默认 `layoutKey`、supportedModes 和 supportedFormats;
|
|||
|
|
- 旧模板无法识别时回退到明确的 legacy layout,不自动套新布局。
|
|||
|
|
|
|||
|
|
### 前端任务
|
|||
|
|
|
|||
|
|
- 新增 `PosterCanvasRouter.vue`;
|
|||
|
|
- 新增 `single/PosterSingleCanvas.vue`;
|
|||
|
|
- 将现有 `long/PosterHtmlCanvas.vue` 收敛为真正的 `PosterLongCanvas.vue`;
|
|||
|
|
- 抽取共享的事实格式化、免责声明和品牌组件,不抽象视觉结构;
|
|||
|
|
- 建立画布级 theme CSS variables;
|
|||
|
|
- summary/chart/features 等组件移除硬编码绿色;
|
|||
|
|
- 单图只渲染允许的信息预算;
|
|||
|
|
- 长图移除 `minHeight: export height`;
|
|||
|
|
- 模板选择按 mode/format 过滤;
|
|||
|
|
- 切换模式时显示内容调整提示。
|
|||
|
|
|
|||
|
|
### 单图构图要求
|
|||
|
|
|
|||
|
|
- 主视觉是第一视觉焦点;
|
|||
|
|
- 标题、核心数据条和 3 个卖点在一屏内完整可读;
|
|||
|
|
- footer 和免责声明始终可见;
|
|||
|
|
- 不依赖 `overflow:hidden` 裁掉正文。
|
|||
|
|
|
|||
|
|
### 长图构图要求
|
|||
|
|
|
|||
|
|
- 章节间有明确的色块和节奏变化;
|
|||
|
|
- 背景和 theme 贯穿全部章节;
|
|||
|
|
- 内容结束后立即进入 footer,不存在人为尾部留白;
|
|||
|
|
- 图表和数据卡具有适合 414 CSS px 逻辑宽度的字号。
|
|||
|
|
|
|||
|
|
### 主要文件
|
|||
|
|
|
|||
|
|
- `api/insurance/models/poster_template_model.py`
|
|||
|
|
- `api/insurance/db/migrate_0xx.py`(使用实际下一个编号)
|
|||
|
|
- `api/insurance/admin/ppt_admin_service.py`
|
|||
|
|
- `frontend/src/components/poster/PosterCanvasRouter.vue`(新增)
|
|||
|
|
- `frontend/src/components/poster/single/PosterSingleCanvas.vue`(新增)
|
|||
|
|
- `frontend/src/components/poster/long/PosterHtmlCanvas.vue`
|
|||
|
|
- `frontend/src/components/poster/long/*.vue`
|
|||
|
|
- `frontend/src/components/poster/workspace/PosterCreativePanel.vue`
|
|||
|
|
- `frontend/src/components/poster/workspace/PosterSectionPanel.vue`
|
|||
|
|
|
|||
|
|
### 视觉基准
|
|||
|
|
|
|||
|
|
至少建立三张基准图:
|
|||
|
|
|
|||
|
|
- 单图 2:3;
|
|||
|
|
- 单图 9:16;
|
|||
|
|
- 标准长图。
|
|||
|
|
|
|||
|
|
使用同一 fixture 与批准样图逐项对比:信息层级、主视觉占比、文字可读性、数据完整性、章节节奏、footer 和空白尾部。
|
|||
|
|
|
|||
|
|
### 完成定义
|
|||
|
|
|
|||
|
|
单图和长图在 DOM 结构、内容预算和模板白名单上均相互独立;隐藏任意章节后不会破坏剩余布局。
|
|||
|
|
|
|||
|
|
## 阶段 4:可靠导出 v2(2~3 人日)
|
|||
|
|
|
|||
|
|
### 前端任务
|
|||
|
|
|
|||
|
|
- 将 `renderComposite()` 拆到独立 `poster-exporter.ts`;
|
|||
|
|
- 导出时使用未应用预览 transform 的画布;
|
|||
|
|
- 根据 format spec 计算导出倍率;
|
|||
|
|
- 单图固定宽高,长图读取真实内容高度;
|
|||
|
|
- 将 ECharts ready 纳入资源屏障;
|
|||
|
|
- 使用 Blob API;
|
|||
|
|
- 导出后在客户端校验真实像素;
|
|||
|
|
- 导出状态明确区分 `preparing`、`rendering`、`uploading`、`ready`、`failed`;
|
|||
|
|
- 对失败给出具体错误,不直接回退下载旧文件。
|
|||
|
|
|
|||
|
|
### 后端任务
|
|||
|
|
|
|||
|
|
- `save_rendered()` 使用 Pillow `verify()` 和重新打开读取尺寸;
|
|||
|
|
- 校验实际尺寸是否符合 format spec;
|
|||
|
|
- 将 actualOutput、byteSize 和校验结果保存到 document/extraData;
|
|||
|
|
- 只有验证通过才写 `export_url`;
|
|||
|
|
- `/download/<id>` 在最终文件未就绪时返回“成品仍在准备/需要重新导出”,不返回模糊的“文件不存在”。
|
|||
|
|
|
|||
|
|
### 主要文件
|
|||
|
|
|
|||
|
|
- `frontend/src/utils/poster-exporter.ts`(新增)
|
|||
|
|
- `frontend/src/pages/PosterPage.vue`
|
|||
|
|
- `frontend/src/components/poster/long/PosterBenefitChart.vue`
|
|||
|
|
- `frontend/src/components/poster/workspace/PosterStage.vue`
|
|||
|
|
- `api/insurance/poster/service.py`
|
|||
|
|
- `api/insurance/poster/routes.py`
|
|||
|
|
|
|||
|
|
### 测试
|
|||
|
|
|
|||
|
|
- `single_2_3` 必须严格输出 1024×1536;
|
|||
|
|
- `single_9_16` 必须严格输出 1080×1920;
|
|||
|
|
- `long_1242_auto` 宽度严格为 1242,高度等于内容测量值乘导出倍率;
|
|||
|
|
- 图表在导出图中非空;
|
|||
|
|
- 服务端拒绝损坏 PNG、错误尺寸和不匹配 formatId 的文件;
|
|||
|
|
- 导出失败不会覆盖上一版有效文件;
|
|||
|
|
- 同一文档连续导出三次尺寸一致。
|
|||
|
|
|
|||
|
|
### 完成定义
|
|||
|
|
|
|||
|
|
UI 规格、renderDocument、PNG 真实尺寸和服务端记录四者一致。
|
|||
|
|
|
|||
|
|
## 阶段 5:可靠性决策门(0.5 人日评审)
|
|||
|
|
|
|||
|
|
完成阶段 4 后,使用至少 20 次真实或仿真导出评估:
|
|||
|
|
|
|||
|
|
- 浏览器最终合成成功率是否达到 99%;
|
|||
|
|
- 长图最大像素下是否出现黑图/空图/截断;
|
|||
|
|
- 是否存在明确业务要求:用户提交后关闭页面,仍必须自动完成最终海报;
|
|||
|
|
- worker/Docker 是否允许增加 Chromium 的资源成本。
|
|||
|
|
|
|||
|
|
只有满足以下任一条件,才进入服务端 Headless 阶段:
|
|||
|
|
|
|||
|
|
- 关闭页面后自动成品是硬需求;
|
|||
|
|
- 浏览器导出成功率低于 99%;
|
|||
|
|
- 多端浏览器结果无法保持一致;
|
|||
|
|
- 历史任务必须支持无人值守重新渲染。
|
|||
|
|
|
|||
|
|
## 阶段 6(条件性):服务端 Headless 渲染(额外 4~7 人日)
|
|||
|
|
|
|||
|
|
### 任务
|
|||
|
|
|
|||
|
|
- 评估 Playwright/Chromium 进入 worker 镜像的体积和内存;
|
|||
|
|
- 建立只接受短时签名 token 的内部 render route;
|
|||
|
|
- 固定字体、浏览器版本、viewport、device scale 和 locale;
|
|||
|
|
- Celery 在背景素材完成后打开 render route,生成最终 PNG;
|
|||
|
|
- 最终任务状态只有在 PNG 校验并落盘后才变为 done;
|
|||
|
|
- 增加超时、重试、幂等和旧成品保护;
|
|||
|
|
- 保留浏览器本地导出作为人工应急能力。
|
|||
|
|
|
|||
|
|
### 完成定义
|
|||
|
|
|
|||
|
|
用户生成后立即关闭页面,任务仍能在历史记录中完成,并下载与预览一致的最终海报。
|
|||
|
|
|
|||
|
|
## 7. 测试计划
|
|||
|
|
|
|||
|
|
### 7.1 后端单元测试
|
|||
|
|
|
|||
|
|
新增或扩展:
|
|||
|
|
|
|||
|
|
- format registry 映射和拒绝规则;
|
|||
|
|
- renderDocument 数据优先级;
|
|||
|
|
- single/long 字段预算;
|
|||
|
|
- template 兼容过滤;
|
|||
|
|
- fallback 无文字;
|
|||
|
|
- PNG 解码、尺寸和格式校验;
|
|||
|
|
- schema v1/v2 兼容恢复。
|
|||
|
|
|
|||
|
|
建议文件:
|
|||
|
|
|
|||
|
|
- `tests/poster_format_registry_test.py`
|
|||
|
|
- `tests/poster_render_document_test.py`
|
|||
|
|
- `tests/poster_export_validation_test.py`
|
|||
|
|
|
|||
|
|
### 7.2 前端组件测试
|
|||
|
|
|
|||
|
|
当前前端未配置组件测试框架。阶段 3 开始前决定是否引入 Vitest;若不引入,至少用构建检查和 E2E 覆盖以下逻辑:
|
|||
|
|
|
|||
|
|
- 模式切换自动修正规格与模板;
|
|||
|
|
- single 不渲染 forbidden sections;
|
|||
|
|
- long 高度随内容变化;
|
|||
|
|
- export scale 计算;
|
|||
|
|
- 资源未 ready 时禁止导出;
|
|||
|
|
- 导出失败状态和重试。
|
|||
|
|
|
|||
|
|
### 7.3 端到端与视觉回归
|
|||
|
|
|
|||
|
|
固定浏览器、字体和 fixture,验证:
|
|||
|
|
|
|||
|
|
1. 新建海报;
|
|||
|
|
2. 选择产品并恢复确认数据;
|
|||
|
|
3. 切换 single/long;
|
|||
|
|
4. 选择兼容模板;
|
|||
|
|
5. 生成背景;
|
|||
|
|
6. 导出并下载;
|
|||
|
|
7. 使用 Pillow 校验 PNG 元数据;
|
|||
|
|
8. 与批准基准图做截图差异比较;
|
|||
|
|
9. 刷新页面并重新下载;
|
|||
|
|
10. 导出失败后旧成品仍可用。
|
|||
|
|
|
|||
|
|
### 7.4 性能边界
|
|||
|
|
|
|||
|
|
以像素总量而不是文件 MB 作为主要边界:
|
|||
|
|
|
|||
|
|
- 标准单图;
|
|||
|
|
- 标准长图;
|
|||
|
|
- 2 倍典型长图内容;
|
|||
|
|
- 最大允许长图;
|
|||
|
|
- 低内存移动设备只做预览,不要求在移动端导出最大长图时必须成功;如业务要求移动端导出,则服务端渲染直接升级为硬需求。
|
|||
|
|
|
|||
|
|
## 8. 兼容、灰度与回滚
|
|||
|
|
|
|||
|
|
### 8.1 旧记录
|
|||
|
|
|
|||
|
|
- `schemaVersion` 缺失视为 v1;
|
|||
|
|
- v1 记录继续使用 legacy renderer,不用 v2 规则重新排版;
|
|||
|
|
- v1 可下载文件保持原路径;
|
|||
|
|
- 用户主动选择“升级并重新生成”时才转为 v2。
|
|||
|
|
|
|||
|
|
### 8.2 功能开关
|
|||
|
|
|
|||
|
|
增加项目级配置 `poster_render_v2`:
|
|||
|
|
|
|||
|
|
- 关闭:继续使用旧布局和旧导出;
|
|||
|
|
- 开启:新建记录使用 format registry、renderDocument v2 和新布局;
|
|||
|
|
- 灰度:仅管理员/测试用户开启。
|
|||
|
|
|
|||
|
|
### 8.3 数据迁移
|
|||
|
|
|
|||
|
|
- 模板字段只做增量新增,不删除旧字段;
|
|||
|
|
- 给现有模板补兼容默认值;
|
|||
|
|
- 不重写旧 `document_json`;
|
|||
|
|
- 迁移脚本编号必须在实施时检查 `api/insurance/db/` 当前最大编号,避免与未提交的 `migrate_029.py`、`migrate_030.py` 冲突。
|
|||
|
|
|
|||
|
|
### 8.4 回滚
|
|||
|
|
|
|||
|
|
- 关闭 `poster_render_v2` 即可停止新链路;
|
|||
|
|
- 新增字段保持 nullable,旧代码可忽略;
|
|||
|
|
- 新导出文件使用新 revision 文件名,不覆盖旧文件;
|
|||
|
|
- 任何阶段不得删除用户已有海报或背景素材。
|
|||
|
|
|
|||
|
|
## 9. 监控与诊断信息
|
|||
|
|
|
|||
|
|
每次导出至少记录:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
recordId
|
|||
|
|
schemaVersion
|
|||
|
|
formatId
|
|||
|
|
layoutKey
|
|||
|
|
logicalWidth/logicalHeight
|
|||
|
|
requestedWidth/requestedHeight
|
|||
|
|
actualWidth/actualHeight
|
|||
|
|
pixelCount
|
|||
|
|
blobByteSize
|
|||
|
|
backgroundProvider/backgroundModel
|
|||
|
|
backgroundRequestedSize/backgroundActualSize
|
|||
|
|
renderDurationMs
|
|||
|
|
uploadDurationMs
|
|||
|
|
validationResult
|
|||
|
|
failureStage/failureCode
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
首发观察指标:
|
|||
|
|
|
|||
|
|
- 背景生成成功率;
|
|||
|
|
- 最终合成成功率;
|
|||
|
|
- 最终文件验证通过率;
|
|||
|
|
- 下载成功率;
|
|||
|
|
- 各阶段 P50/P95 时长;
|
|||
|
|
- 浏览器导出失败原因分布。
|
|||
|
|
|
|||
|
|
## 10. 文件级修改矩阵
|
|||
|
|
|
|||
|
|
| 文件/目录 | 计划改动 | 阶段 |
|
|||
|
|
|---|---|---:|
|
|||
|
|
| `api/insurance/poster/format_registry.py` | 统一格式规格与校验 | 1 |
|
|||
|
|
| `api/insurance/poster/render_document_builder.py` | 构建唯一 renderDocument | 2 |
|
|||
|
|
| `api/insurance/poster/content_builder.py` | 修正 case facts/product rules 合并 | 2 |
|
|||
|
|
| `api/insurance/poster/image_generator.py` | asset size 严格映射、无文字 fallback | 1/2 |
|
|||
|
|
| `api/insurance/poster/service.py` | format 校验、document v2、PNG 校验 | 1/2/4 |
|
|||
|
|
| `api/insurance/poster/routes.py` | formats API、明确下载错误 | 1/4 |
|
|||
|
|
| `api/insurance/generation/celery_tasks.py` | 使用 renderDocument 生成背景 | 2 |
|
|||
|
|
| `api/insurance/models/poster_template_model.py` | 最少模板兼容字段 | 3 |
|
|||
|
|
| `api/insurance/db/migrate_0xx.py` | 增量模板字段迁移 | 3 |
|
|||
|
|
| `frontend/src/composables/usePosterWorkspace.ts` | formatId、renderDocument、v1/v2 恢复 | 1/2 |
|
|||
|
|
| `frontend/src/utils/poster-api.ts` | formats 和 v2 document API | 1/2 |
|
|||
|
|
| `frontend/src/utils/poster-exporter.ts` | 独立确定性导出器 | 4 |
|
|||
|
|
| `frontend/src/components/poster/PosterCanvasRouter.vue` | 单图/长图布局路由 | 3 |
|
|||
|
|
| `frontend/src/components/poster/single/` | 单图专用布局 | 3 |
|
|||
|
|
| `frontend/src/components/poster/long/` | 长图布局与主题 token | 3 |
|
|||
|
|
| `frontend/src/components/poster/workspace/PosterCreativePanel.vue` | 兼容格式和模板选择 | 1/3 |
|
|||
|
|
| `frontend/src/components/poster/workspace/PosterStage.vue` | 预览与未缩放画布管理 | 3/4 |
|
|||
|
|
| `frontend/src/pages/PosterPage.vue` | 简化为编排、状态与持久化 | 1/2/4 |
|
|||
|
|
| `tests/poster_*` | 单元、契约、导出验证 | 全阶段 |
|
|||
|
|
|
|||
|
|
## 11. 明确不做的事项
|
|||
|
|
|
|||
|
|
为了控制改动范围,本期不做:
|
|||
|
|
|
|||
|
|
- 任意自定义宽高;
|
|||
|
|
- A4/横版/方图;
|
|||
|
|
- 所见即所得自由拖拽编辑器;
|
|||
|
|
- 管理员可视化搭建模板;
|
|||
|
|
- 自动生成多页拼版;
|
|||
|
|
- 无真实案例支撑的长图自动分片;
|
|||
|
|
- 在阶段 5 决策前直接引入 Chromium;
|
|||
|
|
- 重构无关 PPT、认证、产品推荐或基座代码。
|
|||
|
|
|
|||
|
|
## 12. 最终验收清单
|
|||
|
|
|
|||
|
|
只有以下项目全部通过,才能认为修复完成:
|
|||
|
|
|
|||
|
|
- [ ] 首发仅显示已确认的三种格式;
|
|||
|
|
- [ ] 未知格式不会静默回退;
|
|||
|
|
- [ ] CSS 逻辑尺寸与 PNG 输出尺寸明确分离;
|
|||
|
|
- [ ] single 和 long 使用不同的画布组件与内容预算;
|
|||
|
|
- [ ] 单图没有被 `overflow:hidden` 静默裁掉的内容;
|
|||
|
|
- [ ] 长图内容结束后没有人为 `min-height` 空白;
|
|||
|
|
- [ ] 模板只出现在兼容 mode/format 中;
|
|||
|
|
- [ ] 模板主题贯穿所有章节,不再固定白底绿色;
|
|||
|
|
- [ ] AI 和 fallback 都不绘制任何文案、数字或 Logo;
|
|||
|
|
- [ ] prompt、预览、导出读取同一 renderDocument;
|
|||
|
|
- [ ] 单图 PNG 像素严格符合规格;
|
|||
|
|
- [ ] 长图 PNG 宽度严格符合规格,高度来自真实内容;
|
|||
|
|
- [ ] 图表、字体和图片全部 ready 后才导出;
|
|||
|
|
- [ ] 服务端能拒绝损坏或错误尺寸的 PNG;
|
|||
|
|
- [ ] 导出失败不会覆盖上一版有效成品;
|
|||
|
|
- [ ] 历史记录能正确恢复 v1/v2 文档;
|
|||
|
|
- [ ] 灰度开关与回滚路径验证通过;
|
|||
|
|
- [ ] 是否引入服务端 Headless Chromium 已通过阶段 5 数据决策。
|
|||
|
|
|
|||
|
|
## 13. 推荐执行顺序
|
|||
|
|
|
|||
|
|
严格按以下顺序实施,不并行修改同一链路:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
复现与规格确认
|
|||
|
|
→ 格式契约
|
|||
|
|
→ 统一 renderDocument
|
|||
|
|
→ 单图/长图布局
|
|||
|
|
→ 确定性导出与服务端校验
|
|||
|
|
→ 真实可靠性评估
|
|||
|
|
→ 条件性服务端 Headless
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
前一阶段的完成定义未通过,不进入下一阶段。这样可以避免一边改模板、一边改尺寸、一边换渲染器,最终无法定位回归来源。
|