mcpField
G
grid-creator

grid-creator

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

关于 grid-creator

grid-creator 是用于在分支上创建或修改 Datex Studio 网格的技能。它指导数据源选择、网格配置、验证和上传,并着重确保实体字段与动态筛选设置保持同步。

主要功能

  1. 01创建和修改 Datex Studio 网格
  2. 02指导选择 OData 或流程数据源
  3. 03维护网格实体结构各处同步
  4. 04配置筛选、排序和交互
  5. 05通过 dxs 验证并上传配置

适用场景

  1. 01构建多记录表格视图
  2. 02为网格添加筛选、排序或行操作
  3. 03在分支上更新现有网格配置

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/datex/skills --skill grid-creator

安装后在 Claude Code 中输入/grid-creator确认已加载。

附带文件evals/evals.jsonreferences/grids.md

详细介绍

网格创建器

在分支上创建或修改 Datex Studio 网格(configurationTypeId=3)——这是平台主要的数据密度组件。网格会从后端数据源渲染表格行,并支持可选的行内编辑、选择、工具栏、动态筛选和排序,以及针对每行/每个单元格的交互。它们通常嵌入在中枢选项卡中,而且比其他任何组件都涉及更多跨位置不变量——五位置实体形状规则、双站点动态筛选/排序注册镜像,以及 OData 运行时 selects 位置都在这里。

参考资料

依赖项

  • requirements-gathering 技能 — 如果对话上下文中尚不存在需求简报,则调用此技能生成一份
  • schema-explorer 技能 — 在编写任何 PascalCase 复数命名的网格(或任何行看起来像 OData 实体的网格)之前调用此技能,以确认 Footprint 架构中是否存在该实体;花 5 秒查询,比针对错误数据源重建网格更省事
  • datasource-creator 技能 — 当网格需要一个独立数据源(供多个使用方共享)时调用此技能;仅供网格使用的数据源应作为自有条目编写在网格的 datasources[] 数组中,而不是单独的文件。要编写此自有条目,请遵循 ../datasource-creator/references/datasources.md → 创建自有数据源 — 使用 dxs datasource generate-flow 搭建脚手架,然后插入。分页计数应保留在 getListFlow 中;顶层组件/引用输出应遵循最终确定的数据源契约。
  • form-creator / editor-creator / selector-creator 技能 — 当需求实际需要的是临时输入收集(表单)、单实体查看/编辑屏幕(编辑器)或下拉列表/自动补全(选择器),而不是表格列表时调用
  • hub-creator 技能 — 当网格所在的宿主中心选项卡/按钮需要设置 configParameters 契约以挂载网格时调用此技能
  • component-wiring-check 技能 — 推送前调用此技能,审查网格宿主(外层)和网格内嵌数据源(内层)的 configParameters ↔ 目标 inParams 契约
  • tailoring-overlay 技能 — 编写或维护定制网格变体(baseConfiguration + onCustomization*FlowConfig),或次级增强数据源来自覆盖层时调用此技能
  • grid-validator 技能 — 每次编辑网格后都必须调用;重新运行预检清单,捕获五处漂移、部分 lookupcode/id 同步、OData selects 遗漏以及动态筛选器注册镜像漂移。必须修复其阻止项。

CLI 生命周期

网格编写通过 dxs configuration 进行 — 这是适用于所有平台配置类型的通用 CRUD 原语。没有 dxs grid 子命令,也不支持字段级补丁;你需要构建(或获取并提取)整个 JSON 正文,编辑后再将其整体推回。CLI 中的类型标识符为 grid(小写,与 ConfigurationEndpoints.normalize_type 输出一致),映射到 configurationTypeId: 3。

创建新网格:

# 1. Build body.json from scratch (see references/grids.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 grid -b <branchId> -D body.json
# 3. Create
dxs configuration upsert grid -b <branchId> -D body.json

编辑现有网格:

# 1. Fetch — note the envelope wrapper
dxs configuration get grid <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 grid -b <branchId> -D body.json
# 5. Push
dxs configuration upsert grid -b <branchId> -D body.json
往返规则(关键)

编辑现有配置时,切勿将 envelope.json 直接通过管道传给 dxs configuration upsert — 这会在不提示的情况下销毁配置内容。上文更正后的步骤(编辑前使用 jq 提取内部 .json)是任何往返操作的强制要求。规范的往返流程和底层 Bug 请参阅 ../datex-studio-shared/configuration-roundtrip.md。

