关于 nocobase-dsl-reconciler
nocobase-dsl-reconciler 是用于通过已提交的 YAML DSL 文件构建 NocoBase 应用并通过 CLI 部署的可选技能。它引导从 starter 增量构建,在部署前验证规范,并区分 DSL 开发与实时 UI 修改。
主要功能
- 01从 starter 模板搭建应用
- 02用 YAML 定义集合、路由、页面和弹窗
- 03通过 CLI 推送变更到 NocoBase
- 04按组或页面定向增量部署
- 05部署前验证 DSL 规范
适用场景
- 01以已提交的 YAML 构建 CRUD 模块
- 02分阶段扩展应用中的集合和页面
- 03编写 YAML 前规划多集合结构
- 04将 DSL 变更部署到指定页面
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nocobase/ skills --skill nocobase-dsl-reconciler安装后在 Claude Code 中输入/nocobase-dsl-reconciler确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
exports/crm-opportunities/structure.yamlexports/crm-opportunities/js/opportunities_filterForm_0_pipeline_stats_filter_block.jsexports/crm-v2/enhance.yamlexports/crm-v2/charts/analytics_chart_1.jsonanalytics_chart_2.jsonanalytics_chart_3.jsonanalytics_chart_4.jsonanalytics_chart_5.jsonexports/crm-v2/js/analytics_filterForm_0_js_0.jsanalytics_jsBlock_6.jsanalytics_jsBlock_7.jsanalytics_jsBlock_8.jsanalytics_jsBlock_9.jscustomers_filterForm_1_customer_stats_filter_block.jscustomers_popup_name_tab0_contacts_contact_row_column_customer_360.jscustomers_popup_name_tab0_details_0_customer_statistics_block.jscustomers_popup_name_tab0_details_0_item_profile_customer_profile_header.jscustomers_popup_name_tab0_details_1_ai_smart_profile_block.jscustomers_popup_name_tab0_opportunities_opportunity_row_column_customer_360.jscustomers_table_0_col_health_score.jscustomers_table_0_col_last_activity.jsleads_filterForm_1_leads_stats_filter_block.jsleads_popup_name_tab0_details_0_lead_badges_row_component.jsleads_popup_name_tab0_details_0_lead_key_stats_block.jsleads_popup_name_tab0_details_5_lead_stage_flow_block.jsleads_popup_name_tab0_lead_insights_lead_ai_insights_block.jsleads_popup_name_tab0_lead_owner_lead_profile_header_block.jsleads_table_0_col_ai_score.jsleads_table_0_col_contact.jsleads_table_0_col_created.js详细介绍
NocoBase 应用构建器(DSL 路径)
使用此技能前
这是可选的 DSL 路径。默认使用 nocobase-ui-builder。
只有当用户明确需要可以提交的 YAML 文件,并希望执行 cli push 时,才继续使用此技能。如果你是从一般的“帮我构建一个 NocoBase 应用”请求进入此处,且用户没有提到 DSL/YAML/git,请改用 nocobase-ui-builder——这是默认入口。
黄金法则
templates/crm/ 是只读参考库。如果不确定某个字段、区块或弹窗的具体结构,请打开最接近的 CRM 示例,阅读后在自己的工作区中编写适配版本。下方的工作流章节中提供了各场景的参考指引。
切勿将 CRM 文件整体复制到工作区。不要使用 cp -r templates/crm/... 开始构建,不要复制 collections/nb_crm_*.yaml,也不要以 CRM 的文件为基础编写自己的 routes.yaml。批量复制会把无关的线索、商机、订单状态和工作流带入项目——之后整个开发过程都会耗费在处理数百条无关的验证错误上,而不是构建你的模块。
部署前的规范验证器可以发现大多数结构性错误,并给出清晰的错误消息。相信验证器:出现错误时,按提示修复,不要猜测——不要用 grep 搜索 src/deploy/*.ts。
不适用此技能的情况
如果用户的请求与此技能无关,请转交给对应技能:
| 用户请求…… | 技能 |
|---|---|
| 对已运行页面进行一次性的实时 UI 调整(移动 / 重新排序 / 重新配置单个区块、字段、操作)——不需要提交 DSL | nocobase-ui-builder |
| ACL / 角色权限 / 路由权限 | nocobase-acl-manage |
| 创建 / 更新 / 修订 / 执行工作流 | nocobase-workflow-manage(此技能只负责接入触发按钮;工作流图的编写由该技能处理) |
| 在 DSL 项目之外编写集合 / 字段 / 关联 | nocobase-data-modeling |
插件开发(.tsx 组件、服务端代码) | nocobase-plugin-development |
| 安装 / 启用插件 | nocobase-plugin-manage |
| 环境配置 / 应用安装 / 升级 | nocobase-env-manage |
任何应该以已提交 YAML 文件的形式保存在 workspaces/<project>/ 下的变更——都属于此技能的范围。
环境
cd <skill-dir>/src
export [email protected] NB_PASSWORD=admin123 NB_URL=http://localhost:14000
快速开始——新建项目
“帮我构建一个 NocoBase 应用”的默认路径:复制起始模板并进行修改。不要手动编写骨架,也不要先研究 CRM。
cd <skill-dir>/src
cp -r ../templates/starter ../workspaces/<name>
# First push: --copy bypasses validation rules that only matter once
# popups exist (m2o popup binding, clickToOpen file presence). The
# starter ships with its own popups so the first push is actually fully
# valid — --copy is for your *extensions* before popups are wired.
npx tsx cli/cli.ts push <name> --force
起始模板是完整的最小 CRUD,包含 1 个集合(Projects)、1 个仪表板页面(4 个 KPI 磁贴 + 2 个图表)、1 个列表页面(包含 filterForm / table / addNew 弹窗 / detail 弹窗 / 2 个 updateRecord recordActions)。按原样推送 → 在 NB 中查看 → 然后编辑。
自定义起始模板(敏捷迭代流程)
每次只迭代一个事项,并在每次迭代之间推送:
- 重命名标识符,使其符合用户的业务领域。
collections/nb_starter_projects.yaml→ 改为你的集合名称(遵循nb_<module>_<entity>约定)routes.yaml:更改 "Starter" / "Projects" 标题- 如果需要,可更改
pages/starter/目录名称(与routes.yaml键保持一致) - 在所有文件中查找并替换
nb_starter_projects
- 调整集合 YAML 中的字段列表,使其符合用户的实体。相应更新
pages/<...>/layout.yaml中的fields:和弹窗模板中的field_layout:。 - 仅在第一个实体端到端运行正常后,才添加第二个实体。
创建新的
collections/*.yaml和pages/<module>/<entity>/目录,并以pages/starter/projects/layout.yaml为起点。 - 逐步扩展:添加标签页、图表或工作流触发器。每次变更后都推送。下方的“高级模式”介绍了各类模式对应的 CRM 文件。
**切勿一次性编写整个模块。**对于面向客户的构建,先提交骨架、展示给用户、收集反馈,再逐步迭代。推送起始模板只需几分钟;手动构建整个模块则需要数小时。
快速上手:何时适合使用 --copy
当工作区中还没有弹窗文件时,传入 --copy(早期阶段——验证器会报错,指出“m2o field X has no popup binding”,用户会在下一次推送时修复)。在这种状态下,协调器会自动绕过规范错误。一旦存在任何 popups/*.yaml 文件,就移除 --copy,让验证正常运行。
增量编辑——现有工作区
- 添加区块/字段/操作/弹窗 → 编写 DSL → 推送。
- 从 DSL 中移除 → 推送。协调器会销毁 NB 端对应的实时模型,并清理
state.yaml。手动通过 NB-UI 创建的元素(未在state.yaml中跟踪)会保留。 - 重命名 → 不支持自动处理。先删除,再重新添加。
定向推送:
--group <key>将范围限定为一个菜单子树--page <key>将范围限定为一个页面--incremental跳过自上次推送以来 DSL 未发生变化的页面
如果只是调整实时 UI,且没有提交 DSL,请改用
nocobase-ui-builder(参见上方的路由表)。
高级工作流——当入门模板不够用时
需要超出敏捷入门流程的情况:
- 集合超过约 3 个,且存在交叉关系(m2m、树形结构)
- 用户希望提前设计专用的工作流/审批/权限逻辑
- 多标签页、子表或跨模块导航
- 仪表板包含映射到用户领域语言的定制 KPI
流程:第 0 轮设计 → 第 1 轮搭建脚手架 → 第 2 轮填充 → 第 2' 轮填充种子数据 → 第 3 轮 JS。每一轮的状态都可部署。
第 0 轮:系统架构——与用户确认
编写 DESIGN.md(Markdown,而非 YAML),涵盖:
- 集合——每张表、其字段及其关系。
字段接口参考请参阅
nocobase-data-modeling技能。 - 页面列表——每个页面及其一句话用途,按菜单分组。
- 导航连线——哪些 m2o 字段会打开哪些弹窗模板;哪些页面彼此链接。
编写 YAML 前,先等待用户确认。一次设计就能减少 3 倍的返工。
如果用户的需求符合入门模板的形态(单个实体、基础 CRUD),则跳过第 0 轮。
示例:
## Collections
- nb_lib_books (title, author, isbn, category, status, loans: o2m → nb_lib_loans)
- nb_lib_members (name, email, phone, join_date, loans: o2m → nb_lib_loans)
- nb_lib_loans (loan_no, book: m2o, member: m2o, borrowed_at, due_date, returned_at, status)
## Pages (under menu "Library")
- Books list — browse + search books, add new
- Members list — browse members, their loan history
- Loans list — active/overdue loans, return action
- Dashboard — KPIs + charts
## Navigation
- books.table.title → books detail popup (shared template)
- loans.table.book → books detail popup (shared via defaults.yaml)
- loans.table.member → members detail popup (shared via defaults.yaml)
第 0.5 轮:子 Agent 的 CWD(仅在启动子 Agent 时)
如果启动子 Agent(kimi TUI、Claude Code 子进程),其 CWD 会成为默认写入目标。启动前请设置好:
mkdir -p <user-workdir>
cd <user-workdir>
kimi --yolo # or claude, codex
如果跳过 cd,Agent 就会写入父项目根目录。
第 1 轮:搭建脚手架——仍从入门模板开始
即使采用高级流程,也不要从头手写 routes.yaml +
collections/*.yaml。复制入门模板,再逐步扩展:
| 步骤 | 操作 |
|---|---|
| 基础 | cp -r templates/starter workspaces/<name>——推送一次 |
| 添加集合 | 编写 collections/<next_coll>.yaml(格式与入门模板一致)。遵循 nb_<module>_<entity> 命名约定。 |
| 添加页面 | mkdir pages/<module>/<page>/ + layout.yaml;参照入门模板的 projects 布局。 |
| 更新路由 | 在 routes.yaml 中现有分组下添加条目。 |
| 部署 | 每次添加集合/页面后,执行 cli push <name> --force |
CRM 参考(仅在结构方面遇到困难时查阅):
templates/crm/collections/nb_crm_leads.yaml——集合格式templates/crm/routes.yaml——多分组路由(仅结构)
第 2 轮:填充内容——区块、弹出窗口、模板
对于入门基础之外的每个页面:
| 构建内容 | CRM 参考 |
|---|---|
| 主列表表格 + 筛选器 | templates/crm/pages/main/leads/layout.yaml |
| 多标签页 | templates/crm/pages/main/customers/(page.yaml + tab_*/layout.yaml) |
| 带有行内子表(用于 o2m 子项)的创建表单 | templates/crm/templates/block/form_add_new_opportunities_quotations_quotations.yaml——items 是在 fields: 中列出的 o2m 字段,并以行内可编辑子表的形式呈现。另请参考 templates/crm/pages/main/products/ 中的主项/子项 UX。 |
| 详情弹出窗口模板 | templates/crm/templates/popup/activity_view.yaml |
| m2o 自动弹出窗口绑定 | templates/crm/defaults.yaml |
| 父级详情 + 子项列表弹出窗口 | templates/crm/pages/main/customers/tab_customers/popups/ |
| addNew + 字段点击弹出窗口模式 | templates/crm/pages/main/leads/popups/ |
从 CRM 复制时的规则:
- 复制 10–30 行,并调整名称。切勿复制整个文件。
- 不要复制
uid:/targetUid:/route_id:——部署器会分配新的值。 - 对于表格中显示的每个 m2o 字段,请设置
clickToOpen: templates/popup/popup_detail_<target>.yaml,或在defaults.yaml中添加popups.<target>: ...。否则验证器会报错。
每行操作(recordActions)
默认情况下,表格的行操作列为空——除非 recordActions: 中列出了操作按钮,否则 NB 不会渲染任何操作按钮。“只提供编辑和删除”是常见但不够理想的默认设置:对于大多数列表表格,用户真正需要的是紧挨着编辑按钮的一键状态更改(标记为已完成、批准、归档)。
决策顺序:
- 是否有布尔值/枚举类型的状态字段? → 添加带有
linkageRules的updateRecord,仅在记录处于相应状态时显示匹配的按钮。每种状态转换对应一个按钮(已完成时隐藏“标记为已完成”;未完成时隐藏“重新打开”)。 - 记录是否需要与默认编辑弹窗不同的第二个详情/表单视图? → 使用带有
templateRef的popup。 - 是否为树形/层级集合? → 添加
addChild。 - 是否需要携带此记录的 id/筛选条件导航到其他位置? → 使用
link,并设置url: /admin/...?filter={{ctx.record.id}}。 - 是否要让用户克隆复杂记录? → 使用
duplicate。 - 是否需要手动启动工作流? → 使用
workflowTrigger。
对于状态更改,优先使用 updateRecord + linkageRules,而不是自定义 JS 按钮。linkageRules 无需接触 JS,即可覆盖 80% 的行级 UX(条件显示/隐藏、基于字段的限制、通过 ctx.user 进行角色检查)。只有多步骤逻辑(查询,然后更新,再导航)才需要 JS 操作,而当前此协调器无法部署这类操作。
完整的每行操作选项(在 recordActions: 中声明):
DSL type | 用途 | CRM 参考 |
|---|---|---|
edit | 编辑弹窗(默认样式) | 多处 |
view | 只读详情弹窗 | templates/crm/templates/popup/opportunity_view.yaml |
delete | 删除单行并确认 | 多处 |
updateRecord | 赋值字段并可选配置 linkageRules——状态更改的主力操作 | templates/crm/pages/main/overview/layout.yaml(已完成/未完成按钮对) |
popup | 使用 templateRef 打开自定义弹窗(不同于编辑的表单/详情) | templates/crm/pages/main/leads/popups/table.name.yaml |
link | 导航到另一个管理页面,并在 URL 中携带记录上下文 | templates/crm/pages/lookup/layout.yaml |
addChild | 树形集合:在此行下添加子节点 | templates/crm/pages/main/products/tab_categories/layout.yaml |
duplicate | 将记录克隆到新表单中 | — |
workflowTrigger | 手动触发此记录上的工作流 | (templates/crm/pages/main/customers/tab_customers/layout.yaml 中的工具栏——相同样式也适用于每行) |
historyExpand / historyCollapse | 记录历史插件的行内展开 | — |
ai | AI 员工按钮(tasks_file + employee) | templates/crm/pages/main/leads/layout.yaml(工具栏——相同样式也适用于每行) |
工具栏操作(在区块级 actions: 中声明)使用相同的类型名称,另加 filter、refresh、addNew、bulkDelete、export、import。并非每种类型都适用于这两种上下文——作为工具栏操作的 updateRecord 不会针对任何特定行,因此应将其放在 recordActions 中。
第 2 轮目标:所有页面都具备可用的 CRUD——正确连接新增/编辑/查看弹窗,行操作列反映真实的逐条记录操作(而不只是编辑和删除)。验证器无报错,NB UI 不显示“集合可能已删除”横幅。
第 2′ 轮:测试数据(与第 2 轮并行)
可与填充页面并行运行。通过 API 插入数据:
TOKEN=$(curl -sS -X POST $NB_URL/api/auth:signIn \
-H 'Content-Type: application/json' -H 'X-Authenticator: basic' \
-d '{"account":"'$NB_USER'","password":"'$NB_PASSWORD'"}' \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["data"]["token"])')
# Always GET existing record IDs first — they're snowflake integers
# (e.g. 359571523764224), NEVER 1/2/3.
curl -sS -X POST $NB_URL/api/<collection>:create -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{...fields..., "owner":{"id": <real-user-id>}}'
先处理父表;为子表填写所有外键(留空会导致该行成为孤儿)。然后:
npx tsx cli/cli.ts verify-data <name> # FK & completeness check
为何并行:第 3 轮的 JS(图表、KPI)需要数据才能渲染内容。创建好集合后(第 1 轮结束时)就开始填充;到第 2 轮完成页面时,你就有记录可供测试。
第 3 轮:JS——CRM 用到的地方,你大概也需要
现在 CRUD + 数据已经能用了,接下来检查 JavaScript 能发挥作用的地方。浏览 CRM 模板,并针对项目中的每个位置询问“CRM 在这里有 JS 吗?” 三种典型的 JS 用途:
| 位置 | CRM 有 JS 吗? | 如果……,你的项目可能需要 JS |
|---|---|---|
| 字段渲染器 / 列(例如,颜色编码的状态标签、距离到期日的天数徽标) | ✅ 大多数列表表格中都有 | 字段显示依赖派生值(日期计算、状态转颜色、多字段组合) |
| 区块(KPI 卡片、表单内的自定义小组件) | ✅ 概览、分析、各表单中的提示 | 需要一个汇总小组件、内嵌图表,或从多个集合读取数据的“辅助面板” |
| 仪表板页面(包含图表 + KPI 的整页) | ✅ 分析页面 | 模块至少有 3 个用户关心的可衡量指标;对于标题为“Dashboard” / “Analytics”的页面,验证器要求至少有 5 个图表 |
先搜索对应的 CRM 页面及其 js/ 和 charts/ 文件夹,确认 CRM 在哪里添加了 JS。然后根据下方的单文件表编写适配你项目的 JS 文件。
仪表板如果凭空设计,通常效果很差——请参照 CRM 的结构。 先打开参考布局,将其区块数量、顺序和网格宽度复制到自己的 layout.yaml 中;然后填写各叶子文件的内容。
| 参考布局 | 参照内容 |
|---|---|
templates/crm/pages/main/overview/layout.yaml | 概览:1 个 jsBlock 英雄区行,下方有 2 个小型表格。适用于包含少量 KPI 的着陆页。 |
templates/crm/pages/main/analytics/layout.yaml | 完整仪表板:筛选表单行 → 同一行 4 个 KPI jsBlock → 在 16/8 ∣ full ∣ 14/10 网格中排列 5 个图表。适用于需要至少 5 个图表的场景(当页面标题包含 dashboard 或 analytics 时,验证器要求至少有 5 个图表)。 |
步骤:
- 打开参考
layout.yaml。记下区块键 / 类型 / 宽度。 - 按相同结构编写自己的
layout.yaml——使用相同数量的区块,以及layout:部分中相同的网格宽度——但使用自己的区块键和集合名称。 - 对于每个区块的叶子 JS/SQL 文件,请从下方的单文件表中复制。逐个复制文件;不要使用
cp -r复制整个文件夹。
| 要复制的叶子文件 | 参考 |
|---|---|
| KPI 卡片 jsBlock | templates/crm/pages/main/overview/js/overview_jsBlock.js |
| 筛选汇总 jsBlock | templates/crm/pages/main/analytics/js/analytics_jsBlock.js |
| 图表 SQL(分组计数) | templates/crm/pages/main/analytics/charts/analytics_chart_2.sql |
| 图表渲染(echarts 柱状图 / 饼图) | templates/crm/pages/main/analytics/charts/analytics_chart_2_render.js |
| filterForm 上的筛选统计按钮 | templates/crm/pages/main/customers/tab_customers/js/customers_customers_filterForm_customer_stats_filter_block.js |
| 全页自定义 UI(向导 / 多步骤 / 自定义流程) | templates/crm/pages/main/customers/tab_merge/js/customers_merge_jsBlock.js——整页是一个 type: jsBlock,约 580 行 React |
复制每个叶子文件后:
- 在原位置重命名,并调整 SQL / 集合 / 字段名称。
- 如果你的模块没有 i18n,请移除
ns: 'nb_crm'i18n 包装。 - 如果页面的 filterForm 使用了不同的字段键,请简化
ctx.var_form1.*筛选变量引用。
SQL 图表:按两步模式保存并运行——
ctx.sql.save({uid, sql}) 然后 ctx.sql.runById(uid)。
核心概念
两种标识符:key 和 title
key = lower_snake_ascii 标识符——用于生成 pages/ 下的目录名称和 state.yaml 中的条目。如果标题不是纯 ASCII,请务必显式填写(中文 / 空格会被转换成乱码)。
title = 按用户意愿显示的文本。
- key: it_ops
title: IT 运维
type: group
children:
- key: tickets
title: 工单
两种弹窗模式:key: reference 与裸 ref:
key: reference —— 弹窗区块是模板的引用。
编辑模板会更新所有引用它的弹窗。适用于任何共享的添加/编辑表单。
blocks:
- ref: templates/block/form_add_new_tickets.yaml
key: reference # REQUIRED for shared refs
裸 ref:(没有 key: reference)—— 模板内容会按弹窗内联,每个副本彼此独立。仅用于将较大的区块从页面文件中提取出来。
部署后,共享模板的 usageCount 应 ≥ 1。如果仍为 0,说明漏掉了 key: reference。
自动创建的列——请勿声明
NocoBase 会自动创建这些列;声明它们会导致静默过滤或类型冲突:
- 系统列:
id、createdAt、updatedAt、createdBy、updatedBy - m2o / o2m 外键列:声明
owner: m2o → users会自动创建owner_id;不要再添加第二个owner_id: integer行 - m2m 联接表:
through: nb_x_y会自动创建;不要为它编写 collection YAML
Table 与 sub-table——两者不同,请勿混淆
| Table | Sub-table | |
|---|---|---|
| 定义 | 面向父记录子记录的完整 CRUD 区块(筛选器 + 列表 + 添加/编辑弹窗) | 用于子行的内联可编辑网格,位于父表单内部 |
| DSL | type: table + resource_binding.sourceId + associationName | 位于 createForm/editForm 的 fields: 中的 { field: tasks, type: subTable, columns: [...] } |
| 使用位置 | 详情弹窗、标签页、独立列表弹窗 | createForm / editForm 内 |
| 适用场景 | 单独浏览子记录(客户详情 → 订单列表) | 与父记录一起录入子记录(发票 + 行项目) |
表单中裸写 - tasks 是第三种选项:RecordSelect 选择器(“选择现有记录”)。这通常不是你想要的——验证器会发出警告。
规范的 CRM 示例:
- Table(独立 CRUD 区块):
templates/crm/pages/main/customers/tab_customers/popups/table.name.yaml - Sub-table(内联可编辑网格):
templates/crm/templates/block/form_add_new_opportunities_quotations_quotations.yaml(items)
foreignKey 会改变含义
在 m2o 中,foreignKey 指当前表上的外键列(owner: m2o, foreignKey: owner_id → SELF 上的 owner_id)。
在 o2m 中,foreignKey 指目标表上的外键列(tasks: o2m → nb_pm_tasks, foreignKey: project_id → nb_pm_tasks 上的 project_id)。
命令参考
cd <skill-dir>/src
export NB_USER=... NB_PASSWORD=... NB_URL=...
npx tsx cli/cli.ts push <name> --force # deploy DSL → NocoBase
npx tsx cli/cli.ts push <name> --group <key> # only one subtree
npx tsx cli/cli.ts push <name> --incremental # skip unchanged (git diff)
npx tsx cli/cli.ts pull <name> # NocoBase → DSL (full round-trip)
npx tsx cli/cli.ts diff <left> <right> # compare two DSL trees
npx tsx cli/cli.ts duplicate-project <src> <dst> --key-suffix _v2
npx tsx cli/cli.ts verify-data <name> # FK / completeness check
push 和 pull 都是单向操作。往返操作 = push + pull + git diff。
常见错误
| 错误 | 修复方法 |
|---|---|
fields not in collection | 字段名与 collection YAML 不匹配 |
titleField is missing | 设置 titleField: <field> 或添加 name/title 字段 |
| 仅部署了部分页面 | key 与 pages/<key>/ 目录不匹配 |
创建时出现 string violation | YAML 中声明了 createdAt/updatedAt——请移除 |
| 图表 SQL 执行失败 | 先填充种子数据;字段名需加引号,例如 "createdAt" |
| UI 中 m2o 链接返回 400 | 缺少目标 collection 的 defaults.yaml popups: 绑定 |
Collection X not found in data source main | associationName 使用了短名称——请使用完整 collection 名称(nb_pm_projects.tasks,而不是 project.tasks)。请参阅 templates/crm/pages/main/customers/tab_customers/popups/ |
| 每行列只显示编辑和删除 | 缺少 recordActions——要添加的内容请参阅下文“每行操作”。现在,从 DSL 中移除操作/字段/列会在下次 push 时同步删除 NB 端对应内容。 |
如果上述任何内容与运行时观察到的情况不符,说明手册已过时——请记录缺失内容并告知用户。