# HTML 报告生成规范(活动视图模版) > 2026-08-02 初版,从《活动开发规范》拆出成册。基准:`activities/quote-intake@42` 的三张模版 > (`intake-overview` / `intake-classified` / `intake-release`)—— 用户验收通过的那套。 > 渲染引擎:`apps/api/src/modules/interaction/internal/domain/view-render.ts`; > 容器:`packages/ui-kit/src/ViewContainer.tsx`;保存链路:`apps/api/src/app/routes/desk.ts`。 > **本文与代码冲突时以代码为准。** --- ## 1. 报告是什么:同一份 HTML 的三个身份 | 身份 | 在哪 | 状态 | |---|---|---| | **件**(给人看) | 工作区 · 待处理 | 锁定态 —— 进屏先是一份能读的报告,不是一张待填的表 | | **表单**(给人改) | 工作区 · 待处理,按「编辑」后 | 可改格子现出输入件,能增删行 | | **归档件**(存档) | 工作区 · 工作成果 | 同一份 HTML 原样定稿,零输入框零外壳 | **为什么必须是同一份**:渲成两三套就是存两三份,存多份就会分叉,分叉之后没人看得出 以哪份为准。**看与改是两件事**(整份件一进屏就是编辑态,人还没决定改什么, 眼前已经是 54 个亮着的输入框 —— 踩过),但**看的与改的必须是同一份东西**。 三条硬规矩: 1. **每份件两态 1:1,不同工作各有各的件**(2026-08-02/03 用户两次校准): 「1:1」的落点是**每份件与它的办结快照**(在办可编辑 ↔ 回执收下那一刻存 `finalHtml`,成果页静态重放,数据/内容/布局一个字不差)—— 不是全程只许一张。 作业的件(如 A2 选型 One Pager)与收口的件(整合清单)是两件工作两份件: 「清单是所有选型完成后收口才整合的东西」。别再把两态统一误执行成一张到底 (踩过:验收工单打开还是选型页,用户当场指出)。 2. **剧本每步的 `view` 定推哪张**,写清单时定死,不归模型临场挑;放行那一步的 `view` 必须指向验收目标件 —— 最后摆上去的那张就是归档件。 3. **数据变了基座重渲**(repush):人在件上保存、在卡上回填,落草稿层后服务端拿 新数据重渲同一张 —— 由数据算出来的东西(类徽、分布条、去向)跟着一起变。 **手改的全部意义就是纠正 AI 的错,改了数而画面不动,这个兜底功能就是假的**(用户原话级教训)。 ## 2. 报告与卡(表单)的关系 —— 这套里最容易做错的一章 三个区各归各位(2026-08-01 用户定,别再混): | 区 | 放什么 | 不放什么 | |---|---|---| | **工作区 · 待处理** | **只放那份件**(活动的 HTML 模版),整篇铺满工作区宽度、可就地改 | 题头 / 编号带 / 依据带 / 判据表 / 指导书 / 提交钮 —— 一样都不要 | | **工作区 · 工作成果** | 同一份件,定稿归档 | 同上 | | **对话区的卡** | 作业指导书(steps/standard)· 依据 · 判据 · **整单级表单字段** · **提交与裁决** · 落地结果 | 件本身(给一枚「执行工单/验收工单」四字钮跳过去就够) | 分工的原则:**逐行数据归件,整单级归卡;编辑在件,提交在卡。** - 一张 9 行的清单,人要逐格核、逐格改 —— 那是件的事(报告即表单); - 「结果说明」这种整单一格的字段、「先受理还是退回」这种裁决 —— 那是卡的事; - 卡上那枚提交钮是**唯一的交卷动作**;件上的保存只落草稿层,**工单不办结、 放行门的位置不动**。 **同一个数管三处(同源纪律)**:台账声明 `required` 产出的 `域_对象_missing`—— ``` 件上的缺项红带(gapBar) ←┐ 卡上的提交闸(gate) ←┼── 同一个基座读数、同一套算法 放行门的 L1 机检 ←┘ ``` 三处各算各的,就会出现「件上说齐了、卡上说不能交、放行门又说行」—— 那种矛盾人是判不出对错的。所以**模版与卡都不算这个数,只摆基座算好的**。 **保存链路(件 → 库 → 屏)**: ``` 人按「保存」→ 前端按件自己说的话收值(data-edit 地址 + data-col 列名 + drops) → POST /tasks/:id/table {tables, drops} → 服务端按【活动清单的台账声明】过授权门,归型,只写真改了的行, 自增主键补号,draftDrop 撤显式点名的行 → 留一条人痕(改了几行/新增几行/撤掉哪几行 —— 那条痕是下一拍模型的行动指南) → repush 重渲 → 闸、判据、类徽、分布条、去向全部跟着变 ``` 三条实测教训,每条都别再犯: - **授权面是台账声明,不是「当下那张卡长什么样」** —— 从前保存借道工单 `blocks[i]` 取列名,熔断卡(没有 blocks)开着时,人补的那一格一个字都送不出去, 而他要补的恰恰是把这一单卡住的必填格; - **删要说出来**(载荷 `drops` 点名主键),不靠「没回传就是删了」—— 那种口径会把 一次不完整的回传变成一次删表; - **只写真改了的行** —— 件上按一次保存整张表都回传,照单全写会留「修订 4 行」的痕, 而人只改了一格;日志是给人看的,那句话就成了假的。 **卡侧还有一条老路**(与件侧并存,别混):卡面 `blocks` 里绑了 `ledger`+`fields` 的表, 由**基座铺行**(发卡那一刻从草稿层铺,活动别自己抄行 —— 回复有长度上限,铺长了截断 整拍作废)、人提交时由**基座写回**(`table:N` 载荷)。验收卡的 `editableAtRelease` 成果表走的就是它。两条路的授权面是同一份台账声明。 ## 3. 沙箱与安全 - 容器:`sandbox="allow-same-origin"` 的 iframe,**永不给 `allow-scripts`** (两者同给才是破沙箱)。父文档读得到也改得动里面的 DOM:开编辑 = 加 class、 收值 = 读 input,全在父文档侧;**iframe 里始终一行脚本都没有**。 - 模版含 ``)。 - ⚠️ **跨 realm 陷阱**:父文档里 `event.target instanceof Element` 判 iframe 元素 **恒为 false**(Element 是父文档的构造器)。症状:点「编辑」毫无反应而 class/CSS/ 选择器逐个查都对。**认能力不认血统**(有 `closest` 就当元素);监听挂载写成幂等的, 每次有机会就挂一遍(srcDoc 换文档的时机抓不准)。 - 内容更新**就地换 `body.innerHTML`,不换 srcDoc** —— srcDoc 一变整个文档重载, 高度掉回兜底再涨回来,人刚按下保存件就「闪黑」一下。 ## 4. 模版语言参考(绑定全表) 渲染 = 纯取值绑定。基座三遍扫描:槽位填值 → 输入件填 value → 独立 class 投影。 | 属性 | 写在哪 | 语义 | |---|---|---| | `data-slot="键"` | 任意元素 | 取值填为文本。数值整数原样、小数截 4 位;数组给长度 | | `data-rows="mine:域.对象"` | 行容器(tbody 等) | 按本单草稿行逐行复制内部结构;行内 `data-slot` 相对该行取值;序号交 CSS 计数器 | | `data-by="子列"` / `data-by="子列=父列"` | 与 `data-rows` 同元素(**嵌套**在另一个行模版里) | 只留子列值 = 外层那一行父列值的行 —— 「一个候选一张表」(外层按候选复制 `
`,里层只列它名下的明细);也能当查表用(`` 从整表里挑这一颗料那一行摆品牌)。父值缺席一行不留。行模版可以嵌套:里层先按本行展开,再填本行标量;**外层容器的标签名不得在行模版内部再出现**(用 `
` 包 `
`) | | `data-blank` | 与 `data-slot` 同元素 | 空值**真空**(不写「无」)—— 空格子的胶囊、占位交给 CSS `:empty::after` | | `data-class-of="键"` | 任意元素 | 把值消毒成 `v-<小写值>` 追加进 class(数值就是 `v-0`/`v-1`/…)。**判断留在样式表** | | `data-col="台账列名"` | 可改格子(与 `data-slot` 同元素) | 渲染时多渲一个孪生 ``;写的是**台账列名**不是表头文字(表头改说法不该让写回错位) | | `data-options="域.对象"`(配 `data-option-value` / `data-option-label`) | `` | 值进 value 属性(孪生件用,模版一般不手写) | | `data-file-of="主键列"` | 行内任意元素 | 这一格**点开是一份资料文件**:渲成 `data-file="域.对象/主键值"`,外层接住就跳到查看面。那一列空着就整个属性去掉 —— 没有文件的行不该长得像能点开 | **`data-options` 与 `data-slot` 不能共存**(2026-08-29):同一个 `` 的内容是一串 `