网格包含 columns、datasourceConfig、内嵌的 datasources[] 数组(每个条目都是完整的 flow 或 OData 类型数据源,具有各自的 getListFlow / getByKeysFlow 和实体形状位置)、flows / rowFlows(每个 executeCodeConfig 步骤都带有 code 字符串)、topToolbar / toolbar(多态控件描述符),以及网格级的 inParams / outParams / vars / rowVars 架构 — 往返操作必须严守流程(获取 → 使用 jq 提取 → 编辑 → 验证 → 推送),而且只同步部分实体形状位置是上传时契约错误最常见的根源。

工作流

[Phase 1: Setup + Requirements]
Follow branch-setup.md for branch/connection selection
        |
[requirements brief in context?]
  +-----+-----+
  |            |
 YES          NO -> invoke `requirements-gathering`
  |            |
  +-----+------+
        |
[Phase 2: Pre-author rows-source decision]
Consult references/grids.md → "Pre-Author Decision — What Backs the Rows?":
  - PascalCase-plural name -> invoke `schema-explorer` (mandatory)
    before defaulting to flow-type; if entity exists, build OData-backed
  - snake_case name -> check the branch for a storage config
    (`dxs source explore configs --type storage`) or treat as flow-type
  - ambiguous -> invoke `schema-explorer` first
Owned vs standalone:
  - grid-only datasource -> embedded entry in `datasources[]`
  - shared across consumers -> standalone (invoke `datasource-creator`)
If requirement is single-entity view/edit -> invoke `editor-creator` and stop.
If requirement is transient input collection -> invoke `form-creator` and stop.
If requirement is a dropdown/autocomplete -> invoke `selector-creator` and stop.
        |
[Phase 3: Author grid body]
Build body.json:
  - File shape (configurationTypeId=3, *-grid.json suffix,
    referenceName ends _grid, snake_case matches filename stem)
  - Five-location entity-shape rule (flow-type) or five+selects (OData);
    every entity field present at every site
  - Two-site dynamic filter/sort registration mirror (datasources[0] and
    datasourceConfig); per-column dynamicFilter/dynamicOrderBy/
    dynamicFilterType/dynamicFilterControl; no $filter/$orderby in any
    inParams or configParameters
  - Dynamic filter/sort is all-or-nothing — flow-type backings either
    fully wire (registrations + per-column + applyDynamicFilter/
    applyDynamicOrderBy in getListFlow) or fully disable
  - Toolbar items polymorphic — click handlers inside the type-specific
    config block (buttonConfig.clickFlowConfig.flowId), siblings null;
    destructive buttons get buttonDefaultStyleClass: "destructive";
    selection-gated buttons seed readOnly: true statically AND gate
    live in on_select_row
  - Secondary enrichment datasources owned in same datasources[] array;
    invoke from on_data_loaded; batch with Id in ${ids}; write via
    imperative cell API ($row.cells.<col>.displayControl.text / value)
  - Imperative cell API for post-load enrichment and change-guarded
    saves; empty declarative bindings are legitimate when value is
    populated imperatively — don't "fix" to $row.entity.<field>
  - Text display bindings coerce non-string entity fields at the bind
    site, not in the entity schema (entity type drives filter operators)
  - Every $grid.vars.<id> / $row.vars.<id> written in flow code is
    declared in top-level vars[] / rowVars[]
  - Embedded datasource carries the full component-identity envelope
    (referenceName matching datasourceConfig.configId, title, description,
    hasKey, hasResult, accessModifier, ...)
  - Invoke `schema-explorer` for OData entity / property / navigation
    property validation before authoring OData-backed grids
  - Invoke `component-wiring-check` to audit BOTH the outer host
    contract AND the inner grid → embedded-datasource contract
    (full_text_search and any inParam need explicit configParameters
    entries)
        |
[Phase 4: Validate + push]
dxs configuration validate grid -b <branchId> -D body.json
        |
   +----+----+
   |         |
  CREATE   MODIFY-EXISTING
   |         |
   |         use the corrected round-trip
   |         (get -O envelope -> jq .json -> body)
   |         |
   +----+----+
        |
        v
dxs configuration upsert grid -b <branchId> -D body.json
   (upsert creates or updates by referenceName — one command for both)
        |
[Phase 5: Validate with `grid-validator` (MANDATORY)]
Delegate to `grid-validator` with the grid's file path / configId.
It re-runs the full Pre-Flight Checklist, catches five-location drift,
partial lookupcode/id syncs, OData `selects` misses, and registration
mirror drift. Treat its blockers as must-fix.
        |
[invoke `post-edit-verification` for description/JSON/schema hygiene]

阶段详情

阶段 1:设置与需求
  1. 按照 ../datex-studio-shared/branch-setup.md 选择分支和连接。切勿假设分支 ID——请用户确认。
  2. 检查对话上下文中是否已有需求简述(由 requirements-gathering 或其他调用技能生成)。
    • 已有简述——使用该简述。简述应明确行实体(或聚合后的行结构)、网格要呈现的列、可筛选/可排序的字段、挂载网格的宿主,以及用户需要的任何行操作/工具栏操作流程。
    • 没有简述——先调用 requirements-gathering 技能。预先确定正确的行结构、可筛选性和宿主挂载方式,可避免从头重新编写实体结构位置和动态筛选器注册。
