logologo
Start
Handbuch
Entwicklung
Plugins
API
Startseite
English
简体中文
日本語
한국어
Español
Português
Deutsch
Français
Русский
Start
Handbuch
Entwicklung
Plugins
API
Startseite
logologo
RunJS Übersicht
Module importieren
Rendern im Container

Globale Variablen

window
document
navigator

ctx

ctx.blockModel
ctx.collection
ctx.collectionField
ctx.dataSource
ctx.dataSourceManager
ctx.element
ctx.exit()
ctx.exitAll()
ctx.filterManager
ctx.form
ctx.getModel()
ctx.getValue()
ctx.getVar()
ctx.i18n
ctx.importAsync()
ctx.initResource()
ctx.libs
ctx.location
ctx.logger
ctx.makeResource()
ctx.message
ctx.modal
ctx.model
ctx.notification
ctx.off()
ctx.on()
ctx.openView()
ctx.render()
ctx.request()
ctx.requireAsync()
ctx.resource
ctx.route
ctx.router
ctx.setValue()
ctx.sql
ctx.t()
ctx.view
Previous Pagectx.i18n
Next Pagectx.initResource()
KI-Übersetzungshinweis

Dieses Dokument wurde von KI übersetzt. Für genaue Informationen lesen Sie bitte die englische Version.

#ctx.importAsync()

Lädt ESM-Module oder CSS dynamisch über eine URL, anwendbar in verschiedenen RunJS-Szenarien. Verwenden Sie ctx.importAsync(), wenn ESM-Bibliotheken von Drittanbietern erforderlich sind, und ctx.requireAsync() für UMD/AMD-Bibliotheken. Die Übergabe einer .css-Adresse lädt und injiziert die Stile in die Seite.

#Anwendungsfälle

SzenarioBeschreibung
JSBlockDynamisches Laden von ESM-Bibliotheken wie Vue, ECharts oder Tabulator zur Implementierung benutzerdefinierter Diagramme, Tabellen, Dashboards usw.
JSField / JSItem / JSColumnLaden leichtgewichtiger ESM-Hilfsbibliotheken (z. B. dayjs-Plugins) zur Unterstützung beim Rendering.
Workflow / AktionsereignisseBedarfsgerechtes Laden von Abhängigkeiten vor der Ausführung der Geschäftslogik.

#Typdefinition

importAsync<T = any>(url: string): Promise<T>;

#Parameter

ParameterTypBeschreibung
urlstringDie Adresse des ESM-Moduls oder der CSS-Datei. Unterstützt die Kurzschreibweise <Paketname>@<Version> oder Unterpfade <Paketname>@<Version>/<Dateipfad> (z. B. vue@3.4.0, dayjs@1/plugin/relativeTime.js), die gemäß der Konfiguration mit dem CDN-Präfix verkettet werden. Vollständige URLs werden ebenfalls unterstützt. Wenn eine .css-Datei übergeben wird, wird diese geladen und als Stil injiziert. Für Bibliotheken, die von React abhängen, können Sie ?deps=react@18.2.0,react-dom@18.2.0 anhängen, um sicherzustellen, dass sie dieselbe React-Instanz wie die Seite verwenden.

#Rückgabewert

  • Ein Promise, das zum Namespace-Objekt des Moduls aufgelöst wird.

