Tạo một nút Action tùy chỉnh

Trong NocoBase, Action là các nút trong block dùng để kích hoạt logic nghiệp vụ — ví dụ như "Tạo mới", "Chỉnh sửa", "Xóa". Ví dụ này hướng dẫn cách dùng ActionModel để tạo nút Action tùy chỉnh, và dùng ActionSceneEnum để kiểm soát kịch bản nút xuất hiện.

Đọc trước

Nên tìm hiểu các nội dung sau trước khi phát triển để mượt mà hơn:

Kết quả cuối cùng

Chúng ta sẽ tạo ba nút Action tùy chỉnh, tương ứng với ba kịch bản Action:

  • Action ở mức collection (collection) — Xuất hiện trong action bar phía trên block, ví dụ cạnh nút "Tạo mới"
  • Action ở mức record (record) — Xuất hiện trong cột action của mỗi hàng trong table, ví dụ cạnh nút "Chỉnh sửa", "Xóa"
  • Cả hai đều áp dụng (both) — Xuất hiện ở cả hai kịch bản

Toàn bộ source code xem tại @nocobase-example/plugin-simple-action. Nếu bạn muốn chạy thử trực tiếp ở local:

yarn pm enable @nocobase-example/plugin-simple-action

Sau đây ta sẽ xây dựng plugin này từ đầu, từng bước một.

Bước 1: Tạo khung plugin

Tại thư mục gốc của repo, chạy:

yarn pm create @my-project/plugin-simple-action

Chi tiết xem tại Viết Plugin đầu tiên.

Bước 2: Tạo Action Model

Mỗi Action cần khai báo kịch bản xuất hiện qua thuộc tính static scene:

Kịch bảnGiá trịMô tả
collectionActionSceneEnum.collectionÁp dụng cho collection, ví dụ nút "Tạo mới"
recordActionSceneEnum.recordÁp dụng cho từng record, ví dụ nút "Chỉnh sửa", "Xóa"
bothActionSceneEnum.bothCả hai kịch bản đều dùng được

Action ở mức collection

Tạo file mới src/client-v2/models/SimpleCollectionActionModel.tsx:

// src/client-v2/models/SimpleCollectionActionModel.tsx
import { ActionModel, ActionSceneEnum } from '@nocobase/client-v2';
import { ButtonProps } from 'antd';
import { tExpr } from '../locale';

export class SimpleCollectionActionModel extends ActionModel {
  static scene = ActionSceneEnum.collection;

  defaultProps: ButtonProps = {
    children: tExpr('Simple collection action'),
  };
}

SimpleCollectionActionModel.define({
  label: tExpr('Simple collection action'),
});

// Lắng nghe sự kiện click qua registerFlow, dùng ctx.message để phản hồi cho người dùng
SimpleCollectionActionModel.registerFlow({
  key: 'clickFlow',
  title: tExpr('Simple collection action'),
  on: 'click',
  steps: {
    showMessage: {
      async handler(ctx) {
        ctx.message.success(ctx.t('Collection action clicked'));
      },
    },
  },
});

Action ở mức record

Tạo file mới src/client-v2/models/SimpleRecordActionModel.tsx:

// src/client-v2/models/SimpleRecordActionModel.tsx
import { ActionModel, ActionSceneEnum } from '@nocobase/client-v2';
import { ButtonProps } from 'antd';
import { tExpr } from '../locale';

export class SimpleRecordActionModel extends ActionModel {
  static scene = ActionSceneEnum.record;

  defaultProps: ButtonProps = {
    children: tExpr('Simple record action'),
  };
}

SimpleRecordActionModel.define({
  label: tExpr('Simple record action'),
});

// Action ở mức record có thể lấy dữ liệu và index của hàng hiện tại qua ctx.model.context
SimpleRecordActionModel.registerFlow({
  key: 'clickFlow',
  title: tExpr('Simple record action'),
  on: 'click',
  steps: {
    showMessage: {
      async handler(ctx) {
        const index = ctx.model.context.recordIndex;
        const record = ctx.model.context.record;
        const id = record?.id;
        ctx.message.info(ctx.t('Record action clicked, record ID: {{id}}, row index: {{index}}', { id, index }));
      },
    },
  },
});

