Частые проблемы и руководство по устранению неполадок

Здесь собраны ловушки, в которые легко попасть при разработке клиентских плагинов. Если у Вас ситуация «вроде бы написано правильно, а не работает» — приходите сюда первым делом.

Плагин

После создания плагин не виден в менеджере

Убедитесь, что Вы выполняли yarn pm create, а не создавали каталог вручную. yarn pm create, помимо генерации файлов, регистрирует плагин в таблице applicationPlugins базы данных. Если каталог был создан вручную, можно выполнить yarn nocobase upgrade для повторного сканирования.

После включения плагина страница не изменилась

Проверьте по порядку:

  1. Убедитесь, что Вы выполнили yarn pm enable <pluginName>
  2. Обновите браузер (иногда нужна принудительная перезагрузка Ctrl+Shift+R)
  3. Проверьте консоль браузера на наличие ошибок

После изменения кода страница не обновляется

Поведение горячей перезагрузки разное для разных типов файлов:

Тип файлаЧто нужно после изменения
tsx/ts в src/client-v2/Автоматическая горячая перезагрузка, ничего не нужно
Файлы перевода в src/locale/Перезапустить приложение
Добавление или изменение collection в src/server/collections/Выполнить yarn nocobase upgrade

Если код клиента изменён, но горячая перезагрузка не сработала, сначала попробуйте обновить браузер.

Маршрутизация

Зарегистрированный маршрут страницы недоступен

В NocoBase v2 маршруты по умолчанию имеют префикс /v. Например, если Вы зарегистрировали path: '/hello', фактический адрес — /v/hello:

this.router.add('hello', {
  path: '/hello', // 实际访问 -> /v/hello
  componentLoader: () => import('./pages/HelloPage'),
});

Подробнее см. в Router (Маршрутизация).

Страница настроек плагина пустая

Если меню страницы настроек появилось, но содержимое пустое, обычно одна из двух причин:

Причина первая: в v1 client использован componentLoader

componentLoader — это синтаксис client-v2, в v1 client нужно использовать Component с прямой передачей компонента:

// ❌ v1 client 不支持 componentLoader
this.pluginSettingsManager.addPageTabItem({
  menuKey: 'my-settings',
  key: 'index',
  componentLoader: () => import('./pages/MyPage'),
});

// ✅ v1 client 用 Component
import MyPage from './pages/MyPage';
this.pluginSettingsManager.addPageTabItem({
  menuKey: 'my-settings',
  key: 'index',
  Component: MyPage,
});

Причина вторая: компонент страницы не экспортирован через export default

componentLoader требует, чтобы у модуля был экспорт по умолчанию — без default загрузить не получится.

Блоки

Пользовательский блок не виден в меню «Добавить блок»

Убедитесь, что в load() зарегистрирована модель:

this.flowEngine.registerModelLoaders({
  MyBlockModel: {
    loader: () => import('./models/MyBlockModel'),
  },
});

Если используется registerModels (без ленивой загрузки), убедитесь, что в models/index.ts модель корректно экспортирована.

При добавлении блока в списке выбора таблицы данных нет моей таблицы

Таблица, определённая через defineCollection, является внутренней серверной таблицей и по умолчанию не появляется в списке таблиц данных в UI.

Рекомендуемый подход: добавьте соответствующую таблицу данных в Управлении источниками данных интерфейса NocoBase, после настройки полей и типа интерфейса таблица автоматически появится в списке выбора таблиц данных в блоке.

Если действительно нужно регистрировать в коде плагина (например, в демонстрационном сценарии плагина-примера), можно вручную зарегистрировать через addCollection. Подробнее см. в Создание плагина управления данными с интеграцией фронтенда и бэкенда. Внимание: регистрировать нужно через шаблон eventBus, нельзя напрямую вызывать в load()ensureLoaded() будет выполнено после load() и очистит и заново установит все collection.

Хочу, чтобы пользовательский блок был привязан только к определённой таблице данных

Переопределите static filterCollection на модели — в списке выбора появятся только collection, для которых функция вернула true:

export class MyBlockModel extends TableBlockModel {
  static filterCollection(collection: Collection) {
    return collection.name === 'myTable';
  }
}

Поля

Пользовательский компонент поля не виден в выпадающем меню «Компонент поля»

Проверьте по порядку:

  1. Убедитесь, что вызван DisplayItemModel.bindModelToInterface('ModelName', ['input']), и тип interface совпадает — например, input соответствует полю однострочного текста, checkbox — флажку
  2. Убедитесь, что модель зарегистрирована в load() (registerModels или registerModelLoaders)
  3. Убедитесь, что для модели поля вызван define({ label })

В выпадающем меню «Компонент поля» отображается имя класса

Забыли вызвать define({ label }) на модели поля — добавьте это:

MyFieldModel.define({
  label: tExpr('My field'),
});

Также убедитесь, что в файлах перевода src/locale/ есть соответствующий ключ — иначе в китайской среде будет отображаться английский оригинал.

Действия

Пользовательская кнопка действия не видна в «Настройка действий»

Убедитесь, что на модели правильно установлен static scene:

ЗначениеГде появится
ActionSceneEnum.collectionПанель действий вверху блока (например, рядом с кнопкой «Создать»)
ActionSceneEnum.recordСтолбец действий каждой строки таблицы (например, рядом с «Редактировать», «Удалить»)
ActionSceneEnum.bothПоявляется в обоих сценариях

Клик по кнопке действия не реагирует

Убедитесь, что on в registerFlow установлено в 'click':