#Beschreibung des URL-Formats

  • ESM und CSS: Neben ESM-Modulen wird auch das Laden von CSS unterstützt (übergeben Sie eine .css-URL, um diese zu laden und in die Seite zu injizieren).
  • Kurzformat: Standardmäßig wird https://esm.sh als CDN-Präfix verwendet, sofern nichts anderes konfiguriert ist. Beispielsweise fragt vue@3.4.0 tatsächlich https://esm.sh/vue@3.4.0 ab.
  • ?deps: Bibliotheken, die von React abhängen (wie @dnd-kit/core, react-big-calendar), sollten ?deps=react@18.2.0,react-dom@18.2.0 anhängen, um Konflikte mit der React-Instanz der Seite zu vermeiden, die zu Fehlern wie „Invalid hook call“ führen könnten.
  • Selbstgehostetes CDN: Sie können ein internes Netzwerk oder einen selbstgehosteten Dienst über Umgebungsvariablen angeben:
    • ESM_CDN_BASE_URL: Die Basis-URL für das ESM-CDN (Standard ist https://esm.sh).
    • ESM_CDN_SUFFIX: Optionales Suffix (z. B. /+esm für jsDelivr).
    • Für selbstgehostete Dienste siehe: nocobase/esm-server

#Unterschied zu ctx.requireAsync()

  • ctx.importAsync(): Lädt ESM-Module und gibt den Modul-Namespace zurück. Geeignet für moderne Bibliotheken (ESM-Builds wie Vue, dayjs usw.).
  • ctx.requireAsync(): Lädt UMD/AMD-Module oder Skripte, die an den globalen Scope gebunden sind. Wird häufig für UMD-Bibliotheken wie ECharts oder FullCalendar verwendet. Wenn eine Bibliothek sowohl ESM als auch UMD anbietet, wird ctx.importAsync() bevorzugt.

#Beispiele

#Grundlegende Verwendung

Demonstriert die einfachste Verwendung des dynamischen Ladens von ESM-Modulen und CSS über den Paketnamen oder eine vollständige URL.

const Vue = await ctx.importAsync('vue@3.4.0');
// Entspricht dem Laden von https://esm.sh/vue@3.4.0

const relativeTime = await ctx.importAsync('dayjs@1/plugin/relativeTime.js');
// Mit Unterpfad (z. B. dayjs-Plugin)

const pkg = await ctx.importAsync('https://cdn.example.com/my-module.js');
// Vollständige URL

await ctx.importAsync('https://cdn.example.com/theme.css');
// CSS laden und in die Seite injizieren

#ECharts-Beispiel

Verwendung von ECharts zum Zeichnen eines Verkaufsübersichtsdiagramms mit Balken- und Liniendiagrammen.

// 1. ECharts-Modul dynamisch laden
const echarts = await ctx.importAsync('echarts@5.4.3');

// 2. Diagramm-Container erstellen und rendern
const chartEl = document.createElement('div');
chartEl.style.width = '100%';
chartEl.style.height = '400px';
ctx.render(chartEl);

// 3. ECharts-Instanz initialisieren
const chart = echarts.init(chartEl);

// 4. Diagramm konfigurieren
const option = {
  title: {
    text: 'Sales Overview',
    left: 'center',
  },
  tooltip: {
    trigger: 'axis',
  },
  legend: {
    data: ['Sales', 'Profit'],
    top: '10%',
  },
  xAxis: {
    type: 'category',
    data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
  },
  yAxis: {
    type: 'value',
  },
  series: [
    {
      name: 'Sales',
      type: 'bar',
      data: [120, 200, 150, 80, 70, 110],
    },
    {
      name: 'Profit',
      type: 'line',
      data: [20, 40, 30, 15, 12, 25],
    },
  ],
};

// 5. Konfiguration setzen und Diagramm rendern
chart.setOption(option);

// 6. Optional: Responsive Größenanpassung
window.addEventListener('resize', () => {
  chart.resize();
});

// 7. Optional: Event-Listener
chart.on('click', (params) => {
  ctx.message.info(`Clicked ${params.seriesName} on ${params.name}, value: ${params.value}`);
});

#Tabulator-Beispiel

Demonstriert das Rendern einer Datentabelle mit Paginierung und Zeilenklick-Ereignissen in einem Block unter Verwendung von Tabulator.

// 1. Tabulator-Stile laden
await ctx.importAsync('tabulator-tables@6.2.5/dist/css/tabulator.min.css');

// 2. Tabulator-Modul dynamisch laden
const { TabulatorFull } = await ctx.importAsync('tabulator-tables@6.2.5');

// 3. Tabellen-Container erstellen und rendern
const tableEl = document.createElement('div');
ctx.render(tableEl);

// 4. Tabulator-Tabelle initialisieren
const table = new TabulatorFull(tableEl, {
  data: [
    { id: 1, name: 'Alice', age: 25, city: 'Beijing' },
    { id: 2, name: 'Bob', age: 30, city: 'Shanghai' },
    { id: 3, name: 'Charlie', age: 28, city: 'Guangzhou' },
  ],
  columns: [
    { title: 'ID', field: 'id', width: 80 },
    { title: 'Name', field: 'name', width: 150 },
    { title: 'Age', field: 'age', width: 100 },
    { title: 'City', field: 'city', width: 150 },
  ],
  layout: 'fitColumns',
  pagination: true,
  paginationSize: 10,
});

// 5. Optional: Event-Listener
table.on('rowClick', (e, row) => {
  const rowData = row.getData();
  ctx.message.info(`Row clicked: ${rowData.name}`);
});

#FullCalendar (ESM) Beispiel

Zeigt, wie FullCalendar und seine Plugins über ESM geladen und ein einfacher Kalender in der Monatsansicht gerendert werden.

// 1. FullCalendar Core-Modul dynamisch laden
const { Calendar } = await ctx.importAsync('@fullcalendar/core@6.1.20');

// 2. dayGrid-Plugin dynamisch laden
const dayGridPlugin = await ctx.importAsync('@fullcalendar/daygrid@6.1.20');

// 3. Kalender-Container erstellen und rendern
const calendarEl = document.createElement('div');
calendarEl.id = 'calendar';
ctx.render(calendarEl);

// 4. Kalender initialisieren und rendern
const calendar = new Calendar(calendarEl, {
  plugins: [dayGridPlugin.default || dayGridPlugin],
  headerToolbar: {
    left: 'prev,next today',
    center: 'title',
    right: 'dayGridMonth',
  },
});

calendar.render();

#dnd-kit einfaches Drag-and-Drop-Beispiel

Verwendet @dnd-kit/core, um ein minimales Beispiel für das Ziehen einer Box in einen Zielbereich innerhalb eines Blocks zu implementieren.

// 1. React, react-dom, @dnd-kit/core laden (?deps stellt dieselbe React-Instanz sicher, um Invalid hook call zu vermeiden)
const React = await ctx.importAsync('react@18.2.0');
const { createRoot } = await ctx.importAsync('react-dom@18.2.0/client');
const core = await ctx.importAsync('@dnd-kit/core@6.3.1?deps=react@18.2.0,react-dom@18.2.0');
const { DndContext, closestCenter, PointerSensor, useSensor, useSensors, useDraggable, useDroppable } = core;

function DraggableBox() {
  const { attributes, listeners, setNodeRef, transform } = useDraggable({ id: 'box' });
  const style = {
    padding: 12,
    marginBottom: 8,
    background: '#e6f7ff',
    cursor: 'grab',
    transform: transform ? 'translate3d(' + transform.x + 'px,' + transform.y + 'px,0)' : undefined,
  };
  return React.createElement('div', { ref: setNodeRef, style, ...attributes, ...listeners }, 'Drag me');
}

function DropZone() {
  const { setNodeRef, isOver } = useDroppable({ id: 'zone' });
  return React.createElement(
    'div',
    {
      ref: setNodeRef,
      style: { padding: 24, background: isOver ? '#b7eb8f' : '#f5f5f5', borderRadius: 8, minHeight: 80 },
    },
    'Drop here',
  );
}

function App() {
  const sensors = useSensors(useSensor(PointerSensor));
  function onDragEnd(e) {
    if (e.over && e.over.id === 'zone') ctx.message.success('Dropped in zone');
  }
  return React.createElement(
    DndContext,
    { sensors, collisionDetection: closestCenter, onDragEnd },
    React.createElement(
      'div',
      { style: { maxWidth: 280 } },
      React.createElement(DraggableBox),
      React.createElement(DropZone),
    ),
  );
}

// 2. Rendern
ctx.render(<App />);

Dieses Beispiel basiert nur auf @dnd-kit/core, um eine Benachrichtigung auszulösen, wenn eine Box in einen bestimmten Bereich gezogen wird. Es demonstriert die einfachste Drag-and-Drop-Interaktion durch die Kombination von ctx.importAsync + React in RunJS.

#dnd-kit sortierbare Liste Beispiel

Implementiert eine vertikale sortierbare Liste unter Verwendung von dnd-kit Core, Sortable und Utilities.

// 1. React und dnd-kit-bezogene Pakete laden (?deps stellt dieselbe React-Instanz sicher)
const React = await ctx.importAsync('react@18.2.0');
const { createRoot } = await ctx.importAsync('react-dom@18.2.0/client');
const dndCore = await ctx.importAsync('@dnd-kit/core@6.3.1?deps=react@18.2.0,react-dom@18.2.0');
const dndSortable = await ctx.importAsync('@dnd-kit/sortable@10.0.0?deps=react@18.2.0,react-dom@18.2.0');
const dndUtils = await ctx.importAsync('@dnd-kit/utilities@3.2.2');

const { useState } = React;
const { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } = dndCore;
const {
  arrayMove,
  SortableContext,
  sortableKeyboardCoordinates,
  verticalListSortingStrategy,
  useSortable,
} = dndSortable;
const { CSS } = dndUtils;

// 2. SortableItem-Komponente (muss sich innerhalb von SortableContext befinden)
function SortableItem(props) {
  const { id, label } = props;
  const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    padding: '12px 16px',
    marginBottom: 8,
    background: '#f5f5f5',
    borderRadius: 6,
    cursor: 'grab',
  };
  return React.createElement('div', { ref: setNodeRef, style, ...attributes, ...listeners }, label);
}

