表单开发
import { RenderFieldPresenter, RenderFormPanel, RenderNumberInput, RenderTextBox,} from 'ds-ui'
表单是业务系统中最常见的页面形态。DirectSurface UI 推荐用 RenderFormPanel 管理字段排列,用 RenderFieldPresenter 管理标签、必填、单位、校验状态和提示信息。
如果你正在搭完整业务页面,先看 常用业务场景组合。本页重点说明表单本身如何组织、绑定和校验。
表单结构
RenderFormPanel
RenderFieldPresenter
RenderTextBox / RenderComboBox / RenderDatePicker
RenderFormPanel 负责字段网格,RenderFieldPresenter 负责单个字段的标签和状态,具体输入组件只负责值编辑。
组件选择
| 字段类型 | 推荐组件 | 说明 |
|---|---|---|
| 单行文本 | TextField | 公共类名是 RenderTextBox。适合姓名、编码、关键字。 |
| 多行文本 | TextArea | 适合备注、批注、说明。 |
| 数字 | NumberInput | 适合年龄、数量、金额、比例。 |
| 日期时间 | DatePicker | 适合日期、时间、日期时间字段。 |
| 单选枚举 | Dropdown | 公共类名是 RenderComboBox。值必须来自候选项。 |
| 多选枚举 | MultiSelectDropdown | 适合标签、诊断集合、权限集合。 |
| 树选择 | DropTreeEdit | 适合科室、分类、层级字典。 |
| 表格选择 | LookupEdit | 适合需要多列候选信息的选择。 |
最小示例
const form = new RenderFormPanel({ columns: 2, labelWidth: 88, adaptive: true, columnGap: 16, rowGap: 8,}) form.addField(new RenderFieldPresenter({ label: '姓名', required: true, child: new RenderTextBox({ placeholder: '请输入姓名' }),})) form.addField(new RenderFieldPresenter({ label: '年龄', unit: '岁', child: new RenderNumberInput({ value: 0 }),}))
字段值管理
表单组件只负责交互和值变化通知,不内置业务表单模型。页面层通常维护一个 draft 对象:
const draft = { name: '', age: 0,}const form = new RenderFormPanel({ columns: 2 }) form.addField(new RenderFieldPresenter({ label: '姓名', required: true, child: new RenderTextBox({ value: draft.name, onChange: value => { draft.name = value }, }),}))
推荐规则:
- 输入组件的
value是 UI 当前值。 - 页面 draft 是提交前的业务草稿。
- 服务层 DTO 在提交时由 draft 转换出来。
- 不要把后端原始对象直接作为多个输入组件共享的可变对象,避免取消编辑时难以回滚。
校验状态
字段校验应该落在字段展示层,而不是只改变输入组件边框。
const nameField = new RenderFieldPresenter({ label: '姓名', required: true, status: 'error', message: '姓名不能为空', child: new RenderTextBox({ placeholder: '请输入姓名' }),})
这样标签、提示、输入控件可以形成一个完整的校验区域。
校验可以分两类:
| 类型 | 时机 | 展示 |
|---|---|---|
| 即时校验 | 输入、失焦、选择值变化 | 更新 RenderFieldPresenter 的 status 和 message。 |
| 提交校验 | 点击保存或提交命令 | 汇总所有字段错误,并把第一个错误滚动到可视区域。 |
异步校验要和页面生命周期绑定。页面关闭后返回的异步结果不应继续修改已经释放的字段。
表单提交
表单提交建议分三层:
- UI 层读取输入控件值。
- 页面层执行校验、组装请求对象。
- 服务层提交数据并返回结果。
按钮、菜单、快捷键应尽量绑定页面命令,不要在每个按钮里重复写权限和保存逻辑。
let saving = falseconst saveCommand = { id: 'form.save', title: '保存', canExecute: () => !saving, execute: () => { // 校验 draft,然后提交服务 },}
命令状态变化后调用 commandManager.invalidate()。详见 命令与权限。
响应式规则
- 桌面宽度下可以使用 2 到 4 列。
- 窄宽度下使用
adaptive: true降低列数。 - 字段阅读顺序必须稳定,不能因为响应式导致业务语义混乱。
- 超长标签优先省略号加 tooltip,超长值根据业务决定省略或换行。
使用建议
- 简单字段用
RenderFormPanel。 - 大量动态字段可以先分组,再在组内使用 FormPanel。
- 表格内编辑不要嵌完整表单,应使用 GridView 的单元格编辑器。
- 表单页关闭时清理未完成的异步校验和页面级上下文。
典型组合
查询表单
查询表单通常短而密集,适合放在页面顶部:
PageHeader
RenderFormPanel 查询条件
CommandToolbar 查询 / 重置 / 导出
GridView
查询条件只作为页面 draft。点击查询后再请求数据,不建议每个字段变化都立即刷新大表格。
编辑表单
编辑表单通常需要分组和滚动:
ScrollViewer
Section 基本信息
RenderFormPanel
Section 联系方式
RenderFormPanel
Section 备注
TextArea
Footer CommandToolbar
保存、取消、提交、审核等操作应放在固定操作区或页面命令栏,避免用户滚动到表单底部才看到关键动作。
弹窗表单
弹窗表单适合短字段和临时编辑:
Modal / Popover
RenderFormPanel
Confirm / Cancel
弹窗打开时复制 draft,取消时丢弃,确定后把结果交给打开源页面合并。
常见错误
只改输入框边框表示错误
这样标签、必填标识、错误文案和输入框状态会割裂。应把错误状态放到 RenderFieldPresenter。
在表格单元格里嵌完整表单
表格编辑应使用 GridView 的编辑能力或单元格 popup 编辑器。完整表单适合页面、抽屉或弹窗。
把页面草稿放到应用级上下文
页面草稿应放在页面对象、页面 controller 或页面级 App Context 中。放到应用级上下文会导致 tab 间串数据和关闭后内存不释放。