Form 控件介绍
Form 用于收集、校验和提交一组相关字段。它适合申请、登记、设置、编辑详情和流程步骤,不只是把多个输入框排在一起:Form 还统一管理字段分组、布局、校验状态和提交范围。
阅读本手册后,你可以完成以下配置:
- 用 Form Section 组织较长的业务表单
- 配置每行控件数量以及标签和内容的栅格比例
- 让说明类字段独占整行并保持标签对齐
- 使用必填、长度、格式和数字范围校验
- 配置 Reset、Validate 和 Submit 三类操作
- 只提交当前 Form 的字段,而不是整个页面状态
- 在 Java 后端识别 DEV / TEST / PROD 和设计预览 / 运行态
- 配置 Create / Edit / View、条件字段、远程校验和后端字段错误
- 使用可重复分组、草稿恢复、差量提交和 Schema 生成
1. Form 能力与配置项
Form 的配置分为四个层级:
- Form:管理整个表单的布局、模式、数据加载、提交、草稿和公共状态。
- Form Section:按业务主题组织字段,也可以配置为可重复的数据集合。
- Field:配置标签、Field Key、默认值、占行方式、条件行为和校验规则。
- Button Set / Interaction:通过 Reset、Validate、Submit 和组件函数编排用户操作。
1.1 属性与功能速览
| 配置区域 | 主要属性或功能 | 解决的问题 |
|---|---|---|
| 基本结构 | ID / Name、Form Sections、Fields、Button Set | 定义表单边界、业务分组和提交范围 |
| 布局 | Controls Per Row、Form column split、Layout、Label Alignment、Full row | 控制每行字段数量、标签宽度、排列方式和整行字段 |
| 模式 | Create、Edit、View、Warn about unsaved changes | 区分新建、编辑、只读查看及未保存提醒 |
| 数据适配器 | Load / Create / Update URL、Response Data Path、Record ID、Revision | 加载记录并连接新增、修改接口和乐观锁 |
| 提交 | Full values、Changed values only、Custom URL | 决定提交完整表单还是仅提交变化字段 |
| 字段校验 | Required、Length、Pattern、Number、Custom Validator、Remote validation | 提供即时校验、远程校验和后端字段错误定位 |
| 条件字段 | Visible / Disabled / Required When、Dependencies、Clear When Hidden | 根据其他字段、Page Context 或当前模式动态调整字段 |
| 可重复分组 | Collection Key、Minimum / Maximum Items、Add / Delete | 编辑联系人、明细行等对象数组 |
| 草稿 | Browser / Remote storage、Draft Key、Autosave Delay、revision | 自动保存、恢复或放弃未完成内容 |
| 操作与状态 | reset、validate、submit、saveDraft、restoreDraft、discardDraft;forms.* | 编排按钮和流程,并读取 valid、dirty、submitting 等状态 |
| 字段生成 | Business Object、JSON Schema、OpenAPI | 从已有模型快速生成可继续编辑的字段结构 |
1.2 表单结构与提交边界
Form 是字段状态和提交范围的边界。一个页面可以放置多个 Form,每个 Form 只管理自己的 Section、Field 和 Button Set:
- Form Section 用于表达“基本信息、权限、附件”等业务分组,不只是视觉间距。
- Field Key 是字段在 Form 状态和提交 JSON 中的稳定名称,应与后端 DTO 属性保持一致。
- Button Set 负责摆放表单操作;按钮是否执行 Reset、Validate 或 Submit,仍由 Interaction 配置决定。
- Submit Form 只收集目标 Form 已注册的字段,不会自动混入其他 Form、Page Context 或 Tree Current Record。
因此,页面上存在“查询条件”和“编辑详情”两个 Form 时,应分别设置 ID,并让各自按钮明确指向对应 Form。不要依赖字段在页面中的视觉位置推断它属于哪个提交范围。
1.3 布局与响应式
Form 使用一个共享布局规则控制全部普通字段:
- Controls Per Row 决定一行最多放置几个字段,也决定每个字段可用的栅格宽度。
- Form column split 在单个字段区域内分配 Label 和 Content。每行两个控件时两者合计为 12;每行一个控件时合计为 24。
- Layout 可选择 Horizontal、Vertical 或 Inline;Label Alignment 只控制 Horizontal 标签文字的对齐。
- 字段的 Full row / Start on new row 让长文本或说明字段独占整行,但标签仍与普通字段的第一列对齐。
- 窄屏时字段按阅读顺序堆叠,不需要复制一套移动端 Form。
Form 的布局配置控制结构,字段自身只声明是否换行或跨行。这样调整 Controls Per Row 时,普通字段和整行字段会一起重新计算,而不是分别维护互相冲突的宽度。
1.4 Configure Form capabilities 对话框
选择 Form,在属性面板点击 Configure Form capabilities,会打开一个集中配置 Form 运行态契约的对话框。它把配置按四个页签组织,从上到下就是配置一张业务表单的推荐顺序:
| 页签 | 解决什么 | 何时配置 | 详见 |
|---|---|---|---|
| Lifecycle | 选择 Create / Edit / View 模式,并开启未保存提醒 | 每个 Form 都要先定模式 | 第 1.4、10 节 |
| Data | 配置 Data adapter:加载、创建、更新记录的接口与数据格式 | 需要"加载记录 + 保存"完整生命周期时 | 第 14 节 |
| Draft | 自动保存/恢复尚未提交的内容 | 长表单、跨设备或重要流程 | 第 15 节 |
| Generate fields | 从 Business Object / JSON Schema / OpenAPI 生成字段 | 从已有模型快速搭表单 | 第 16 节 |
Lifecycle 页签用同一份表单结构覆盖三种生命周期:
| 模式 | 页面行为 | 常见接口 |
|---|---|---|
| Create | 使用默认值建立新记录 | Create URL,通常使用 POST |
| Edit | 先从 Load URL 加载记录,再提交修改 | Load URL + Update URL,通常使用 PUT / PATCH |
| View | 复用标签和值布局,只读展示 | Load URL,不显示可编辑操作 |
开启 Warn about unsaved changes 后,只有相对基线发生变化时才提示离开确认。
Custom URL 还是 Data adapter?
这是最容易混淆的一点:Data adapter 不是"提交 URL",而是一整套"读 + 写 + 版本控制"的数据接口,和模式配合使用。两者按复杂度二选一:
- 一次性简单提交(只新建、不需要回填)——直接用 Submit 按钮的 Custom URL 即可,无需配置 Data adapter。
- 完整生命周期(打开时先加载、区分新建/修改、冲突检测、草稿恢复)——在 Data 页签配置 Data adapter。
Data adapter 的字段各管一件事:
| 字段 | 适用模式 | 作用 |
|---|---|---|
| Load URL | Edit / View | 打开页面时 GET 加载记录,回填并作为基线 |
| Create URL | Create | 新建时 POST 提交 |
| Update URL | Edit | 修改时 PUT / PATCH 提交 |
| Response Data Path | 加载时 | 从响应 JSON 中取记录的位置,如 data = response.data |
| Record ID Field | 加载 / 提交 | 记录主键字段名(如 id) |
| Revision Field | 提交 | 乐观锁版本;冲突时后端返回 409,避免覆盖别人的修改 |
| Submission Payload | 提交 | 提交全量 values,还是仅提交变化字段(Changed values only) |
三个 URL 都支持运行态变量:直接写 [[context.params.id]]、[[state.id]]、[[record.id]] 等,或点输入框右侧的 ƒx 按钮从页面变量列表插入,避免把 ID 写死。加载完成后的值会成为 Form 基线:Reset 恢复这个基线,Changed values only 也以它计算差量。
1.5 校验、字段错误与条件行为
Form 把校验分成三个层次:
- 本地规则:Required、Length、Pattern、Number 和 Custom Validator 提供即时反馈。
- 远程校验:Remote validation 适合用户名、邮箱、业务编码等需要请求后端判断的字段;可配置触发时机和 Debounce。
- 提交校验:后端返回顶层
fieldErrors,Form 按 Field Key 把消息写回具体字段,同时保留整体displayText。
字段还可以配置 Visible When、Disabled When 和 Required When。表达式读取 state.*、context.*、record.* 或 form.*;Dependencies 明确声明哪些 Field Key 变化时需要重新计算。启用 Clear When Hidden 后,字段隐藏时会清除旧值,避免不可见数据继续提交。
前端校验用于交互反馈,不能替代后端校验、权限和审计。条件隐藏也不是安全控制;敏感字段是否可读写仍应由后端决定。
1.6 可重复分组与草稿
Form Section 可以变成可重复分组。Section 内的字段作为一行模板,Collection Key 对应提交 JSON 中的对象数组;Minimum / Maximum Items 约束行数,运行态提供 Add / Delete。每行校验错误会保留数组索引,例如 emergencyContacts[0].phone,便于定位具体记录。
草稿用于保存“尚未提交的 Form 状态”,它与正式业务记录不同:
- Browser storage 适合个人、单设备的短期录入。
- Remote service 适合跨设备或重要流程,需要配置 Load / Save URL、Draft Key 和 revision。
- Autosave Delay 控制停止输入多久后保存;远端 revision 冲突必须提示用户恢复或重新加载,不能静默覆盖。
1.7 操作函数与公共状态
Form 的常用操作可以放在按钮、Step 或其他 Interaction 动作链中:
| 操作 | 行为 |
|---|---|
reset | 恢复加载完成后的基线或创建模式默认值,并清除校验消息 |
validate | 校验当前 Form;失败时停止后续动作 |
submit | 校验、组装 payload、发送请求并处理后端字段错误 |
saveDraft / restoreDraft / discardDraft | 保存、恢复或放弃草稿 |
页面表达式可以读取 forms.<formId>.valid、dirty、submitting、submitError、lastSubmit 和 mode。例如,Submit 按钮可以在 submitting 时禁用;Step 的 Next 可以先 Submit Form,成功后再执行 nextStep。这些状态由 Form 维护,不应由页面动作直接改写。
1.8 字段生成与后端协作
Generate fields 可以从 Business Object、JSON Schema 或 OpenAPI Schema 生成 Section 和 Field。生成结果是普通页面元数据,设计人员仍可修改标签、控件类型、选项、布局和校验;生成后必须检查敏感字段、枚举文案、权限和接口配置。
Form 的数据加载、远程校验、草稿和提交请求都会由平台统一携带:
X-Spark-Page-Environment: DEV | TEST | PROD
X-Spark-Page-Render-Mode: DESIGN_PREVIEW | RUNTIME
后端可以在 DESIGN_PREVIEW 返回稳定的调试数据或进入沙箱,在 RUNTIME 执行真实业务流程。环境和渲染模式只用于选择调试策略,不能代替认证、租户隔离和业务授权。
1.9 如何选择配置
| 目标 | 建议起点 | 继续阅读 |
|---|---|---|
| 普通新增表单 | Create + Sections + 本地校验 + Custom URL | 第 2~8 节基础示例 |
| 编辑已有记录 | Edit + Load / Update URL + Record ID / Revision | 第 9、10、14 节 |
| 只读详情 | View + Load URL | 第 10、18 节 |
| 动态显示字段 | Conditions + Dependencies | 第 11、12 节 |
| 联系人或明细行 | Repeatable Group | 第 13 节 |
| 长时间录入 | Draft + Warn about unsaved changes | 第 15 节 |
| 从已有模型建表单 | Generate fields | 第 16 节 |
| 流程步骤中的表单 | Validate / Submit → Step nextStep | 第 7、18 节 |
1.10 Form 中可以使用的字段控件
先根据数据形状选择字段,再配置布局、校验和 Interaction。下面列出当前组件面板中可直接添加到 Form 的 22 种字段;已隐藏的旧版 Select、MultiSelect 和旧 MultiRowInput 只用于兼容已有页面,不作为新页面的配置入口。
| 分类 | 字段控件 | 适合的数据 |
|---|---|---|
| 基础输入 | Input、Address、Text Area | 单行文字、地址和长文本 |
| 选择与人员 | Select、Radio Button、Checkbox、Switch、Cascade Select、Tree Select、Transfer、User Select、Department Select | 枚举、层级、集合、用户和部门 ID |
| 日期与数值 | Date Picker、Time Picker、Calendar、Rate、Slider | 日期时间、评分、单值或数值范围 |
| 编辑器 | Rich Text、Code Editor、JSON Editor | 带格式正文、脚本和 JSON |
| 文件与结构化数据 | Upload、Multi-Row Input | 文件元数据和对象数组 |
每个字段页面分别说明适用场景、主要属性、提交值形状、条件与 Interaction。选择控件时以准备提交给后端的数据契约为准,不要只按外观选择。
2. 示例:员工入职申请
Spark Sample 的 Form 页面演示一份员工入职申请。它包含两个分组、两列布局、一个整行说明字段,以及重置、校验和提交按钮。
页面结构如下:
Screen
└── Form: Employee Onboarding Form
├── Form Section: Employee details
│ ├── Field: Full name
│ ├── Field: Work email
│ ├── Select: Department
│ ├── RadioBox: Employment type
│ └── DatePicker: Start date
├── Form Section: Equipment and access
│ ├── Field: Laptop budget
│ ├── Switch: Administrator access
│ └── TextArea: Request justification
└── Button Set
├── Reset
├── Validate
└── Submit request
示例使用有代表性的初始值,打开页面后即可观察布局与提交行为。真正的业务页面可以清空初始值,或在进入编辑页面时由数据源填充。
3. 添加 Form 与分组
- 打开目标页面并进入 Page Designer。
- 把 Form 添加到 Screen、Panel、Grid Column、Tab、Step 或 Content 中。
- 选择 Form,在 Form Sections 子元素区域添加一个或多个 Section。
- 选择 Section,在 Fields 区域添加 Field、Select、RadioBox、DatePicker、Switch、TextArea 等字段。
- 选择 Form 的 Button Set,添加提交和辅助操作按钮。
- 为 Form 和字段设置稳定的 ID / Name,保存并检查设计画布和运行态。
分组适合表达业务含义,不建议只为了留白而创建空分组。一个短表单可以只用一个 Section;较长表单可按“基本信息、权限、附件、确认”等主题拆分。
4. 配置布局
4.1 Controls Per Row
Controls Per Row 决定 Form 内一行最多放几个普通字段:
| 值 | 适用场景 |
|---|---|
1 | 长文本、移动端优先、字段说明较多 |
2 | 常规 PC 业务表单,推荐起点 |
3 或 4 | 筛选表单或字段很短的紧凑区域 |
窄屏下字段会按顺序堆叠,不需要为手机另建一份 Form。
4.2 Form column split
Form 内部仍使用 24 列栅格,但比例按“单个字段所占区域”计算。Controls Per Row 为 2 时,每个普通字段占 12/24;因此 Label 和 Content 应合计为 12,例如:
Label 4 + Content 8 = 12
Controls Per Row 为 1 时,一个字段占 24,可以使用 6 + 18、7 + 17 等组合。拖动 Form column split 刻度尺时,Label 和 Content 会同步变化,不需要分别填写两个数字。
4.3 独占整行
选择 TextArea 等需要更大空间的字段,启用 Start on new row / Full row。整行字段的标签仍使用当前 Form 的 Label 比例,与上一行第一个标签对齐;内容区域自动跨越剩余列。
不要为了独占整行把 Controls Per Row 临时改成 1,这会同时改变其他字段。
4.4 Layout 与 Alignment
| 属性 | 说明 |
|---|---|
| Horizontal | 标签和控件在同一行,适合 PC 业务表单 |
| Vertical | 标签位于控件上方,适合窄容器和长标签 |
| Inline | 字段和按钮连续排列在同一行,适合搜索条件,不适合长申请表 |
| Label Alignment | 只影响 Horizontal 下标签文字的左/右对齐 |
5. 按示例配置字段
| 字段 | 控件 | 关键配置 |
|---|---|---|
| Full name | Field | Required;Length 2–60 |
| Work email | Field | Required;Pattern ^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$ |
| Department | Select | Engineering、Product、Sales、Operations |
| Employment type | RadioBox | Full time、Contractor、Intern |
| Start date | DatePicker | Format YYYY-MM-DD |
| Laptop budget | Number Field | Required;Number range 500–5000;Suffix USD |
| Administrator access | Switch | Default Checked 关闭 |
| Request justification | TextArea | Full row;Initial Height 4;Maximum Height 8;Length 20–500 |
Name / Field Key 是提交 JSON 的属性名。示例最终提交:
{
"fullName": "Avery Chen",
"workEmail": "avery.chen@example.com",
"department": "ENGINEERING",
"employmentType": "FULL_TIME",
"startDate": "2026-09-01",
"laptopBudget": 1800,
"adminAccess": false,
"justification": "Prepare the standard engineering workstation before the employee starts."
}
字段名应与后端 DTO 一致。不要让两个可编辑字段使用同一个 Field Key,否则后写入的值会覆盖前一个。
6. 配置校验
Form 在提交前会校验当前 Form 的字段。常用规则包括:
- Required:值不能为空。
- Length:字符串最短和最长长度。
- Pattern:用正则表达式检查邮箱、编码等格式。
- Number:数字最小值和最大值。
- Custom Validator:表达式无法覆盖的业务规则。
校验失败时,错误显示在对应控件附近,提交动作停止;不要只用 Toast 告诉用户“表单错误”,却不标出具体字段。
前端校验用于即时反馈,后端仍必须保留等价校验。浏览器请求可以绕过,不能把 Required 或 Pattern 当成安全边界。
7. 配置 Reset、Validate 和 Submit
7.1 Reset
在 Reset 按钮的 onClick 中添加 Call Component Function:
Component: employee_onboarding_form
Function: reset
Parameters: none
Reset 把字段恢复到初始值,并清除校验提示。它不是把所有字段强制清空。
7.2 Validate
在 Validate 按钮的 onClick 中添加 Validate Form,Form 选择 employee_onboarding_form。校验成功后可以继续显示成功提示或执行下一步;失败时后续动作不会执行。
7.3 Submit
在 Submit 按钮的 onClick 中添加 Submit Form:
Form: employee_onboarding_form
Submission Mode: Custom URL
URL: /spark/api/sample/form/requests
Method: POST
提交顺序为:
validate current Form
↓
collect current Form fields only
↓
POST JSON to Custom URL
↓
show backend displayText or configured success action
页面中即使存在另一个 Form、Tree Current Record 或 Page Context,Submit 也只发送选中 Form 已注册的字段,不会把整页状态混入请求体。
8. Java 后端示例
Controller 使用共享请求头常量解析环境和渲染模式,并统一返回 ApiResult<DTO>:
@RestController
@RequestMapping("/spark/api/sample/form/requests")
public class FormRequestController {
private final FormRequestService requestService;
public FormRequestController(FormRequestService requestService) {
this.requestService = requestService;
}
@PostMapping
public ApiResult<FormRequestReceiptDTO> submit(
@RequestBody FormRequestSubmission request,
@RequestHeader(value = "Accept-Language", required = false)
String language,
@RequestHeader(value = SparkPageRequestHeaders.ENVIRONMENT,
required = false) String environment,
@RequestHeader(value = SparkPageRequestHeaders.RENDER_MODE,
required = false) String renderMode) {
Locale locale = SparkApiResultHelper.resolveLocale(language);
SparkPageRequestContext context =
SparkPageRequestHeaders.resolve(environment, renderMode);
return SparkApiResultHelper.success(
locale,
requestService.submit(request, context),
"SPARK-SAMPLE-FORM-0001");
}
}
Service 可以在设计预览中返回可识别但不落库的回执,在运行态执行真实业务流程:
public FormRequestReceiptDTO submit(
FormRequestSubmission request,
SparkPageRequestContext context) {
validate(request); // 后端等价校验
boolean preview = context.getRenderMode()
== SparkPageRenderMode.DESIGN_PREVIEW;
if (preview) {
return previewReceipt(request, context);
}
return onboardingUseCase.submit(request, context.getEnvironment());
}
平台自动携带:
X-Spark-Page-Environment: DEV | TEST | PROD
X-Spark-Page-Render-Mode: DESIGN_PREVIEW | RUNTIME
Data Source 或 Interaction 中不需要手工重复配置这些 Header。完整协议见页面请求上下文与后端调试。
9. 进阶示例:员工资料编辑
第二个示例在基础提交之上加入完整的编辑生命周期:页面打开后从后端加载记录,邮箱离开焦点时执行远程校验,管理员权限说明按条件显示,紧急联系人可以增删,修改会自动保存草稿,最终只提交变化过的字段。
对应的可复现页面定义位于 spark-sample/src/main/resources/samples/form-capabilities-page.json,后端位于 FormCapabilityController 和 FormCapabilitySampleService。读者不需要访问 Spark Sample 应用,按下面配置即可重建。
10. 模式和公共状态
选择 Form,打开 Form capabilities → Lifecycle 页签:
| 配置 | 行为 |
|---|---|
| Create | 使用默认值创建记录,提交到 Create URL |
| Edit | 从 Load URL 读取记录,提交到 Update URL |
| View | 使用相同布局只读显示,不渲染可编辑控件 |
| Warn about unsaved changes | 离开已修改页面前要求确认 |
每个 Form 暴露只读状态,可用于表达式、按钮提示或后续动作:
forms.employee_profile_form.valid
forms.employee_profile_form.dirty
forms.employee_profile_form.submitting
forms.employee_profile_form.submitError
forms.employee_profile_form.lastSubmit
forms.employee_profile_form.mode
Form 内部交互也可以用 form.dirty、form.submitting 读取当前 Form。不要直接修改这些状态;它们由字段变化、校验、提交和草稿操作共同维护。
11. 条件字段和依赖
示例中的 Access reason 仅在打开 Administrator access 后出现:
Visible When: state.adminAccess === true
Required When: state.adminAccess === true
Dependencies: adminAccess
Clear When Hidden: on
字段支持:
- Visible When:表达式为
true时显示。 - Disabled When:表达式为
true时禁用。 - Required When:在固定 Required 之外增加动态必填条件。
- Dependencies:填写逗号分隔的稳定 Field Key,让依赖字段变化时重新计算。
- Clear When Hidden:字段因条件隐藏时清空旧值,避免提交不可见的敏感或过期数据。
表达式可读取 state.*、context.*、record.* 和 form.*,例如:
state.country === 'US' && form.mode !== 'VIEW'
context.permissions.canApprove === true
循环依赖会让业务含义难以理解。若 A 控制 B,通常不要再让 B 反向控制 A。
12. 远程校验和后端字段错误
在 Work email 的 Validators 中添加 Remote validation:
Validation URL: /spark/api/sample/form/validate-email
Method: POST
Debounce: 400
Value Parameter: value
Valid Response Path: data.valid
Message Response Path: data.message
Trigger: blur
请求会携带当前值、Field Key、Form ID 和当前 Form values。新输入会取消旧请求并忽略过期响应,避免慢响应覆盖新结果。后端返回:
{
"success": true,
"data": {
"valid": false,
"message": "This email address is already used by the sample data."
}
}
提交后的业务校验应使用统一的字段错误协议:
{
"success": false,
"code": 400,
"errorCode": "SPARK-SAMPLE-FORM-0008",
"displayText": "请检查表单中标记的字段后重试。",
"fieldErrors": {
"workEmail": ["该邮箱已被样例数据占用。"],
"adminAccessReason": ["请说明需要管理员权限的原因。"]
}
}
运行态会把 fieldErrors 写回对应 Form Item,而不是只显示一个全局 Toast。Java Service 使用 SparkFieldValidationException(Message, fieldErrors) 保持字段消息为 Lattice Message,Controller 边界再按 Accept-Language 转换。
13. 可重复分组
选择 Form Section,打开 Repeatable Group,配置:
Collection Key: emergencyContacts
Minimum Items: 1
Maximum Items: 3
Add Button Text: Add contact
Section 内的字段成为每一行的模板。运行态提供 Add / Delete,校验路径保留数组索引,提交结果为:
{
"emergencyContacts": [
{
"name": "Jordan Chen",
"relationship": "Family",
"phone": "+1 555 0101"
}
]
}
Collection Key 必须稳定,且不要与同一 Form 中的普通字段重名。View 模式只显示已有记录,不显示 Add / Delete。
14. 数据适配器和差量提交
在 Form capabilities → Data 页签配置 Data adapter。进阶示例的配置如下:
Load URL: /spark/api/sample/form/records/employee-1001
Create URL: /spark/api/sample/form/records
Update URL: /spark/api/sample/form/records/[[state.id]]
Response Data Path: data
Submission Payload: Changed values only
Record ID Field: id
Revision Field: revision
Edit 模式先读取 data 并建立基线。选择 Changed values only 后,提交使用差量数据:
{
"recordId": "employee-1001",
"revision": 3,
"changes": [
{ "op": "replace", "path": "/department", "value": "PRODUCT" },
{ "op": "replace", "path": "/emergencyContacts", "value": [] }
]
}
Create 模式始终提交完整 values;Edit 模式可以选择完整 values 或 change set。revision 用于后端乐观锁,冲突时返回 HTTP 409,不能静默覆盖其他会话的修改。
15. 草稿自动保存和恢复
在 Draft 页签开启 Autosave:
| 存储方式 | 使用场景 | 关键配置 |
|---|---|---|
| Browser storage | 个人临时录入、无需跨设备 | Draft Key、Autosave Delay |
| Remote service | 跨设备、多人或重要业务 | Load URL、Save URL、Draft Key、revision |
示例使用:
Storage: Remote service
Draft Key: employee-1001
Load URL: /spark/api/sample/form/drafts/employee-1001
Save URL: /spark/api/sample/form/drafts/employee-1001
Autosave Delay: 1500 ms
Form 还提供 saveDraft、restoreDraft、discardDraft 组件函数,可配置明确的“保存草稿”“恢复”“放弃草稿”按钮。远端草稿必须返回 revision;保存旧 revision 时后端返回 409,页面显示冲突状态,不覆盖新草稿。
16. 从模型或 Schema 生成字段
选择 Form,在 Form capabilities → Generate fields 中选择:
- Business Object:选择 Spark Business Object,读取字段类型、必填和选项。
- JSON Schema:粘贴对象 Schema;string、number、boolean、enum、date 会映射到对应控件。
- OpenAPI:粘贴文档并填写
#/components/schemas/Employee等 Schema Path;未填写时使用首个可用对象 Schema。
选择 Append generated groups 或 Replace current fields,点击 Generate,检查生成数量后保存。对象数组会生成 Repeatable Group。生成结果是普通、可编辑的页面元数据;运行态不会执行后端返回的任意组件 JSON 或脚本。
生成后仍需人工检查:字段标题、敏感字段、枚举文案、布局、帮助信息、权限、提交 URL 和后端校验。
17. Java 进阶接口示例
记录、草稿和远程校验使用同一页面请求上下文:
@PatchMapping("/records/{recordId}")
public ApiResult<FormCapabilityRecordDTO> updateRecord(
@PathVariable String recordId,
@RequestBody FormChangeSetSubmission input,
@RequestHeader(value = "Accept-Language", required = false)
String language,
@RequestHeader(value = SparkPageRequestHeaders.ENVIRONMENT,
required = false) String environment,
@RequestHeader(value = SparkPageRequestHeaders.RENDER_MODE,
required = false) String renderMode) {
Locale locale = SparkApiResultHelper.resolveLocale(language);
SparkPageRequestContext context =
SparkPageRequestHeaders.resolve(environment, renderMode);
return SparkApiResultHelper.success(
locale,
service.update(recordId, input, context),
"SPARK-SAMPLE-FORM-0015");
}
样例 Service 按 Environment + Render Mode 隔离内存数据。DESIGN_PREVIEW 返回明显的 Preview Employee,方便 Test / 设计画布检查;真实项目仍应使用认证后的租户、权限、Repository 和业务 UseCase。
18. 常见场景
- 详情页:Mode 设为 View,保留 Form 的标签和值布局。
- 新建页:Mode 设为 Create,配置 Create URL,由后端生成业务 ID。
- 编辑页:Mode 设为 Edit,配置 Load / Update URL 和 revision。
- 流程步骤:每个 Step 使用独立 Form;Validate 或 Submit 失败会中断后续 Next 动作。
- 筛选条:使用 Inline Form,字段保持少量,按钮放在末尾。
- 长流程:开启草稿和未保存提醒,不要依赖浏览器页面一直打开。
19. 常见问题
标签与上一行没有对齐
先确认 Controls Per Row,再确认 Label + Content 等于每个字段可用列数。两列 Form 推荐 4 + 8;整行字段会沿用 Label 4,并自动跨越剩余内容区域。
修改布局后预览没有变化
确认当前选择的是 Form 而不是某个字段;保存页面后同时检查设计画布和运行态。若 Form 位于很窄的 Grid Column 中,响应式规则可能已经把字段堆叠成单列。
默认值看得到,但提交仍提示必填
确认使用当前版本并保存最新页面。Form 的默认值应进入同一个校验和提交状态,不需要先手工修改一次。
Submit 发送了意外字段
检查 Submit Form 的 Form 目标和各字段的 Field Key。正确行为是只收集目标 Form 的已注册字段;Page Context 和其他 Form 不应进入请求体。
设计预览不应真正创建记录
后端读取 X-Spark-Page-Render-Mode。DESIGN_PREVIEW 返回稳定的预览回执或进入沙箱;RUNTIME 才执行真实写入,同时仍要完成认证、授权与审计。
条件字段没有重新计算
检查 Dependencies 是否填写了被依赖字段的 Field Key,例如 adminAccess;表达式读取的是 state.adminAccess,不是字段 Display Name。
后端错误只显示在顶部
确认响应使用顶层 fieldErrors,键与 Form Field Key 完全一致。普通 message / displayText 只能表达整体错误,无法定位字段。
草稿反复提示冲突
远端保存成功后必须返回新的 revision;下一次保存要带回该 revision。若服务端已经有草稿,而客户端没有 revision,应先 Load / Restore,不要直接覆盖。