BPMN.js 自定义工具栏、节点图标和右键菜单

把 BPMN.js 嵌入 Vue 页面后,企业通常会继续增加顶部工具栏、企业节点、节点图标和右键菜单。真正的难点不是"把按钮画出来",而是让所有入口共享同一套动作、权限、建模规则和撤销机制。

结论可以概括为四句话:顶部业务工具栏由 Vue 实现;Palette、Context Pad 和 Popup Menu 通过 Provider 扩展;节点持久图标使用 Custom Renderer,运行状态使用 Overlay;所有模型写操作进入 Modeling 或 CommandStack,并由 Rules 与后端发布校验共同约束。

图 1 不同入口负责不同交互场景,但底层动作和权限策略应统一。

一、先分清四种操作入口

"工具栏"在流程设计器中常被混用,至少应拆成四类:

入口 位置与用途 推荐扩展点
顶部工具栏 保存、撤销、缩放、校验、发布 Vue 组件调用公开服务
Palette 创建任务、网关和企业节点 PaletteProvider
Context Pad 选中节点后的追加、连接、删除 ContextPadProvider
右键菜单 配置、复制、转换、删除 element.contextmenu + PopupMenuProvider

四类入口可以显示不同动作,但不要分别实现业务逻辑。推荐建立统一 actionCatalog,再通过 actionPolicy 判断当前用户、元素、设计模式和只读状态是否允许执行。

二、BPMN.js 扩展应该怎样分层

BPMN.js 建立在 diagram-js 和 bpmn-moddle 之上。前者提供画布、事件总线、Palette、Context Pad、Popup Menu、Modeling 和 CommandStack;后者负责 BPMN 元模型与 XML 读写。企业扩展应围绕公开服务和模块注入组织,而不是直接操作 SVG DOM。

javascript 复制代码
export default {
  __init__: [
    "enterprisePalette",
    "enterpriseContextPad",
    "enterpriseRenderer",
    "enterprisePopupMenu",
    "enterpriseRules"
  ],
  enterprisePalette: ["type", EnterprisePalette],
  enterpriseContextPad: ["type", EnterpriseContextPad],
  enterpriseRenderer: ["type", EnterpriseRenderer],
  enterprisePopupMenu: ["type", EnterprisePopupMenu],
  enterpriseRules: ["type", EnterpriseRules]
};

建议按五层组织代码:

  1. Vue 应用层:页面、业务工具栏、弹窗、路由和发布权限;
  2. Provider 层:Palette、Context Pad、Popup Menu 条目;
  3. 建模服务层:Create、AutoPlace、Modeling、CommandStack;
  4. 规则与协议层:Rules、moddle 扩展、后端发布校验;
  5. 渲染层:Custom Renderer、图标注册表和 Overlay。

三、顶部工具栏为什么留在 Vue

保存草稿、发布版本、打开评审和切换环境属于宿主应用能力,不是 BPMN 图形建模行为。让 Vue 工具栏通过 modeler.get() 调用公开服务,能避免查询 bpmn-js 内部 DOM 或依赖私有字段。

typescript 复制代码
const commandStack = modeler.get("commandStack");
const canvas = modeler.get("canvas");

function undo() {
  if (commandStack.canUndo()) commandStack.undo();
}

function zoomIn() {
  canvas.zoom(canvas.zoom() + 0.1);
}

发布按钮也不应在浏览器中直接携带流程引擎凭证。正确链路是:浏览器提交草稿修订号,平台后端重新读取模型、执行权限与语义校验、冻结不可变版本,再由服务端适配器部署到目标引擎并记录审计。

四、Palette 和 Context Pad 怎样扩展

Palette 负责创建元素,Context Pad 负责对当前元素执行高频动作。两者应使用 Create、AutoPlace、Modeling 等服务产生建模命令,不能手写 XML 或直接插入 SVG。

