BPMAXBPMAX
  • 快速入门
  • 核心概念
  • 管理员手册
  • 仿真和回放
  • 流程相关脚本
  • 表单相关脚本
  • 数据集相关脚本
  • 界面相关脚本
  • 系统相关脚本
  • 流程集成
  • 数据集
  • 接口集成
  • 实体映射
  • OpenAPI
  • 实体列表
  • 插件开发
  • 日志排查
  • 飞书平台

    • 同步组织架构
    • 同步团队组织架构
    • 一键拉群
    • 高级卡片消息
    • 服务台能力
  • 实用功能

    • 系统公告
    • 项目日历
    • 超时自动化
    • 报告自动生成
    • 流程资源档案
  • 文档更新记录
  • 系统更新说明
  • 快速入门
  • 核心概念
  • 管理员手册
  • 仿真和回放
  • 流程相关脚本
  • 表单相关脚本
  • 数据集相关脚本
  • 界面相关脚本
  • 系统相关脚本
  • 流程集成
  • 数据集
  • 接口集成
  • 实体映射
  • OpenAPI
  • 实体列表
  • 插件开发
  • 日志排查
  • 飞书平台

    • 同步组织架构
    • 同步团队组织架构
    • 一键拉群
    • 高级卡片消息
    • 服务台能力
  • 实用功能

    • 系统公告
    • 项目日历
    • 超时自动化
    • 报告自动生成
    • 流程资源档案
  • 文档更新记录
  • 系统更新说明
  • 插件开发入门

    • 插件开发
    • 插件架构与加载机制
    • 环境准备与开发模式
    • 第一个最小插件
  • 插件能力开发

    • 前端扩展点实战
    • 后端扩展点实战
    • 前后端联动完整案例:任务集成插件
  • 插件运行机制

    • 配置、安装、升级与发布
    • 定时任务与异步处理
    • Hook 机制与平台事件接入
    • 外部系统集成模式
  • 进阶与参考

    • 调试与排错
    • 设计规范与最佳实践
    • 能力类型索引与选型

前端扩展点实战

本文系统介绍 BPMAX 插件前端的主要扩展能力,重点是把插件代码挂接到平台前端页面和流程编辑器中。

学习目标

  • 理解 BPMAX 前端插件入口的职责
  • 掌握路由、UI 插槽、流程配置组件和国际化的注册方式
  • 能为业务插件选择合适的前端扩展点

插件前端入口文件

main.ts 的职责

插件前端通常以入口文件作为注册中心。这个文件不负责启动一个新的 Vue 应用,而是把插件能力注册给平台系统。

你通常会在这里做这些事情:

  • 注册页面路由
  • 注册 UI 插槽
  • 注册流程配置组件
  • 合并国际化语言包

一个入口文件可以先组织成下面这样:

import HelloPage from './pages/HelloPage.vue';
import DetailExtraPanel from './components/DetailExtraPanel.vue';
import TaskStepConfig from './components/TaskStepConfig.vue';
import zhCN from './languages/zhCN';

console.log('plugin_example frontend loaded');

BPMAX.router.addRoute({
  path: '/plugin/hello-demo',
  component: HelloPage,
});

BPMAX.ui.register('Slot.Page.Detail.ExtraPanel', DetailExtraPanel);

BPMAX.flow.stepComponent({
  field: 'plugin_task_integration',
  component: TaskStepConfig,
  defaultValue: {
    enabled: false,
    platform_id: '',
    mapping_function: '',
  },
});

BPMAX.mergeLocaleMessage('zhCN', zhCN);

什么时候只需要前端入口

如果插件只是:

  • 增加一个独立页面
  • 在已有页面插一个展示组件
  • 给流程编辑器增加纯前端配置界面

那么很多时候只写前端入口也能先跑通。

什么时候必须联动后端接口

如果插件需要:

  • 调用第三方接口
  • 读写平台数据
  • 执行业务校验
  • 保存插件自己的配置或状态

那么前端入口只能解决“挂载位置”,还需要联动插件后端。

扩展方式一:注册独立页面

适用场景

适合:

  • 插件管理页面
  • 配置页面
  • 报表页面
  • 调试页面