阶段 2:预先确定行数据来源

在编写任何网格之前,先确定行数据的来源。此决定会影响嵌入式数据源的 type(oDataQuery 还是 flows),并改变五处规则。若判断错误,整个数据源都会基于错误的数据来源编写。编写前,请参阅 references/grids.md(数据源结构和五处规则)以及下文阶段 2 中的行数据来源决策。

流数据源结构检查: 网格的行数据源必须采用键控集合结构—— resultIsCollection: true,且同时填充 getListFlow 和 getByKeysFlow,并且 keyDef 非空。只有 getListFlow 的流数据源无法为网格提供数据:操作后的单行刷新会调用 getByKeys,而该方法并不存在。接线前,应根据已实现的方法判断其适用性——请参阅 ../datasource-creator/references/flow-datasources.md →“根据现有流数据源判断适用性”。这不仅是运行时风险——该分支的服务端使用门控会在契约验证时强制检查此要求;如果网格的数据源缺少 getList 或 getByKeys,就会阻止发布。

网格名称是最有力的线索:

  • PascalCase 复数形式(TaskStatuses、Orders、Shipments、Warehouses)——很可能是 Footprint 架构中的 OData 实体。编写前,先使用 schema-explorer 调用 describe entity <Name>(或 search <Name>)。如果能找到该实体,就基于该实体构建 OData 网格。硬性规则: 对于 PascalCase 复数形式的名称,必须先运行 schema-explorer,不得默认使用流类型数据源。
  • snake_case(task_statuses、invoicing_rules、widget_options)——很可能是功能自有的存储组件,或计算/聚合数据源。检查分支中是否存在匹配的存储配置(dxs source explore configs --branch <id> --type storage --search <name>),或者将其视为由功能代码提供数据的流类型数据源。
  • 名称模糊 / 不确定——先调用 schema-explorer。花 5 秒查询,比用户指出实体存在后再重建网格更划算。

如果 schema-explorer 没有返回匹配项,则改用流类型数据源,但要在网格的 description 中或嵌入式数据源旁说明这一选择(以免下一位编写者重复查询)。

自有数据源与独立数据源。 如果某个数据源仅用于为此网格提供行数据,应将其编写为网格 datasources[] 数组中的一个自有条目,而不是单独创建 <name>-datasource 配置。独立数据源适用于多个使用方共享的场景(多个网格 / 表单 / 编辑器 / 操作)。如果某个数据源唯一用途是提供此网格的行结构,就应将其放在网格内部——这样五处规则仅需在一个文件中处理,也能避免意外复用此数据源,导致之后难以修改行结构。辅助数据源(见阶段 3)也应设为自有数据源。如果需求要求使用共享数据源,请改为调用 datasource-creator。这是平台范围内的默认规则,并非网格专属偏好——规则以及允许采用独立数据源的完整条件,请参阅 ../datasource-creator/references/datasources.md → 默认自有。

检查是否选错组件类型。 如果需求实际上是查看/编辑单个实体,请改为调用 editor-creator 并停止。如果需求是收集临时输入(返回 outParams 的对话框),请调用 form-creator 并停止。如果需求是下拉框 / 自动补全字段,请调用 selector-creator 并停止。网格用于以表格形式展示多条记录;其他三种组件不能互相替代。

阶段 3:编写网格主体

