运维咖啡吧

追求技术的道路上,我从不曾停下脚步

运维咖啡吧 MENU

最好用的流程编辑器bpmn-js系列之Modeler

最好用的流程编辑器bpmn-js系列文章

上一篇文章『最好用的流程编辑器bpmn-js系列之本地文件』介绍了如何将本地的bpmn文件渲染到流程编辑器以及将编辑器内的流程作为图片或bpmn文件保存到本地,这篇文章将会介绍流程编辑界面的一些优化

以下演示代码基于上一节搭建好的vue环境,使用bpmn版本为当前最新版7.3.0

7.3.0之后版本的回调写法

文章开头先说一下我在写本系列教程中除了阅读部分源码和论坛外,还参考了很多网上的优秀文章,在此感谢这些无私的贡献者,目前网上大多数教程都是基于7.3.0之前的版本写的,在使用importXML等方法时用了如下callback的方式

modeler.importXML(xml, (err, warnings) => {
  // ...
});

但当将bpmn-js我升级到了7.3.0之后,再使用上边的方式就会出现报错

Error: Passing callbacks to importXML is deprecated and will be removed in a future major release.

提示这种回调方式不建议使用,并且会在后续版本中移除,推荐如下这样的写法来代替

try {
    const result = await modeler.importXML(xml);
    const { warnings } = result;
    console.log(warnings);
} catch (err) {
    console.log(err.message, err.warnings);
}

除了importXML方法外,其他需要使用上边这种写法的还有importDefinitionsopensaveXMLsaveSVGcreateDiagram

撤销恢复

在流程编辑的过程中,会出现操作撤销和恢复撤销的需求,bpmn-js提供了redoundo方法来实现,使用比较简单

先添加两个按钮

<li>
  <a href="javascript:" class="active" @click="handlerUndo" title="撤销操作">撤销</a>
</li>
<li>
  <a href="javascript:" class="active" @click="handlerRedo" title="恢复操作">恢复</a>
</li>

然后编写对应的方法

handlerRedo() {
  this.bpmnModeler.get("commandStack").redo();
},
handlerUndo() {
  this.bpmnModeler.get("commandStack").undo();
}

放大缩小

当流程比较复杂,元素较多的时候,我们需要放大流程,关注于流程的某一块编辑,这时候就需要用到bpmn-js提供的zoom方法来实现流程图的放大缩小

添加三个按钮,分别为放大、缩小、还原

<li>
  <a href="javascript:" class="active" @click="handlerZoom(0.1)" title="放大">放大</a>
</li>
<li>
  <a href="javascript:" class="active" @click="handlerZoom(-0.1)" title="缩小">缩小</a>
</li>
<li>
  <a href="javascript:" class="active" @click="handlerZoom(0)" title="还原">还原</a>
</li>

然后编写对应的方法handlerZoom来控制放大和缩小,放大缩小实际上是通过zoom方法来实现的,zoom方法接收一个num参数,这个参数为显示的比例,默认为1,放大就是增加这个值,缩小就是减小这个值,还原就是将这个值重置为1

handlerZoom(radio) {
  const newScale = !radio ? 1.0 : this.scale + radio;
  this.bpmnModeler.get("canvas").zoom(newScale);

  this.scale = newScale;
}

需要注意的是这里的this.scale就是默认值1,需要在data中定义这个变量

快捷键

bpmn-js除了提供如上一些方法能方便操作画布元素外,还提供键盘快捷键的操作支持,可通过在创建BpmnModeler时添加keybord配置来实现

this.bpmnModeler = new BpmnModeler({
  container: canvas,
  keyboard: {
    bindTo: window
  }
});

目前bpmn-js支持如下一些快捷键操作

  • ctrl + z : 撤销
  • ctrl + y : 恢复
  • ctrl + c : 复制
  • ctrl + v : 粘贴
  • ctrl + + : 放大
  • ctrl + - : 缩小
  • ctrl + 0 : 恢复
  • ctrl + del : 删除
  • ctrl + 箭头 : 上下左右移动

除了键盘快捷键外,bpmn-js也支持ctrl+鼠标滚轮来控制放大缩小

网格背景

许多流程图软件的背景都是网格状,bpmn-js如何添加网格背景呢?修改相应class的css即可

.containers {
  background: white;
  overflow: auto;
  background-image: linear-gradient(
      90deg,
      rgba(220, 220, 220, 0.5) 6%,
      transparent 0
    ),
    linear-gradient(rgba(192, 192, 192, 0.5) 6%, transparent 0);
  background-size: 12px 12px;
  width: 100%;
  height: calc(100vh - 82px);
  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
}

功能禁用

如果你想修改或禁用bpmn-js的某些默认功能,例如你不想要左侧的工具栏Palette,则可以通过additionalModules选项来实现,additionalModules允许你使用自定义模块来修改或替换现有功能