BPMAX.router.addRoute

最小用法如下:

import HelloPage from './HelloPage.vue';

BPMAX.router.addRoute({
  path: '/plugin/hello-demo',
  component: HelloPage,
});

页面路径命名建议

  • 统一使用 /plugin/ 前缀
  • 路径名尽量和插件英文名相关
  • 避免使用平台已有页面路径

页面组件可以先写成:

<template>
  <section class="plugin-page">
    <h2>插件管理页</h2>
    <p>这里可以展示插件配置、状态和调试信息。</p>
  </section>
</template>

扩展方式二:注册 UI 插槽

适用场景

适合:

  • 给现有页面增加按钮
  • 增加详情区块
  • 扩展表格操作列
  • 扩展配置区域

BPMAX.ui.register

最小用法如下:

BPMAX.ui.register('Slot.Page.Detail.ExtraPanel', DetailExtraPanel);

或者把组件挂到某个业务插槽中:

BPMAX.ui.register('Slot.Table.RowActions', TableRowAction);

常见插槽类型

常见插槽通常覆盖以下区域:

  • 页面详情区
  • 列表操作区
  • 表单扩展区
  • 流程操作区

这类方式的优点是侵入性低,不需要修改平台页面本身。

一个最小插槽组件可以写成:

<script setup lang="ts">
const props = defineProps<{
  row?: Record<string, any>;
}>();

const handleClick = () => {
  console.log('current row', props.row);
};
</script>

<template>
  <button type="button" @click="handleClick">同步任务</button>
</template>

插槽组件的输入输出

不同插槽会给组件传入不同上下文,因此写插槽组件时要先确认:

  • 插槽位置在哪里
  • 平台会传什么参数
  • 组件应该只展示,还是可以发起交互

如果插槽需要调用后端接口,可以直接在组件中发请求:

async function syncTask(rowId: string) {
  const res = await BPMAX.app.post('/api/plugin_task_integration/sync', {
    row_id: rowId,
  });

  console.log(res);
}

扩展方式三:注册流程元配置组件

适用场景

适合给流程整体增加配置,例如:

  • 流程级开关
  • 流程级外部系统参数
  • 流程级自动化规则

BPMAX.flow.metaComponent

最小用法如下:

BPMAX.flow.metaComponent({
  field: 'meta_config_field_name',
  component: ConfigComponent,
  defaultValue: {
    enabled: false,
    platform_id: '',
  },
});

对应的配置组件可以先保持简单:

<script setup lang="ts">
const model = defineModel<any>({
  default: {
    enabled: false,
    platform_id: '',
  },
});
</script>

<template>
  <div>
    <label>
      <span>启用能力</span>
      <input v-model="model.enabled" type="checkbox" />
    </label>
    <label>
      <span>平台实例</span>
      <input v-model="model.platform_id" type="text" />
    </label>
  </div>
</template>

默认值设计

建议 defaultValue 直接给完整结构,而不是只给布尔值或空字符串。这样可以减少:

  • 旧数据兼容问题
  • 表单渲染判空问题
  • 版本升级后的结构不一致问题

配置结构设计建议

建议配置对象遵循:

  • 字段稳定
  • 命名可读
  • 预留扩展空间

例如优先使用:

{
  enabled: false,
  platform_id: '',
  mapping_function: '',
}

而不是把多个含义塞进一个字符串。

扩展方式四:注册流程环节配置组件

适用场景

适合给某个流程环节增加配置,例如:

  • 当前环节是否启用某能力
  • 当前环节执行什么外部动作
  • 当前环节的特殊审批策略

BPMAX.flow.stepComponent

最小用法如下:

BPMAX.flow.stepComponent({
  field: 'plugin_task_integration',
  component: TaskStepConfig,
  defaultValue: {
    enabled: false,
    mapping_function: '',
    platform_id: '',
  },
});

环节级配置组件示例如下:

<script setup lang="ts">
const model = defineModel<any>({
  default: {
    enabled: false,
    mapping_function: '',
    platform_id: '',
  },
});
</script>

<template>
  <div>
    <label>
      <span>启用</span>
      <input v-model="model.enabled" type="checkbox" />
    </label>
    <label>
      <span>映射函数</span>
      <input v-model="model.mapping_function" type="text" />
    </label>
  </div>
