FlowEngine

In NocoBase ist die FlowEngine (Flow-Engine) die zentrale Engine, die die visuelle Konfiguration antreibt. Blöcke, Felder und Aktionsbuttons in der NocoBase-Oberfläche werden alle über die FlowEngine verwaltet — einschließlich ihres Renderings, ihres Konfigurationspanels sowie der Persistenz der Konfiguration.

20260403215904

Für Plugin-Entwickler stellt die FlowEngine zwei zentrale Konzepte bereit:

  • FlowModel — ein konfigurierbares Component-Modell, zuständig für UI-Rendering und Verwaltung der Props
  • Flow — ein Konfigurationsablauf, definiert das Konfigurationspanel und die Datenverarbeitungslogik des Components

Wenn Ihr Component im Menü „Block / Feld / Aktion hinzufügen" erscheinen soll oder eine visuelle Konfiguration durch den Benutzer in der Oberfläche unterstützen muss, verpacken Sie es mit einem FlowModel. Wenn diese Fähigkeiten nicht benötigt werden, reicht ein gewöhnliches React Component aus — siehe Component vs FlowModel.

Was ist FlowModel

Im Gegensatz zu einem gewöhnlichen React Component verwaltet ein FlowModel neben dem UI-Rendering auch die Quelle der Props, die Definition des Konfigurationspanels und die Persistenz der Konfiguration. Kurz gesagt: Die Props eines gewöhnlichen Components sind hartcodiert, die Props eines FlowModel werden über einen Flow dynamisch erzeugt.

Wenn Sie sich tiefer mit der Gesamtarchitektur der FlowEngine auseinandersetzen möchten, lesen Sie die vollständige FlowEngine-Dokumentation. Im Folgenden wird die Verwendung aus Sicht des Plugin-Entwicklers vorgestellt.

Minimalbeispiel

Ein FlowModel von der Erstellung bis zur Registrierung in drei Schritten:

1. Basisklasse erweitern, renderComponent implementieren

// models/HelloBlockModel.tsx
import React from 'react';
import { BlockModel } from '@nocobase/client-v2';
import { tExpr } from '@nocobase/flow-engine';

export class HelloBlockModel extends BlockModel {
  renderComponent() {
    return (
      <div>
        <h3>Hello FlowEngine!</h3>
        <p>Dies ist ein benutzerdefinierter Block.</p>
      </div>
    );
  }
}

// define() legt den im Menü angezeigten Namen fest
HelloBlockModel.define({
  label: tExpr('Hello block'),
});

renderComponent() ist die Render-Methode dieses Modells, ähnlich der render()-Methode eines React Components. tExpr() dient zur verzögerten Übersetzung — denn define() wird beim Laden des Moduls ausgeführt, zu diesem Zeitpunkt ist i18n noch nicht initialisiert. Details siehe Context Häufige Fähigkeiten → tExpr.

2. Im Plugin registrieren

// plugin.tsx
import { Plugin } from '@nocobase/client-v2';

export class MyPlugin extends Plugin {
  async load() {
    this.flowEngine.registerModelLoaders({
      HelloBlockModel: {
        // Lazy Loading, Modul wird erst beim ersten Verwenden geladen
        loader: () => import('./models/HelloBlockModel'),
      },
    });
  }
}

3. In der Oberfläche verwenden

Nach der Registrierung und dem Aktivieren des Plugins (siehe Plugin-Entwicklungs-Übersicht zum Aktivieren) können Sie in der NocoBase-Oberfläche eine neue Seite anlegen, auf „Block hinzufügen" klicken und Ihren „Hello block" sehen.

20260403221815

Konfigurationselemente mit registerFlow hinzufügen

Reine Darstellung reicht nicht — der Kernwert eines FlowModel liegt in seiner Konfigurierbarkeit. Über registerFlow() können Sie dem Modell ein Konfigurationspanel hinzufügen, sodass Benutzer Eigenschaften in der Oberfläche ändern können.

Beispiel für einen Block, der die Bearbeitung von HTML-Inhalt unterstützt:

// 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() {
    // Der Wert von this.props stammt aus dem Setzen im Flow-Handler
    return <div dangerouslySetInnerHTML={{ __html: this.props.html }} />;
  }
}

SimpleBlockModel.define({
  label: tExpr('Simple block'),
});

SimpleBlockModel.registerFlow({
  key: 'flow1',
  title: tExpr('Simple Block Flow'),
  on: 'beforeRender', // Vor dem Rendern ausführen
  steps: {
    editHtml: {
      title: tExpr('Edit HTML Content'),
      // uiSchema definiert die UI des Konfigurationspanels
      uiSchema: {
        html: {
          type: 'string',
          title: tExpr('HTML Content'),
          'x-decorator': 'FormItem',
          'x-component': 'Input.TextArea',
        },
      },
      // Standardwerte
      defaultParams: {
        html: `<h3>This is a simple block</h3>
<p>You can edit the HTML content.</p>`,
      },
      // Im Handler den Wert aus dem Konfigurationspanel auf die props des Modells setzen
      handler(ctx, params) {
        ctx.model.props.html = params.html;
      },
    },
  },
});

