NocoBase 区块扩展开发:从 BlockModel 到 TableBlockModel 的自定义区块实战指南
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
区块(Block)是 NocoBase 页面上承载内容的区域——表格、表单、图表、详情等都是一种区块。本指南以 NocoBase 客户端 Flow Engine 的区块模型体系为核心,讲解如何通过继承BlockModel系列基类创建自定义区块,并通过define()与registerFlow()将其注册到「添加区块」菜单,最终交付可直接在界面上拖入使用的自定义区块。读完本文,你将掌握四个区块基类的选型逻辑、完整示例代码、区块注册方式,以及各基类的底层源码机制。
基类选择:四个区块基类与继承链路
NocoBase 提供了三个区块基类(外加一个内置的完整实现),根据数据需求选择:
| 基类 | 继承关系 | 适用场景 |
|---|---|---|
BlockModel | 最基础的区块 | 不需要数据源的展示区块 |
DataBlockModel | 继承BlockModel | 需要数据但不绑定 NocoBase 数据表 |
CollectionBlockModel | 继承DataBlockModel | 绑定 NocoBase 数据表,自动获取数据 |
TableBlockModel | 继承CollectionBlockModel | 完整的表格区块,自带字段列、操作栏、分页等 |
继承链路是:BlockModel→DataBlockModel→CollectionBlockModel→TableBlockModel。
选择建议:
- 想要一个开箱即用的表格区块,直接用
TableBlockModel——它自带字段列、操作栏、分页、排序等完整能力,是用得最多的基类; - 需要完全自定义渲染方式(比如卡片列表、时间线等),用
CollectionBlockModel自己写renderComponent; - 只是展示静态内容或自定义 UI,用
BlockModel就够了。
DataBlockModel的定位比较特殊——它本身不添加任何新属性或方法,类体是空的(见 DataBlockModel.tsx)。它的作用是分类标识:继承DataBlockModel的区块会被归入 UI 上的「数据区块」分组菜单。如果你的区块需要自己管理数据获取逻辑(不走 NocoBase 标准的 Collection 绑定),可以继承DataBlockModel。比如图表插件的ChartBlockModel就是这样——它用自定义的ChartResource获取数据,不需要标准的数据表绑定。大多数场景下你不需要直接用DataBlockModel,用CollectionBlockModel或TableBlockModel就够了。
认识 BlockModel 基类:区块的最小契约
BlockModel定义在 BlockModel.tsx 中,继承自 Flow Engine 的FlowModel。它是所有区块的契约基类,核心机制包括:
renderComponent():渲染区块 UI 的抽象方法,基类默认直接抛错——throw new Error('renderComponent method must be implemented in subclasses of BlockModel'),子类必须实现(见 BlockModel.tsx);render():把renderComponent()的返回值用observer包装成ObservedRenderComponent后放进BlockItemCard卡片容器中渲染,同时检查collectionRequired——当区块声明需要数据表但上下文没有 collection 时,渲染BlockDeletePlaceholder占位(见 BlockModel.tsx);define():静态元信息定义,label用于「添加区块」菜单中的显示名,hide表示不在菜单中直接展示;registerFlow():为区块注册可视化配置流程,registerFlow({ key, title, steps })中的steps会出现在区块的配置面板中;- 内置
cardSettings流:基类已经自带「Card settings」配置流,包含titleDescription(标题与描述)、linkageRules(联动规则)、blockHeight(区块高度)三个步骤,也就是说任何区块都天然支持标题描述、联动规则与高度设置(见 BlockModel.tsx); beforeRender事件:基类通过FlowModel.registerEvents注册了beforeRender事件,支持用条件构建器(ConditionBuilder)配置触发条件,这正是后续示例中registerFlow({ on: 'beforeRender' })得以生效的基础(见 BlockModel.tsx)。
基类还提供了setDecoratorProps()、getModelClassName()、hasActiveFilters()、getDataLoadingMode()等辅助方法,供子类在渲染与数据刷新时使用。
BlockModel 示例:一个支持编辑 HTML 的最简区块
原文档附带一段演示视频(编辑 HTML 内容并实时渲染),对应的完整代码实现如下,仓库中的真实示例位于 plugin-simple-block/src/client-v2/models/SimpleBlockModel.tsx:
// models/SimpleBlockModel.tsx import React from 'react'; import { BlockModel } from '@nocobase/client-v2'; import { tExpr } from '@nocobase/flow-engine'; export class SimpleBlockModel extends BlockModel { renderComponent() { return <div dangerouslySetInnerHTML={{ __html: this.props.html }} />; } } SimpleBlockModel.define({ label: tExpr('Simple block'), }); SimpleBlockModel.registerFlow({ key: 'flow1', title: tExpr('Simple Block Flow'), on: 'beforeRender', steps: { editHtml: { title: tExpr('Edit HTML Content'), uiSchema: { html: { type: 'string', title: tExpr('HTML Content'), 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', }, }, defaultParams: { html: `<h3>This is a simple block</h3> <p>You can edit the HTML content.</p>`, }, handler(ctx, params) { ctx.model.props.html = params.html; }, }, }, });这个示例覆盖了区块开发的三个步骤:
renderComponent()— 渲染区块 UI,通过this.props读取属性。这里读取的是this.props.html,即用户在配置面板中编辑的 HTML 内容;define()— 设置区块在「添加区块」菜单里的显示名。label使用tExpr()包装,使其支持国际化翻译;registerFlow()— 添加可视化配置面板。on: 'beforeRender'表示该配置流在渲染前执行;steps.editHtml定义了名为editHtml的配置步骤,uiSchema用 schema 描述「HTML Content」输入框(Input.TextArea),defaultParams给出默认 HTML 内容,handler在用户保存配置时把params.html写回ctx.model.props.html,从而触发renderComponent()重新渲染。用户在界面上点击区块的配置按钮即可编辑 HTML,实现可视化配置。
场景与分组:BlockSceneEnum 与「数据区块」菜单
BlockSceneEnum定义在 BlockModel.tsx,完整枚举如下:
| 枚举值 | 类型 | 含义 |
|---|---|---|
new | 'new' | 新建记录场景(如「Add new」弹窗) |
one | 'one' | 单条记录详情/表单场景 |
many | 'many' | 多条记录列表场景 |
select | 'select' | 记录选择器场景 |
filter | 'filter' | 筛选区块场景 |
oam | ['one', 'many'] | 单条与多条的组合 |
subForm | 'subForm' | 子表单场景 |
bulkEditForm | 'bulkEditForm' | 批量编辑表单场景 |
BlockSceneType支持数组形式(如oam),静态方法_getScene()会把static scene用_.castArray归一化为数组,_isScene(scene)则判断区块是否属于某个场景——「添加区块」菜单正是根据场景来决定展示哪些区块的。
DataBlockModel虽然是空类体,但通过define()声明了分组身份:
DataBlockModel.define({ hide: true, label: tExpr('Data blocks'), async children(ctx) { // 根据 scene 过滤子菜单项: // select 场景只保留支持 select 的区块; // subForm / bulkEditForm 场景各自过滤; // new 场景或带 collectionName 且无 filterByTk 时只保留支持 new 的区块; // 其余场景排除 select/subForm/bulkEditForm 专属区块。 }, });见 DataBlockModel.tsx。BlockModel同样以hide: true, label: tExpr('Other blocks')定义了「其他区块」分组。因此「添加区块」菜单的顶层结构就是:数据区块(DataBlockModel子孙)、其他区块(BlockModel子孙)等分组。
仓库测试 BlockGridModel.selectSceneAddBlock.test.ts 验证了这一机制:在select场景下,「其他区块」分组只保留JSBlockModel、IframeBlockModel、MarkdownBlockModel,ActionPanelBlockModel与ReferenceBlockModel会被过滤掉——说明场景过滤逻辑对菜单项的真实影响。
CollectionBlockModel 示例:绑定数据表的多记录区块
如果区块需要绑定 NocoBase 的数据表,用CollectionBlockModel。它会自动处理数据获取。仓库中的真实示例位于 plugin-collection-block/src/client/models/ManyRecordBlockModel.tsx:
// models/ManyRecordBlockModel.tsx import React from 'react'; import { BlockSceneEnum, CollectionBlockModel } from '@nocobase/client-v2'; import { MultiRecordResource } from '@nocobase/flow-engine'; import { tExpr } from '@nocobase/flow-engine'; export class ManyRecordBlockModel extends CollectionBlockModel { // 声明这是一个多条记录的区块 static scene = BlockSceneEnum.many; createResource() { return this.context.makeResource(MultiRecordResource); } get resource() { return this.context.resource as MultiRecordResource; } renderComponent() { return ( <div> <h3>数据表区块</h3> {/* resource.getData() 获取数据表的数据 */} <pre>{JSON.stringify(this.resource.getData(), null, 2)}</pre> </div> ); } } ManyRecordBlockModel.define({ label: tExpr('Many records'), });跟BlockModel比,CollectionBlockModel多了这些:
static scene— 声明区块场景。常用值:BlockSceneEnum.many(多条记录列表)、BlockSceneEnum.one(单条记录详情/表单),完整枚举见上文;createResource()— 创建数据资源,MultiRecordResource用于获取多条记录。基类的createResource同样是个必须由子类实现的抽象方法(抛错要求子类实现,见 CollectionBlockModel.tsx);this.resource.getData()— 获取数据表的数据。
数据获取的底层机制
CollectionBlockModel在onInit时向 Flow 上下文注入了blockModel、actionName、resourceName、dataSource、collection、resource、association等属性(见 CollectionBlockModel.tsx),其中resource的 getter 会调用子类的createResource创建资源,再设置dataSourceKey与resourceName,并监听refresh事件同步数据脏版本。因此子类里this.context.resource、this.context.collection、this.context.dataSource都是直接可用的。
CollectionBlockModel还注册了三个内置配置流(见 CollectionBlockModel.tsx):
resourceSettings(sort: -999置顶):collectionCheck步骤在没有 collection 时直接exitAll()退出;aclCheck检查访问权限;init步骤校验dataSourceKey、collectionName必填,并把sourceId、filterByTk这类运行时参数写入 resource——注意代码注释明确说明sourceId/filterByTk是运行时参数,必须放在运行时 context 中;refreshSettings(sort: 10000):refresh步骤负责准备筛选区块(FilterManager.prepareFiltersForTarget)、根据数据加载模式(auto/manual)决定是否执行resource.refresh(),manual模式且无活跃筛选时清空数据不加载;dataLoadingModeSettings:数据加载模式配置,对应getDataLoadingMode()返回的'auto' | 'manual'。
数据表菜单过滤
CollectionBlockModel通过两个静态方法控制「添加区块」菜单里数据表的展示:
filterCollection(collection)— 静态过滤数据表,默认实现要求数据表存在filterTargetKey才返回true(见 CollectionBlockModel.tsx)。子类可以覆盖它来限制只对特定数据表可用;isCollectionAvailable(collection)— 结合区块声明的能力(capability)与数据表能力做交集判断(areCapabilitiesSupported),只有能力匹配的数据表才会出现在菜单里。
此外defineChildren(ctx)会为菜单构建数据源 → 数据表 → 关联记录的子菜单层级,包括select/new场景下的「Current collection」「Associated records」「Other collections」等菜单项,以及one场景下的「Current record」菜单项(见 CollectionBlockModel.tsx)。
TableBlockModel 示例:开箱即用的完整表格区块
TableBlockModel继承自CollectionBlockModel,是 NocoBase 内置的完整表格区块——自带字段列、操作栏、分页、排序等能力。用户在「添加区块」里选择「Table」用的就是它,其define()声明为label: 'Table', group: 'Content', searchable: true, sort: 300(见 TableBlockModel.tsx)。
通常来说,如果内置的TableBlockModel已经满足需求,用户直接在界面上添加就行,开发者不需要做任何事。只有当你需要在 TableBlockModel 基础上做定制时,才需要继承它——比如:
- 覆盖
customModelClasses替换内置的操作组或字段列模型; - 通过
filterCollection限制只对特定数据表可用; - 注册额外的 Flow 添加自定义配置项。
// 示例:限制只对 todoItems 数据表可用的表格区块 import { TableBlockModel } from '@nocobase/client-v2'; import type { Collection } from '@nocobase/flow-engine'; import { tExpr } from '../locale'; export class TodoBlockModel extends TableBlockModel { static filterCollection(collection: Collection) { return collection.name === 'todoItems'; } } TodoBlockModel.define({ label: tExpr('Todo block'), });仓库中的真实实现位于 plugin-custom-table-block-resource/src/client-v2/models/TodoBlockModel.tsx。该插件在load()中还向主数据源注册了todoItems数据表(含id、title、completed、priority字段,见 plugin.tsx),从而让「Todo block」只对todoItems数据表出现在添加菜单中。
TableBlockModel 的内部能力与可替换点
从 TableBlockModel.tsx 源码可以看到它内置的完整能力:
static scene = BlockSceneEnum.many:表格区块天然是多条记录场景;_defaultCustomModelClasses:声明了可替换的子模型映射——CollectionActionGroupModel(集合操作组)、RecordActionGroupModel(记录操作组)、TableColumnModel(字段列)、TableAssociationFieldGroupModel(关联字段组列)、TableCustomColumnModel(自定义列)(见 TableBlockModel.tsx)。子类通过customModelClasses覆盖其中任意一个 key,getModelClassName()会优先返回自定义类、否则回退默认类——这就是文档所说「覆盖customModelClasses替换内置的操作组或字段列模型」的实现位置;createResource:使用MultiRecordResource并额外添加X-With-ACL-Meta请求头(见 TableBlockModel.tsx);tableSettings配置流(sort: 500,见 TableBlockModel.tsx)——内置的可视化配置项完整列表:
| 配置步骤 | 类型 | 默认值 | 说明 |
|---|---|---|---|
quickEdit | switch | false | 启用行内快速编辑,保存后同步到各列 |
enableRowSelection | switch | true | 启用行选择,关闭时清空已选行 |
showRowNumbers | switch | true | 显示行号 |
pageSize | select | 20 | 每页条数,可选 5/10/20/50/100/200 |
dataScope | 复用 | — | 数据范围筛选 |
defaultSorting | 复用 | — | 默认排序规则 |
treeTable | switch | false | 树形表格(仅 tree 模板数据表显示) |
defaultExpandAllRows | switch | false | 默认展开全部行(仅 tree 模板) |
tableDensity | select | middle | 表格密度 large/middle/small |
dragSort/dragSortBy | 复用 | — | 拖拽排序与排序字段 |
refreshData | action | — | 刷新数据,遍历列重新派发beforeRender |
- 渲染细节:
getColumns()通过mapSubModels('columns', ...)收集字段列模型,配置模式下追加「添加字段列」按钮列;行内快速编辑通过QuickEditFormModel.open弹出表单,保存后写回 resource 数据并触发refresh事件;分页逻辑根据count元数据在完整分页与简化分页(未知总数场景)之间切换(见 TableBlockModel.tsx)。
完整的TableBlockModel定制示例(含自定义字段、自定义操作的前后端联动插件)见 做一个前后端联动的数据管理插件。
注册区块
在 Plugin 的load()中通过this.flowEngine.registerModelLoaders()注册,loader 使用动态import()实现按需懒加载:
// plugin.tsx import { Plugin } from '@nocobase/client-v2'; export class MyPlugin extends Plugin { async load() { this.flowEngine.registerModelLoaders({ SimpleBlockModel: { loader: () => import('./models/SimpleBlockModel'), }, ManyRecordBlockModel: { loader: () => import('./models/ManyRecordBlockModel'), }, }); } }注册完成后,在 NocoBase 界面点击「添加区块」就能看到你的自定义区块了。registerModelLoaders是 Flow Engine 提供的模型懒加载注册 API——把区块模型类的加载函数按名称注册进引擎,引擎在「添加区块」菜单需要展示或实例化某个模型时才真正加载对应模块。
完整示例源码
- @nocobase-example/plugin-simple-block — BlockModel 示例(SimpleBlockModel)
- @nocobase-example/plugin-collection-block — CollectionBlockModel 示例(ManyRecordBlockModel)
- @nocobase-example/plugin-custom-table-block-resource — TableBlockModel 定制示例(TodoBlockModel,含
todoItems数据表与客户端注册逻辑)
相关链接
- 插件实战:做一个自定义展示区块 — 从零搭建一个可配置的 BlockModel 区块
- 插件实战:做一个前后端联动的数据管理插件 — TableBlockModel + 自定义字段 + 自定义操作的完整示例
- FlowEngine 概述 — FlowModel 基础用法和 registerFlow
- 字段扩展 — 自定义字段组件
- 操作扩展 — 自定义操作按钮
- Resource API 速查表 — MultiRecordResource / SingleRecordResource 的完整方法签名
- FlowDefinition 流定义 — registerFlow 的完整参数和事件类型
- FlowEngine 完整文档 — 完整参考
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考