// 3. App: DndContext + SortableContext + Drag-End-Handler
const labels = { 1: 'First', 2: 'Second', 3: 'Third', 4: 'Fourth' };
function App() {
  const [items, setItems] = useState([1, 2, 3, 4]);
  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
  );

  function handleDragEnd(event) {
    const { active, over } = event;
    if (over && active.id !== over.id) {
      setItems((prev) => {
        const oldIndex = prev.indexOf(active.id);
        const newIndex = prev.indexOf(over.id);
        return arrayMove(prev, oldIndex, newIndex);
      });
      ctx.message.success('List reordered');
    }
  }

  return React.createElement(
    DndContext,
    {
      sensors,
      collisionDetection: closestCenter,
      onDragEnd: handleDragEnd,
    },
    React.createElement(
      SortableContext,
      { items, strategy: verticalListSortingStrategy },
      React.createElement(
        'div',
        { style: { maxWidth: 320 } },
        items.map((id) => React.createElement(SortableItem, { key: id, id, label: labels[id] })),
      ),
    ),
  );
}

// 4. Container erstellen und React einhängen
const rootEl = document.createElement('div');
ctx.render(rootEl);
createRoot(rootEl).render(React.createElement(App));

Dieses Beispiel verwendet @dnd-kit/core, @dnd-kit/sortable und @dnd-kit/utilities, um eine sortierbare Liste zu implementieren, die ihre Reihenfolge aktualisiert und nach dem Ziehen die Meldung „List reordered“ anzeigt.