</template>

与 metaComponent 的区别

  • metaComponent:流程整体只配置一次
  • stepComponent:每个环节可以独立配置

如果能力与单个节点行为强相关,应优先选择 stepComponent。

编辑器组件复用建议

流程配置相关组件可以复用平台流程编辑器中的标准组件。这样做的好处是:

  • 风格一致
  • 输入结构一致
  • 减少重复造轮子

扩展方式五:注册国际化语言包

BPMAX.mergeLocaleMessage

典型写法如下:

import en from '../languages/en/index';
import zhCN from '../languages/zhCN/index';

BPMAX.mergeLocaleMessage('en', en);
BPMAX.mergeLocaleMessage('zhCN', zhCN);

zhCN 语言包可以先按下面结构组织:

export default {
  'plugin_example.page_title': '插件管理页',
  'plugin_example.sync_button': '同步任务',
};

多语言目录组织建议

推荐结构:

languages/
  en/
  zhCN/
  jaJP/

每个插件维护自己的语言命名空间,避免与平台或其他插件冲突。

可复用的平台能力

全局对象 BPMAX

插件运行时可直接使用平台暴露的全局对象。前端入口与页面组件中,最常用的是下面这些能力:

  • BPMAX.app:当前应用实例,可访问 i18n、请求能力等运行时上下文
  • BPMAX.route:当前路由信息
  • BPMAX.router:路由注册与跳转能力
  • BPMAX.ui:页面插槽注册能力
  • BPMAX.flow:流程元配置、环节配置等扩展能力
  • BPMAX.Vue:运行时 Vue 对象
  • BPMAX.getSetting(name):读取插件配置
  • BPMAX.addSetting(name, setting):保存插件配置
  • BPMAX.mergeLocaleMessage(lang, message):合并插件语言包
  • BPMAX.getEnv():读取插件运行环境变量
  • BPMAX.eventBus:插件事件总线

可以把前端入口整理成下面这样:

import HelloPage from './pages/HelloPage.vue';
import DetailExtraPanel from './components/DetailExtraPanel.vue';
import TaskStepConfig from './components/TaskStepConfig.vue';
import zhCN from './languages/zhCN';

BPMAX.router.addRoute({
  path: '/plugin/hello-demo',
  component: HelloPage,
});

BPMAX.ui.register('Slot.Page.Detail.ExtraPanel', DetailExtraPanel);

BPMAX.flow.stepComponent({
  field: 'plugin_task_demo',
  component: TaskStepConfig,
  defaultValue: {
    enabled: false,
    notify_users: [],
  },
});

BPMAX.mergeLocaleMessage('zhCN', zhCN);

运行时配置读写

当插件需要保存自己的前端配置时,可以直接调用运行时 API:

async function loadPluginSetting() {
  const setting = await BPMAX.getSetting('plugin_example');
  return {
    enabled: !!setting.enabled,
    api_base_url: setting.api_base_url || '',
  };
}

async function savePluginSetting(formData: {
  enabled: boolean;
  api_base_url: string;
}) {
  await BPMAX.addSetting('plugin_example', formData);
}

运行环境读取

const env = BPMAX.getEnv();

console.log(env.API_HOST);
console.log(env.RUNTIME_ENV);
console.log(env.PLUGIN_NAME);

Vite 全局依赖注入

为什么要注入全局变量

插件前端不是独立站点,而是运行在平台页面里的一个扩展包。
因此,像 Vue、Element UI、echarts、部分平台控件和组合式方法,通常由平台运行时统一提供,插件构建时不再重复打包。

这样做有三个直接收益:

  • 避免把平台已经提供的运行时再次打进插件包
  • 减少插件产物体积
  • 保证插件与平台使用同一份基础依赖和控件实例

external 与 globals 的作用

在 vite.config.ts 里,核心就是两步:

  1. 在 external 中声明哪些依赖不要打包
  2. 在 output.globals 中声明这些依赖运行时对应哪个全局变量

最小示例如下:

import path from 'node:path';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue2';

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      entry: path.resolve(__dirname, './src/main.ts'),
      name: 'plugin-example',
    },
    rollupOptions: {
      external: [
        'vue',
        'element-ui',
        'echarts',
        'BPMAX',
        '@bpmax/UseAxios',
        '@bpmax/UserPicker',
      ],
      output: {
        globals: {
          vue: '$__VUE',
          'element-ui': '$__ELEMENT_UI',
          echarts: '$__ECHARTS',
          BPMAX: '$__BPMAX',
          '@bpmax/UseAxios': '$__UseAxios',
          '@bpmax/UserPicker': '$__UserPicker',
        },
      },
    },
  },
});

如何理解这段配置

以上配置的含义是:

  • 源码里如果引用了 vue,构建结果不会再把 Vue 打进去
  • 运行到平台页面时,这个依赖会从全局变量 $__VUE 取值
  • 组件、工具、组合式方法也可以按同样方式映射到全局变量

什么时候要放进 external

推荐放进 external 的依赖:

  • 平台已统一提供的基础库,例如 vue、element-ui、echarts
  • 平台已暴露的运行时对象,例如 BPMAX
  • 平台已暴露的前端控件、工具方法、组合式方法

不建议放进 external 的依赖:

  • 你的插件私有工具库
  • 只在当前插件内部使用、平台没有提供的第三方包
  • 与插件业务强绑定、需要自己控制版本的库

推荐写法:显式读取全局变量

对于平台已暴露的组件和工具,教程更推荐直接按全局变量使用,而不是写相对路径导入。

例如:

const { useAxios } = $__UseAxios;
const { Message, MessageBox } = $__ELEMENT_UI;
const UserPicker = $__UserPicker;
const UserGroupPicker = $__UserGroupPicker;
const PdfReader = $__PdfReader;

这样做的好处是:

  • 依赖来源清晰,能一眼看出这是平台运行时提供的能力
  • 不会把平台内部目录结构暴露到插件代码里
  • 文档和实际加载机制一致

平台已暴露的全局变量与控件

基础运行时

常见基础全局变量包括:

  • window.BPMAX
  • window.$__BPMAX
  • window.$__VUE
  • window.$__ROUTER
  • window.$__APP
  • window.$__ROUTE
  • window.$__VUE_ROUTER
  • window.$__VUEX
  • window.$__Pinia
  • window.$__VUEUSE
  • window.$__VueI18n

通用库

  • window.$__ELEMENT_UI
  • window.$__FORM_CREATE
  • window.$__ECHARTS
  • window.$__LODASH
  • window.$__MOMENT
  • window.$__SOCKETIO

平台前端控件

运行时已经暴露了一批常用控件,可在插件中直接复用:

  • window.$__UserPicker
  • window.$__UserGroupPicker
  • window.$__PdfReader
  • window.$__AttachmentReader
  • window.$__CodeEditor
  • window.$__ConsoleCodeEditor
  • window.$__FileUpload
  • window.$__ImageUpload
  • window.$__ImageUploadMultiple
  • window.$__ImagePreview
  • window.$__VisibilityControl
  • window.$__HomeNav
  • window.$__LayoutHeader
  • window.$__LayoutFooter
  • window.$__LayoutList
  • window.$__BreadCrumb
  • window.$__SectionItem
  • window.$__SideColumnSection
  • window.$__MainColumnSection
  • window.$__SimpleMessageTemplateDialog
  • window.$__IconStatusProject

流程设计器与表单设计器相关能力

  • window.$__FormDesignerUtils
  • window.$__FormDesignerConfig
  • window.$__FormDesignerUseFormCreate
  • window.$__FormDesignerUseFormCreateMap
  • window.$__FormDesignerIFormCreate
  • window.$__FormDesignerProjectSelectorNew
  • window.$__FormDesignerUserSelect
  • window.$__FormDesignerGroupSelect
  • window.$__useFormCreateMap
  • window.$__useStepEditorContext
  • window.$__UseStepEditorManager
  • window.$__FlowEditorAsideRightConfig
  • window.$__GridLayoutGridLayout
  • window.$__GridLayoutGridLayoutAside
  • window.$__GridLayoutWidget