Wichtige Punkte hier:

  • on: 'beforeRender' — bedeutet, dass dieser Flow vor dem Rendern ausgeführt wird; der Wert aus dem Konfigurationspanel wird vor dem Rendern in this.props geschrieben
  • uiSchema — definiert die UI des Konfigurationspanels im JSON-Schema-Format, Syntax siehe UI Schema
  • handler(ctx, params)params sind die im Konfigurationspanel ausgefüllten Werte, die über ctx.model.props auf das Modell gesetzt werden
  • defaultParams — Standardwerte des Konfigurationspanels

Häufige Schreibweisen für uiSchema

uiSchema basiert auf JSON Schema. v2 ist syntaktisch zu uiSchema kompatibel, allerdings mit begrenzten Verwendungsszenarien — hauptsächlich zur Beschreibung der Formular-UI im Konfigurationspanel von Flows. Für das Rendern von Components zur Laufzeit wird in den meisten Fällen empfohlen, direkt mit Antd-Components zu arbeiten, ohne uiSchema zu durchlaufen.

Hier die häufigsten Components (vollständige Referenz siehe UI Schema):

uiSchema: {
  // Texteingabe
  title: {
    type: 'string',
    title: 'Titel',
    'x-decorator': 'FormItem',
    'x-component': 'Input',
  },
  // Mehrzeiliger Text
  content: {
    type: 'string',
    title: 'Inhalt',
    'x-decorator': 'FormItem',
    'x-component': 'Input.TextArea',
  },
  // Dropdown-Auswahl
  type: {
    type: 'string',
    title: 'Typ',
    'x-decorator': 'FormItem',
    'x-component': 'Select',
    enum: [
      { label: 'Primär', value: 'primary' },
      { label: 'Standard', value: 'default' },
      { label: 'Gestrichelt', value: 'dashed' },
    ],
  },
  // Schalter
  bordered: {
    type: 'boolean',
    title: 'Rahmen anzeigen',
    'x-decorator': 'FormItem',
    'x-component': 'Switch',
  },
}

Jedes Feld wird mit 'x-decorator': 'FormItem' umhüllt, wodurch automatisch Titel und Layout hinzugefügt werden.

Erläuterung der define()-Parameter

FlowModel.define() dient zum Setzen der Metadaten des Modells und steuert, wie es im Menü angezeigt wird. Bei der Plugin-Entwicklung wird label am häufigsten verwendet, es werden aber noch weitere Parameter unterstützt:

ParameterTypHinweis
labelstring | ReactNodeIm Menü „Block / Feld / Aktion hinzufügen" angezeigter Name, unterstützt verzögerte Übersetzung mit tExpr()
iconReactNodeIcon im Menü
sortnumberSortiergewicht, kleinere Zahlen erscheinen weiter vorne. Standard 0
hideboolean | (ctx) => booleanIm Menü ausblenden, dynamische Bedingungen unterstützt
groupstringGruppen-Identifier, zur Einordnung in eine bestimmte Menügruppe
childrenSubModelItem[] | (ctx) => SubModelItem[]Untermenüpunkte, asynchrone dynamische Erzeugung unterstützt
toggleableboolean | (model) => booleanOb Umschalten unterstützt wird (eindeutig unter demselben Eltern)
searchablebooleanOb das Untermenü Suche aktiviert

Die meisten Plugins setzen nur label:

MyBlockModel.define({
  label: tExpr('My block'),
});

Wenn Sortierung oder Ausblenden gesteuert werden soll, fügen Sie sort und hide hinzu:

MyBlockModel.define({
  label: tExpr('My block'),
  sort: 10,       // Weiter hinten einsortieren
  hide: (ctx) => !ctx.someCondition,  // Bedingt ausblenden
});

Auswahl der FlowModel-Basisklasse

NocoBase stellt mehrere FlowModel-Basisklassen bereit, je nach erweitertem Typ:

BasisklasseZweckDetaillierte Doku
BlockModelGewöhnlicher BlockBlock-Erweiterung
DataBlockModelBlock, der Daten selbst beschaffen mussBlock-Erweiterung
CollectionBlockModelAn eine Datentabelle gebunden, holt Daten automatischBlock-Erweiterung
TableBlockModelVollständiger Tabellenblock, mit Feldspalten, Aktionsleiste usw.Block-Erweiterung
FieldModelFeld-ComponentFeld-Erweiterung
ActionModelAktionsbuttonAktions-Erweiterung

Verwenden Sie für Tabellenblöcke in der Regel TableBlockModel (am häufigsten verwendet, sofort einsatzbereit), für vollständig benutzerdefiniertes Rendering CollectionBlockModel oder BlockModel, für Felder FieldModel, für Aktionsbuttons ActionModel.