#react-big-calendar Beispiel

Rendert eine Kalenderkomponente mit Unterstützung für die Anzeige von Ereignissen im aktuellen Block unter Verwendung von react-big-calendar und date-fns für die Lokalisierung.

// 1. Stile laden (ctx.importAsync verwendet ctx.loadCSS für .css-Dateien)
await ctx.importAsync('react-big-calendar@1.11.4/lib/css/react-big-calendar.css');

// 2. React, react-dom, react-big-calendar, date-fns und Locale laden (dieselbe React-Instanz sicherstellen)
const React = await ctx.importAsync('react@18.2.0');
const { Calendar, dateFnsLocalizer } = await ctx.importAsync('react-big-calendar@1.11.4?deps=react@18.2.0,react-dom@18.2.0');
const { format, parse, startOfWeek, getDay } = await ctx.importAsync('date-fns@2.30.0');
const enUS = await ctx.importAsync('date-fns@2.30.0/locale/en-US.js');

const localizer = dateFnsLocalizer({
  format,
  parse,
  startOfWeek,
  getDay,
  locales: { 'en-US': enUS },
});

const events = [
  { title: 'All Day Event', start: new Date(2026, 0, 28), end: new Date(2026, 0, 28), allDay: true },
  { title: 'Meeting', start: new Date(2026, 0, 29, 10, 0), end: new Date(2026, 0, 29, 11, 0) },
];

// 3. React-Kalender rendern
ctx.render(
  <Calendar
    localizer={localizer}
    events={events}
    startAccessor="start"
    endAccessor="end"
    style={{ height: '80vh' }}
  />
);

