/** * 视图区容器 —— **净化渲染,无脚本执行权**(编码规范 §9.2;语义出处 AM 07)。 * * 「视图区容器渲染活动推送的 HTML:沙箱 iframe(srcdoc + sandbox)或净化后注入, * 二选一由 `ui-kit` 的 `ViewContainer` 统一实现,**feature 不得自行 dangerouslySetInnerHTML`。」 * * 这里选沙箱 iframe:结构上就没有脚本执行权,不依赖净化算法的完备性。 * * **高度随内容长**:HTML 报告既是独立文件,也是交互视图的组成部分 —— * 它得像页面里的一段正文那样整篇铺开、跟着外层一起滚,而不是缩在一个定高小窗里自己滚。 * 定高会把长报告拦腰截断,人得在两层滚动条之间来回找。 * * ── 就地改:默认锁着,按「编辑」才开(2026-08-01 用户定)──────────────── * 待处理那一屏摆的**只有这份件**,而件本身要能改 —— 于是编辑态长在件里面: * 每个可改的格子在渲染时就配了一个 `` 孪生件 * (见 `view-render.ts` 的 `twinInputs`),平时收着,开了闸才现身。 * * **看与改是两件事**:进这一屏整份件就是编辑态的话,人还没决定要改什么, * 屏上已经是一张待填的表(A1 那份是 54 个输入框一起亮着)。所以每张可改的表 * 各带一道闸(`[data-edit-for]`):锁着 → 「编辑」;开了 → 「取消 / 保存」。 * * 外层怎么开、怎么收:`allow-same-origin` 让父文档**读得到也改得动** iframe 的 DOM—— * · 挂事件、加 class、读 input,全在父文档这一侧; * · **iframe 里始终一行脚本都没有**:`allow-scripts` 永远不给,两者同时给才是破沙箱。 */ import { useCallback, useEffect, useRef, useState } from 'react'; /** 一张可改的表收上来的样子:`{ 台账地址: [{ 列名: 值 }] }` */ export type ViewEdits = Readonly>>>>; /** 按一次保存交出去的东西:现在这张表长什么样(`tables`)+ 人删掉了哪几行(`drops`) */ export type ViewSave = { readonly tables: ViewEdits; readonly drops: ViewEdits }; export type ViewContainerProps = { readonly html: string; readonly title?: string; /** 这一屏的件**可以改** —— 闸出现(默认仍是锁着的,要按「编辑」才开) */ readonly editable?: boolean; /** 人按下「保存」时把那张表收上来;不传就是纯呈现 */ readonly onEdits?: (save: ViewSave) => void; /** * **件上的资料名被点了** —— 收到 `data-file="域.对象/主键值"` 那一下。 * * 容器只报「点的是哪一份」,取文件、渲文件都不在这儿:这份组件的本分是 * 把沙箱里发生的事如实交出去,至于跳到哪一屏是工作区的事。 */ readonly onOpenFile?: (ref: { address: string; key: string }) => void; /** * **多页件**(2026-08-02 用户:「一个执行单可以派多个 HTML 待执行报告, * 合理利用右上角的翻页」):件里带 `data-page` 的可见段各算一页, * 外层用这两个口翻 —— `page` 显示第几页(0 起),`onPages` 回报共几页。 * 不传 `page` 或只有一页时全部照常显示。 */ readonly page?: number; readonly onPages?: (total: number) => void; }; /** 量不到高度时的兜底,避免塌成 0 高 */ const FALLBACK_HEIGHT = 420; /** * **就地换内容的第二层:只换真变了的节点**(2026-08-03 用户:「保存的时候 HTML 会闪一下」)。 * * 2026-08-02 那一刀把 srcDoc 重载换成了 `body.innerHTML = html` —— 文档不再重载, * 可整棵 DOM 仍是**推倒重建**:47KB 样式表在 body 里再解析一遍(head 那份还在,从此一文档 * 两份同样的表)、二十来个 SVG 全部重建、CSS 动画一律回到第 0 帧重新起跳 —— 人眼里就是 * 保存那一下整张件「闪一下」。同一个症状,病根在下一层。 * * 治法:新旧两棵树**逐节点对齐**,一样的一根手指都不碰 —— 样式表不重解析、精灵不重建、 * 没变的段落连重绘都没有;保存的涟漪只落在真改了的那几个格子上。 * * 细则: * · 顶层 `