Resource API

NocoBase FlowEngine предоставляет два класса Resource для работы с данными на стороне клиента: MultiRecordResource — для списков и таблиц (несколько записей), SingleRecordResource — для форм и страниц деталей (одна запись). Они инкапсулируют вызовы REST API и обеспечивают реактивное управление данными.

Цепочка наследования: FlowResourceAPIResourceBaseRecordResourceMultiRecordResource / SingleRecordResource

MultiRecordResource

Используется для списков, таблиц, канбан-досок и других сценариев с несколькими записями. Импортируется из @nocobase/flow-engine.

Операции с данными

МетодПараметрыОписание
getData()-Возвращает TDataItem[], начальное значение — []
hasData()-Не пуст ли массив данных
create(data, options?)data: object, options?: { refresh?: boolean }Создать запись. По умолчанию после создания автоматически вызывается refresh
get(filterByTk)filterByTk: string | numberПолучить одну запись по первичному ключу
update(filterByTk, data, options?)filterByTk: string | number, data: objectОбновить запись. После завершения автоматически вызывается refresh
destroy(filterByTk, options?)filterByTk: string | number | ArrayУдалить запись. Поддерживает пакетное удаление
destroySelectedRows()-Удалить все выбранные строки
refresh()-Обновить данные (вызов действия list). Несколько вызовов в одном цикле событий объединяются

Постраничная навигация

МетодОписание
getPage()Получить текущий номер страницы
setPage(page)Установить номер страницы
getPageSize()Получить количество записей на странице (по умолчанию 20)
setPageSize(pageSize)Установить количество записей на странице
getCount()Получить общее количество записей
getTotalPage()Получить общее количество страниц
next()Перейти на следующую страницу и обновить данные
previous()Перейти на предыдущую страницу и обновить данные
goto(page)Перейти на указанную страницу и обновить данные

Выбранные строки

МетодОписание
setSelectedRows(rows)Установить выбранные строки
getSelectedRows()Получить выбранные строки

Пример: использование в 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 из кнопки действия

В обработчике registerFlow модели ActionModel через ctx.blockModel?.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) {
        // Получаем ресурс текущего блока
        const resource = ctx.blockModel?.resource as MultiRecordResource;
        if (!resource) return;

        ctx.viewer.dialog({
          title: ctx.t('New todo'),
          content: (view) => (
            <MyForm
              onSubmit={async (values) => {
                // Создаём запись; ресурс автоматически обновится после создания
                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

Используется для форм, страниц деталей и других сценариев с одной записью. Импортируется из @nocobase/flow-engine.

Операции с данными

МетодПараметрыОписание
getData()-Возвращает TData (один объект), начальное значение — null
save(data, options?)data: object, options?: { refresh?: boolean }Умное сохранение: при isNewRecord = true вызывается create, иначе — update
destroy(options?)-Удалить текущую запись (используя ранее установленный filterByTk)
refresh()-Обновить данные (вызов действия get); пропускается, если isNewRecord = true

Ключевые свойства

СвойствоОписание
isNewRecordПризнак того, что запись новая. Метод setFilterByTk() автоматически устанавливает его в false

Пример: сценарий формы деталей

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'),
});

Пример: создание и редактирование записи

async handler(ctx) {
  const resource = ctx.model.context.resource as SingleRecordResource;

  // --- Создание новой записи ---
  resource.isNewRecord = true;
  await resource.save({ name: 'John', age: 30 });
  // Внутри save вызывается действие create, после чего данные автоматически обновляются

  // --- Редактирование существующей записи ---
  resource.setFilterByTk(1);  // Автоматически устанавливает isNewRecord = false
  await resource.refresh();   // Сначала загружаем актуальные данные
  const data = resource.getData();
  await resource.save({ ...data, name: 'Jane' });
  // Внутри save вызывается действие update

  // --- Удаление текущей записи ---
  await resource.destroy();   // Используется ранее установленный filterByTk
}

Общие методы

Следующие методы доступны как у MultiRecordResource, так и у SingleRecordResource:

Фильтрация

МетодОписание
setFilter(filter)Напрямую установить объект фильтра
addFilterGroup(key, filter)Добавить именованную группу фильтров (рекомендуется: можно объединять и удалять)
removeFilterGroup(key)Удалить именованную группу фильтров
getFilter()Получить итоговый фильтр; несколько групп автоматически объединяются через $and

Управление полями

МетодОписание
setFields(fields)Установить возвращаемые поля
setAppends(appends)Установить appends для полей связей
addAppends(appends)Добавить appends (с удалением дубликатов)
setSort(sort)Установить сортировку, например ['-createdAt', 'name']
setFilterByTk(value)Установить фильтрацию по первичному ключу

Конфигурация ресурса

МетодОписание
setResourceName(name)Установить имя ресурса, например 'users' или ресурса связи 'users.tags'
setSourceId(id)Установить ID родительской записи для ресурса связи
setDataSourceKey(key)Установить источник данных (добавляет заголовок запроса X-Data-Source)

Метаданные и состояние

МетодОписание
getMeta(key?)Получить метаданные. Если ключ не передан, возвращается весь объект meta
loadingИдёт ли загрузка (getter)
getError()Получить информацию об ошибке
clearError()Очистить ошибку

События

СобытиеВремя срабатывания
'refresh'После успешного получения данных через refresh()
'saved'После успешных операций create / update / save
resource.on('saved', (data) => {
  console.log('Запись сохранена:', data);
});

Синтаксис фильтра

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' } },
  ]
}

Для управления условиями фильтрации в ресурсе рекомендуется использовать addFilterGroup:

// Добавляем несколько групп фильтров
resource.addFilterGroup('status', { status: { $eq: 'active' } });
resource.addFilterGroup('age', { age: { $gt: 18 } });
// getFilter() автоматически объединяет в: { $and: [...] }

// Удаляем группу фильтра
resource.removeFilterGroup('status');

// Применяем фильтрацию через обновление
await resource.refresh();

Сравнение MultiRecordResource и SingleRecordResource

ВозможностьMultiRecordResourceSingleRecordResource
Что возвращает getData()TDataItem[] (массив)TData (один объект)
Действие для обновления по умолчаниюlistget
Постраничная навигацияПоддерживаетсяНе поддерживается
Выбранные строкиПоддерживаютсяНе поддерживаются
Созданиеcreate(data)save(data) + isNewRecord=true
Обновлениеupdate(filterByTk, data)save(data) + setFilterByTk(id)
Удалениеdestroy(filterByTk)destroy()
Типичные сценарииСписки, таблицы, канбан-доскиФормы, страницы деталей

Связанные ссылки