Resource API
NocoBase FlowEngine cung cấp hai lớp Resource để xử lý các thao tác dữ liệu phía frontend - MultiRecordResource cho danh sách/bảng (nhiều bản ghi) và SingleRecordResource cho form/chi tiết (một bản ghi). Chúng đóng gói các lệnh gọi REST API và cung cấp quản lý dữ liệu phản ứng (reactive).
Chuỗi kế thừa: FlowResource → APIResource → BaseRecordResource → MultiRecordResource / SingleRecordResource
MultiRecordResource
Dùng cho các tình huống nhiều bản ghi như danh sách, bảng, kanban. Import từ @nocobase/flow-engine.
Thao tác dữ liệu
Phân trang
Hàng đã chọn
Ví dụ: Sử dụng trong CollectionBlockModel
Khi kế thừa CollectionBlockModel, bạn cần tạo resource qua createResource(), sau đó đọc dữ liệu trong 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;
// Khai báo dùng MultiRecordResource để quản lý dữ liệu
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(); // Tổng số bản ghi
return (
<div>
<h3>Tổng cộng {count} bản ghi (trang {this.resource.getPage()})</h3>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
}
ManyRecordBlockModel.define({
label: tExpr('Many records block'),
});
Xem ví dụ đầy đủ tại FlowEngine → Mở rộng block.
Ví dụ: Gọi CRUD trong nút thao tác
Trong handler của registerFlow của ActionModel, lấy resource của block hiện tại qua ctx.blockModel?.resource và gọi các phương thức 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) {
// Lấy resource của block hiện tại
const resource = ctx.blockModel?.resource as MultiRecordResource;
if (!resource) return;
ctx.viewer.dialog({
title: ctx.t('New todo'),
content: (view) => (
<MyForm
onSubmit={async (values) => {
// Tạo bản ghi, sau khi tạo resource sẽ tự động refresh
await resource.create(values);
ctx.message.success(ctx.t('Created successfully'));
view.close();
}}
onCancel={() => view.close()}
/>
),
});
},
},
},
});
Xem ví dụ đầy đủ tại Tạo Plugin quản lý dữ liệu liên kết frontend-backend.
Ví dụ: Tham khảo nhanh thao tác CRUD
async handler(ctx) {
const resource = ctx.blockModel?.resource as MultiRecordResource;
// --- Tạo ---
await resource.create({ title: 'New item', completed: false });
// Không tự động refresh
await resource.create({ title: 'Draft' }, { refresh: false });
// --- Đọc ---
const items = resource.getData(); // TDataItem[]
const count = resource.getCount(); // Tổng số bản ghi
const item = await resource.get(1); // Lấy một bản ghi theo khóa chính
// --- Cập nhật ---
await resource.update(1, { title: 'Updated' });
// --- Xóa ---
await resource.destroy(1); // Xóa một bản ghi
await resource.destroy([1, 2, 3]); // Xóa hàng loạt
// --- Phân trang ---
resource.setPage(2);
resource.setPageSize(50);
await resource.refresh();
// Hoặc dùng phương thức tắt
await resource.goto(3);
await resource.next();
await resource.previous();
// --- Refresh ---
await resource.refresh();
}
SingleRecordResource
Dùng cho các tình huống một bản ghi như form, trang chi tiết. Import từ @nocobase/flow-engine.
Thao tác dữ liệu
Thuộc tính chính
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(); // Đối tượng đơn hoặc null
if (!data) return <div>Đang tải...</div>;
return (
<div>
<h3>{data.title}</h3>
<p>{data.content}</p>
</div>
);
}
}
DetailBlockModel.define({
label: tExpr('Detail block'),
});
Ví dụ: Tạo mới và chỉnh sửa bản ghi
async handler(ctx) {
const resource = ctx.model.context.resource as SingleRecordResource;
// --- Tạo bản ghi mới ---
resource.isNewRecord = true;
await resource.save({ name: 'John', age: 30 });
// save bên trong gọi action create, tự động refresh sau khi xong
// --- Chỉnh sửa bản ghi đã có ---
resource.setFilterByTk(1); // Tự động đặt isNewRecord = false
await resource.refresh(); // Tải dữ liệu hiện tại trước
const data = resource.getData();
await resource.save({ ...data, name: 'Jane' });
// save bên trong gọi action update
// --- Xóa bản ghi hiện tại ---
await resource.destroy(); // Dùng filterByTk đã đặt
}
Phương thức chung
Các phương thức sau khả dụng trên cả MultiRecordResource và SingleRecordResource:
Lọc
Kiểm soát field
Cấu hình resource
Sự kiện
resource.on('saved', (data) => {
console.log('Bản ghi đã được lưu:', data);
});
Cú pháp Filter
NocoBase sử dụng cú pháp filter kiểu JSON, các operator bắt đầu bằng $:
// Bằng
{ status: { $eq: 'active' } }
// Khác
{ status: { $ne: 'deleted' } }
// Lớn hơn
{ age: { $gt: 18 } }
// Chứa (so khớp mờ)
{ name: { $includes: 'test' } }
// Điều kiện kết hợp
{
$and: [
{ status: { $eq: 'active' } },
{ age: { $gt: 18 } },
]
}
// Điều kiện hoặc
{
$or: [
{ status: { $eq: 'active' } },
{ role: { $eq: 'admin' } },
]
}
Trên Resource, khuyến nghị dùng addFilterGroup để quản lý điều kiện lọc:
// Thêm nhiều nhóm filter
resource.addFilterGroup('status', { status: { $eq: 'active' } });
resource.addFilterGroup('age', { age: { $gt: 18 } });
// getFilter() tự động tổng hợp thành: { $and: [...] }
// Gỡ bỏ một nhóm filter
resource.removeFilterGroup('status');
// Refresh để áp dụng filter
await resource.refresh();
So sánh MultiRecordResource và SingleRecordResource
Liên kết liên quan