javascript 复制代码
getPaletteEntries() {
  return {
    "create-approval-task": {
      group: "activity",
      className: "icon-approval-task",
      title: "创建审批节点",
      action: {
        dragstart: e => this.create(e),
        click: e => this.create(e)
      }
    }
  };
}

企业节点优先使用标准 BPMN 类型加业务属性。例如"审批节点"仍可使用 bpmn:UserTask,通过 moddle 扩展保存审批人策略、表单、按钮和数据权限。只有标准类型无法表达结构语义时,才考虑新增自定义类型。

Provider 只负责返回条目,不要在 getPaletteEntries() 或 getContextPadEntries() 中请求后端。权限、字典和节点模板应提前缓存,否则每次选中节点都会触发异步请求,造成菜单闪烁和性能问题。

五、节点图标应该放在哪一层

Palette 图标、模型节点图标和运行时状态图标不是同一件事:

  • Palette 图标只是创建入口,可以使用 bpmn-font 或 CSS 图标;
  • 模型节点图标属于持久语义,适合由 Custom Renderer 绘制;
  • "运行中、已完成、异常"等状态不属于模型,适合使用 Overlay。

图 2 业务对象保存稳定图标键,Renderer 从白名单注册表解析 SVG,运行状态不写回模型。

不要把任意 SVG 或外部 URL 直接保存到 BPMN XML。更安全的方式是保存稳定键值,例如 yc:iconKey="approval",再从前端白名单图标注册表中解析。

javascript 复制代码
class EnterpriseRenderer extends BaseRenderer {
  canRender(element) {
    return !element.labelTarget &&
      is(element, "bpmn:UserTask") &&
      !!element.businessObject.iconKey;
  }

  drawShape(parent, element) {
    const shape = this.bpmnRenderer.drawShape(parent, element);
    appendIcon(shape, iconRegistry[element.businessObject.iconKey]);
    return shape;
  }
}

如果改变节点几何尺寸,还要同步处理连接点和 getShapePath();如果只是增加角标或中心小图标,尽量复用默认 BpmnRenderer 画出的基础形状,减少与升级版本的耦合。

六、右键菜单应该怎样实现

element.contextmenu 是事件入口,Popup Menu Provider 是菜单条目提供者,两者不是同一个概念。事件处理器负责阻止浏览器默认菜单、换算可视区域坐标并打开指定 Provider;Provider 根据元素类型和权限返回可用动作。

图 3 右键事件只负责打开菜单,动作过滤交给策略层,模型变更最终进入建模内核。

javascript 复制代码
eventBus.on("element.contextmenu", 1500, event => {
  event.originalEvent.preventDefault();
  if (!canOpen(event.element)) return;

  popupMenu.open(event.element, "enterprise-actions", {
    x: event.originalEvent.clientX,
    y: event.originalEvent.clientY
  });
});

右键菜单至少应过滤 Label、根元素、只读模式和无权限动作。复制、删除、转换类型、修改属性等写操作必须调用 Modeling 或自定义 CommandHandler,不能直接修改 businessObject,否则撤销、重做、脏状态和相关图形更新都会失效。

右键不是唯一入口。键盘用户和触屏用户无法稳定使用右键,因此关键动作还应出现在顶部工具栏、Context Pad 或可聚焦的更多菜单中。

七、所有入口如何共享动作和权限

统一动作目录可以把显示条件与执行逻辑分开:

typescript 复制代码
const actions = {
  delete: {
    visible: ctx => !ctx.readonly && ctx.policy.canDelete(ctx.element),
    execute: ctx => ctx.modeling.removeElements([ctx.element])
  },
  configure: {
    visible: ctx => ctx.policy.canConfigure(ctx.element),
    execute: ctx => ctx.bridge.openConfig(ctx.element.id)
  }
};

Palette、Context Pad、右键菜单和顶部工具栏只把自身上下文传入动作目录。对于真正修改模型的动作,再由 Rules 判断连接、移动、删除或替换是否合法。前端规则改善建模体验,但导入 XML 和发布请求仍要经过后端校验,因为前端可以被绕过。

