# 报告模板版本管理 - 前端联调方案(保存校验 + 停用约束) ## 涉及页面 - 报告模板设计器(保存 / 另存新版本,走 `create` 与 `update`) - 报告模板详情 → 版本列表(发布 / 回滚 / 停用) ## 本次变更概述 `qc_report_template_version` 的写入口新增两条**拒绝规则**,各对应一个新错误码。 两条都不是新增字段、也不是新增接口,而是把「以前会静默存下 / 存下后才炸」的情况改成**当场报错**。 前端需要做的是:把后端返回的 `msg` 原样展示(文案已可直接给用户看),并保证这两种失败**不弹通用「系统异常」**。 | 接口 | 方法 | 新增拒绝规则 | 错误码 | |---|---|---|---| | `/qc-report/template-version/create` | POST | 画布含不允许渲染的内容 | `1_070_101_009` | | `/qc-report/template-version/update` | PUT | 同上 | `1_070_101_009` | | `/qc-report/template-version/disable` | POST | 目标是模板的**当前版本** | `1_070_101_010` | > `create` 与 `update` 共用同一段 Schema 校验,两条入口行为一致:**只要提交的 Schema 不合格,一律不落库**。 ## 规则一:画布内容安全校验(`1_070_101_009`) ### 挡的是什么 报告产物是拼字符串拼出来的,模板画布里有三处内容会**不经转义**直接进入 HTML: 1. **节点的标签名**(如 `tagName: "h2"`); 2. **属性名**(属性**值**会转义,属性**名**不会); 3. **整段 CSS**(`styles` 写成字符串时会被原样塞进 `` 能提前闭合样式块,把后面的内容变成真 HTML) | ### 不会误杀的三种情况 设计器正常保存的画布**不会**被拦,以下三类在实现里被明确排除,前端无需特殊处理: - **`textnode` 节点上的 `tagName`**:渲染器根本不读它; - **GrapesJS 挂在节点上的 `docEl` / `head` 元数据**:渲染器不读,校验也只沿 `components` 走; - **存量画布**:已对运行库中全部真实版本实测通过。 ### 报错文案 一次最多返回 **8 条**问题,每条都带**位置**与**违规内容**,形如: ``` 模板画布里有不允许出现的内容,已拒绝保存:画布「根 > 第 1 个组件」的标签是「script」, 报告不支持该标签。可用的只有常规排版标签(div/p/span/h1~h6/img/hr 与表格、列表、行内文本标签), 脚本、样式、内嵌页面、表单类标签一律不允许。请在设计器中删掉这些内容后重新保存 ``` 多条问题以 `;` 连接。改完再保存一次即可看到下一批。 ## 规则二:不能停用「当前版本」(`1_070_101_010`) ### 挡的是什么 模板表上的 `current_version` 指向「出件时默认用哪个版本」。 停用某个版本**不会**清空 `current_version`,于是停用当前版本会造出一份**自相矛盾**的模板: `current_version` 还指着它,它却已经不可用。这种模板自此出不了报告, 而且失败发生在**出件那一刻**(报 `1_070_101_003`),用户不会想到起因是之前那次停用。 因此 `disable` 直接拒绝,**不会**顺手替用户改掉 `current_version`—— 「改哪个版本」是用户的决定,不该由「停用」这个动作悄悄代劳。 ### 报错文案 ``` 版本「v1.1」是模板「来料检验报告模板」当前正在使用的版本,不能停用。 请先到该模板的版本列表里对另一个已发布版本执行「回滚」,把当前版本切走,再停用本版本; 若要让整个模板停止使用,请改为停用模板本身 ``` 文案里给出了**两条可执行出路**,前端不需要自己组织引导话术。 ### 被拒的判定口径 - 比较对象是**模板表 `current_version` 的字符串**与**该版本记录的 `version`**; - 只要相等就拒绝,**不看版本是否已发布**(草稿版本同样可能被 `current_version` 指到); - 停用**非当前版本**的已发布版本依然允许,行为不变。 ## 字段展示规则 本次无新增字段,无需调整列表或表单列。 | 场景 | 前端表现 | |---|---| | 保存模板报 `1_070_101_009` | 用 `msg` 全文提示(可能含多条,用 `;` 分隔);**不要**截断成「保存失败」 | | 停用版本报 `1_070_101_010` | 用 `msg` 全文提示;提示文案已含后续操作步骤,无需另加指引 | | 其它错误码 | 行为不变 | ## 业务规则说明 | 场景 | 规则 | |---|---| | 新建版本 / 更新版本时 Schema 为空 | 允许(先建版本后设计),不触发画布校验 | | 新建版本 / 更新版本时 Schema 非空但缺 `schemaVersion` | 拒绝,`1_070_101_004` | | 画布有问题 | 拒绝保存,`1_070_101_009`,**不落库** | | 更新**已发布**版本 | 仍然拒绝,`1_070_101_002`(改动只能另存新草稿),与本次变更无关 | | 停用当前版本 | 拒绝,`1_070_101_010` | | 停用非当前版本 | 正常停用 | | 停用整个模板 | 走「停用模板」入口,不受本次变更影响 | ## 注意事项 - **错误提示不要吞掉**:这两条都是**用户在编辑器里的操作反馈**,必须把后端 `msg` 原样展示。 画布问题一次最多 8 条,多条时建议按 `;` 拆行展示,便于逐条处理。 - **对校验上线前就已入库的脏数据没有兜底**:校验只在保存时执行,历史版本不会被回溯检查。 若怀疑存量版本有问题,需要另行核查(出件链路不受影响,仍按原样渲染)。 - **`1_070_101_009` 的定位信息是「第 N 个组件」**:指该组件在其父节点 `components` 数组中的序号(从 1 开始), 路径形如 `根 > 第 2 个组件 > 第 1 个组件`;层级过深时中间部分会折叠为 `…`。 - 画布校验**不改变渲染结果**:渲染引擎与前端 `engine/render.ts` 的逐字一致性和存量报告的 `regenerate` 复现性都不受影响,本次只在**写入侧**加闸门。