根据 references/grids.md → 最小有效骨架 中的骨架编写 body.json。要点如下:

  1. 文件基础信息。 configurationTypeId: 3。referenceName 以 _grid 结尾(例如,task_statuses_grid-grid.json → task_statuses_grid)。文件后缀为 -grid.json,并且要通过通用检查(../datex-studio-conventions/universal-checklist.md)。请参阅 references/grids.md → File Shape at a Glance 和 ../datex-studio-conventions/naming-conventions.md。

  2. 五处位置的基础实体形状规则。 网格中嵌入的 Flow 数据源会在 五 个相互独立的位置携带基础行实体:datasources[0].queryOptionsObjectTypeDef、datasources[0].outParams[result].objectTypeDef、datasources[0].getListFlow.outParams[result].objectTypeDef、datasources[0].getByKeysFlow.outParams[result].objectTypeDef,以及 datasourceConfig.configOutParameters[result].objectTypeDef。此外,还包括 getListFlow / getByKeysFlow 中用于填充新字段的 code 字符串。对于关联字段/自定义字段,请遵循最终确定的输出契约,而不要将派生字段插入基础实体。编辑基础实体形状后,请在文件中搜索一个未更改的相邻字段 ID——新字段必须在每个位置都有对应项。**OData 驱动的网格会改变此规则:**位置 3 和 4 为 null,而第六个仅在运行时生效的位置变得至关重要——datasources[0].queryOptions.selects(以及嵌套字段对应的 expands[].queryOptions.selects)。即使所有类型元数据位置都正确,缺少 selects 仍会导致实体在运行时未定义。请参阅 references/grids.md → Datasource Wiring — Five Places Must Stay in Sync。

  3. 动态筛选和排序——四个位置加列配置。 添加可筛选/可排序字段时,需完成以下事项:(a) 字段存在于上述全部五个实体形状位置;(b) 在 datasources[0].dynamicFilters / dynamicOrderBys(嵌入式数据源侧)和 datasourceConfig.dynamicFilters / dynamicOrderBys(使用方侧)中以相同方式注册字段——两侧不一致会在导入时显示为 Outdated contract. Type mismatch for dynamic filtering/sorting;(c) 配置列的 dynamicFilter(点分隔路径)、dynamicOrderBy、dynamicFilterType(形如 inParam 的叶类型)、dynamicFilterControl(textBox / numberBox / dateBox);(d) 切勿在任何 inParams 列表中声明 $filter / $orderby,也不要将它们添加到 datasourceConfig.configParameters——平台会根据注册信息自动注入类型化版本;手动声明会导致冲突。请参阅 references/grids.md → Dynamic Filters and Sorting。

  4. 动态筛选/排序的应用必须全部启用或全部禁用。 对于 Flow 类型的后端,平台不会自动将 $filter / $orderby 应用到行集。如果注册了筛选/排序 UI,却没有在 getListFlow 代码中应用这些输入,UI 控件就会明显不起作用——这就是错误状态。可接受的最终状态有两种:(A) 完整接入——完成注册和列配置,并在 getListFlow 中使用 ($flow.inParams as any).$filter 和 ($flow.inParams as any).$orderby 执行筛选/排序(规范的 applyDynamicFilter / applyDynamicOrderBy 辅助函数适用于扁平标量路径;集合筛选需要使用 collection-filter-authoring 中介绍的求值器);或 (B) 完全禁用——两侧均不注册 dynamicFilters / dynamicOrderBys,也不配置列。对于 OData 类型的后端,平台会以声明方式将注册信息转换为 OData URL——状态 A 为默认状态。规范辅助函数请参阅 references/grids.md → Application Is All-Or-Nothing 和 Default In-Memory $filter / $orderby Application。

  5. 数组字段注意事项。 基元数组不能直接作为标量筛选器/排序的目标。对于通过对象集合访问的标量叶字段,请改用 collection-filter-authoring;排序仍仅适用于标量。若要筛选拼接后的数组显示值,请在旁边添加一个标量辅助字段(例如,accounts_display = accounts.join(', '));列仍使用按数组索引访问的显示值($row.entity.accounts[0]);dynamicFilter 则指向辅助字段。辅助字段必须出现在实体形状的全部五个位置,并在流程代码中填充。参见 references/grids.md → 数组字段注意事项——标量辅助字段模式。

  6. 工具栏项具有多态性。 每个 topToolbar / toolbar 条目的 type 决定平台读取哪个同级 <type>Config 块——button → buttonConfig、selectBox → selectBoxConfig 等。所有其他同级配置块都必须明确设为 null。分隔符条目的所有配置均为 null。点击处理程序位于类型专属配置块内(buttonConfig.clickFlowConfig.flowId),而不是条目顶层。每个工具栏按钮都应遵循以下三项约定:(a)破坏性操作应设置 buttonDefaultStyleClass: "destructive";(b)受选择状态控制的按钮应在 buttonConfig 中初始化 readOnly: true(以便在首次触发 on_select_row 事件前以禁用状态挂载),并由 on_select_row 实时切换状态;(c)没有实用工具提示的按钮应设置 tooltip: "''"(TS 空字符串字面量),以阻止平台回退显示标签。动态工具提示应通过已声明的 $grid.vars.<name> 设置——在流程代码中直接赋值给 .tooltip 不会生效。参见 references/grids.md → 工具栏项。

  7. 次要(丰富数据)数据源。 网格的 datasources[] 数组可以包含主数据源之外的其他数据源。典型用途:为每行补充主实体中没有的字段(UDF、外部系统字段、外键显示值解析)。在 $grid.rows 加载后,从 on_data_loaded 调用;使用 Id in ${ids} 批量查询,并通过 命令式单元格 API 写入结果。次要数据源不包含 dynamicFilters / dynamicOrderBys——这些配置仅驱动主数据源的筛选界面。次要数据源仍须遵循其自身实体形状的五处位置规则。定制网格大量采用此模式——参见 ../tailoring-overlay/references/tailoring.md。

  8. 命令式单元格 API。 行流程和网格流程可以修改单个单元格:$row.cells.<col>.displayControl.text = "..."(只读模式文本)、$row.cells.<col>.displayControl.value = ...(checkBox / selectBox 等控件的只读模式值)、$row.cells.<col>.editControl.value = ...(编辑模式值),以及由平台管理的 $row.cells.<col>.editControl.isChanged 布尔值。两种惯用方式:在 on_data_loaded 中加载后补充数据(通过命令式方式填充值的列,应将 displayControl.<cfg>.value 字符串留空——除非实体确实包含该字段,不要“修正”为 $row.entity.<field>);在 on_save_existing_row 中仅对变更项写入(对于精简的 PATCH 负载,使用 if ($row.cells.<col>.editControl.isChanged) { payload.<X> = $row.cells.<col>.editControl.value; })。参见 references/grids.md → 命令式单元格 API 和 允许空的声明式绑定。

  9. 文本显示绑定在绑定位置强制转换非字符串实体字段。displayControl.textConfig.value 的类型为 string;当 <Id> 为 Edm.Int32、布尔值或日期时,绑定 "$row.entity.<Id>" 会导致导入失败,并报错 Type '<number|boolean|...>' is not assignable to type 'string'。应在绑定位置进行类型转换("$row.entity.Id?.toString()"、"`${$row.entity.Id}`"、"$row.entity.Active ? 'Yes' : 'No'"、"$utils.date.format($row.entity.CreatedSysDateTime, 'MM/DD/YYYY')"),不要在实体架构中转换——实体声明的 type 决定动态筛选和排序运算符的选择。参见 references/grids.md → 文本显示绑定需要字符串值。

  10. **嵌入式数据源的组件标识信封。**嵌入式 datasources[] 条目不只是查询规范,而是一个完整的数据源组件,包含独立 -datasource.json 文件所需的相同标识字段。datasources[0].referenceName 必须与 datasourceConfig.configId 完全一致(查找键);该条目还需要 title、非空且长度不超过 100 个字符的 description、分页或集合数据源的 hasKey: true、hasResult: true、新建项的 id: null、未使用时为 null 的 linkedDatasources / customColumns、已声明的 inParams / outParams / vars / events,以及 accessModifier: "private"。缺少该信封会报错 Invalid contract. Referenced own configuration <name> does not exist or has been renamed,并引发 Cannot find name 'get' / 'getList' / 'inParams' / 'refresh' TypeScript 错误(这些错误都是同一处未解析引用导致的后续问题)。参见 references/grids.md → 嵌入式数据源组件标识信封。

  11. fullTextSearch 和内部 configParameters 合约。fullTextSearch 是不可为 null 的布尔值。设为 true 会挂载内置搜索框;只有通过 datasourceConfig.configParameters 中 value 为 "$grid.fullTextSearch" 的条目进行连接时,其值才会作为 $flow.inParams.full_text_search 传入数据源。不会自动连接。其他在嵌入式数据源中声明的 inParam 也适用相同规则:已声明但没有对应 configParameters 条目的 inParam 在运行时不会绑定,依赖它的筛选条件永远不会触发,导入时还会标记网格与其嵌入式数据源之间的合约不匹配。参见 references/grids.md → datasourceConfig.configParameters — 向数据源传递输入。

  12. **必须声明 $grid.vars / $row.vars。**流程代码中使用的每个 $grid.vars.<id> 都必须在顶层 vars[] 数组中声明;每个 $row.vars.<id> 都必须在 rowVars[] 中声明。其属性描述符结构与 inParams / outParams 相同。缺少声明会导致导入失败,并报错 Property 'vars' does not exist on type 'IGrid'(或 IRow)。

  13. **同级 *Config 键显式设为 null。**在每个 displayControl、editControl、工具栏条目和筛选条目中,只填充当前生效的子配置,其余配置均显式设为 null。结构差异比较时应遵循此约定——现有网格组件会保留所有同级配置槽,并将其设为 null。

  14. 遵循调用层级规范。网格代码通过 $flows.<Package>.<fn> 调用函数;该函数再通过 $apis.<Package>.FootprintApi.extendedActions.<action_name>({...}) 调用 CRUD 操作。Utilities 中内置的 crud_create_entity / crud_update_entity / crud_delete_entity 是常规持久化路径。网格代码不得直接调用操作。参见 ../datex-studio-runtime/calling-conventions.md。

  15. 从 Hub 挂载。 Hub 会在 tabs[].contentConfig 中嵌入网格(当 contentType: "grid" 时)。挂载配置自带 configId 和 moduleId——moduleId 对应的是网格的包,而不是承载它的 Hub 的功能文件夹。挂载配置上的 configParameters 会传入网格自身的 inParams(网格声明的每个 inParam 在宿主中都必须有对应条目);configEvents 用于订阅网格发出的事件。调用 component-wiring-check,审查两侧的引用契约——外部的宿主 → 网格契约,以及内部的网格 → 嵌入式数据源契约。请参阅 ../component-wiring-check/references/component-wiring.md 和 references/grids.md → 从 Hub 挂载网格。

