nocobase-dsl-reconciler
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNocoBase Application Builder (DSL path)
NocoBase应用构建器(DSL路径)
Before you use this skill
使用本技能前须知
This is the opt-in DSL path. Default is .
Stay on this skill only when the user explicitly wants YAML files they
can commit + . If you arrived here from a generic "build me a
NocoBase app" request without the user naming DSL/YAML/git, switch to
instead — it's the default entry point.
nocobase-ui-buildercli pushnocobase-ui-builder这是可选DSL路径,默认路径为。仅当用户明确需要可提交的YAML文件以及功能时,才使用本技能。如果用户只是泛泛地提出“帮我构建一个NocoBase应用”,未提及DSL/YAML/git,请切换至——这是默认的入口路径。
nocobase-ui-buildercli pushnocobase-ui-builderGolden rule
黄金准则
templates/crm/Never copy CRM files wholesale into your workspace. Do not
to get started, do not duplicate
, do not base your on CRM's.
Bulk-copying drags unrelated leads/opportunities/orders state and
workflows into your project — you then spend the whole session fighting
hundreds of irrelevant validator errors instead of building your module.
cp -r templates/crm/...collections/nb_crm_*.yamlroutes.yamlThe pre-deploy spec validator catches most structural mistakes with a
clear error message. Trust the validator: when it errors, fix what
it says rather than guessing — don't grep through .
src/deploy/*.tstemplates/crm/切勿将CRM文件整体复制到你的工作区。不要通过来启动项目,不要复制,不要以CRM的为基础编写你的配置。批量复制会将无关的线索/商机/订单状态及工作流带入你的项目——你将花费大量时间处理数百个无关的验证器错误,而非构建你的模块。
cp -r templates/crm/...collections/nb_crm_*.yamlroutes.yaml预部署规范验证器会捕获大多数结构错误并给出清晰的错误信息。信任验证器:当出现错误时,按照提示修复,而非猜测——不要去查找的内容。
src/deploy/*.tsWhen NOT to use this skill
本技能的不适用场景
Hand off to the matching skill when the user's request is orthogonal:
| User asks for… | Skill |
|---|---|
| One-off live-UI tweak on an already-running page (move / reorder / reconfigure a single block, field, action) — no DSL commit wanted | |
| ACL / role permissions / route permissions | |
| Workflow create / update / revision / execution | |
| Collection / field / relation authoring outside a DSL project | |
Plugin development ( | |
| Install / enable plugin | |
| Environment setup / app install / upgrade | |
Any change that should live as a committed YAML file under
— stays here.
workspaces/<project>/当用户的请求属于以下范畴时,请转交至对应的技能:
| 用户要求… | 对应技能 |
|---|---|
| 对已运行页面进行一次性实时UI调整(移动/重新排序/重新配置单个区块、字段、操作)——无需提交DSL | |
| ACL/角色权限/路由权限 | |
| 工作流创建/更新/修订/执行 | |
| 在DSL项目外进行集合/字段/关联关系创作 | |
插件开发( | |
| 安装/启用插件 | |
| 环境搭建/应用安装/升级 | |
所有需要以提交的YAML文件形式存储在下的变更——均保留在本技能中处理。
workspaces/<project>/Environment
环境配置
bash
cd <skill-dir>/src
export NB_USER=admin@nocobase.com NB_PASSWORD=admin123 NB_URL=http://localhost:14000bash
cd <skill-dir>/src
export NB_USER=admin@nocobase.com NB_PASSWORD=admin123 NB_URL=http://localhost:14000Quick start — new build
快速开始——新建项目
Default path for "build me a NocoBase app": copy the starter and
modify it. Do not hand-write the skeleton; do not study CRM first.
bash
cd <skill-dir>/src
cp -r ../templates/starter ../workspaces/<name>“帮我构建一个NocoBase应用”的默认路径:复制启动模板并进行修改。不要手动编写骨架,也不要先研究CRM模板。
bash
cd <skill-dir>/src
cp -r ../templates/starter ../workspaces/<name>First push: --copy bypasses validation rules that only matter once
首次推送:--copy 参数可绕过仅当弹窗存在时才生效的验证规则(如m2o弹窗绑定、clickToOpen文件存在性)。
popups exist (m2o popup binding, clickToOpen file presence). The
启动模板自带弹窗,因此首次推送实际上是完全有效的——--copy参数仅适用于你在弹窗配置完成前的扩展操作。
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
The starter is a complete minimal CRUD — 1 collection (Projects),
1 Dashboard page (4 KPI tiles + 2 charts), 1 list page with
filterForm / table / addNew popup / detail popup / 2 updateRecord
recordActions. Push as-is → visible in NB → then edit.npx tsx cli/cli.ts push <name> --force
启动模板是一个完整的极简CRUD应用——包含1个集合(Projects)、1个仪表盘页面(4个KPI卡片+2个图表)、1个列表页面(含筛选表单/表格/新建弹窗/详情弹窗/2个更新记录的recordActions)。直接推送即可在NocoBase中查看,之后再进行编辑。Customizing the starter (the agile loop)
自定义启动模板(敏捷迭代流程)
Iterate one concern at a time, push between each:
- Rename identifiers to match the user's domain.
- → your collection name (match the
collections/nb_starter_projects.yamlconvention)nb_<module>_<entity> - : change "Starter" / "Projects" titles
routes.yaml - directory name if you want (match
pages/starter/key)routes.yaml - Find-replace across all files
nb_starter_projects
- Adjust the field list in your collection YAML to match the
user's entity. Update
pages/<...>/layout.yamland the popup templates'fields:accordingly.field_layout: - Add a 2nd entity only when the first one works end-to-end.
Create a new +
collections/*.yamldir, copypages/<module>/<entity>/as a starting point.pages/starter/projects/layout.yaml - Extend incrementally: add a tab, a chart, a workflow trigger. Push after every change. See "Advanced patterns" below for which CRM file matches which pattern.
Never write the whole module in one shot. For customer-facing
builds — land the skeleton, show the user, gather feedback, iterate.
The starter push takes minutes; a hand-built module takes hours.
每次只处理一个关注点,每次变更后推送:
- 重命名标识符以匹配用户的业务领域。
- → 你的集合名称(遵循
collections/nb_starter_projects.yaml命名规范)nb_<module>_<entity> - :修改“Starter”/“Projects”标题
routes.yaml - 可修改目录名称(需与
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文件匹配对应的模式。
切勿一次性编写完整模块。面向客户的构建流程:先搭建骨架,展示给用户,收集反馈,再迭代。推送启动模板只需几分钟,而手动构建模块需要数小时。
Fast-track: when --copy
helps
--copy快速技巧:何时使用--copy
参数
--copyPass when the workspace has no popup files yet (early
stage — validator would fire errors about "m2o field X has no popup
binding" that the user will fix in the next push). The reconciler
auto-bypasses spec errors in this state. Once any
exists, drop and let validation run.
--copypopups/*.yaml--copy当工作区还没有弹窗文件时(早期阶段——验证器会触发“m2o字段X无弹窗绑定”的错误,用户会在下一次推送时修复),使用参数。协调器会自动绕过此状态下的规范错误。一旦存在任何文件,就不再使用参数,让验证器正常运行。
--copypopups/*.yaml--copyIncremental edits — existing workspace
增量编辑——现有工作区
- Add a block / field / action / popup → write the DSL → push.
- Remove from DSL → push. The reconciler destroys the matching
live model on the NB side and cleans . Manual NB-UI authored elements (not tracked in
state.yaml) are left alone.state.yaml - Rename → not supported automatically. Delete + re-add.
Targeted pushes:
- scopes to one menu subtree
--group <key> - scopes to one page
--page <key> - skips pages whose DSL hasn't changed since last push
--incremental
For pure live-UI tweaks without a DSL commit, hand off to
instead (see the routing table above).
nocobase-ui-builder- 添加区块/字段/操作/弹窗 → 编写DSL → 推送。
- 删除DSL中的内容 → 推送。协调器会在NocoBase端销毁对应的实时模型并清理。手动通过NB-UI创作的元素(未在
state.yaml中跟踪)会保留。state.yaml - 重命名 → 不支持自动重命名。请先删除再重新添加。
定向推送:
- :仅作用于一个菜单子树
--group <key> - :仅作用于一个页面
--page <key> - :跳过自上次推送以来DSL未变更的页面
--incremental
对于无需提交DSL的纯实时UI调整,请转交至(见上方路由表)。
nocobase-ui-builderAdvanced workflow — when the starter isn't enough
高级工作流——当启动模板不足以满足需求时
Triggers for going beyond the agile starter loop:
- More than ~3 collections with cross-relations (m2m, tree structures)
- Dedicated workflow / approval / permission logic that the user wants designed up-front
- Multi-tab pages, sub-tables, or cross-module navigation
- Dashboard with bespoke KPIs mapped to the user's domain language
Progression: Round 0 design → Round 1 scaffold → Round 2 fill →
Round 2' seed → Round 3 JS. Each round is a deployable state.
需要超越敏捷启动模板流程的场景:
- 超过约3个带有交叉关联(m2m、树形结构)的集合
- 用户需要预先设计的专用工作流/审批/权限逻辑
- 多标签页页面、子表格或跨模块导航
- 包含与用户业务领域语言匹配的定制KPI的仪表盘
流程:第0轮设计 → 第1轮搭建骨架 → 第2轮填充内容 → 第2'轮测试数据 → 第3轮JS开发。每一轮都是可部署的状态。
Round 0: System architecture — confirm with user
第0轮:系统架构——与用户确认
Write a (markdown, not YAML) covering:
DESIGN.md- Collections — every table, its fields, and its relations.
See skill for field-interface reference.
nocobase-data-modeling - Page list — every page, one-line purpose each, grouped by menu.
- Navigation wiring — which m2o fields open which popup templates; which pages link to each other.
Wait for user confirmation before writing YAML. A single design pass
saves 3× redesigns.
Skip Round 0 if the user's ask fits the starter shape (single entity,
basic CRUD).
Example:
markdown
undefined编写(Markdown格式,非YAML),涵盖以下内容:
DESIGN.md- 集合——每个表、其字段及关联关系。请参考技能的字段接口说明。
nocobase-data-modeling - 页面列表——每个页面,一行描述其用途,按菜单分组。
- 导航配置——哪些m2o字段打开哪些弹窗模板;哪些页面相互链接。
在编写YAML前等待用户确认。一次设计可以避免3次重新设计。
如果用户的需求符合启动模板的结构(单个实体、基础CRUD),可跳过第0轮。
示例:
markdown
undefinedCollections
集合
- 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)
- 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")
页面(菜单“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
- 书籍列表——浏览+搜索书籍,添加新书
- 会员列表——浏览会员及其借阅历史
- 借阅列表——活跃/逾期借阅,归还操作
- 仪表盘——KPIs+图表
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)
undefined- books.table.title → 书籍详情弹窗(共享模板)
- loans.table.book → 书籍详情弹窗(通过defaults.yaml共享)
- loans.table.member → 会员详情弹窗(通过defaults.yaml共享)
undefinedRound 0.5: Sub-agent CWD (only when spawning)
第0.5轮:子代理工作目录(仅当生成子代理时)
If launching a sub-agent (kimi TUI, Claude Code subprocess), its
CWD becomes the default write target. Set it before launch:
bash
mkdir -p <user-workdir>
cd <user-workdir>
kimi --yolo # or claude, codexSkip the and the agent writes to the parent project root.
cd如果启动子代理(kimi TUI、Claude Code子进程),其工作目录会成为默认写入目标。在启动前设置:
bash
mkdir -p <user-workdir>
cd <user-workdir>
kimi --yolo # 或claude、codex如果跳过命令,代理会写入父项目根目录。
cdRound 1: Scaffold — still start from the starter
第1轮:搭建骨架——仍从启动模板开始
Even in the advanced path, don't hand-write +
from scratch. Copy the starter, then grow:
routes.yamlcollections/*.yaml| Step | What to do |
|---|---|
| Base | |
| Add collection | Write |
| Add page | |
| Update routes | Add entry under the existing group in |
| Deploy | |
CRM references (consult only when stuck on structure):
- — collection format
templates/crm/collections/nb_crm_leads.yaml - — multi-group routes (shape only)
templates/crm/routes.yaml
即使在高级流程中,也不要从头手动编写+。复制启动模板,再逐步扩展:
routes.yamlcollections/*.yaml| 步骤 | 操作 |
|---|---|
| 基础 | |
| 添加集合 | 编写 |
| 添加页面 | 创建 |
| 更新路由 | 在 |
| 部署 | 每次添加集合/页面后执行 |
CRM参考(仅当结构上遇到问题时查阅):
- ——集合格式
templates/crm/collections/nb_crm_leads.yaml - ——多组路由(仅参考结构)
templates/crm/routes.yaml
Round 2: Fill content — blocks, popups, templates
第2轮:填充内容——区块、弹窗、模板
For each page beyond the starter basics:
| Building | CRM reference |
|---|---|
| Main list table + filter | |
| Multi-tab page | |
| Create-form with inline sub-table for o2m children | |
| Detail-popup template | |
| m2o auto-popup bindings | |
| Parent-detail + child-list popup | |
| addNew + field click-popup pattern | |
Rules for copying from CRM:
- Copy 10–30 lines, adapt names. Never copy whole files.
- Don't copy /
uid:/targetUid:— deployer assigns fresh.route_id: - For every m2o field displayed in a table, either set
OR add
clickToOpen: templates/popup/popup_detail_<target>.yamlinpopups.<target>: .... Validator errors otherwise.defaults.yaml
对于超出启动模板基础功能的每个页面:
| 构建内容 | CRM参考 |
|---|---|
| 主列表表格+筛选 | |
| 多标签页页面 | |
| 包含o2m子项内联子表格的创建表单 | |
| 详情弹窗模板 | |
| m2o自动弹窗绑定 | |
| 父详情+子列表弹窗 | |
| 新建+字段点击弹窗模式 | |
从CRM复制的规则:
- 复制10-30行代码,适配名称。切勿复制整个文件。
- 不要复制/
uid:/targetUid:——部署器会自动分配新的ID。route_id: - 对于表格中显示的每个m2o字段,要么设置,要么在
clickToOpen: templates/popup/popup_detail_<target>.yaml中添加defaults.yaml。否则会触发验证器错误。popups.<target>: ...
Per-row actions (recordActions
)
recordActions每行操作(recordActions
)
recordActionsBy default a table's row-action column is empty — NB won't render
any action buttons unless lists them. "Just edit
and delete" is a common but weak default: for most list tables the
user actually wants a one-click state change (Mark Done,
Approve, Archive) sitting next to edit.
recordActions:Decision order:
- Is there a boolean / enum status field? → add with
updateRecordto show the matching button only when the record is in the right state. One button per state transition (Mark Done hidden when already done; Reopen hidden when not done).linkageRules - Does the record need a second detail/form view different from
the default edit popup? → with
popup.templateRef - Tree/hierarchy collection? → add .
addChild - Need to navigate elsewhere with this record's id/filter? →
with
link.url: /admin/...?filter={{ctx.record.id}} - Want to let the user clone a complex record? → .
duplicate - Need to start a workflow manually? → .
workflowTrigger
Prefer over custom JS buttons for
state changes. linkageRules covers 80% of row-level UX (conditional
show/hide, field-based gating, role checks via ) without
touching JS. JS actions are only needed for multi-step logic
(query, then update, then navigate) and are not currently
deployable by this reconciler.
updateRecord + linkageRulesctx.userFull per-row action palette (declared in ):
recordActions:DSL | Purpose | CRM reference |
|---|---|---|
| Edit popup (default shape) | many |
| Read-only detail popup | |
| Single-row delete with confirm | many |
| Assign fields + optional linkageRules — the state-change workhorse | |
| Open a custom popup with | |
| Navigate to another admin page, carrying record context in URL | |
| Tree collection: add a child node under this row | |
| Clone the record into a new form | — |
| Manually trigger a workflow on this record | (toolbar in |
| Record-history plugin inline expand | — |
| AI employee button (tasks_file + employee) | |
Toolbar actions (declared in block-level ) use the same
type names plus , , , ,
, . Not every type makes sense in both contexts —
as a toolbar action would apply to no specific row,
so put it in .
actions:filterrefreshaddNewbulkDeleteexportimportupdateRecordrecordActionsGoal of Round 2: all pages have working CRUD — add / edit / view
popups wired correctly, row-action columns reflect real per-record
operations (not just edit+delete). Validator clean, NB UI shows no
"Collection may have been deleted" banners.
默认情况下,表格的行操作列是空的——除非列出操作,否则NB不会渲染任何操作按钮。“仅编辑和删除”是常见但较弱的默认设置:对于大多数列表表格,用户实际上需要一键状态变更(标记完成、批准、归档)按钮,与编辑按钮并列。
recordActions:决策顺序:
- 是否存在布尔/枚举状态字段? → 添加,并使用
updateRecord仅当记录处于正确状态时显示匹配按钮。每个状态转换对应一个按钮(记录已完成时隐藏“标记完成”按钮;记录未完成时隐藏“重新打开”按钮)。linkageRules - 记录是否需要与默认编辑弹窗不同的第二个详情/表单视图? → 使用并设置
popup。templateRef - 是否为树形/层级集合? → 添加。
addChild - 是否需要携带此记录的ID/筛选条件导航到其他页面? → 使用并设置
link。url: /admin/...?filter={{ctx.record.id}} - 是否允许用户克隆复杂记录? → 添加。
duplicate - 是否需要手动启动工作流? → 添加。
workflowTrigger
对于状态变更,优先使用而非自定义JS按钮。linkageRules覆盖了80%的行级UX(条件显示/隐藏、基于字段的限制、通过进行角色检查),无需接触JS。仅当需要多步骤逻辑(查询、更新、导航)时才需要JS操作,且目前该协调器不支持部署JS操作。
updateRecord + linkageRulesctx.user完整的每行操作选项(在中声明):
recordActions:DSL | 用途 | CRM参考 |
|---|---|---|
| 编辑弹窗(默认结构) | 多处 |
| 只读详情弹窗 | |
| 单行删除(带确认) | 多处 |
| 分配字段+可选linkageRules——状态变更的核心操作 | |
| 使用 | |
| 导航到另一个管理页面,在URL中携带记录上下文 | |
| 树形集合:在此行下添加子节点 | |
| 克隆记录到新表单 | — |
| 手动触发针对此记录的工作流 | ( |
| 记录历史插件内联展开 | — |
| AI员工按钮(tasks_file + employee) | |
工具栏操作(在区块级中声明)使用相同的类型名称,外加、、、、、。并非所有类型都适用于两种场景——作为工具栏操作不会应用于特定行,因此应放在中。
actions:filterrefreshaddNewbulkDeleteexportimportupdateRecordrecordActions第2轮目标:所有页面具备可用的CRUD功能——添加/编辑/查看弹窗配置正确,行操作列反映真实的每条记录操作(而非仅编辑+删除)。验证器无错误,NB UI无“集合可能已被删除”的提示。
Round 2': Test data (parallel with Round 2)
第2'轮:测试数据(与第2轮并行)
Can run concurrently with page-filling. Insert data via API:
bash
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"])')可与页面填充同时进行。通过API插入数据:
bash
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
始终先获取现有记录ID——它们是雪花整数
(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>}}'
-H 'Content-Type: application/json' -d '{...fields..., "owner":{"id": <real-user-id>}}'
Parent tables first; fill every FK on children (leaving it null
orphans the row). Then:
```bash
npx tsx cli/cli.ts verify-data <name> # FK & completeness checkWhy parallel: Round 3 JS (charts, KPIs) needs data to render
anything. Start the seed once you have collections (end of Round 1);
by the time Round 2 finishes pages, you have records to test against.
#(例如359571523764224),绝不是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>}}'
-H 'Content-Type: application/json' -d '{...fields..., "owner":{"id": <real-user-id>}}'
先填充父表;填充子表的每个外键(留空会导致行成为孤儿记录)。然后执行:
```bash
npx tsx cli/cli.ts verify-data <name> # 外键及完整性检查为什么并行:第3轮JS开发(图表、KPIs)需要数据才能渲染内容。在完成第1轮(集合创建)后开始插入测试数据;当第2轮页面填充完成时,你已有可测试的记录。
Round 3: JS — where CRM uses it, you probably need it
第3轮:JS开发——CRM使用JS的场景,你可能也需要
Now that CRUD + data work, audit where JavaScript adds value. Walk the
CRM template and ask "does the CRM have JS here?" for each spot in your
project. Three typical JS opportunities:
| Spot | CRM has JS? | Your project likely needs JS if... |
|---|---|---|
| Field renderer / column (e.g. color-coded status tag, days-until-due badge) | ✅ in most list tables | Any field whose display depends on a derived value (date math, status-to-color, multi-field composite) |
| Block (KPI card, custom widget inside a form) | ✅ overview, analytics, per-form tips | A summary widget, inline chart, or "helper panel" that reads from multiple collections |
| Dashboard page (whole page of charts + KPIs) | ✅ analytics page | Module has ≥3 measurable metrics users care about; validator requires ≥5 charts on pages titled "Dashboard" / "Analytics" |
Start by grepping the matching CRM page and its +
folders to confirm where the CRM adds JS. Then write YOUR JS file
adapted from the single-file table below.
js/charts/Dashboards specifically look bad when designed freehand — mirror the
CRM shape. Open the reference layout first, copy its block count,
ordering, and grid widths into your own ; then fill in
leaf files with your content.
layout.yaml| Reference layout | What to mirror |
|---|---|
| Overview: 1 jsBlock hero row, 2 small tables underneath. Use for a landing page with a few KPIs. |
| Full dashboard: filterForm row → 4 KPI jsBlocks in one row → 5 charts in a |
Procedure:
- Open the reference . Note the block keys / types / widths.
layout.yaml - Write YOUR with the SAME shape — same number of blocks, same grid widths in the
layout.yamlsection — but your own block keys and your own collection names.layout: - For each block's leaf JS/SQL file, copy from the single-file table
below. Copy files individually; do not the folder.
cp -r
| Leaf file to copy | Reference |
|---|---|
| KPI card jsBlock | |
| Filtered summary jsBlock | |
| Chart SQL (grouped counts) | |
| Chart render (echarts bar/pie) | |
| Filter stat buttons on filterForm | |
| Full-page custom UI (wizard / multi-step / custom flow) | |
After copying each leaf file:
- Rename in place and retarget SQL/collection/field names.
- Remove i18n wrappers unless your module has i18n.
ns: 'nb_crm' - Simplify filter var references if your page's filterForm uses different field keys.
ctx.var_form1.*
SQL charts: save + run as a two-step pattern —
then .
ctx.sql.save({uid, sql})ctx.sql.runById(uid)在CRUD和数据都正常工作后,评估JavaScript能带来的价值。遍历CRM模板,针对项目中的每个位置问自己“CRM在此处使用了JS吗?”。三种典型的JS应用场景:
| 场景 | CRM使用了JS? | 你的项目可能需要JS的情况 |
|---|---|---|
| 字段渲染器/列(例如颜色编码的状态标签、到期天数徽章) | ✅ 大多数列表表格 | 任何显示依赖于派生值(日期计算、状态转颜色、多字段组合)的字段 |
| 区块(KPI卡片、表单内的自定义小部件) | ✅ 概览、分析、表单提示 | 汇总小部件、内联图表或读取多个集合的“辅助面板” |
| 仪表盘页面(全页图表+KPIs) | ✅ 分析页面 | 模块包含≥3个用户关心的可衡量指标;验证器要求标题包含“Dashboard”/“Analytics”的页面至少有5个图表 |
首先通过grep查找匹配的CRM页面及其+文件夹,确认CRM在哪里添加了JS。然后根据下表的单文件示例编写你自己的JS文件。
js/charts/仪表盘自由设计效果很差——请镜像CRM的结构。先打开参考布局,将其区块数量、顺序和网格宽度复制到你自己的中;然后用你的内容填充叶子文件。
layout.yaml| 参考布局 | 需镜像的内容 |
|---|---|
| 概览:1个jsBlock hero行,下方2个小表格。用于包含少量KPIs的着陆页。 |
| 完整仪表盘:筛选表单行 → 一行4个KPI jsBlocks → 5个图表按 |
步骤:
- 打开参考。记录区块键/类型/宽度。
layout.yaml - 编写你自己的,保持相同结构——相同数量的区块,
layout.yaml部分相同的网格宽度,但使用你自己的区块键和集合名称。layout: - 对于每个区块的叶子JS/SQL文件,复制下表中的示例。单独复制文件;不要整个文件夹。
cp -r
| 需复制的叶子文件 | 参考 |
|---|---|
| KPI卡片jsBlock | |
| 筛选汇总jsBlock | |
| 图表SQL(分组计数) | |
| 图表渲染(echarts柱状图/饼图) | |
| 筛选表单上的统计按钮 | |
| 全页自定义UI(向导/多步骤/自定义流程) | |
复制每个叶子文件后:
- 重命名并调整SQL/集合/字段名称。
- 移除国际化包装,除非你的模块支持国际化。
ns: 'nb_crm' - 如果你的页面筛选表单使用不同的字段键,请简化筛选变量引用。
ctx.var_form1.*
SQL图表:采用两步模式保存并运行——然后。
ctx.sql.save({uid, sql})ctx.sql.runById(uid)Core concepts
核心概念
Two identifiers: key
and title
keytitle两种标识符:key
和title
keytitlekeypages/state.yamltitleyaml
- key: it_ops
title: IT 运维
type: group
children:
- key: tickets
title: 工单keypages/state.yamlkeytitleyaml
- key: it_ops
title: IT 运维
type: group
children:
- key: tickets
title: 工单Two popup modes: key: reference
vs bare ref:
key: referenceref:两种弹窗模式:key: reference
vs 裸ref:
key: referenceref:key: referenceyaml
blocks:
- ref: templates/block/form_add_new_tickets.yaml
key: reference # REQUIRED for shared refsBare (no ) — template content is inlined
per popup; each copy is independent. Use only to factor a bulky block
out of the page file.
ref:key: referenceAfter deploy, a shared template's should be ≥ 1. If it
stays at 0, was forgotten.
usageCountkey: referencekey: referenceyaml
blocks:
- ref: templates/block/form_add_new_tickets.yaml
key: reference # 共享引用必填裸(无)——模板内容会内联到每个弹窗中;每个副本都是独立的。仅用于将庞大的区块从页面文件中拆分出来。
ref:key: reference部署后,共享模板的应≥1。如果始终为0,说明遗漏了。
usageCountkey: referenceAuto-created columns — do NOT declare them
自动创建的列——请勿声明
NocoBase auto-creates these; declaring them causes silent filtering or
type conflicts:
- System columns: ,
id,createdAt,updatedAt,createdByupdatedBy - m2o / o2m FK columns: declaring auto-creates
owner: m2o → users; don't add a secondowner_idrowowner_id: integer - m2m join tables: is auto-created; don't write a collection YAML for it
through: nb_x_y
NocoBase会自动创建以下列;声明这些列会导致静默过滤或类型冲突:
- 系统列:、
id、createdAt、updatedAt、createdByupdatedBy - m2o/o2m外键列:声明会自动创建
owner: m2o → users;不要额外添加owner_id行owner_id: integer - m2m关联表:会自动创建;不要为其编写集合YAML文件
through: nb_x_y
Table vs sub-table — two different things, don't confuse
表格 vs 子表格——两种不同的事物,请勿混淆
| Table | Sub-table | |
|---|---|---|
| What it is | Full CRUD block (filter + list + add/edit popups) for child records of a parent | Inline editable grid for child rows, lives INSIDE a parent form |
| DSL | | |
| Where used | Detail popup, tab page, standalone-list popup | Inside createForm / editForm |
| Use when | Children browsed separately (customer detail → orders list) | Children entered alongside parent (invoice + line items) |
Bare in a form is the third option: a RecordSelect picker
("pick existing record"). Rarely what you want — validator warns.
- tasksCanonical CRM examples:
- Table (standalone CRUD block):
templates/crm/pages/main/customers/tab_customers/popups/table.name.yaml - Sub-table (inline editable grid): (
templates/crm/templates/block/form_add_new_opportunities_quotations_quotations.yaml)items
| 表格 | 子表格 | |
|---|---|---|
| 定义 | 用于父记录子项的完整CRUD区块(筛选+列表+添加/编辑弹窗) | 用于子行的内联可编辑网格,位于父表单内部 |
| DSL配置 | | 在createForm/editForm的 |
| 使用场景 | 详情弹窗、标签页页面、独立列表弹窗 | createForm/editForm内部 |
| 使用时机 | 子项可单独浏览(客户详情→订单列表) | 子项与父项一同录入(发票+行项目) |
表单中仅写是第三种选项:RecordSelect选择器(“选择现有记录”)。很少是你想要的——验证器会发出警告。
- tasksCRM典型示例:
- 表格(独立CRUD区块):
templates/crm/pages/main/customers/tab_customers/popups/table.name.yaml - 子表格(内联可编辑网格):(
templates/crm/templates/block/form_add_new_opportunities_quotations_quotations.yaml字段)items
foreignKey
flips meaning
foreignKeyforeignKey
含义反转
foreignKeyOn m2o, names the FK column on the current table
( → on SELF).
foreignKeyowner: m2o, foreignKey: owner_idowner_idOn o2m, names the FK column on the target table
( → on
).
foreignKeytasks: o2m → nb_pm_tasks, foreignKey: project_idproject_idnb_pm_tasks在m2o中,指当前表上的外键列( → 在自身表上)。
foreignKeyowner: m2o, foreignKey: owner_idowner_id在o2m中,指目标表上的外键列( → 在表上)。
foreignKeytasks: o2m → nb_pm_tasks, foreignKey: project_idproject_idnb_pm_tasksCommand reference
命令参考
bash
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 checkpush and pull are both one-way. Round-tripping = push + pull + git diff.
bash
cd <skill-dir>/src
export NB_USER=... NB_PASSWORD=... NB_URL=...
npx tsx cli/cli.ts push <name> --force # 将DSL部署到NocoBase
npx tsx cli/cli.ts push <name> --group <key> # 仅部署一个子树
npx tsx cli/cli.ts push <name> --incremental # 跳过未变更的内容(基于git diff)
npx tsx cli/cli.ts pull <name> # 将NocoBase内容同步到DSL(完整往返)
npx tsx cli/cli.ts diff <left> <right> # 比较两个DSL树
npx tsx cli/cli.ts duplicate-project <src> <dst> --key-suffix _v2
npx tsx cli/cli.ts verify-data <name> # 外键/完整性检查push和pull都是单向操作。往返同步=push+pull+git diff。
Common errors
常见错误
| Error | Fix |
|---|---|
| Field names don't match the collection YAML |
| Set |
| Only some pages deployed | |
| |
| Chart SQL failed | Seed data first; quote field names like |
| m2o link 400 in UI | Missing |
| |
| Per-row column shows only edit+delete | |
If any of the above contradicts what you observe at runtime, the manual
is stale — note what was missing and tell the user.
| 错误 | 修复方法 |
|---|---|
| 字段名称与集合YAML不匹配 |
| 设置 |
| 仅部分页面部署成功 | |
创建时出现 | YAML中声明了 |
| 图表SQL执行失败 | 先插入测试数据;字段名需加引号,如 |
| UI中m2o链接返回400错误 | 目标集合缺少 |
| |
| 每行操作列仅显示编辑+删除 | 缺少 |
如果以上内容与你运行时观察到的情况不符,说明本手册已过时——请记录缺失的内容并告知用户。