
NocoBase FlowEngine Resource API 实战MultiRecordResource 与 SingleRecordResource 完整指南【免费下载链接】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/nocobaseNocoBase FlowEngine 的 Resource 体系是前端数据操作的核心抽象MultiRecordResource与SingleRecordResource分别面向列表/表格和表单/详情两类场景封装 REST API 调用并提供响应式数据管理。本文以 Resource API 参考文档 为主线结合 flow-engine 源码 讲解完整方法签名、filter 语法、事件体系与 BlockModel/ActionModel 集成方式帮助开发者快速掌握 FlowEngine 中的数据读写能力。继承链路与设计定位FlowEngine 的 Resource 采用四层继承结构FlowResource → APIResource → BaseRecordResource → MultiRecordResource / SingleRecordResource各层职责如下FlowResourceflowResource.ts最底层基类提供响应式数据存储_data/_meta/_error均为formily/reactive的observable.ref、getData()/setData()/getMeta()/setMeta()、getError()/setError()/clearError()以及on/once/off/emit事件机制APIResourceapiResource.ts绑定APIClient来自nocobase/sdk提供setURL()/getURL()、setRequestMethod()、addRequestHeader()/removeRequestHeader()、addRequestParameter()/removeRequestParameter()、setRequestBody()等底层请求配置能力BaseRecordResourcebaseRecordResource.ts抽象出记录型资源的公共能力——filter 分组、fields/appends/sort 字段控制、按主键过滤filterByTk、关联资源 URL 构建、runAction()统一请求执行MultiRecordResource/SingleRecordResource面向具体场景的最终实现。从源码结构看BaseRecordResource是抽象类声明了abstract refresh()它维护了统一请求配置对象request其params字段与nocobase/sdk的 APIClient 请求参数保持一致filter、filterByTk、appends、fields、sort、except、whitelist、blacklist、page、pageSize。在 BlockModel 中获取 Resource 的两种方式Resource 实例由 FlowContext 统一创建相关方法定义在 flowContext.tsctx.makeResource(resourceType)创建一个新的 Resource 实例内部调用engine.createResource将当前上下文的代理作为 context 传入适合在CollectionBlockModel.createResource()中声明区块使用的资源类型ctx.initResource(className)惰性初始化仅当当前上下文还没有resource属性时才定义并返回重复调用返回同一个实例适合在 RunJS 等没有预绑定 resource 的场景中首次初始化ctx.useResource(className)initResource的废弃别名源码注释标注deprecated use initResource instead。对于MultiRecordResource/SingleRecordResource/APIResource/SQLResource四类资源均可通过上述方法创建。MultiRecordResource列表 / 表格 / 看板场景MultiRecordResourceTDataItemmultiRecordResource.ts用于多条记录场景从nocobase/flow-engine导入。数据操作方法参数说明getData()-返回TDataItem[]初始值为[]hasData()-数据数组是否非空FlowResource提供create(data, options?)data: object,options?: { refresh?: boolean }创建记录默认创建后自动 refresh传{ refresh: false }可跳过刷新get(filterByTk)filterByTk: string \| number通过主键获取单条记录内部执行getactionupdate(filterByTk, data, options?)filterByTk,data: object更新记录完成后自动 refreshdestroy(filterByTk, options?)filterByTk: string \| number \| Array删除记录支持批量删除后自动修正页码并刷新destroySelectedRows()-删除所有选中行无选中时抛出No rows selected for deletion.refresh()-刷新数据默认调用listaction同一事件循环内多次调用合并为一次请求源码细节值得注意create内部先runAction(create)随后markDataSourceDirty()标记数据源脏状态用于多视图栈“激活时自动刷新”的协调见 baseRecordResource.ts然后emit(saved, data)最后按options.refresh ! false决定是否自动刷新update与create类似但始终自动刷新destroy在删除后会计算currentPage与lastPage若当前页已被删空则回退页码setPage(lastPage || 1)避免停留在空页上refresh()采用定时器合并策略源码注释明确说明“在同一个事件循环内多次调用 refresh 方法时只有最后一次调用会生效避免触发多次相同的接口请求”。实现上每次调用先clearTimeout旧定时器将 resolve/reject 收集到refreshWaiters在下一个事件循环统一执行请求并emit(refresh)成功/失败分别批量 resolve/reject。分页方法说明getPage()获取当前页码setPage(page)设置页码同步写入请求参数与 metagetPageSize()获取每页条数默认 20setPageSize(pageSize)设置每页条数getCount()获取总记录数来自 metagetTotalPage()获取总页数来自 metanext()下一页并刷新previous()上一页并刷新页码大于 1 时才执行goto(page)跳转到指定页并刷新仅当 page 0分页参数page/pageSize默认值在 multiRecordResource.ts 的request.params中声明page: 1、pageSize: 20。refresh()完成后会用响应meta中的page/pageSize回写当前状态。注意next/previous/goto直接修改request.params.page而非调用setPage因此不会写入 meta但都会触发一次refresh()。选中行方法说明setSelectedRows(rows)设置选中行写入 meta 的selectedRowsgetSelectedRows()获取选中行无选中时返回[]示例在 CollectionBlockModel 中使用继承CollectionBlockModel时通过createResource()声明资源类型在renderComponent()中读取数据import React from react; import { BlockSceneEnum, CollectionBlockModel } from nocobase/client-v2; import { MultiRecordResource } from nocobase/flow-engine; import { tExpr } from ../locale; export class ManyRecordBlockModel extends CollectionBlockModel { static scene BlockSceneEnum.many; // 声明使用 MultiRecordResource 管理数据 createResource() { return this.context.makeResource(MultiRecordResource); } get resource() { return this.context.resource as MultiRecordResource; } renderComponent() { const data this.resource.getData(); // TDataItem[] const count this.resource.getCount(); // 总记录数 return ( div h3共 {count} 条记录第 {this.resource.getPage()} 页/h3 pre{JSON.stringify(data, null, 2)}/pre /div ); } } ManyRecordBlockModel.define({ label: tExpr(Many records block), });完整示例见 FlowEngine → 区块扩展。示例在操作按钮中调用 CRUD在ActionModel的registerFlowhandler 中通过ctx.blockModel?.resource拿到当前区块的 resource调用 CRUD 方法import { ActionModel, ActionSceneEnum } from nocobase/client-v2; import { MultiRecordResource } from nocobase/flow-engine; import { tExpr } from ../locale; export class NewTodoActionModel extends ActionModel { static scene ActionSceneEnum.collection; defaultProps { type: primary, children: tExpr(New todo), }; } NewTodoActionModel.define({ label: tExpr(New todo), }); NewTodoActionModel.registerFlow({ key: newTodoFlow, title: tExpr(New todo), on: click, steps: { openForm: { async handler(ctx) { // 拿到当前区块的 resource const resource ctx.blockModel?.resource as MultiRecordResource; if (!resource) return; ctx.viewer.dialog({ title: ctx.t(New todo), content: (view) ( MyForm onSubmit{async (values) { // 创建记录创建后 resource 会自动 refresh await resource.create(values); ctx.message.success(ctx.t(Created successfully)); view.close(); }} onCancel{() view.close()} / ), }); }, }, }, });完整示例见 做一个前后端联动的数据管理插件。CRUD 操作速查async handler(ctx) { const resource ctx.blockModel?.resource as MultiRecordResource; // --- 创建 --- await resource.create({ title: New item, completed: false }); // 不自动刷新 await resource.create({ title: Draft }, { refresh: false }); // --- 读取 --- const items resource.getData(); // TDataItem[] const count resource.getCount(); // 总记录数 const item await resource.get(1); // 通过主键获取单条 // --- 更新 --- await resource.update(1, { title: Updated }); // --- 删除 --- await resource.destroy(1); // 单条删除 await resource.destroy([1, 2, 3]); // 批量删除 // --- 分页 --- resource.setPage(2); resource.setPageSize(50); await resource.refresh(); // 或者用快捷方法 await resource.goto(3); await resource.next(); await resource.previous(); // --- 刷新 --- await resource.refresh(); }SingleRecordResource表单 / 详情页场景SingleRecordResourceTDatasingleRecordResource.ts用于单条记录场景从nocobase/flow-engine导入。数据操作方法参数说明getData()-返回TData单个对象初始值为nullsave(data, options?)data: object,options?: { refresh?: boolean }智能保存——isNewRecord为 true 时调用 create否则调用 updatedestroy(options?)-删除当前记录使用已设置的 filterByTk删除后setData(null)refresh()-刷新数据默认调用getactionisNewRecord为 true 时跳过关键属性属性说明isNewRecord标识是否为新记录。setFilterByTk()会自动将其设为false源码细节save()内部通过!this.isNewRecord判断走update还是create更新时会从getFilterByTk()读取已设置的主键并写入config.params.filterByTk见 singleRecordResource.ts。这与文档表格中“创建用save(data)isNewRecordtrue更新用save(data)setFilterByTk(id)”的约定一一对应setFilterByTk()被覆写为“先设isNewRecord false再调用父类实现”因此一旦设置了主键过滤资源自动从“新建态”切换到“编辑态”refresh()在isNewRecord为 true 时直接 return源码注释避免在没有指定记录的情况下进行不必要的 API 调用destroy()直接使用this.request.params.filterByTk无需再传参数成功后清空数据为null。示例表单详情场景import React from react; import { BlockSceneEnum, CollectionBlockModel } from nocobase/client-v2; import { SingleRecordResource } from nocobase/flow-engine; import { tExpr } from ../locale; export class DetailBlockModel extends CollectionBlockModel { static scene BlockSceneEnum.one; createResource() { return this.context.makeResource(SingleRecordResource); } get resource() { return this.context.resource as SingleRecordResource; } renderComponent() { const data this.resource.getData(); // 单个对象或 null if (!data) return div加载中.../div; return ( div h3{data.title}/h3 p{data.content}/p /div ); } } DetailBlockModel.define({ label: tExpr(Detail block), });注意区块场景枚举BlockSceneEnum.one表示单条记录详情/表单与多条记录的BlockSceneEnum.many对应。示例新建和编辑记录async handler(ctx) { const resource ctx.model.context.resource as SingleRecordResource; // --- 新建记录 --- resource.isNewRecord true; await resource.save({ name: John, age: 30 }); // save 内部调用 create action完成后自动 refresh // --- 编辑已有记录 --- resource.setFilterByTk(1); // 自动设置 isNewRecord false await resource.refresh(); // 先加载当前数据 const data resource.getData(); await resource.save({ ...data, name: Jane }); // save 内部调用 update action // --- 删除当前记录 --- await resource.destroy(); // 使用已设置的 filterByTk }通用方法以下方法在MultiRecordResource和SingleRecordResource上都可用定义于BaseRecordResource/APIResource/FlowResource基类。过滤方法说明setFilter(filter)直接设置 filter 对象内部 JSON 序列化后写入请求参数addFilterGroup(key, filter)添加命名过滤组推荐可组合可移除支持FilterItem实例或普通对象removeFilterGroup(key)移除命名过滤组getFilter()获取聚合后的 filter多个 group 自动用$and组合从源码看过滤组存储在protected filterGroups new Mapstring, any()见 baseRecordResource.ts。getFilter()将所有非空 group 收集后用{ $and: value }聚合addFilterGroup/removeFilterGroup每次变更都会调用resetFilter()重新同步请求参数。若 filter 传入的是FilterItem实例会先toJSON()再存储。字段控制方法说明setFields(fields)设置返回字段支持字符串或数组字符串按逗号拆分setAppends(appends)设置关联字段的 appendsaddAppends(appends)追加 appends去重removeAppends(appends)移除指定 appendssetSort(sort)设置排序比如[-createdAt, name]负号表示倒序setFilterByTk(value)设置按主键过滤对象类型会被 JSON 序列化setExcept(except)排除指定字段setWhitelist(whitelist)设置写入白名单字段setBlacklist(blacklist)设置写入黑名单字段资源配置方法说明setResourceName(name)设置资源名称比如users或关联资源users.tagssetSourceId(id)设置关联资源的父记录 IDsetDataSourceKey(key)设置数据源添加X-Data-Source请求头关联资源 URL 的构建逻辑在buildURL()见 baseRecordResource.ts当sourceId存在且资源名包含.时拆分为父资源与子资源生成parentResource/sourceId/childResource:action形式的 URL如users/1/tags:get否则为resourceName:action。元数据和状态方法说明getMeta(key?)获取元数据不传 key 返回整个 meta 对象loading是否正在加载getter读取 meta 中的loadinggetError()获取错误信息ResourceError或nullclearError()清除错误ResourceError定义在 flowResource.ts它会从响应中提取错误消息支持error.response.data.error、errors、messages等格式并提供codegetter未命中时返回UNKNOWN_ERROR。事件事件触发时机refreshrefresh()成功获取数据后savedcreate/update/save操作成功后resource.on(saved, (data) { console.log(记录已保存:, data); });事件系统在FlowResource中实现支持on/once/off/emitonce注册的回调触发一次后自动移除。Filter 语法NocoBase 使用 JSON 风格的过滤语法操作符以$开头// 等于 { status: { $eq: active } } // 不等于 { status: { $ne: deleted } } // 大于 { age: { $gt: 18 } } // 包含模糊匹配 { name: { $includes: test } } // 组合条件 { $and: [ { status: { $eq: active } }, { age: { $gt: 18 } }, ] } // 或条件 { $or: [ { status: { $eq: active } }, { role: { $eq: admin } }, ] }在 Resource 上推荐用addFilterGroup管理过滤条件// 添加多个过滤组 resource.addFilterGroup(status, { status: { $eq: active } }); resource.addFilterGroup(age, { age: { $gt: 18 } }); // getFilter() 自动聚合为: { $and: [...] } // 移除某个过滤组 resource.removeFilterGroup(status); // 刷新应用过滤 await resource.refresh();命名过滤组的优势在于可组合、可移除多个来源如用户界面筛选、区块配置、脚本逻辑可以各自维护自己的过滤组互不干扰最终由getFilter()统一聚合为$and结构发送给服务端。MultiRecordResource 与 SingleRecordResource 对比特性MultiRecordResourceSingleRecordResourcegetData() 返回TDataItem[]数组TData单个对象默认 refresh actionlistget分页支持不支持选中行支持不支持创建create(data)save(data)isNewRecordtrue更新update(filterByTk, data)save(data)setFilterByTk(id)删除destroy(filterByTk)destroy()典型场景列表、表格、看板表单、详情页两者底层都通过runAction()执行 REST 请求默认 POST 方法URL 形如users:create、users:list差异主要体现在响应式数据形态数组 vs 对象、刷新 actionlistvsget与 save 语义上。从继承结构看它们共享BaseRecordResource提供的全部请求配置能力因此 filter、fields、appends、sort、数据源切换等用法完全一致。服务端对应ResourceManager客户端 Resource 调用的users:list、users:create等 REST 接口对应服务端 ResourceManager 资源管理 中定义的资源。客户端setResourceName()设置的资源名、setSourceId()设置的父记录 ID最终都会拼接到请求 URL 中由服务端 ResourceManager 路由到对应的 action 处理器。理解这条链路有助于排查前后端联调问题。相关链接做一个前后端联动的数据管理插件 — 完整示例resource.create()在自定义操作按钮中的实际用法FlowEngine → 区块扩展 — CollectionBlockModel 中createResource()和resource.getData()的用法ResourceManager 资源管理服务端 — 服务端 REST API 资源定义客户端 Resource 调用的就是这些接口FlowContext API —ctx.makeResource()、ctx.initResource()等方法的说明FlowEngine 上下文体系 — 运行态与配置态mode、上下文层级结构等背景知识【免费下载链接】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),仅供参考