阶段 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 grid -b <branchId> -D body.json

# For a new grid
dxs configuration upsert grid -b <branchId> -D body.json

# For modify-existing (round-trip — never skip the jq extract)
dxs configuration get grid <configId> -b <branchId> -O envelope.json
jq .json envelope.json > body.json
# ... edit body.json ...
dxs configuration upsert grid -b <branchId> -D body.json

验证会在推送前发现缺失的必填字段、格式错误的参数描述符结构、未定义的 flow-id 引用和引用错误。但它无法发现五处位置不一致、OData selects 漏项、datasources[0] 与 datasourceConfig 之间动态筛选器注册镜像不一致、lookupcode/id 部分同步、动态筛选器/排序半接线(状态 C)、未包装的 TypeScript 表达式插槽,或需要强制转换的文本显示绑定——这些属于结构性或行为性问题,只有在上传时验证或运行时才会显现。推送前请逐项检查下方的预检清单,并务必调用 grid-validator(阶段 5)。

阶段 5:使用 grid-validator 验证(必须)

任何网格编辑——新增网格、更改实体结构、更改筛选器/排序注册、更改工具栏、更改次级数据源,甚至只调整标签——都必须在完成后对受影响的网格调用 grid-validator 子代理。网格存在的静默失败陷阱比任何其他组件都多(五处位置规则、双处注册镜像、OData selects 的第六处位置、lookupcode/id 部分同步、筛选器/排序半接线错误状态);通用的 component-validator 并不了解这些问题。

