← 返回文章列表

Blog / Deer404

配置式表单的运行设计

Jul 14·10 min
AI 生成的摘要
配置式表单从文档读取、版本迁移和发布,到上下文渲染、数据往返与异步保存的设计,以及各阶段适合承担的职责。

做固定页面时,表单一般直接写在组件里:有哪些输入框、哪些字段必填、提交到哪里,都能在代码中找到。表单编辑器要把其中一部分能力交给使用者,让他们自行调整字段、布局和规则,再把配置保存下来,供填写页面使用。

这里讨论的就是这样一个编辑器。设计表单的人在编辑页保存草稿、查看预览,填写者通过发布后的页面录入数据。它支持多语言、角色和视图,也需要打开以前保存的配置。编辑页和填写页共用配置,但用途不同:前者要容纳尚未设计完的内容,后者要按确定的规则运行。

如果每个入口都自行解释配置,差异很快会落到用户面前。预览中可填的字段,正式页变成只读;字段已经隐藏,进度条却还要求填写;清空后保存,再次打开又出现原来的值。修正某个控件可以解决眼前的显示问题,但同一份配置怎样被读取、转换和保存,也需要一起确定。

配置文档与填写数据

先分开两种很容易混在一起的数据。设计者保存的是字段定义、布局和规则;填写者提交的是某次填写的值。修改字段标签,不应让已有记录失去归属;换一个查看角色,也不应直接修改原始文档。

因此,字段需要一个独立于标签的 ID。布局引用哪个字段、权限作用于哪个字段、填写值属于哪个字段,都通过这个 ID 关联。标签只负责显示,可以被修改或翻译。后端记录还可能使用另一套业务路径,所以字段 ID 也不必等于接口里的属性名。

原始配置不能直接当成某次填写的界面。当前用户的角色、语言和视图都可能改变展示结果。编辑器保存一份可复用文档,运行时再根据上下文生成控件需要的描述,填写值则单独通过映射与业务记录往返。三者的关系可以写成:

snippet
text
配置文档 + 当前语言、角色、视图 → 本次渲染描述
业务记录 → 表单值 → 提交数据

这样划分之后,控件可以只关心当前要显示的字段。旧文档该如何兼容、某个字段为何不可见,以及接口数据该如何转换,都有各自的处理位置。

从外部 JSON 读到可用文档

配置会来自接口、本地草稿或导入文件。TypeScript 中给它声明了类型,并不能保证读到的数据真的符合约定。旧版本可能缺少后来加入的属性,导入文件也可能损坏;把两种情况都补成默认值,容易得到一份能打开、含义却已经变了的表单。

这套实现把读取集中在 parseFormDoc 中。它先读取 schemaVersion,拒绝不支持的版本,将支持的旧版本迁移到当前结构,再检查字段身份和文档结构,整理允许的属性与默认值,最后复核关键约束。

迁移和清洗分成了两个阶段。迁移需要知道旧结构原来表达的意思,例如为旧表格补齐行根路径,同时保留已有的自定义字段映射;清洗处理当前结构中允许保留的内容。如果清洗遇到不认识的格式就返回空字段,用户下一次保存时可能把原来的配置覆盖掉。

字段 ID 也要在运行前检查。两个字段共享 ID 时,界面即使画出了两个输入框,数据字典、权限引用和布局引用仍然无法分清它们。配置中的接口路径则同时检查原始形式与解码后的片段,避免越界。这里检查的是配置能否被正确解释,实际读取业务数据时,服务端还要独立鉴权。

读取成功后,草稿和发布会走向不同的要求。比如设计者正在配置记录搜索,两个条件暂时用了相同的 query key。这个未完成状态可以先保存,发布时却必须解决,否则运行端无法按预期构造请求。因此发布入口在普通解析之外增加业务校验,导入和编辑持久化仍复用基础解析。

允许保存未完成的业务配置,不代表允许损坏的文档结构。重复 ID、非法路径等问题在草稿阶段也要拦住。至于旧填写记录始终按当时的表单展示,则还涉及发布快照和记录绑定版本,不能仅靠一次发布校验实现。

根据上下文生成表单