只读模式也不能只靠隐藏按钮。纯查看优先使用 Viewer;需要选择、缩放和查看属性时,可使用受限 Modeler,但要同时禁用键盘编辑、直接编辑、拖拽、Context Pad、Palette 和程序化命令入口。

八、从当前项目看,精简改造从哪里开始

当前项目的 ych-bpm-designer 已经组装了 Modeler、Viewer、Properties Panel、Camunda Provider、moddle 扩展以及 simpleDesign / simpleRender,并在 UserTask 属性中加入候选人、多实例、自动选人和审批操作等能力。

这套基础适合采用渐进方式治理:

  1. 先建立统一动作目录和权限策略;
  2. 把顶部工具栏改为只调用公开服务;
  3. 将企业节点创建收敛到 Palette Provider;
  4. 用稳定 iconKey 和注册表管理节点图标;
  5. 用 Popup Menu Provider 承载右键动作;
  6. 把所有模型写操作收敛到 Modeling 或 CommandStack;
  7. 增加 XML、SVG、条目集合和撤销重做回归测试;
  8. 最后再升级较旧的 bpmn-js 依赖并减少对内部源码的修改。

云程低代码开发平台可以保留当前审批属性与简洁渲染能力,同时把交互扩展逐步迁移到公开模块边界,降低后续升级和多人协作维护成本。

九、上线检查清单

检查对象 必查项
顶部工具栏 撤销状态来自 CommandStack;发布只调用可信后端
Palette / Context Pad 条目按模式和权限过滤;创建动作进入 Create/AutoPlace
Renderer canRender() 排除 Label;图标来自白名单;SVG 导出已回归
右键菜单 坐标正确;过滤根元素和只读模式;有键盘与触屏替代入口
模型变更 全部进入 Modeling/CommandStack;Rules 与后端共同校验
升级与测试 锁定依赖;不修改 node_modules;覆盖 XML、SVG 和撤销重做

十、如果只记住六句话

  1. 顶部业务工具栏留在 Vue;
  2. Palette、Context Pad 和 Popup Menu 使用 Provider;
  3. 企业节点优先使用标准 BPMN 类型加业务属性;
  4. 持久图标用 Renderer,运行状态用 Overlay;
  5. 所有写操作进入 Modeling 或 CommandStack;
  6. 前端 Rules 负责体验,后端校验负责可信发布。

这六条能让工具栏、节点图标和右键菜单继续扩展,同时保持模型语义、撤销能力、权限边界和版本升级路径一致。

相关推荐
愚农搬码5 天前
Flowable工作流引擎如何适配国产数据库?以达梦数据库举例说明
工作流引擎
songgeb5 天前
mspec体验:基于SDD的轻量AI工作流
ai编程·工作流引擎
大龄码农有梦想5 天前
企业工作流系统如何设计用户、部门、角色、岗位、动态关系五类流程办理人?
工作流引擎·flowable·流程引擎·oa·工作流系统·选人规则·bpm平台
愚农搬码8 天前
工作流里的转办、委托和工作移交的业务语义与技术实现
工作流引擎
大龄码农有梦想8 天前
工作流中的子流程节点能否驳回到父流程?
工作流引擎·流程引擎·oa·bpm·子流程·驳回·退回
愚农搬码8 天前
工作流中的子流程节点能否驳回到父流程?
工作流引擎
愚农搬码8 天前
流程图上的回退线与运行时动态回退,应该选择哪一种?
工作流引擎
大龄码农有梦想9 天前
开源流程引擎 Camunda 如何实现任意节点跳转?
工作流引擎·流程引擎·camunda·oa·bpn·流程跳转·会签
愚农搬码12 天前
在开源流程引擎 Flowable 里如何实现任意节点跳转?
工作流引擎
Behavior16 天前
一条指令,让 Claude Code 每天定时帮你追热点:动态工作流Workflows从 0 到 1 全流程
claude·workflow·工作流引擎