组合式方法与工具方法

  • window.$__UseAxios
  • window.$__UseStore
  • window.$__UseWorkspace
  • window.$__UseVueRouter
  • window.$__UseTabBindRoute
  • window.$__UsePluginEventBus
  • window.$__UseDataSet
  • window.$__UseDataSource
  • window.$__UseCrud
  • window.$__UseVconsole
  • window.$__UtilsLocation
  • window.$__ProjectTableHelper
  • window.$__ParserUtil
  • window.$__ProjectMixins
  • window.$__ObjectEvents
  • window.$__reflect
  • window.$__FeishuHelper
  • window.$__getMimeTypeByFile
  • window.$__isLocalUploadType
  • window.$__getLocalUploadUrl

全局控件使用示例

示例一:直接使用 Element UI

const { Message, MessageBox, Loading } = $__ELEMENT_UI;

async function handleSync() {
  const loading = Loading.service({
    text: '正在同步',
  });

  try {
    await MessageBox.confirm('确认立即同步吗?', '提示', {
      type: 'warning',
    });
    Message.success('同步已提交');
  } finally {
    loading.close();
  }
}

示例二:使用平台提供的 useAxios

const { useAxios } = $__UseAxios;
const axios = useAxios();

export async function queryTaskList() {
  return axios.$get('/plugin_example/tasks', {
    params: {
      page: 1,
      page_size: 20,
    },
  });
}

示例三:复用人员选择器

<script setup lang="ts">
const UserPicker = $__UserPicker;
const { ref } = $__VUE;
const visible = ref(false);
const selectedUsers = ref<any[]>([]);

function handleConfirm(list: any[]) {
  selectedUsers.value = list;
  visible.value = false;
}
</script>

<template>
  <div>
    <el-button type="primary" @click="visible = true">选择人员</el-button>

    <component
      :is="UserPicker"
      :visible.sync="visible"
      @confirm="handleConfirm"
      @cancel="visible = false"
    />
  </div>
</template>

示例四:复用附件与预览控件

<script setup lang="ts">
const AttachmentReader = $__AttachmentReader;
const PdfReader = $__PdfReader;

const fileUrl = 'https://example.com/demo.pdf';
</script>

<template>
  <section>
    <component :is="AttachmentReader" :url="fileUrl" />
    <component :is="PdfReader" :url="fileUrl" style="height: 600px" />
  </section>
</template>

示例五:流程配置组件里复用设计器控件

<script setup lang="ts">
const UserSelect = $__FormDesignerUserSelect;
const GroupSelect = $__FormDesignerGroupSelect;

const model = defineModel<any>({
  default: {
    reviewers: [],
    groups: [],
  },
});
</script>

<template>
  <div>
    <component :is="UserSelect" v-model="model.reviewers" />
    <component :is="GroupSelect" v-model="model.groups" />
  </div>
</template>

平台组件与样式约定

流程配置型插件建议优先复用平台已有编辑器组件,而不是自己再造一套表单体系。

建议优先顺序如下:

  • 优先复用平台已经暴露的选择器、上传器、编辑器、预览器
  • 需要表单配置时优先复用 FormDesigner 相关能力
  • 只有平台没有现成能力时,再引入插件私有组件

这样可以减少:

  • 视觉风格不一致
  • 数据结构不一致
  • 同类控件交互体验分裂

常见扩展类型

页面型扩展

特点:

  • 以路由注册为主
  • 提供独立页面或配置页面

UI 插槽型扩展

特点:

  • 挂接到现有页面局部区域
  • 适合按钮、区块、操作栏和状态展示

流程配置型扩展

特点:

  • 挂接到流程级或环节级配置
  • 与流程执行逻辑绑定较深

常见问题

组件注册后未显示

  • 插槽名写错
  • 当前页面没有这个插槽
  • 插件入口没有执行

流程配置默认值异常

  • defaultValue 结构和组件期望结构不一致
  • 历史数据缺少新字段

国际化 key 冲突

建议统一加插件前缀,例如:

  • plugin_example.xxx
  • plugin_task_integration.xxx

下一步

  • 后端扩展点实战
  • 前后端联动完整案例:任务集成插件
Next
后端扩展点实战