this.bpmnModeler = new CustomModeler({
    container: canvas,
    additionalModules: [
      {
         // 禁用滚轮滚动
        zoomScroll: ["value", ""],
        // 禁止拖动线
        bendpoints: ["value", ""],
        // 禁用左侧面板
        paletteProvider: ["value", ""],
        // 禁止点击节点出现contextPad
        contextPadProvider: ["value", ""],
        // 禁止双击节点出现label编辑框
        labelEditingProvider: ["value", ""]
      }
    ]
});

bpmn-js基本使用文章中我们讲到了bpmn-js有两个模式Modeler编辑模式和Vierer预览模式,实际上有很多小伙伴并不使用bpmn-js提供的Viewer模式,而是通过禁用以上这些组件来达到类似Viewer展示的模式

监听绑定

很多时候我们需要监听用户的操作并给予相应的反馈,例如在bpmn-js本地文件文章中关于监听变化下载文件的介绍中,我们需要监听到流程的变化,并实时将数据补充到a标签中,用到了如下代码

this.bpmnModeler.on("commandStack.changed", opscoffee)

这里的bpmnModeler.on就是个监听方法,能监听到流程的变化,除此之外bpmn-js还提供了eventBus来帮助我们做事件监听,用法如下

export default {
  ...
  methods: {
    init() {
      const canvas = this.$refs.canvas;
      this.bpmnModeler = new BpmnModeler({
        container: canvas
      });

      this.createNewDiagram();
    },
    async createNewDiagram() {
      try {
        const result = await this.bpmnModeler.importXML(this.xmlStr);
        const { warnings } = result;
        console.log(warnings);

        this.success();
      } catch (err) {
        console.log(err.message, err.warnings);
      }
    },
    success() {
      this.addEventBusListener();
    },
    addEventBusListener() {
      const that = this;
      const eventBus = this.bpmnModeler.get("eventBus");

      eventBus.on("element.click", function(e) {
        console.log("eventBusListener", e);
      });
    }
  }
};
</script>

createNewDiagram渲染完成后调用success方法,success中添加了addEventBusListener监听方法,然后通过eventBus.on来实现对事件的监听,例如这里监听了element.click点击事件

监听到事件后就很方便的进行后续的操作了,例如我想判断如果用户点击了UserTask任务,那么就打印任务id、type、name,则可以这样实现

addEventBusListener() {
  const that = this;
  const eventBus = this.bpmnModeler.get("eventBus");

  eventBus.on("element.click", function(e) {
    that.elementClick(e);
  });
},
elementClick(e) {
  if (e.element.businessObject.$type === "bpmn:UserTask") {
    console.log(
      "这是一个用户节点",
      e.element.businessObject.id,
      e.element.businessObject.$type,
      e.element.businessObject.name
    );
  }
}

事件列表

那么究竟有哪些事件类型呢?我们可以通过bpmnModeler.get("eventBus")方法来获取,常用的有如下这些

  • canvas.destroy
  • canvas.init
  • canvas.viewbox.changed
  • canvas.viewbox.changing
  • connect.end
  • connection.added
  • connection.changed
  • connection.remove
  • connection.removed
  • create.end
  • diagram.clear
  • diagram.destroy
  • diagram.init
  • element.changed
  • element.click
  • element.hover
  • element.marker.update
  • element.out
  • elements.changed
  • interactionEvents.createHit
  • interactionEvents.updateHit
  • render.connection
  • render.getConnectionPath
  • render.getShapePath
  • render.shape
  • selection.changed
  • shape.added
  • shape.changed
  • shape.move.end
  • shape.remove
  • shape.removed

获取节点

当我们需要获取流程中的节点时,可以使用如下方法来获取全部节点或检索指定类型的节点

获取全部节点

bpmnModeler.get("elementRegistry").getAll()

获取指定ID节点

bpmnModeler.get("elementRegistry").get(ID)

修改节点

通过modeling的updateProperties方法可以修改节点属性,例如这里修改节点nameops-coffee.cn

const modeling = this.bpmnModeler.get("modeling");

const element = this.bpmnModeler
  .get("elementRegistry")
  .get("Activity_1w1vj9r");

modeling.updateProperties(element, {
  name: "ops-coffee.cn"
});

写在最后

接触bpmn-js不久,且第一次用VUE,边学边写,文章难免出错,各位多多包含。想要打造一个好用的适合自己的流程编辑器,需要了解的内容比较多,bpmn-js会分多篇文章来介绍,下一篇介绍bpmn-js的Viewer模式下的节点颜色修改、鼠标拖动等内容,欢迎关注

部分小伙伴对流程编辑器不了解,或是对BPMN不了解,我搭建了个在线的Demo: https://bpmn.ops-coffee.cn,点击链接即可轻松体验,建议PC端打开效果更好