关于 form-creator
用于编写或修改 Datex Studio 表单的技能,表单收集临时输入并将数据返回调用方。它指导组件类型选择、JSON 编写、校验及通过命令行更新分支。
主要功能
- 01表单、编辑器与网格的选择
- 02表单 JSON 编写指南
- 03输入与输出参数契约
- 04校验和确认流程模式
- 05基于分支的命令行生命周期
适用场景
- 01构建收集输入的对话框
- 02将收集的数据返回调用组件
- 03校验字段后再启用确认
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ datex/ skills --skill form-creator安装后在 Claude Code 中输入/form-creator确认已加载。
附带文件evals/evals.jsonreferences/forms.md
详细介绍
表单创建器
在某个分支上创建或修改 Datex Studio 表单(configurationTypeId=5)——这是一种用于收集用户数据的输入收集组件,可选择承载先验证后执行的工作流,并在关闭时通过 outParams 将载荷返回给调用方。表单最常作为模态或飞出对话框,由网格、中心页和其他表单打开。
参考资料
- ../datex-studio-shared/branch-setup.md — 分支/连接选择(各技能共用)
- references/forms.md — 权威的表单创建参考:文件结构、运行时全局变量、调用契约、常见模式、预检清单
- ../datex-studio-conventions/file-format.md —
configurationTypeId表和 TypeScript 表达式编码规则 - ../datex-studio-conventions/naming-conventions.md —
_form后缀、文件名主体匹配、显示名称规则 - ../datex-studio-runtime/runtime-globals.md — 表单代码中可用的平台注入全局变量(
$form、$flows、$shell、$utils等) - ../datex-studio-runtime/calling-conventions.md — UI 层调用规则(调用函数,不调用操作;通过
$apis.<Package>.FootprintApi.extendedActions.<action_name>执行 CRUD) - ../editor-creator/references/editors.md — 同类组件,用于实体绑定的查看/编辑(表单与编辑器的选择)
- ../embed-creator/references/embeds.md — 同类组件,用于不含字段控件的 iframe/URL/HTML 字符串渲染(表单与嵌入组件的选择)
- ../grid-creator/references/grids.md — 表单的典型宿主,通过行操作/工具栏按钮流程打开表单
- ../selector-creator/references/selectors.md — 下拉/自动补全字段会挂载选择器
- ../component-wiring-check/references/component-wiring.md — 宿主引用契约、必须声明 vars 的规则、moduleId 规则
依赖项
requirements-gathering技能 — 如果当前对话上下文中尚无需求简报,则调用此技能生成一份editor-creator/grid-creator技能 — 当需求实际是单实体查看/编辑屏幕(编辑器)或多记录列表(网格),而非临时输入收集时调用selector-creator技能 — 当表单上的下拉/自动补全字段需要一个尚不存在的后备选择器时调用hub-creator技能 — 当表单的宿主是中心页标签/按钮,且必须设置其configParameters契约才能以对话框形式打开表单时调用component-wiring-check技能 — 推送前,审查表单宿主(中心页按钮/网格行操作/父表单)上的configParameters↔ 目标inParams契约type-definition-creator技能 — 当表单的inParams/outParams/vars结构引用了需要创建或扩展的接口时调用
CLI 生命周期
表单创建通过 dxs configuration 完成——这是适用于所有平台配置类型的通用 CRUD 基元。没有 dxs form 子命令,也不支持字段级修补;你需要构建(或获取并提取)完整的 JSON 正文,编辑后再将完整内容推送回去。CLI 中的类型标识符为 form(小写,与 ConfigurationEndpoints.normalize_type 的输出一致),对应 configurationTypeId: 5。
创建新表单:
# 1. Build body.json from scratch (see references/forms.md → Minimal Valid Skeleton)
# 2. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI
dxs configuration validate form -b <branchId> -D body.json
# 3. Create
dxs configuration upsert form -b <branchId> -D body.json
编辑现有表单:
# 1. Fetch — note the envelope wrapper
dxs configuration get form <configId> -b <branchId> -O envelope.json
# 2. EXTRACT THE INNER BODY (round-trip footgun guard — see "Round-trip rule" below)
jq .json envelope.json > body.json
# 3. Edit body.json
# 4. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI
dxs configuration validate form -b <branchId> -D body.json
# 5. Push
dxs configuration upsert form -b <branchId> -D body.json
往返规则(重要)
编辑现有配置时,绝不要将 envelope.json 直接通过管道传给 dxs configuration upsert——这会静默破坏配置内容。上述修正后的步骤(先用 jq 提取内部 .json,再进行编辑)是任何往返操作的必备流程。规范的往返流程及其底层缺陷,请参阅 ../datex-studio-shared/configuration-roundtrip.md。
表单包含 toolbar、fieldsets(其中含有每个字段各自的 controlConfig 块)、flows(每个 executeCodeConfig 步骤都带有一个 code 字符串),以及 inParams/outParams/vars 架构——必须严格遵循往返流程(获取 → 用 jq 提取 → 编辑 → 验证 → 推送),不可妥协。
工作流
[Phase 1: Setup + Requirements]
Follow branch-setup.md for branch/connection selection
|
[requirements brief in context?]
+-----+-----+
| |
YES NO -> invoke `requirements-gathering`
| |
+-----+------+
|
[Phase 2: Form vs Editor vs Grid decision]
Consult references/forms.md → "Purpose & When to Use":
- transient input collection, returns outParams, no entity -> form
- single hydrated entity, view/edit toggle, persist via CRUD -> editor
- multi-record tabular view -> grid
If editor -> invoke `editor-creator` instead and stop here.
If grid -> invoke `grid-creator` instead and stop here.
Create-only dialogs are usually forms, not editors.
|
[Phase 3: Author form body]
Build body.json:
- File shape (configurationTypeId=5, *-form.json suffix,
referenceName ends _form, snake_case matches filename stem)
- TypeScript-expression encoding on every declarative string slot
(value, tooltip, placeholder, format): backticks for display text,
TS quotes for plain literals, raw expressions unwrapped
- Declarative vs imperative init — don't mix; if both are present
they must read the same inParam path
- Open form as dialog via $shell.<TargetPackage>.open<referenceName>Dialog
(target's package, not the caller's; snake_case preserved)
- Validate-then-gate-confirm via onFormValidateFlowConfig +
formValidationFlows, not one-shot onInitFlowConfig assignments
- Emit is_confirmed: boolean outParam if callers must distinguish
confirm from cancel
- Every $form.vars.<id> written in flow code is declared in vars[];
every $form.<flowName>() call has a matching flow in flows[]
- Sibling *Config keys explicitly null on every controlConfig
- No $types.<Package>.e_<enum> in vars/inParams/outParams —
primitives only at the param layer; cast at usage in flow code
- Invoke `selector-creator` if a dropdown field needs a new selector
- Invoke `component-wiring-check` to audit the host's configParameters
|
[Phase 4: Validate + push]
dxs configuration validate form -b <branchId> -D body.json
|
+----+----+
| |
CREATE MODIFY-EXISTING
| |
| use the corrected round-trip
| (get -O envelope -> jq .json -> body)
| |
+----+----+
|
v
dxs configuration upsert form -b <branchId> -D body.json
(upsert creates or updates by referenceName — one command for both)
|
[Phase 5: Verify in Studio (optional)]
Open the form as a dialog (from its host hub/grid/form);
confirm field hydration, validate-then-gate-confirm, confirm vs cancel
return shapes
|
[invoke `post-edit-verification`; then `component-validator`]
阶段详情
阶段 1:准备与需求
- 按照 ../datex-studio-shared/branch-setup.md 选择分支和连接。绝不要假定分支 ID——请用户确认。
- 检查对话上下文中是否已有需求说明(由
requirements-gathering或其他调用技能生成)。- 已有说明——使用它。说明应明确表单收集哪些输入、有效性规则、调用方如何处理返回的载荷(确认时会运行哪种 CRUD 操作——如果有),以及表单如何打开(哪个宿主会调用此对话框)。
- 没有说明——先调用
requirements-gathering技能。预先明确输入结构、有效性规则以及确认与取消的语义,可避免重新编写“先验证、再确认”的流程。
阶段 2:选择表单、编辑器或网格
编写前,请参阅 references/forms.md → Purpose & When to Use。这不是风格上的选择——表单、编辑器和网格承担不同的职责,不能互换。
在以下情况选择表单:
- 用于临时收集输入,且这些输入尚未对应任何已存储记录。
- 对话框将
outParams返回给调用方;表单本身不会隐式持久化任何内容。 - 对话框仅用于创建,尚无实体可供填充。
- 对话框显示配置提示词(例如计划安排或一组选项),并将值交还给调用方。
在以下情况改用编辑器:
- 用户查看或修改由键标识的单个实体。
- 字段级输入与该实体的属性一一对应。
- UX 需要提供独立的查看模式(只读)与编辑模式(解锁输入、启用保存)切换。
- 流程通过 CRUD 操作持久化更改;对话框并非只是返回值。
在以下情况改用网格:
- 用户以表格布局查看或操作多条记录。
如果答案是编辑器,请停止并调用 editor-creator。如果答案是网格,请停止并调用 grid-creator。表单可以在确认时调用 CRUD 操作(先验证、再执行操作的模式),但这仍然是表单——没有已填充的实体引用,只有收集到的输入和调用的操作。
阶段 3:编写表单主体
根据 references/forms.md → Minimal Valid Skeleton 中的骨架构建 body.json。要点:
-
文件基础信息。 遵循下方的预检清单以及 ../datex-studio-conventions/universal-checklist.md;有关
-form.json文件的结构,请参阅 references/forms.md → File Location & Naming。 -
所有声明式字符串槽位都使用 TypeScript 表达式编码。
controlConfig中的每个value、tooltip、placeholder、format,以及绑定到变量的按钮label,都会原样内联到生成的 TS 中。显示文本要用反引号括起来("`Set schedule…`"),普通字符串字面量要用 TS 引号括起来("'MM/DD/YYYY'"),原始表达式则不加引号("$form.inParams.foo")。未加引号的 tooltip(例如"Opens the dialog.")会被编译成裸标记并导致构建失败——由于控件暴露的字符串槽位很多,表单最常违反这条规则。请参阅 ../datex-studio-conventions/file-format.md → 声明式字符串值是 TypeScript 表达式。 -
动态 tooltip 通过变量传递。 Flow 代码不能直接赋值给
$form.fields.x.control.tooltip——它是仅限声明式配置的槽位。要在运行时更改 tooltip,请声明$form.vars.<name>(字符串),将字段的tooltip绑定到"$form.vars.<name>"(作为原始 TS 表达式),并在 Flow 代码中赋值给该变量。请参阅 ../datex-studio-conventions/file-format.md → 声明式字符串值是 TypeScript 表达式 和 → 动态 tooltip 值通过变量传递。 -
声明式与命令式初始化——不要混用。 从
inParams为字段设置初始值有两种方式:在控件的value字段上使用声明式绑定(例如"$form.inParams.foo",在渲染时求值),或在onInitFlowConfig中进行命令式赋值($form.fields.foo.control.value = $form.inParams.foo,在初始化 Flow 后运行)。一次性初始化使用声明式绑定,有条件或异步初始化使用命令式赋值。不要混用——如果声明式绑定和命令式赋值读取的是不同的 inParam 路径,就会产生静默错误:渲染时声明式绑定会生效,而命令式赋值看起来正确,却不会产生任何效果。如果两者都存在,它们必须读取相同路径,否则就删除其中一个。请参阅 references/forms.md → 在表单中接收 inParams。 -
作为对话框打开。 对话框打开的 shell 调用由表单的
referenceName推导而来:$shell.<TargetPackage>.open<referenceName>Dialog(inParams, 'modal' | 'flyout', EModalSize.<size>)。<TargetPackage>是目标表单的包,而不是调用方的功能文件夹,也不默认是Utilities。<referenceName>是目标表单的referenceName,保留 snake_case(不要转换为 camelCase)。返回值是目标表单的outParams对象;取消时如果没有调用$form.close(),则 outParam 字段会是 undefined。请参阅 references/forms.md → 将表单作为对话框打开 和 ../component-wiring-check/references/component-wiring.md。 -
先验证,再控制确认按钮。 多字段验证应放在
formValidationFlows中(由onFormValidateFlowConfig引用),而不是放在各字段的onValidationFlowConfig中。该 Flow 会在每次字段更改时运行,计算有效性,并设置$form.toolbar.confirm.control.readOnly。在onInitFlowConfig中进行的一次性赋值会在用户交互后过时——不适用于控制确认按钮。字段级错误会推送到$validation.fieldErrors.<id>。请参阅 references/forms.md → 先验证,再控制确认按钮。 -
确认与取消可区分。 调用方的
await openXxxDialog(...)会解析为目标表单的outParams对象;如果取消时没有调用$form.close(),这些字段就会保持undefined。如果调用方必须区分确认和取消,目标表单应发送显式的is_confirmed: booleanoutParam,然后由调用方据此分支处理。不要依赖对单个 outParam 字段进行真值检查来检测取消。 -
outParams类型契约与每条关闭路径一致。required: true的 outParam 不能只在某些分支中写入——类型契约必须满足每条关闭路径的要求。如果确认分支写入outParams.value,但取消分支不写入(因为取消时根本不会调用$form.close()),则value.required应保持为false。请参阅 references/forms.md 中的预检清单第 11 项。 -
已声明
$form.vars,且$form.<flow>()对应真实流程。 流程代码中写入的每个$form.vars.<id>都必须在顶层vars[]数组中声明("vars": null加上$form.vars.x = ...会导致导入时报错:Property 'vars' does not exist on type 'IForm')。每个await $form.<flowName>()调用都必须对应flows[]中的真实条目(平台会为每个流程的referenceName自动在$form上生成一个方法)。一种常见情况是:formValidationFlows调用await $form.set_state(),但不存在set_state流程。请定义缺失的流程,或移除该验证钩子。请参阅 references/forms.md →$form.varsRequires Declaration;$form.<flow>()Requires the Flow。 -
inParams/outParams/vars模式与流程代码一致。 流程代码读取或写入的每个嵌套字段,都必须以相同的属性描述符结构出现在对应的顶层objectTypeDef中。这三者中任何一处缺少字段,导入时都会报错:Property 'X' does not exist on type {...}。如果向流程代码会操作的对象添加字段,请在同一次编辑中将该字段添加到所有相关的objectTypeDef(vars + inParams + outParams)中。 -
同级
*Config键显式设为 null。 对每个controlConfig,只填充当前启用的子配置(例如textBox字段中的textBoxConfig);其余配置(buttonConfig、selectBoxConfig等)都应显式保持为null。结构差异对比时请遵循此约定——现有表单组件会保留所有同级槽位并将其设为 null。 -
vars/inParams/outParams中不使用自定义枚举的 FQN。 表单无法在参数声明中解析$types.<Package>.e_<enum>——请将这些字段声明为基本类型(字符串值枚举使用string,数值枚举使用number),并在流程代码中使用时进行类型转换。 -
遵循调用层级约定。 表单代码通过
$flows.<Package>.<fn>调用函数;该函数再通过$apis.<Package>.FootprintApi.extendedActions.<action_name>({...})封装 CRUD 操作调用。表单代码不得直接调用操作。请参阅 ../datex-studio-runtime/calling-conventions.md。 -
审计宿主契约。 将此表单作为对话框打开的中心按钮、网格行操作或表单,必须声明完整的
configParameters契约——表单声明的每个inParam都必须在宿主中有对应条目,包括未使用且value: null的条目。推送前请运行component-wiring-check审计引用契约。请参阅 ../component-wiring-check/references/component-wiring.md。 -
数据源和关联数据源的形状(如果表单绑定了数据源)。 大多数表单由参数驱动,并且
datasourceConfig: null/linkedDatasources: null。如果表单绑定了数据源,则必须是单条结果(get),要求与编辑器相同,并且每个linkedDatasources条目都必须与其链接的type匹配。服务端的使用门禁会阻止存在任一不匹配情况时发布。绑定的数据源默认归表单所有——嵌入表单自身的datasources[]中;独立数据源仅用于 ../datasource-creator/references/datasources.md → 默认归属 中的覆盖场景。请参阅 references/forms.md → 可选数据源接线。
阶段 4:验证并推送
# Validate the body locally against the branch. Exit 1 = validation found errors
# (read validation_errors, fix body.json, re-run) — not a broken CLI. Do not push on exit 1.
dxs configuration validate form -b <branchId> -D body.json
# For a new form
dxs configuration upsert form -b <branchId> -D body.json
# For modify-existing (round-trip — never skip the jq extract)
dxs configuration get form <configId> -b <branchId> -O envelope.json
jq .json envelope.json > body.json
# ... edit body.json ...
dxs configuration upsert form -b <branchId> -D body.json
验证会在推送前指出缺少必填字段、参数描述符形状不正确、未定义的 flow-id 引用以及引用错误。但它无法捕获声明式与命令式初始化之间的冲突、未声明的 $form.vars.<id> 写入、未包装的 TypeScript 表达式槽位,或位于错误钩子中的确认按钮门控——这些属于行为问题,只会在运行时显现。推送前请逐项检查 references/forms.md → 预检清单。
阶段 5:在 Studio 中验证(可选)
通过常规调用路径以对话框形式打开表单(例如 hub 工具栏按钮、网格行操作,或链式调用该表单的父表单):
- 字段填充成功——声明式绑定和命令式
onInitFlowConfig赋值,会从调用方实际填充的 inParam 路径中获取值。 - 在
onFormValidateFlowConfig报告有效之前,确认按钮始终处于禁用状态。 - 确认后关闭表单,并将已填充的
outParams解析为调用方await openXxxDialog(...)的结果。 - 取消(点击 X、按 ESC、点击显式的取消按钮)时,调用方的 promise 会解析为 outParam 字段值均为
undefined;如果调用方需要区分确认和取消,则确认时会输出显式的is_confirmed: booleanoutParam,调用方据此进行分支处理。 - 验证字段错误会显示在正确的字段上(
$validation.fieldErrors.<id>.push(message)会将错误添加到指定字段)。
如果无法访问正在运行的应用,请重新获取配置(使用修正后的 jq .json 提取模式),并与 body.json 进行差异比较,以确认推送已生效。
提交前检查清单
推送前,请完整检查 references/forms.md → 提交前检查清单。快速版:
- 文件基本信息。
configurationTypeId: 5,后缀为-form.json,referenceName以_form结尾——另外还要通过通用检查(../datex-studio-conventions/universal-checklist.md)。 - Flow ID 引用均有效。
onInitFlowConfig.flowId、每个clickFlowConfig.flowId、uiValueChangeFlowConfig.flowId、onValidationFlowConfig.flowId、onFormValidateFlowConfig.flowId——都必须指向flows[]中存在的 flow。 - TypeScript 表达式字符串的包装方式正确——显示文本用反引号;原始表达式不加包装;普通字面量加引号。动态工具提示通过已声明的
$form.vars.<name>设置(直接赋值给.control.tooltip不会生效)。 - 声明式初始化与命令式初始化不冲突。 如果某个字段同时有
value绑定和onInitFlowConfig赋值,它们必须读取同一路径的 inParam——否则声明式绑定会静默覆盖赋值。 - 确认按钮通过
formValidationFlows控制,而不是通过一次性的onInitFlowConfig赋值控制。 - 打开对话框的调用使用目标表单所属的包作为
<Package>——默认不要使用调用方的功能文件夹或Utilities。不要改成驼峰命名。 - 如果调用方必须区分确认与取消,则输出
is_confirmed: booleanoutParam。 outParams类型契约与每条关闭路径一致——不得仅在满足条件时写入required: true的 outParam。- 已声明
$form.vars——flow 代码中写入的每个 var 都必须出现在顶层vars[]中。 $form.<flowName>()调用均有效——每个被调用的 flow 都存在于flows[]中。要么定义缺失的 flow,要么移除引用它的验证钩子。vars/inParams/outParams的 schema 与 flow 代码一致——flow 代码读取或写入的每个嵌套字段,都必须出现在对应的objectTypeDef中,且属性描述符匹配。- 每个
controlConfig中的同级*Config键都显式设为 null——仅填充当前激活的配置。 vars/inParams/outParams中不得出现$types.<Package>.e_<enum>——参数层仅使用基本类型;在使用处进行类型转换。- 符合调用层级要求——表单代码通过
$flows.<Package>.<fn>调用函数;函数通过$apis.<Package>.FootprintApi.extendedActions.<action_name>({...})封装 CRUD 操作;表单不得直接调用操作。 - Host 必须携带完整的
configParameters契约——表单声明的每个 inParam 都必须在 host 中有对应条目;未使用的条目设为value: null。使用component-wiring-check审核。 - 限制数据源形状——如果表单绑定了
datasourceConfig,它必须返回单个结果,且每个linkedDatasources条目都必须与其 linktype匹配。否则两者都会阻止发布。
常见错误
| 错误 | 修正方法 |
|---|---|
声明式字符串槽位未加包装("Opens the dialog.",而不是 "`Opens the dialog.`") | 会原样内联到生成的 TS 中——裸标记会导致构建失败。显示文本用反引号;普通字面量用引号;原始表达式不加包装。 |
Flow 代码给 $form.fields.x.control.tooltip 赋值,但运行时提示文本没有变化 | .control.tooltip 仅用于声明。声明 $form.vars.<name>(字符串),将字段的 tooltip 绑定到 "$form.vars.<name>",并在 Flow 代码中给该变量赋值。 |
声明式 value: "$form.inParams.foo" + onInitFlowConfig 写入 $form.fields.x.control.value = $form.inParams.bar | 渲染时以声明式绑定为准;命令式赋值会被静默忽略。让两者读取同一路径,或删掉其中一个。 |
在 onInitFlowConfig 中通过一次性赋值控制确认按钮的 readOnly 状态 | 首次字段变更后就会过时。将控制逻辑移至 onFormValidateFlowConfig + formValidationFlows。 |
调用方的 await openXxxDialog(...) 在取消时返回 undefined outParams,代码却将其视为带空值的确认 | 从目标表单返回明确的 is_confirmed: boolean outParam,并据此分支处理。 |
Flow 代码中使用 $form.vars.x = ...,但顶层 "vars": null | 导入错误:Property 'vars' does not exist on type 'IForm'。在 vars[] 中声明每个变量,其属性描述符结构与 inParams/outParams 相同。 |
Flow 代码中使用 await $form.set_state(),但 flows[] 中没有 set_state Flow | 导入错误:Property 'set_state' does not exist on type 'IForm'。要么定义缺失的 Flow,要么删掉该调用(以及包含它的 formValidationFlows/onFormValidateFlowConfig 钩子)。 |
outParams.value.required: true,但取消分支调用 $form.close() 时未填充 value | 类型契约必须匹配每条关闭路径。要么删除 required: true,要么确保每次关闭时都写入 value。 |
打开对话框的调用使用调用方的功能文件夹作为 <Package>(例如 $shell.MyFeature.openXFormDialog),但目标表单注册在 Utilities 下 | <Package> 指的是目标表单所属模块,而不是调用方的模块。查看目标表单声明的模块。 |
打开对话框的调用将 referenceName 转为驼峰命名(referenceName: my_form 却写成 openMyFormDialog) | 保留 snake_case——$shell.<Package>.openmy_formDialog(...)。 |
漏掉同级的 *Config 槽位(例如只填充 textBoxConfig,缺少其他子配置) | 现有表单应保留所有同级槽位,并将其设为 null,以便进行结构差异比较。遵循此约定。 |
在 vars / inParams / outParams 中使用 $types.<Package>.e_<enum> | 自定义枚举的 FQN 在参数层无法解析。声明为基本类型(string / number);在 Flow 代码中使用时再进行类型转换。 |
| 表单代码直接调用 action | UI 层规则:表单只能调用函数。函数通过 $apis.<Package>.FootprintApi.extendedActions.<action_name>({...}) 封装 action。 |
将 dxs configuration get -O envelope.json 的输出直接传给 dxs configuration upsert -D envelope.json | 会静默破坏配置内容。编辑前务必先运行 jq .json envelope.json > body.json。参见上面的“往返规则”。 |
description 超过 100 个字符 | SQL 列长度有限制——推送时验证会失败。精简描述。 |
referenceName 不以 _form 结尾,或与文件名主体不匹配 | 导入/查找会失败。使用 snake_case,以 _form 结尾,并确保与文件名主体匹配。 |
宿主的引用中,moduleId 被设为宿主的包,而不是表单所属的包 | 跨组件引用规则——moduleId 始终是目标的包。参见 ../component-wiring-check/references/component-wiring.md。 |
编辑后,请调用 post-edit-verification,检查 description/JSON/schema 违规。最终审核时,请调用 component-validator。