#frappe-gantt Beispiel

Verwendet frappe-gantt, um eine Gantt-Diagramm-Ansicht zu rendern, die Start-/Endzeiten und den Fortschritt von Aufgaben anzeigt.

// 1. Gantt-Stile und Konstruktor dynamisch laden
// Hängt von ESM_CDN_BASE_URL ab (Standard https://esm.sh), Kurzpfade können verwendet werden
await ctx.importAsync('frappe-gantt@1.0.4/dist/frappe-gantt.css');
const Gantt = await ctx.importAsync('frappe-gantt@1.0.4');

// 2. Aufgabendaten vorbereiten
let tasks = [
  {
    id: '1',
    name: 'Redesign website',
    start: '2016-12-28',
    end: '2016-12-31',
    progress: 20,
  },
  {
    id: '2',
    name: 'Develop new feature',
    start: '2017-01-01',
    end: '2017-01-05',
    progress: 40,
    dependencies: '1',
  },
  {
    id: '3',
    name: 'QA & testing',
    start: '2017-01-06',
    end: '2017-01-10',
    progress: 10,
    dependencies: '2',
  },
];

// 3. Container erstellen und rendern
const ganttEl = document.createElement('div');
ganttEl.id = 'gantt';
ganttEl.style.height = '400px';
ctx.render(ganttEl);

// 4. Gantt-Diagramm initialisieren
let gantt = new Gantt('#gantt', tasks, {
  view_mode: 'Day', // Ansichtsgranularität: 'Quarter Day' | 'Half Day' | 'Day' | 'Week' | 'Month'
  language: 'en',
  bar_height: 24,
  padding: 18,
  custom_popup_html(task) {
    return `
      <div class="details-container">
        <h5>${task.name}</h5>
        <p>Start: ${task._start.toISOString().slice(0, 10)}</p>
        <p>End: ${task._end.toISOString().slice(0, 10)}</p>
        <p>Progress: ${task.progress}%</p>
      </div>
    `;
  },
});

#@asseinfo/react-kanban Beispiel

Nutzt @asseinfo/react-kanban, um ein einfaches Kanban-Board mit Spalten wie Backlog und Doing innerhalb eines Blocks zu rendern.

// 1. Stile laden (ctx.importAsync lädt .css direkt)
await ctx.importAsync('@asseinfo/react-kanban@2.2.0/dist/styles.css');

// 2. React, react-dom, @asseinfo/react-kanban laden (?deps stellt dieselbe React-Instanz sicher)
const React = await ctx.importAsync('react@18.2.0');
const { default: Board } = await ctx.importAsync('@asseinfo/react-kanban@2.2.0?deps=react@18.2.0,react-dom@18.2.0');

const board = {
  columns: [
    {
      id: 1,
      title: 'Backlog',
      cards: [
        { id: 1, title: 'Add card', description: 'Add capability to add a card in a column' },
      ],
    },
    {
      id: 2,
      title: 'Doing',
      cards: [
        { id: 2, title: 'Drag-n-drop support', description: 'Move a card between the columns' },
      ],
    },
  ],
};

// 4. Board einhängen
ctx.render(<Board initialBoard={board} />);

#Hinweise

  • Diese Funktion ist von einem externen Netzwerk oder CDN abhängig. In internen Netzwerkumgebungen muss ESM_CDN_BASE_URL so konfiguriert werden, dass sie auf einen selbstgehosteten Dienst zeigt.
  • Wenn eine Bibliothek sowohl ESM als auch UMD anbietet, bevorzugen Sie ctx.importAsync() für eine bessere Modulsemantik.
  • Für Bibliotheken, die von React abhängen, stellen Sie sicher, dass Sie ?deps=react@18.2.0,react-dom@18.2.0 anhängen. Die Version muss mit der von der Seite verwendeten React-Version übereinstimmen, da sonst ein „Invalid hook call“-Fehler auftreten kann.

#Verwandte Themen

  • ctx.requireAsync(): Lädt UMD/AMD oder global gebundene Skripte, geeignet für UMD-Bibliotheken wie ECharts und FullCalendar.
  • ctx.render(): Rendert Inhalte in einen Container.