将网格的文件路径或 configId 交给 grid-validator 子代理。它会重新运行完整的预检清单,标出你可能遗漏的不一致(尤其是五处位置不一致),并返回待办清单,而不是重写结果。在宣布网格完成之前,必须解决它报告的阻塞项。跳过这一步,就会让部分同步错误进入上传时验证。

如果运行中的应用可用,还要通过网格的正常挂载路径(承载该网格的 Hub 选项卡)打开网格并确认:数据行已从后端数据源填充;列正常渲染(预期通过命令式方式补充值的列不会为空);筛选器/排序控件会作用于数据行集合(状态 A,而不是状态 C);工具栏中受选择控制的按钮在挂载时处于禁用状态,并在首次选择后启用;$grid.events.outParamsChange.emit() 会将变更传递给宿主。

如果运行中的应用不可用,则重新获取配置(使用修正后的 jq .json 提取模式),并与 body.json 比较差异,以确认推送已生效。

预检清单

推送前,请逐项检查此预检清单(并调用 grid-validator 执行深入的网格专属检查)。快速版如下:

  1. 文件名、后缀和 referenceName 一致。 description 非空且不超过 100 个字符。设置了 accessModifier。
  2. 特定于网格的数据源应嵌入,而不是独立存在。 分支上独立的 <grid-specific-name>-datasource 配置是一个问题信号——应将其提升为网格内部自有的条目,或确认确实有其他组件使用它。
  3. 嵌入式数据源包含完整的组件身份信息——referenceName 与 datasourceConfig.configId 匹配,包含 title、description、hasKey: true、hasResult: true、新建时 id: null、linkedDatasources / customColumns(未使用时为 null)、已声明的 inParams / outParams / vars / events,以及 accessModifier: "private"。
  4. 文本显示绑定在绑定位置转换非字符串实体字段。 用 ?.toString() 转换 Edm.Int32,用三元表达式转换 boolean,用 $utils.date.format(...) 转换 Date——切勿为了满足显示绑定而更改实体架构中声明的 type。
  5. 基础实体形状的位置保持同步。 对于从返回行中读取的字段,搜索相邻字段的 id;新字段出现的次数必须一致。对于由 OData 支持的网格,确认 queryOptions.selects 包含该字段(位置 3 和 4 为 null);嵌套字段放在 expands[].queryOptions.selects 中。显式筛选专用的 OData 导航可以保持未展开且不出现在输出中;应注册其元数据解析后的嵌套筛选树。
  6. 动态筛选/排序接线: 在两个注册位置都添加字段(datasources[0].dynamicFilters 和 datasourceConfig.dynamicFilters);为已启用的筛选器设置每列的纯路径 dynamicFilter / dynamicFilterType / dynamicFilterControl,仅为受支持的标量排序设置 dynamicOrderBy;任何 inParams 或 configParameters 中都不能有 $filter / $orderby。
  7. 动态筛选/排序的应用必须全有或全无。 对于 flow 类型的后端,要么完整接线(注册 + 每列设置 + 在 getListFlow 中应用),要么完全禁用(移除注册和每列接线)。状态 C——已注册 UI 控件,但 flow 代码未应用筛选/排序——就是错误状态。
  8. 基元数组使用标量辅助字段进行联接显示筛选/排序。对象集合的标量叶字段使用 collection-filter-authoring,包括支持量词的执行逻辑。排序仍仅支持标量。
  9. 工具栏点击处理程序位于特定类型的配置块内,而不是条目顶层。同级配置块须显式设为 null。分隔符条目的所有配置都设为 null。
  10. 破坏性按钮设置 buttonDefaultStyleClass: "destructive";没有工具提示的按钮设置 tooltip: "''"(空 TS 字面量),以免平台回退到标签。动态工具提示绑定到已声明的 $grid.vars.<name>——直接在 flow 代码中赋值 .tooltip 不起作用。
  11. on_select_row 控制依赖选择状态的工具栏按钮的 readOnly(实时切换),并且这些按钮还要在 buttonConfig 中静态设置 readOnly: true,确保首次选择事件触发前按钮处于禁用状态。
  12. 通过命令式方式填充的单元格,其 displayControl.<cfg>.value 为空——除非实体确实包含该字段,否则不要“修正”为 $row.entity.<field>。
  13. flow 代码中写入的每个 $grid.vars.<id> 和 $row.vars.<id>,都要在网格顶层的 vars[] / rowVars[] 数组中声明。
  14. 外部宿主契约。 网格声明的每个 inParam 都必须在宿主的 configParameters 中有对应条目;网格未声明的参数不得有额外条目。宿主引用上的 moduleId 必须是网格的包,而不是宿主的包。
  15. 内部数据源契约。 网格自身的 datasourceConfig.configParameters 必须涵盖嵌入式数据源声明的每个 inParam——包括 fullTextSearch: true 时的 full_text_search(不会自动接线;需要显式添加 value: "$grid.fullTextSearch" 条目)。
  16. 每次写入宿主已订阅的 $grid.outParams.* 后,都要调用 $grid.events.outParamsChange.emit()。
  17. 调用层级合规——网格代码通过 $flows.<Package>.<fn> 调用函数;函数通过 $apis.<Package>.FootprintApi.extendedActions.<action_name>({...}) 封装 CRUD 操作;网格不得直接调用操作。
  18. 调用 grid-validator。 必须执行——详见下文。

