mcpField
F
form-creator

form-creator

github.com/datex开发92 次安装
代码仓库

关于 form-creator

用于编写或修改 Datex Studio 表单的技能,表单收集临时输入并将数据返回调用方。它指导组件类型选择、JSON 编写、校验及通过命令行更新分支。

主要功能

  1. 01表单、编辑器与网格的选择
  2. 02表单 JSON 编写指南
  3. 03输入与输出参数契约
  4. 04校验和确认流程模式
  5. 05基于分支的命令行生命周期

适用场景

  1. 01构建收集输入的对话框
  2. 02将收集的数据返回调用组件
  3. 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 将载荷返回给调用方。表单最常作为模态或飞出对话框,由网格、中心页和其他表单打开。

参考资料

依赖项

  • 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:准备与需求
  1. 按照 ../datex-studio-shared/branch-setup.md 选择分支和连接。绝不要假定分支 ID——请用户确认。
  2. 检查对话上下文中是否已有需求说明(由 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。要点:

  1. 文件基础信息。 遵循下方的预检清单以及 ../datex-studio-conventions/universal-checklist.md;有关 -form.json 文件的结构,请参阅 references/forms.md → File Location & Naming。

  2. 所有声明式字符串槽位都使用 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 表达式。

  3. 动态 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 值通过变量传递。

  4. 声明式与命令式初始化——不要混用。 从 inParams 为字段设置初始值有两种方式:在控件的 value 字段上使用声明式绑定(例如 "$form.inParams.foo",在渲染时求值),或在 onInitFlowConfig 中进行命令式赋值($form.fields.foo.control.value = $form.inParams.foo,在初始化 Flow 后运行)。一次性初始化使用声明式绑定,有条件或异步初始化使用命令式赋值。不要混用——如果声明式绑定和命令式赋值读取的是不同的 inParam 路径,就会产生静默错误:渲染时声明式绑定会生效,而命令式赋值看起来正确,却不会产生任何效果。如果两者都存在,它们必须读取相同路径,否则就删除其中一个。请参阅 references/forms.md → 在表单中接收 inParams。

  5. 作为对话框打开。 对话框打开的 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。

  6. 先验证,再控制确认按钮。 多字段验证应放在 formValidationFlows 中(由 onFormValidateFlowConfig 引用),而不是放在各字段的 onValidationFlowConfig 中。该 Flow 会在每次字段更改时运行,计算有效性,并设置 $form.toolbar.confirm.control.readOnly。在 onInitFlowConfig 中进行的一次性赋值会在用户交互后过时——不适用于控制确认按钮。字段级错误会推送到 $validation.fieldErrors.<id>。请参阅 references/forms.md → 先验证,再控制确认按钮。

  7. 确认与取消可区分。 调用方的 await openXxxDialog(...) 会解析为目标表单的 outParams 对象;如果取消时没有调用 $form.close(),这些字段就会保持 undefined。如果调用方必须区分确认和取消,目标表单应发送显式的 is_confirmed: boolean outParam,然后由调用方据此分支处理。不要依赖对单个 outParam 字段进行真值检查来检测取消。

  8. outParams 类型契约与每条关闭路径一致。 required: true 的 outParam 不能只在某些分支中写入——类型契约必须满足每条关闭路径的要求。如果确认分支写入 outParams.value,但取消分支不写入(因为取消时根本不会调用 $form.close()),则 value.required 应保持为 false。请参阅 references/forms.md 中的预检清单第 11 项。

  9. 已声明 $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.vars Requires Declaration; $form.<flow>() Requires the Flow。

  10. inParams / outParams / vars 模式与流程代码一致。 流程代码读取或写入的每个嵌套字段,都必须以相同的属性描述符结构出现在对应的顶层 objectTypeDef 中。这三者中任何一处缺少字段,导入时都会报错:Property 'X' does not exist on type {...}。如果向流程代码会操作的对象添加字段,请在同一次编辑中将该字段添加到所有相关的 objectTypeDef(vars + inParams + outParams)中。

  11. 同级 *Config 键显式设为 null。 对每个 controlConfig,只填充当前启用的子配置(例如 textBox 字段中的 textBoxConfig);其余配置(buttonConfig、selectBoxConfig 等)都应显式保持为 null。结构差异对比时请遵循此约定——现有表单组件会保留所有同级槽位并将其设为 null。

  12. vars / inParams / outParams 中不使用自定义枚举的 FQN。 表单无法在参数声明中解析 $types.<Package>.e_<enum>——请将这些字段声明为基本类型(字符串值枚举使用 string,数值枚举使用 number),并在流程代码中使用时进行类型转换。

  13. 遵循调用层级约定。 表单代码通过 $flows.<Package>.<fn> 调用函数;该函数再通过 $apis.<Package>.FootprintApi.extendedActions.<action_name>({...}) 封装 CRUD 操作调用。表单代码不得直接调用操作。请参阅 ../datex-studio-runtime/calling-conventions.md。

  14. 审计宿主契约。 将此表单作为对话框打开的中心按钮、网格行操作或表单,必须声明完整的 configParameters 契约——表单声明的每个 inParam 都必须在宿主中有对应条目,包括未使用且 value: null 的条目。推送前请运行 component-wiring-check 审计引用契约。请参阅 ../component-wiring-check/references/component-wiring.md。

  15. 数据源和关联数据源的形状(如果表单绑定了数据源)。 大多数表单由参数驱动,并且 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: boolean outParam,调用方据此进行分支处理。
  • 验证字段错误会显示在正确的字段上($validation.fieldErrors.<id>.push(message) 会将错误添加到指定字段)。

如果无法访问正在运行的应用,请重新获取配置(使用修正后的 jq .json 提取模式),并与 body.json 进行差异比较,以确认推送已生效。

提交前检查清单

推送前,请完整检查 references/forms.md → 提交前检查清单。快速版:

  1. 文件基本信息。 configurationTypeId: 5,后缀为 -form.json,referenceName 以 _form 结尾——另外还要通过通用检查(../datex-studio-conventions/universal-checklist.md)。
  2. Flow ID 引用均有效。 onInitFlowConfig.flowId、每个 clickFlowConfig.flowId、uiValueChangeFlowConfig.flowId、onValidationFlowConfig.flowId、onFormValidateFlowConfig.flowId——都必须指向 flows[] 中存在的 flow。
  3. TypeScript 表达式字符串的包装方式正确——显示文本用反引号;原始表达式不加包装;普通字面量加引号。动态工具提示通过已声明的 $form.vars.<name> 设置(直接赋值给 .control.tooltip 不会生效)。
  4. 声明式初始化与命令式初始化不冲突。 如果某个字段同时有 value 绑定和 onInitFlowConfig 赋值,它们必须读取同一路径的 inParam——否则声明式绑定会静默覆盖赋值。
  5. 确认按钮通过 formValidationFlows 控制,而不是通过一次性的 onInitFlowConfig 赋值控制。
  6. 打开对话框的调用使用目标表单所属的包作为 <Package>——默认不要使用调用方的功能文件夹或 Utilities。不要改成驼峰命名。
  7. 如果调用方必须区分确认与取消,则输出 is_confirmed: boolean outParam。
  8. outParams 类型契约与每条关闭路径一致——不得仅在满足条件时写入 required: true 的 outParam。
  9. 已声明 $form.vars——flow 代码中写入的每个 var 都必须出现在顶层 vars[] 中。
  10. $form.<flowName>() 调用均有效——每个被调用的 flow 都存在于 flows[] 中。要么定义缺失的 flow,要么移除引用它的验证钩子。
  11. vars / inParams / outParams 的 schema 与 flow 代码一致——flow 代码读取或写入的每个嵌套字段,都必须出现在对应的 objectTypeDef 中,且属性描述符匹配。
  12. 每个 controlConfig 中的同级 *Config 键都显式设为 null——仅填充当前激活的配置。
  13. vars / inParams / outParams 中不得出现 $types.<Package>.e_<enum>——参数层仅使用基本类型;在使用处进行类型转换。
  14. 符合调用层级要求——表单代码通过 $flows.<Package>.<fn> 调用函数;函数通过 $apis.<Package>.FootprintApi.extendedActions.<action_name>({...}) 封装 CRUD 操作;表单不得直接调用操作。
  15. Host 必须携带完整的 configParameters 契约——表单声明的每个 inParam 都必须在 host 中有对应条目;未使用的条目设为 value: null。使用 component-wiring-check 审核。
  16. 限制数据源形状——如果表单绑定了 datasourceConfig,它必须返回单个结果,且每个 linkedDatasources 条目都必须与其 link type 匹配。否则两者都会阻止发布。

常见错误

错误修正方法
声明式字符串槽位未加包装("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 代码中使用时再进行类型转换。
表单代码直接调用 actionUI 层规则:表单只能调用函数。函数通过 $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。

评论

支持 Markdown