MyActionModel.registerFlow({
  key: 'myFlow',
  on: 'click', // 监听按钮点击
  steps: {
    doSomething: {
      async handler(ctx) {
        // 你的逻辑
      },
    },
  },
});
Внимание

uiSchema в registerFlow — это UI панели конфигурации (режим настройки), а не модальное окно во время выполнения. Если Вы хотите по клику кнопки открыть форму, в handler следует использовать ctx.viewer.dialog() для открытия модального окна.

Интернационализация

Перевод не работает

Самые частые причины:

  • Первое добавление каталога или файла src/locale/ — нужно перезапустить приложение
  • Несовпадение ключей перевода — убедитесь, что ключ полностью совпадает со строкой в коде, обращайте внимание на пробелы и регистр
  • В компоненте напрямую использован ctx.t()ctx.t() не подставляет namespace плагина автоматически; в компоненте следует использовать хук useT() (импортируется из locale.ts)

Неправильное использование tExpr(), useT() и this.t()

У этих трёх методов перевода разные сценарии — при неправильном использовании либо будет ошибка, либо перевод не сработает:

МетодГде используетсяОписание
tExpr()Статические определения define(), registerFlow() и т.д.На этапе загрузки модуля i18n ещё не инициализирован, нужен отложенный перевод
useT()Внутри React-компонентовВозвращает функцию перевода с привязкой к namespace плагина
this.t()В load() плагинаАвтоматически подставляет имя пакета плагина в качестве namespace

Подробнее см. в i18n Интернационализация.

API-запросы

Запрос возвращает 403 Forbidden

Обычно причина в отсутствии настройки ACL на сервере. Например, если Ваш collection называется todoItems, в load() серверного плагина нужно разрешить соответствующие операции:

// 只允许查询
this.app.acl.allow('todoItems', ['list', 'get'], 'loggedIn');

// 允许完整增删改查
this.app.acl.allow('todoItems', ['list', 'get', 'create', 'update', 'destroy'], 'loggedIn');

'loggedIn' означает, что доступ имеет любой авторизованный пользователь. Без acl.allow по умолчанию операции может выполнять только администратор.

Запрос возвращает 404 Not Found

Проверьте по порядку:

  • Если используется defineCollection, убедитесь в правильности написания имени collection
  • Если используется resourceManager.define, убедитесь в правильности имён resource и action
  • Проверьте формат URL запроса — формат API в NocoBase: resourceName:actionName, например todoItems:list, externalApi:get

Сборка и развёртывание

yarn build --tar ошибка "no paths specified to add to archive"

При выполнении yarn build <pluginName> --tar появляется ошибка:

TypeError: no paths specified to add to archive

Однако отдельное выполнение yarn build <pluginName> (без --tar) проходит нормально.

Эта проблема обычно возникает из-за того, что в .npmignore плагина используется синтаксис отрицания (префикс ! в npm). При упаковке через --tar NocoBase читает каждую строку .npmignore и добавляет перед ней !, чтобы преобразовать в шаблон исключения fast-glob. Если в .npmignore уже использован синтаксис отрицания, например:

*
!dist
!package.json

После обработки получится ['!*', '!!dist', '!!package.json', '**/*']. При этом !* исключает все файлы корневого уровня (включая package.json), а !!dist не распознаётся fast-glob как «снова включить dist» — отрицание не работает. Если каталог dist/ оказался пустым или сборка не выдала файлов, итоговый список собранных файлов будет пустым, и tar выбросит эту ошибку.

Решение: не используйте синтаксис отрицания в .npmignore, перечислите только каталоги, которые нужно исключить:

/node_modules
/src

Логика упаковки преобразует это в шаблоны исключения (!./node_modules, !./src), плюс **/* для всех остальных файлов. Так и проще, и без проблем с отрицанием.

После загрузки плагина в продакшен включение завершается ошибкой (локально работает)

Плагин при локальной разработке работает нормально, но после загрузки через «Менеджер плагинов» в продакшен включение завершается ошибкой, в логе появляется примерно такое:

TypeError: Cannot assign to read only property 'constructor' of object '[object Object]'

Эта проблема обычно возникает из-за того, что плагин запаковал встроенные зависимости NocoBase в собственный node_modules/. Система сборки NocoBase поддерживает список external — пакеты в нём (например, react, antd, axios, lodash и т.д.) предоставляются хостом NocoBase и не должны упаковываться в плагин. Если плагин содержит свою копию, во время выполнения может возникнуть конфликт с уже загруженной хостом версией, вызывающий разные странные ошибки.

Почему локально нет проблем: при локальной разработке плагин находится в каталоге packages/plugins/, у него нет собственного node_modules/, зависимости разрешаются в уже загруженные версии в корне проекта, конфликта не возникает.

Решение: перенесите все dependencies в package.json плагина в devDependencies — система сборки NocoBase автоматически обработает зависимости плагина:

{
- "dependencies": {
-   "axios": "1.7.7"
- },
+ "devDependencies": {
+   "axios": "1.7.7"
+ },
}

Затем пересоберите и упакуйте. Так в dist/node_modules/ плагина не попадут эти пакеты, и во время выполнения будут использоваться версии, предоставленные хостом NocoBase.

Общий принцип

Система сборки NocoBase поддерживает список external — пакеты в нём (например, react, antd, axios, lodash и т.д.) предоставляются хостом NocoBase, плагин не должен их упаковывать сам. Все зависимости плагина следует помещать в devDependencies, система сборки автоматически определит, какие нужно упаковать в dist/node_modules/, а какие предоставит хост.

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