模板结构
表单模板是一份 FormDocument,纯 JSON,不含函数,也不含任何会被执行的代码。它可以存进数据库、写成文件、在网络上传输,导入时只做结构检查。
文档
| 字段 | 内容 |
|---|---|
schemaVersion | 结构版本,当前是 1(FORM_SCHEMA_VERSION) |
settings | 表单设置:标签位置、尺寸、整表禁用与只读,见表单设置 |
children | 顶层节点,按顺序排列 |
extensions | 可省,各扩展放在文档上的数据,见扩展数据 |
每个类型的完整字段见模板类型。
节点
节点分三类,type 区分:
type | 是什么 | 有值吗 |
|---|---|---|
field | 字段:文本、数字、选择、日期、上传等 | 有,存在表单数据的 key 下 |
container | 容器:栅格、卡片、标签页等布局,以及子表单、子表两种列表 | 布局容器没有;列表容器的值是行的数组 |
static | 静态元素:分隔线、段落、标题、提示、图片、条码、二维码 | 没有 |
每个节点都有:
id:在整份文档里唯一。设计器的选择、问题的定位、联动的实例都按它找节点。kind:种类,如text、grid、divider,决定渲染成哪个元素、能写哪些属性。- 三个属性包
basic、data、appearance:基础(标签、提示、初始值、隐藏与禁用)、数据(选项、精度、数据来源)与外观(尺寸、前后缀)。只写用到的属性,各种类的属性与取值见种类与属性。 linkage:联动规则;字段与容器还有校验规则validation。规则的写法见校验与联动。
容器另有区域 regions:一个区域是放子节点的一格,带自己的属性 props,如栅格一列的跨度、标签页一页的标题。
字段与表单数据
字段的数据键 key 是它的值在表单数据里的键:ASCII 字母或 _ 开头,后接字母、数字或 _,在所在作用域里不能重复;$ 开头的名字留给表达式里的变量。
值的形状随种类:文本是字符串,数字是数值,开关是布尔,多选组是字符串数组。打开某些属性会改变形状,如下拉打开「多选」后值是字符串数组。每个种类的值形状与空值见种类与属性。
渲染时字段的初始值取 basic.initialValue,没写就是种类的空值;交给渲染器的 values 叠在上面。
布局容器对数据透明:放在栅格、卡片、标签页里的字段,值仍在表单数据的顶层。列表容器(子表单、子表)有自己的数据键,值是行对象的数组,行里字段的键相对于行对象。下面这张出差申请的数据长这样:
export const values = {
applicant: '张三',
department: 'rd',
companions: [{ name: '李四', phone: '13800000001' }],
segments: [{ from: '杭州', to: '北京', by: 'train' }],
nights: 3,
}布局容器
| 种类 | 用法 |
|---|---|
栅格 grid | 按 12 列排版,每个区域是一格,props.span 是这一格占几列,排满一行自动换到下一行 |
卡片 card | 带标题 basic.title 的一块面板,只有一个区域 |
分组 fieldset | 带分组标题 basic.legend 的一组,只有一个区域 |
标签页 tabs | 每个区域是一页,页签文字是区域的 props.title |
折叠面板 collapse | 每个区域是一项,标题是 props.title,props.expanded 决定初始是否展开 |
表格布局 table | appearance.rows × appearance.columns 的格子,每个区域用 props.row、props.column、props.rowSpan、props.columnSpan 占一块;全部区域要恰好铺满表格,不能越界、重叠或留空 |
容器可以嵌套。卡片里放一个两列的栅格:
{
type: 'container',
id: 'c-basic',
kind: 'card',
basic: { title: '基本信息' },
data: {},
appearance: {},
validation: [],
linkage: [],
regions: [{
id: 'c-basic-body',
props: {},
children: [{
type: 'container',
id: 'c-basic-grid',
kind: 'grid',
basic: {},
data: {},
appearance: {},
validation: [],
linkage: [],
regions: [
{
id: 'c-basic-left',
props: { span: 6 },
children: [{
type: 'field',
id: 'f-applicant',
kind: 'text',
key: 'applicant',
basic: { label: '申请人' },
data: {},
appearance: {},
validation: [{ id: 'r-applicant', kind: 'required' }],
linkage: [],
}],
},
{
id: 'c-basic-right',
props: { span: 6 },
children: [{
type: 'field',
id: 'f-department',
kind: 'select',
key: 'department',
basic: { label: '部门' },
data: { options: [{ value: 'rd', label: '研发部' }, { value: 'sales', label: '销售部' }, { value: 'finance', label: '财务部' }] },
appearance: {},
validation: [{ id: 'r-department', kind: 'required' }],
linkage: [],
}],
},
],
}],
}],
},标签页的每个区域是一页:
{
type: 'container',
id: 'c-more',
kind: 'tabs',
basic: {},
data: {},
appearance: {},
validation: [],
linkage: [],
regions: [
{
id: 'c-more-hotel',
props: { title: '住宿' },
children: [{ type: 'field', id: 'f-nights', kind: 'number', key: 'nights', basic: { label: '住几晚' }, data: { precision: 0 }, appearance: {}, validation: [{ id: 'r-nights', kind: 'range', min: 0, max: 30 }], linkage: [] }],
},
{
id: 'c-more-note',
props: { title: '备注' },
children: [{ type: 'field', id: 'f-note', kind: 'textarea', key: 'note', basic: { label: '备注' }, data: {}, appearance: {}, validation: [], linkage: [] }],
},
],
},列表
子表单与子表让用户按行录入,行可以新增、删除,data.movable 不为 false 时还能上下换序;data.initialRows 是没有数据时先给几行。
- 子表单
subform:每一行照着区域里的节点排一遍,区域里能放任意节点,包括栅格与另一个列表。 - 子表
subtable:按表格排,区域里只能放字段,一个字段是一列,列头取字段的标签。字段的外观columnMinWidth(像素)是这一列的最小宽度,金额这类值长的列给它,免得被截;各列下限加起来超过子表宽时,表格区横向滚动,行号与把手留在两侧。这一项只在字段放在子表里时生效,属性面板也只在那时列出它。
子表的区域里放两个字段,就是两列:
{
type: 'container',
id: 'c-companions',
kind: 'subtable',
key: 'companions',
basic: { title: '同行人员' },
data: { movable: false },
appearance: {},
validation: [],
linkage: [],
regions: [{
id: 'c-companions-row',
props: {},
children: [
{
type: 'field',
id: 'f-companion-name',
kind: 'text',
key: 'name',
basic: { label: '姓名' },
data: {},
appearance: {},
validation: [{ id: 'r-companion-name', kind: 'required' }],
linkage: [],
},
{
type: 'field',
id: 'f-companion-phone',
kind: 'text',
key: 'phone',
basic: { label: '手机号' },
data: { inputType: 'tel' },
appearance: {},
validation: [],
linkage: [],
},
],
}],
},列表容器自己也能写校验:必填表示至少一行,长度按行数计。
静态元素
静态元素只展示、没有值:分隔线 divider、段落 paragraph、标题 heading、提示 alert、图片 image、条码 barcode 与二维码 qrcode。它们只有联动,没有校验,联动也只有显示与隐藏。
{
type: 'static',
id: 's-title',
kind: 'heading',
basic: { content: '出差申请' },
data: {},
appearance: { level: 3 },
linkage: [],
},表单设置
settings 管整张表单:
| 属性 | 取值 |
|---|---|
labelPlacement | 标签在控件上方 top(缺省)还是左侧 start |
size | 控件尺寸 sm、md、lg;字段自己写了 appearance.size 时以字段的为准 |
disabled | 整表禁用,不能提交 |
readOnly | 整表只读,仍可提交 |
扩展数据
文档与每个节点都可以带 extensions:扩展 id → 这个扩展自己的 JSON 对象。它只给扩展用,与业务属性分开,设计器编辑与复制粘贴时原样带着走。登记扩展的写法见扩展。
用代码拼模板
不经设计器、在代码里拼模板时,用 createNode 按种类新建节点:它会写好属性的缺省值,容器还会带上缺省的区域。拼好后用 validateDocument 自检:
const { registry } = createFormModel()
const ids = createIdAllocator('n')
const name = createNode(registry, 'text', ids, { key: 'name' }) as FieldNode
const document: FormDocument = {
...createDocument(),
children: [
{ ...name, basic: { ...name.basic, label: '姓名' }, validation: [{ id: ids.next(), kind: 'required' }] },
],
}
const issues = validateDocument(document)validateDocument 返回问题列表,空数组表示没有问题。问题码见问题码。
示例
出差申请:卡片里的栅格、子表、子表单与标签页。改一个值,下方显示整份表单数据。