文档读取之后,才轮到这一次该怎么显示。比如普通填写者只看到输入部分,审核角色还要看到审核字段;相同字段在不同视图里,分组和布局也可能不同。

如果让每个控件自己判断权限,页面上其他地方还会再算一遍。必填进度要知道剩几项,校验器要知道该检查什么,预览页也要决定哪些字段可见。几份判断稍有差异,就会出现隐藏字段阻止提交的情况。

compileFormRender 把这一步集中处理:接收已经解析过的文档,以及语言、角色、视图和进入阶段,生成可见分组、字段描述(schema)和必填集合。这里的“编译”是配置转换,不涉及机器码,网络请求仍由页面协调。

必填集合从生成后的 schema 中取得,再汇总去重。假设文档共有十个必填字段,当前角色只能看到六个,完成进度就应基于这六个计算。若进度重新遍历原始文档,又会把已经排除的四个字段算回来。

预览与正式填写复用这个转换函数,但入口传入的上下文仍需区分。预览可以显式选择查看所有角色,正式填写没有取得角色信息时,却不能沿用“显示全部”的默认值。共用转换减少了规则重复,入口仍要对自己提供的条件负责。

提交和回填要保持同一个意思

界面能正确渲染,只完成了配置运行的一半。填写后需要把控件值转成业务接口的数据,编辑已有记录时又要反向转换。日期、数组和关联记录的控件表示,不一定等于后端保存的格式。

清空字段很能说明这个问题。用户删掉了原值,控件给出 undefined,序列化时属性被省略。如果接口把省略理解成“不修改”,请求虽然成功,旧值仍会留在记录里,下次回填就又出现了。

这套编辑器用字段映射和转换逻辑处理两个方向,并按字段类型区分未修改、明确清空和非法值。不能在提交前统一过滤所有空值,也不能把展示用的对象直接塞进业务字段。检查转换时,需要把提交结果重新读回表单,确认用户刚才表达的修改仍然成立。表单清空笔记记录了其中的具体处理。

字段 ID、业务路径和显示值在这里各有用途。ID 关联表单内部的定义与值,路径决定怎样读写业务记录,显示值服务于控件交互。修改其中一层的表示时,提交和回填都要一起检查。

编辑会话中的保存

设计者修改配置文档,也会遇到异步保存。点击保存时发出的内容是一个快照,请求未完成前,设计者可能继续编辑,甚至已经切换到另一份表单。

假设版本 10 发出保存,页面随后编辑到了版本 11。版本 10 返回成功,可以更新“已经保存到哪里”,却不能清掉版本 11 的未保存状态。若此时打开的是另一份文档,旧响应更不能改它的标题或内容。

保存期间锁住编辑,可以简化时序,但使用者必须等待。这套编辑器允许继续编辑,在操作开始时记录身份、文档与内容版本,结果回来后再判断能接纳哪一部分。防抖只能减少请求次数,已经发出去的请求仍需要这些检查。具体的结果接纳、新建结果未知和草稿恢复,见异步保存笔记。

保存属于编辑会话的协调逻辑,放在配置转换之外。否则,一个原本只需文档和上下文的渲染函数,就得知道当前网络请求和页面身份,预览与填写也更难共用它。

这套分工的适用范围

这里的解析和运行规则对应编辑器中的文档式表单,另一套较轻的入口有不同的解析与默认规则。固定且数量不多的表单,直接写组件往往更省事。配置式表单需要额外维护文档协议、版本迁移和数据转换,节省的页面开发工作会部分转移到这些基础能力上。已有多个运行入口,或配置需要长期保存和复用时,这些成本才更容易体现价值。

验证也可以顺着这些分工进行。解析、迁移和渲染转换有明确的输入输出,可以独立检查旧配置是否保留映射、相同上下文是否得到相同规则。控件展示和输入交互需要组件或浏览器验证;保存竞争则要控制请求返回的顺序。单独测过转换函数,还不能说明整个表单已经可用。

调试时同样可以沿这个顺序找:读进来的文档是否正确,运行上下文是否正确,提交与回填有没有改变值的含义,最后再看异步结果有没有写回错误的编辑状态。这样,预览与填写不一致时,就有比“再修一下这个输入框”更具体的查找位置。

最后修改时间: Nov 5