关于 grid-creator
grid-creator 是用于在分支上创建或修改 Datex Studio 网格的技能。它指导数据源选择、网格配置、验证和上传,并着重确保实体字段与动态筛选设置保持同步。
主要功能
- 01创建和修改 Datex Studio 网格
- 02指导选择 OData 或流程数据源
- 03维护网格实体结构各处同步
- 04配置筛选、排序和交互
- 05通过 dxs 验证并上传配置
适用场景
- 01构建多记录表格视图
- 02为网格添加筛选、排序或行操作
- 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 位置都在这里。
参考资料
- ../datex-studio-shared/branch-setup.md — 分支/连接选择(各技能共用)
- references/grids.md — 权威网格编写参考:文件结构、运行时全局变量、五位置规则、动态筛选器、辅助数据源、命令式单元格 API、预检清单
- ../datex-studio-conventions/file-format.md —
configurationTypeId表和 TypeScript 表达式编码规则 - ../datex-studio-conventions/naming-conventions.md —
_grid后缀、文件名主体匹配、显示名称规则 - ../datex-studio-runtime/runtime-globals.md — 网格代码中可用的平台注入全局变量(
$grid、$row、$flows、$apis、$utils、...) - ../datex-studio-runtime/calling-conventions.md — UI 层调用规则(调用函数,绝不调用操作;通过
$apis.<Package>.FootprintApi.extendedActions.<action_name>执行 CRUD) - ../datasource-creator/references/datasources.md — 数据源分类(变体 × 查询类型),说明行数据的来源
- ../datasource-creator/references/flow-datasources.md — 流类型数据源结构(分页的
getListFlow/getByKeysFlow),用于嵌入网格的数据源 - ../component-wiring-check/references/component-wiring.md — 宿主引用约定、变量必须声明规则、moduleId 规则
- ../tailoring-overlay/references/tailoring.md — 针对定制网格变体和辅助数据源扩充的覆盖模型
- ../collection-filter-authoring/SKILL.md — 通过对象集合中的标量叶节点设置 Any/All 筛选器;包含已验证的 CLI 生成方式和仍待完成的实时集成检查
依赖项
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 同步、ODataselects遗漏以及动态筛选器注册镜像漂移。必须修复其阻止项。
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:设置与需求
- 按照 ../datex-studio-shared/branch-setup.md 选择分支和连接。切勿假设分支 ID——请用户确认。
- 检查对话上下文中是否已有需求简述(由
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。要点如下:
-
文件基础信息。
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。 -
五处位置的基础实体形状规则。 网格中嵌入的 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。 -
动态筛选和排序——四个位置加列配置。 添加可筛选/可排序字段时,需完成以下事项:(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。 -
动态筛选/排序的应用必须全部启用或全部禁用。 对于 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。 -
数组字段注意事项。 基元数组不能直接作为标量筛选器/排序的目标。对于通过对象集合访问的标量叶字段,请改用 collection-filter-authoring;排序仍仅适用于标量。若要筛选拼接后的数组显示值,请在旁边添加一个标量辅助字段(例如,
accounts_display=accounts.join(', '));列仍使用按数组索引访问的显示值($row.entity.accounts[0]);dynamicFilter则指向辅助字段。辅助字段必须出现在实体形状的全部五个位置,并在流程代码中填充。参见 references/grids.md → 数组字段注意事项——标量辅助字段模式。 -
工具栏项具有多态性。 每个
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 → 工具栏项。 -
次要(丰富数据)数据源。 网格的
datasources[]数组可以包含主数据源之外的其他数据源。典型用途:为每行补充主实体中没有的字段(UDF、外部系统字段、外键显示值解析)。在$grid.rows加载后,从on_data_loaded调用;使用Id in ${ids}批量查询,并通过 命令式单元格 API 写入结果。次要数据源不包含dynamicFilters/dynamicOrderBys——这些配置仅驱动主数据源的筛选界面。次要数据源仍须遵循其自身实体形状的五处位置规则。定制网格大量采用此模式——参见 ../tailoring-overlay/references/tailoring.md。 -
命令式单元格 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 和 允许空的声明式绑定。 -
文本显示绑定在绑定位置强制转换非字符串实体字段。
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 → 文本显示绑定需要字符串值。 -
**嵌入式数据源的组件标识信封。**嵌入式
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 → 嵌入式数据源组件标识信封。 -
fullTextSearch和内部configParameters合约。fullTextSearch是不可为 null 的布尔值。设为true会挂载内置搜索框;只有通过datasourceConfig.configParameters中value为"$grid.fullTextSearch"的条目进行连接时,其值才会作为$flow.inParams.full_text_search传入数据源。不会自动连接。其他在嵌入式数据源中声明的 inParam 也适用相同规则:已声明但没有对应configParameters条目的 inParam 在运行时不会绑定,依赖它的筛选条件永远不会触发,导入时还会标记网格与其嵌入式数据源之间的合约不匹配。参见 references/grids.md → datasourceConfig.configParameters — 向数据源传递输入。 -
**必须声明
$grid.vars/$row.vars。**流程代码中使用的每个$grid.vars.<id>都必须在顶层vars[]数组中声明;每个$row.vars.<id>都必须在rowVars[]中声明。其属性描述符结构与inParams/outParams相同。缺少声明会导致导入失败,并报错Property 'vars' does not exist on type 'IGrid'(或IRow)。 -
**同级
*Config键显式设为 null。**在每个displayControl、editControl、工具栏条目和筛选条目中,只填充当前生效的子配置,其余配置均显式设为null。结构差异比较时应遵循此约定——现有网格组件会保留所有同级配置槽,并将其设为 null。 -
遵循调用层级规范。网格代码通过
$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。 -
从 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 执行深入的网格专属检查)。快速版如下:
- 文件名、后缀和
referenceName一致。description非空且不超过 100 个字符。设置了accessModifier。 - 特定于网格的数据源应嵌入,而不是独立存在。 分支上独立的
<grid-specific-name>-datasource配置是一个问题信号——应将其提升为网格内部自有的条目,或确认确实有其他组件使用它。 - 嵌入式数据源包含完整的组件身份信息——
referenceName与datasourceConfig.configId匹配,包含title、description、hasKey: true、hasResult: true、新建时id: null、linkedDatasources/customColumns(未使用时为 null)、已声明的inParams/outParams/vars/events,以及accessModifier: "private"。 - 文本显示绑定在绑定位置转换非字符串实体字段。 用
?.toString()转换Edm.Int32,用三元表达式转换boolean,用$utils.date.format(...)转换Date——切勿为了满足显示绑定而更改实体架构中声明的type。 - 基础实体形状的位置保持同步。 对于从返回行中读取的字段,搜索相邻字段的 id;新字段出现的次数必须一致。对于由 OData 支持的网格,确认
queryOptions.selects包含该字段(位置 3 和 4 为null);嵌套字段放在expands[].queryOptions.selects中。显式筛选专用的 OData 导航可以保持未展开且不出现在输出中;应注册其元数据解析后的嵌套筛选树。 - 动态筛选/排序接线: 在两个注册位置都添加字段(
datasources[0].dynamicFilters和datasourceConfig.dynamicFilters);为已启用的筛选器设置每列的纯路径dynamicFilter/dynamicFilterType/dynamicFilterControl,仅为受支持的标量排序设置dynamicOrderBy;任何inParams或configParameters中都不能有$filter/$orderby。 - 动态筛选/排序的应用必须全有或全无。 对于 flow 类型的后端,要么完整接线(注册 + 每列设置 + 在
getListFlow中应用),要么完全禁用(移除注册和每列接线)。状态 C——已注册 UI 控件,但 flow 代码未应用筛选/排序——就是错误状态。 - 基元数组使用标量辅助字段进行联接显示筛选/排序。对象集合的标量叶字段使用 collection-filter-authoring,包括支持量词的执行逻辑。排序仍仅支持标量。
- 工具栏点击处理程序位于特定类型的配置块内,而不是条目顶层。同级配置块须显式设为
null。分隔符条目的所有配置都设为null。 - 破坏性按钮设置
buttonDefaultStyleClass: "destructive";没有工具提示的按钮设置tooltip: "''"(空 TS 字面量),以免平台回退到标签。动态工具提示绑定到已声明的$grid.vars.<name>——直接在 flow 代码中赋值.tooltip不起作用。 on_select_row控制依赖选择状态的工具栏按钮的readOnly(实时切换),并且这些按钮还要在buttonConfig中静态设置readOnly: true,确保首次选择事件触发前按钮处于禁用状态。- 通过命令式方式填充的单元格,其
displayControl.<cfg>.value为空——除非实体确实包含该字段,否则不要“修正”为$row.entity.<field>。 - flow 代码中写入的每个
$grid.vars.<id>和$row.vars.<id>,都要在网格顶层的vars[]/rowVars[]数组中声明。 - 外部宿主契约。 网格声明的每个 inParam 都必须在宿主的
configParameters中有对应条目;网格未声明的参数不得有额外条目。宿主引用上的moduleId必须是网格的包,而不是宿主的包。 - 内部数据源契约。 网格自身的
datasourceConfig.configParameters必须涵盖嵌入式数据源声明的每个 inParam——包括fullTextSearch: true时的full_text_search(不会自动接线;需要显式添加value: "$grid.fullTextSearch"条目)。 - 每次写入宿主已订阅的
$grid.outParams.*后,都要调用$grid.events.outParamsChange.emit()。 - 调用层级合规——网格代码通过
$flows.<Package>.<fn>调用函数;函数通过$apis.<Package>.FootprintApi.extendedActions.<action_name>({...})封装 CRUD 操作;网格不得直接调用操作。 - 调用
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。 |