Cả hai kịch bản đều áp dụng

Tạo file mới src/client-v2/models/SimpleBothActionModel.tsx:

// src/client-v2/models/SimpleBothActionModel.tsx
import { ActionModel, ActionSceneEnum } from '@nocobase/client-v2';
import { ButtonProps } from 'antd';
import { tExpr } from '../locale';

export class SimpleBothActionModel extends ActionModel {
  static scene = ActionSceneEnum.both;

  defaultProps: ButtonProps = {
    children: tExpr('Simple both action'),
  };
}

SimpleBothActionModel.define({
  label: tExpr('Simple both action'),
});

SimpleBothActionModel.registerFlow({
  key: 'clickFlow',
  title: tExpr('Simple both action'),
  on: 'click',
  steps: {
    showMessage: {
      async handler(ctx) {
        ctx.message.info(ctx.t('Both action clicked'));
      },
    },
  },
});

Cấu trúc của ba cách viết là giống nhau — khác biệt chỉ ở giá trị static scene và text của nút. Mỗi nút đều lắng nghe sự kiện click qua registerFlow({ on: 'click' }), dùng ctx.message để hiển thị thông báo, để người dùng có thể thấy nút thực sự có hiệu lực.

Bước 3: Thêm file đa ngôn ngữ

Chỉnh sửa file dịch trong src/locale/ của plugin:

// src/locale/zh-CN.json
{
  "Simple collection action": "简单数据表操作",
  "Simple record action": "简单记录操作",
  "Simple both action": "简单通用操作",
  "Collection action clicked": "数据表操作被点击了",
  "Record action clicked, record ID: {{id}}, row index: {{index}}": "记录操作被点击了,记录 ID:{{id}},行索引:{{index}}",
  "Both action clicked": "通用操作被点击了"
}
// src/locale/en-US.json
{
  "Simple collection action": "Simple collection action",
  "Simple record action": "Simple record action",
  "Simple both action": "Simple both action",
  "Collection action clicked": "Collection action clicked",
  "Record action clicked, record ID: {{id}}, row index: {{index}}": "Record action clicked, record ID: {{id}}, row index: {{index}}",
  "Both action clicked": "Both action clicked"
}
Lưu ý

Khi thêm file ngôn ngữ lần đầu, bạn cần khởi động lại ứng dụng để có hiệu lực.

Về cách viết file dịch và các cách dùng khác của tExpr(), xem chi tiết tại i18n.

Bước 4: Đăng ký trong Plugin

Chỉnh sửa src/client-v2/plugin.tsx, dùng registerModelLoaders để load theo nhu cầu:

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

export class PluginSimpleActionClient extends Plugin {
  async load() {
    this.flowEngine.registerModelLoaders({
      SimpleCollectionActionModel: {
        loader: () => import('./models/SimpleCollectionActionModel'),
      },
      SimpleRecordActionModel: {
        loader: () => import('./models/SimpleRecordActionModel'),
      },
      SimpleBothActionModel: {
        loader: () => import('./models/SimpleBothActionModel'),
      },
    });
  }
}

export default PluginSimpleActionClient;

Bước 5: Bật plugin

yarn pm enable @my-project/plugin-simple-action

Sau khi bật, trong "Cấu hình action" của block table bạn có thể thêm các nút Action tùy chỉnh này.

Source code đầy đủ

Tóm tắt

Các khả năng được sử dụng trong ví dụ này:

Khả năngCách dùngTài liệu
Nút ActionActionModel + static sceneFlowEngine → Mở rộng Action
Kịch bản ActionActionSceneEnum.collection / record / both / allFlowEngine → Mở rộng Action
Đăng ký menudefine({ label })Tổng quan FlowEngine
Đăng ký Modelthis.flowEngine.registerModelLoaders()Plugin
Dịch trễtExpr()i18n

Liên kết liên quan