Common Mistakes

错误修正
添加了 _lookupcode 列,却忘了在五个实体结构位置中添加配套的 _id存储列通常成对出现:owner_id(数字)+ owner_lookupcode(字符串)。即使只有 lookupcode 有可见列,数字 id 仍需出现在全部五个位置中,否则 configOutParameters[result].objectTypeDef 和 outParams[result].objectTypeDef 对 id 是否存在的定义就会不一致。每个新增存储字段都要添加到全部五个位置,而不只是有可见列的字段。
只修改了五个实体结构位置中的三个很容易漏掉 getListFlow.outParams[result].objectTypeDef 和 getByKeysFlow.outParams[result].objectTypeDef,因为它们嵌套在看起来无关的 flow 定义中。其实它们并非无关——它们声明了 flow 返回的内容,而 datasource 自身的 outParams 会从中获取定义。
使用 OData 的网格:字段已添加到 queryOptionsObjectTypeDef 和 configOutParameters,却漏了 queryOptions.selects类型元数据声明了该字段;实际获取该字段的是 selects。漏掉第六个位置会导致运行时实体未定义——网格可以顺利导入,却渲染出空列;如果绑定表达式解引用未定义值,首次渲染行时还可能抛出异常。对于嵌套字段,请添加到 expands[].queryOptions.selects。
已注册动态筛选 UI,但 getListFlow 未应用 $filter / $orderby(状态 C)要么完整接线(针对 ($flow.inParams as any).$filter / $orderby 应用 applyDynamicFilter / applyDynamicOrderBy),要么完全禁用(移除注册项和各列的接线)。能显示却不起作用的半成品 UI 控件就是错误状态。
datasources[0].dynamicFilters 和 datasourceConfig.dynamicFilters 不一致导入错误:Datasource <id>: Outdated contract. Type mismatch for dynamic filtering/sorting。两个注册位置必须完全一致——在同一次编辑中更新它们。
在 getListFlow.inParams 或 datasourceConfig.configParameters 中声明 $filter / $orderby平台会根据注册项自动注入带类型的版本。手动声明会产生冲突。请移除这些声明;在应用位置使用 ($flow.inParams as any).$filter 进行类型转换。
标量筛选/排序直接针对原始数组对联接显示匹配使用标量 sidecar;对于对象集合中的标量叶字段,请使用 collection-filter-authoring。量化筛选并不会启用集合排序。
工具栏项的 clickFlowConfig 放在项的顶层点击处理器必须放在特定类型的配置块内(buttonConfig.clickFlowConfig.flowId)。放在顶层会被静默忽略。
Id 为 Edm.Int32 时,displayControl.textConfig.value 设为 "$row.entity.Id"导入错误:Type 'number' is not assignable to type 'string'。在绑定位置进行转换("$row.entity.Id?.toString()")——不要把实体声明的 type 改成 string,否则会破坏动态筛选/排序运算符的选择。
将通过命令式方式填充的列“修正”为 $row.entity.<field>此修正会在每次渲染时覆盖命令式填充值。对于值来自 on_data_loaded 丰富处理的单元格,displayControl.<cfg>.value 为空字符串是合理的。
受选择状态控制的工具栏按钮挂载时处于启用状态,尚未选择任何行时也能点击on_row_selected flow 只会在选择状态发生变化后触发。请在 buttonConfig 中静态设置 readOnly: true,并在 on_select_row 中实时控制——双重保险。
嵌入式 datasources[0].referenceName 与 datasourceConfig.configId 不匹配导入错误:Invalid contract. Referenced own configuration <name> does not exist or has been renamed,随后还会引发一连串 Cannot find name 'get' / 'getList' / 'inParams' / 'refresh' TS 错误。修复标识信息后,整串错误就会消失。
fullTextSearch: true 且 datasource 声明了 full_text_search inParam,但网格中没有对应的 configParameters 项搜索框会挂载,但值永远不会传到 datasource。请将 { parameter: { id: "full_text_search", ... }, value: "$grid.fullTextSearch" } 添加到 datasourceConfig.configParameters。
flow 代码中使用 $grid.vars.<id> = ... 或 $row.vars.<id> = ...,但 vars: null / rowVars: null导入错误:Property 'vars' does not exist on type 'IGrid' / IRow。请在顶层数组中声明每个 var / rowVar,并使用与 inParams/outParams 相同的属性描述符结构。
flow 代码给 $grid.topToolbar.<id>.buttonConfig.tooltip(或列 / 筛选器的 .tooltip)赋值,但提示文本没有变化.tooltip 只能通过声明配置。请声明 $grid.vars.<name>(字符串),将该位置绑定到 "$grid.vars.<name>",并在 flow 代码中为该 var 赋值。
Hub 挂载项的 moduleId 被设为 hub 的功能文件夹,而不是网格的 package跨组件引用规则——moduleId 始终属于目标模块。网格所在的位置就是其注册位置。请参见 ../component-wiring-check/references/component-wiring.md。
同级的 *Config 配置槽被删掉(例如列的 displayControl 只填充了 textConfig,其他子配置缺失)现有网格会保留所有同级配置槽,并将其设为 null,以便进行结构差异比较。请遵循此约定。
rowSizingType / columnSizingType 被设为无效成员值("fill"、"Fill")错误:Error converting value '...' to type 'System.Nullable\1[...EGridColumnSizingType]'。成员使用 camelCase:"cellsWidth"、"headersWidth"、"fixedWidth"、"fitedWidth"(注意拼写),rowSizingType则使用"default"、"relaxed"、"compact"`。
fullTextSearch 被设为 null 或 { enabled: true }服务端期望的是裸 System.Boolean 值。这两种格式都会导致导入失败。请使用 true 或 false。
将 dxs configuration get -O envelope.json 的输出直接传给 dxs configuration upsert -D envelope.json会静默破坏配置内容。编辑前务必先执行 jq .json envelope.json > body.json。请参见上文“往返规则”。
description 超过 100 个字符SQL 列长度有限制——推送会因验证失败。请缩短。
referenceName 不以 _grid 结尾,或与文件名主体不匹配导入 / 查找会失败。使用 snake_case、_grid 后缀,并确保与文件名主体一致。
推送网格编辑时未调用 grid-validator通用的 component-validator 无法检测五处定义不一致、遗漏 OData selects、动态筛选注册项镜像不一致,或 lookupcode/id 部分同步的问题。务必调用 grid-validator——这是强制步骤。
编辑后,请调用 grid-validator(必须执行——网格包含通用验证器无法检测的信封/文本显示问题)。对于描述/JSON/架构的规范检查,也请调用 post-edit-verification。

评论

支持 Markdown