Schema 与数据模型
Schema 不只是“组件配置列表”。它同时描述字段如何显示、数据存在哪里、初始模型是什么形状、如何校验,以及字段之间如何联动。SuperForm 会据此建立数据模型,再让输入、只读展示和页面组件共享同一份业务定义。
本章按“配置声明 → 字段路径 → 数据绑定”的顺序展开。第一次阅读建议顺序浏览;查阅具体能力时可直接使用右侧目录定位 field、labelField、endField、dataSource 或字段级 Ref。
Schema 声明
├─ 结构:字段、容器、数组
├─ 视图:label、type、attrs、布局
├─ 模型:field、initialValue、关联字段
├─ 行为:rules、事件、响应式配置
└─ 场景:表单、表格、详情这种设计的价值是:新增一个业务字段时,通常只需在 Schema 中补充一次定义,而不必分别维护表单控件、模型初始化、校验规则和只读文案。
从一项声明到完整行为
下面是一项典型字段配置:
{
type: 'Input',
field: 'profile.name',
label: '姓名',
required: true,
initialValue: '',
span: 12,
attrs: {
maxlength: 30,
allowClear: true,
},
}运行时它会同时产生以下结果:
type: 'Input'选择文本输入控件。field: 'profile.name'建立profile.name数据路径;中间对象缺失时自动补齐。label: '姓名'生成表单标签,并为 Input 推导“请输入姓名”占位提示。required: true生成必填标识和默认必填规则,提示为“姓名不能为空!”。initialValue: ''进入 Schema 标准初始模型,供初始化和重置使用。span: 12让字段在 24 栅格中占半行。attrs继续传给底层 Ant Design Vue Input。
可以把其中的自动推导理解成下面的等价展开:
{
label: '姓名',
required: true,
attrs: {
// 未显式填写 placeholder 时,Input 会根据 label 自动生成
placeholder: '请输入姓名',
},
rules: [
// required: true 会根据 label 自动生成默认提示
{ required: true, message: '姓名不能为空!' },
],
}这段代码用于解释运行效果;组件不会改写传入的原始 Schema。显式配置始终优先,例如 attrs.placeholder: '填写联系人姓名' 或自定义 rules.message 会覆盖默认文案。
不同字段使用符合交互习惯的提示语:Input、Textarea、InputNumber、AutoComplete 默认使用“请输入…”,Select、TreeSelect 默认使用“请选择…”。各类型的默认值与专属配置见基础输入和选择输入。
根节点、容器与字段
SuperForm 根 Schema
└─ subItems
├─ 字段节点:绑定一个值
├─ 容器节点:组织一组 subItems
└─ 数组节点:用 columns 描述数组元素根节点对应 SuperForm,负责数据源、提交、重置和整体布局。它本身不是字段类型,也不需要在 subItems 中再写一个 Form 容器。
节点的角色由结构配置决定:
| 节点 | 主要结构 | 数据结果 | 典型类型 |
|---|---|---|---|
| 普通字段 | field | 标量、对象或组件约定值 | Input、Select、Upload |
| 对象容器 | subItems | 默认补为对象 | Group、Card、Tabs |
| 数组容器 | columns | 默认补为空数组 | InputList、ListGroup、Table |
| 辅助节点 | 无需 field | 不进入提交模型 | InfoSlot、Buttons |
配置分层
业务语义写在节点顶层,底层组件能力写在 attrs 中:
{
// SuperForm 识别的业务配置
type: 'Input',
field: 'name',
label: '名称',
required: true,
hidden: ({ current }) => current.archived,
span: 12,
// 交给 Ant Design Vue Input 的属性
attrs: {
maxlength: 50,
allowClear: true,
},
}不要把 field、required、span、hidden 等 Schema 能力放进 attrs。反过来,底层组件的 allowClear、maxlength、mode 等属性也应留在 attrs,这样 Schema 层与 UI 组件层的职责清晰。
一份字段,多种页面场景
同一字段定义可以用于表单、表格和详情。exclude 用来声明不适用的场景:
{
type: 'Hidden',
field: 'id',
exclude: ['table', 'description'],
}可用值为:
form:不进入编辑表单。table:不生成表格列。description:不进入详情展示。
需要回显和提交、但不应显示的主键或上下文字段,建议声明为 Hidden,不要只把它留在外部对象中。
何时拆分 Schema
优先维护一份共享字段定义;当不同页面的业务语义已经不同,再按场景拆分。例如列表中的“状态”可能只读并支持筛选,编辑页中的“状态”可能需要权限联动,此时可以共享基础字段后再组合:
const statusField = {
type: "Select",
field: "status",
label: "状态",
options: statusOptions,
};
const editStatus = {
...statusField,
required: true,
disabled: ({ formData }) => !formData.canEditStatus,
};这样保留字段名、选项和值语义的一致性,又不会强行把所有场景塞进大量条件函数。
类型辅助
import { defineDetail, defineForm, defineTable } from "antdv-superform";
const schema = defineForm({
subItems: [{ type: "Input", field: "name", label: "名称" }],
});类型辅助函数只约束输入并改善编辑器提示,不改变运行时结果。动态 Schema 仍可以使用函数或异步函数交给相应组合函数。
下面继续从字段路径和数据绑定两个角度展开模型细节。前者解释 Schema 如何确定数据坐标与结构,后者解释业务对象如何成为当前模型并参与重置、提交和双向同步。
字段与数据路径
Schema 与数据模型是相辅相成的:Schema 决定模型应具备的结构,数据源提供当前业务值;模型变化又会驱动控件、校验、联动和只读展示。理解 field,就理解了整个系统的数据坐标。
field 是模型中的地址
field 表示当前节点在所属模型中的存储路径,支持点路径:
{
type: 'Input',
field: 'profile.name',
label: '姓名',
}即使数据源最初是空对象,组件也会按 Schema 建立中间结构:
const dataSource = {
profile: {
name: undefined,
},
};因此 field 不只是取值表达式,它还参与:
- 建立初始模型结构。
- 生成 Ant Design Vue FormItem 的校验路径。
- 确定
effectData.field和effectData.value。 - 决定
setFieldsValue、resetFields能更新哪些字段。 - 在表格和详情中读取对应单元格内容。
字段路径应保持稳定。不要在一次表单生命周期中动态改变同一节点的 field;业务条件变化应使用 hidden、disabled 或切换整份 Schema。
模型怎样被建立
每个节点会按下面的优先级确定初始值:
initialValue
↓ 未提供
value
↓ 未提供
columns ? [] : subItems ? {} : undefined例如:
const schema = {
subItems: [
{ type: "Input", field: "name", initialValue: "" },
{
type: "Group",
field: "address",
subItems: [{ type: "Input", field: "city" }],
},
{
type: "InputList",
field: "contacts",
columns: [{ type: "Input", field: "mobile" }],
},
],
};对应的标准初始模型为:
{
name: '',
address: {
city: undefined,
},
contacts: [],
}数组和对象初始值建议使用函数,避免多次创建表单时共享同一引用:
{
type: 'InputList',
field: 'contacts',
initialValue: () => [{ name: '', mobile: '' }],
columns: [
{ type: 'Input', field: 'name', label: '联系人' },
{ type: 'Input', field: 'mobile', label: '手机号' },
],
}相对路径与嵌套上下文
进入带 field 的对象容器后,子项路径相对于该对象:
{
type: 'Group',
field: 'receiver',
subItems: [
{ type: 'Input', field: 'name', label: '收件人' },
{ type: 'Input', field: 'mobile', label: '手机号' },
],
}最终路径分别是 receiver.name 和 receiver.mobile。在子字段回调中:
current是receiver对象。formData始终是根表单对象。parent指向上一级响应上下文,而不是简单的数据对象副本。
数组的 columns 同样使用相对路径,每一行都会建立独立字段模型,并提供 index 和 record。详见数组与表格。
一个控件绑定多个字段
有些交互展示为一个控件,但业务模型需要保存多个值。SuperForm 用关联字段显式表达这种关系。
labelField:同时保存值与显示文本
{
type: 'Select',
field: 'departmentId',
labelField: 'departmentName',
label: '部门',
options: departmentOptions,
}选中后模型形态为:
{
departmentId: 12,
departmentName: '研发中心',
}field 保存提交值,labelField 保存显示文本。表格和详情的只读渲染也会优先读取 labelField,这能避免只有 ID 时再次查字典。支持范围、选项归一化和 labelAsValue 的关系见选择输入:通用选项。
endField:把范围拆成两个业务字段
{
type: 'DateRange',
field: 'startDate',
endField: 'endDate',
label: '有效期',
}控件仍接收 [start, end],模型则保存为:
{
startDate: '2026-08-01',
endDate: '2026-08-31',
}回显时组件会重新把两个字段组合成范围值;只读模式显示为“开始值 - 结束值”。不配置 endField 时,范围字段也可以保存为数组或通过 stringifyValue 保存为逗号字符串。详见日期与时间:DateRange 值模式。
vModelFields:扩展额外 v-model
{
type: 'ExtAddressPicker',
field: 'districtCode',
vModelFields: {
provinceCode: 'provinceCode',
cityCode: 'cityCode',
},
}键是扩展组件的 v-model 参数名,值可以是当前对象中的字段名或外部 Ref。适用于一个组件同时更新多个业务字段,具体契约见注册自定义字段:多个 v-model。
无 field 的节点
并非每个节点都要进入模型:
InfoSlot、Buttons等辅助节点通常没有field。- 只有
value: someRef、没有field的输入控件会直接绑定该 Ref,但不会进入表单提交模型。 - 容器可以不设
field,此时只组织布局,子字段仍绑定当前对象。
需要提交或回显但不显示的值,应使用 Hidden 明确声明:
{ type: 'Hidden', field: 'id' }Hidden 不生成可见控件,但会让 id 成为标准模型的一部分,并参与重置和提交。详见展示与辅助:Hidden。
表格列路径
表格列也使用 field 读取记录,支持点路径。未声明 type 的列按只读文本列处理;需要进入 Table 容器的行内编辑或弹窗表单时,列必须声明有效字段类型。
页面级 SuperTable 负责独立数据源、查询和 API 绑定;字段级 Table 数组容器 负责模型内部数组的显示与编辑,两者的数据边界不同。
数据源与双向绑定
SuperForm 的数据模型由两部分共同决定:Schema 给出稳定的结构和标准初始值,dataSource 给出本次业务记录。这样无论新增空记录、编辑不完整记录,还是切换到另一条记录,表单始终知道应有哪些字段以及如何重置。
建模与绑定顺序
内部流程可以概括为:
1. 从空对象开始
2. 按 Schema 建立完整字段结构
3. 克隆为“标准初始模型”
4. 读取并绑定 dataSource
5. 按 Schema 补齐 dataSource 缺失字段const record = ref({ id: 1, name: "张三" });
const [register, form] = useForm({
dataSource: record,
subItems: [
{ type: "Hidden", field: "id" },
{ type: "Input", field: "name", label: "姓名", initialValue: "" },
{ type: "Switch", field: "enabled", label: "启用" },
],
});绑定后 record.value 会具备:
{
id: 1,
name: '张三',
enabled: undefined,
}也就是说,传入对象不是只读快照,而是当前表单模型本身;用户输入和 Schema 补齐都会反映到该对象。若业务需要保留原始记录,应在传入前自行克隆。
对象与 Ref 的差异
dataSource 可以是普通对象或 Ref:
// 固定绑定一个响应式对象
dataSource: reactive({ name: "" });
// 支持整体切换记录
dataSource: currentRecord;Ref 会被整体解包。当 currentRecord.value 指向新对象时,SuperForm 清除当前校验状态并切换模型,随后按 Schema 补齐新对象缺失的字段:
currentRecord.value = { id: 2, name: "李四" };这适合弹窗复用同一个表单编辑多条记录。useForm 只接收 Schema;不要使用旧式 useForm(schema, record),外部对象统一通过 Schema 的 dataSource 绑定。
标准初始模型与当前模型
两者用途不同:
| 模型 | 来源 | 用途 |
|---|---|---|
| 标准初始模型 | Schema 的 initialValue / value / 结构默认值 | 无参数重置、缺省值回退 |
| 当前模型 | 当前 dataSource 或内部对象 | 输入绑定、联动、提交 |
例如编辑记录中 name 为“张三”,但 Schema 的 initialValue 为 '';调用无参数 resetFields() 后,字段恢复为 '',不是恢复到第一次传入的“张三”。需要把一条记录作为重置目标时,应显式传入:
form.resetFields(recordSnapshot);数据动作的边界
| 动作 | 语义 | 是否增加模型外字段 |
|---|---|---|
getData() / dataSource | 读取当前绑定模型 | 不适用 |
setFieldsValue(partial) | 只更新已建立且本次提供的字段 | 否 |
resetFields() | 按已建立字段恢复标准初始值 | 否 |
resetFields(record) | 按已建立字段从记录回填,缺项回退初始值 | 否 |
submit() | 校验后返回当前模型深拷贝 | 否 |
form.setFieldsValue({
name: "王五",
unknown: 123, // Schema 模型中没有该字段,不会被加入
});对象会按已建立结构递归更新,数组和新对象会深拷贝后替换,避免直接复用传入集合引用。setFieldsValue 只处理参数中实际出现的字段;未提供的字段保持不变。
需要提交 id、版本号等不可见字段时,请用 Hidden 把它们加入 Schema 模型,而不是依赖动作保留任意外部属性。详见字段与数据路径:无 field 的节点。
字段级 Ref
节点的 value 可以直接绑定外部 Ref。
同时配置 field
const keyword = ref('')
{
type: 'Input',
field: 'keyword',
value: keyword,
label: '关键词',
}此时存在双向同步:
输入控件 ↔ 表单模型 keyword ↔ 外部 Ref字段会进入校验、重置和提交模型。
只有 value,没有 field
{
type: 'Input',
value: keyword,
}控件直接更新 keyword.value,但它没有模型路径,不进入表单提交数据。适合临时筛选器或只服务于页面交互的控件。
复合值的双向转换
有些字段在控件值和业务模型之间存在转换层。
范围拆分
{
type: 'DateRange',
field: 'startDate',
endField: 'endDate',
}控件使用 [startDate, endDate],模型保存两个字段。任一模型字段在外部变化时,控件范围都会重新同步。详见DateRange 值模式。
数组与逗号字符串
{
type: 'Select',
field: 'roleIds',
stringifyValue: true,
attrs: { mode: 'multiple' },
}控件使用数组,模型保存逗号字符串:
['admin', 'editor'] ↔ 'admin,editor'选项标签同步
{
type: 'Select',
field: 'departmentId',
labelField: 'departmentName',
options: departments,
}一次选择同时更新值字段与文本字段;完整语义见选择输入:通用选项。
扩展组件的多个 v-model
自定义字段可以通过 vModelFields 将额外 v-model 映射到同级字段或 Ref,见注册自定义字段。
提交不是重新组装任意对象
submit() 的结果是当前标准模型的深拷贝。这个约束使提交字段可由 Schema 审核,也保证重置、校验和提交围绕同一套路径工作。若后端参数结构不同,建议在 API 层显式转换,相关约定见接口与数据适配。