bpmn.js是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成.
自定义Palette篇
经过前面几章的基础教程相信大家对bpmn.js的基本使用已经有了一个很好的掌握.
从这一章节开始我会讲解一些关于bpmn.js中自定义的部分, 包括自定义左侧工具栏、自定义渲染、自定义contextPad等等.
还是先来看一张图了解一下我们的绘图页面都有哪些东西:
这一章我要介绍的时候如何自定义左侧的工具栏(Palette, 也叫调色板), 通过阅读你可以学习到:
先来看看第一种最简单的, 我们在官方提供的调色板里新增一个自定义的项.
元素类型: bpmn:Task元素名称: lindaidai-task样式: 沿用bpmn:Task原有的样式, 只不过将边框变为红色作用: 创建一个类型为lindaidai-task的任务节点
效果是这样的:
如上所示, 只改变了任务框的颜色为红色, 所以效果不是很明显, 你甚至可以直接给它换一个样貌:
前期准备
因为是新的章节, 这里我也新建一个项目:
$ vue create bpmn-vue-custom
$ npm i vue-router axios bpmn-js-properties-panel bpmn-js --save-D
按照之前的案例LinDaiDai/bpmn-vue-basic配置好相应的路由之类的东西.
在components文件夹下新建一个名为custom-palette.vue的文件, 并将provider.vue(之前的一个基础案例) 的内容复制进去.
继续在components文件夹下新建文件夹custom用于盛放我们后面要写的一些自定义的东西.
来看看我们现在的项目结构:
我已经在custom文件夹新建立了一个CustomPalette.js, 接下来就是要在这里面写上我们要自定义的项.
编写CustomPalette.js代码
首先这个js是导出一个类(类的名称你可以随意取, 但是在引用的时候不能随意取, 后面会说到):
这里我就取为CustomPalette:
// CustomPalette.js export default class CustomPalette { constructor(bpmnFactory, create, elementFactory, palette, translate) { this.bpmnFactory = bpmnFactory; this.create = create; this.elementFactory = elementFactory; this.translate = translate;
palette.registerProvider(this); } // 这个函数就是绘制palette的核心 getPaletteEntries(element) {} }
CustomPalette.$inject = [ 'bpmnFactory', 'create', 'elementFactory', 'palette', 'translate' ]
定义一个类使用$inject注入一些需要的变量在类中使用palette.registerProvider(this)指定这是一个palette
定义完CustomPalette.js之后, 我们需要在其同级的index.js中将它导出:
// custom/index.js import CustomPalette from './CustomPalette'
export default { __init__: ['customPalette'], customPalette: ['type', CustomPalette] }
注:️ 这里__init__中的名字就必须是customPalette了, 还有下面的属性名也必须是customPalette, 不然就会报错了.
同时要在页面中使用它:
编写核心函数getPaletteEntries代码
抛开这些不看, 重点就是如何构造这个getPaletteEntries函数
函数的名称你不能变, 不然会报错, 首先它返回的是一个对象, 对象中指定的就是你要自定义的项, 它大概长成这样:
// CustomPalette.js getPaletteEntries(element) { return { 'create.lindaidai-task': { group: 'model', // 分组名 className: 'bpmn-icon-task red', // 样式类名 title: translate('创建一个类型为lindaidai-task的任务节点'), action: { // 操作 dragstart: createTask(), // 开始拖拽时调用的事件 click: createTask() // 点击时调用的事件 } } } }
可以看到我定义的一项的名称就是: create.lindaidai-task. 它会有几个固定的属性:
group: 属于哪个分组, 比如tools、event、gateway、activity等等,用于分类className: 样式类名, 我们可以通过它给元素修改样式title: 鼠标移动到元素上面给出的提示信息action: 用户操作时会触发的事件
接下来我们要做的无非就是:
通过className来设置样式通过action来定义要触发的事情
编写className代码
我在scr的目录下新建了一个css文件, 里面用来盛放一些全局的样式, 并在main.js中引用这个全局样式:
// main.js // 引入全局的css import './css/app.css'
/* app.css */ .bpmn-icon-task.red { color: #cc0000 !important; }
上面的className我之所以要用bpmn-icon-task, 是因为这个类是bpmn.js中自带的一个iconfont类, 使用它就可以实现一个task的图标的效果:
由于iconfont是一个字体, 所以这里我使用color来改变它的颜色.
如果你想要给它完全换一张图片的话也可以用className来实现:
/* app.css */ .icon-custom { /* 定义一个公共的类名 */ border-radius: 50%; background-size: 65%; background-repeat: no-repeat; background-position: center; }
.icon-custom.lindaidai-task { /* 加上背景图 */ background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png'); }
然后修改create.lindaidai-task中的className:
// CustomPalette.js 'create.lindaidai-task': { className: 'icon-custom lindaidai-task' } 这样页面上显示的就是你定义的那张背景图了:
编写action代码
完成了上面的操作, 其实页面已经能正常渲染出一个我们自定义的元素了, 但是你在点击或者拖拽它的时候是没有效果的.
此时我们期望的是点击或者拖拽它能在画布中画出一个lindaidai-task, 因此你得给它加上事件, 也就是编写一个函数用来创建bpmn:Task这个元素:
// CustomPalette.js function createTask() { return function(event) { const businessObject = bpmnFactory.create('bpmn:Task'); const shape = elementFactory.createShape({ type: 'bpmn:Task', businessObject }); console.log(shape) // 只在拖动或者点击时触发 create.start(event, shape); } }
这里的核心其实就是利用bpmn.js提供的一些方法创建shape然后将其添加到画布上.
(我这里演示的是创建一个类型为bpmn:Task的元素, 你还可以用来创建bpmn:StartEvent、bpmn:ServiceTask、bpmn:ExclusiveGateway等等...)
此时你拖动或者点击lindaidai-task就可以在页面上创建一个Task元素了.
我们看到虽然lindaidai-task在左侧工具栏中是金黄金黄的, 但是实际画到页面却还是呈现“裸体”状态, 这就和自定义渲染有关系了, 不要着急, 这些在后面的章节中会讲到.
完整的CustomPalette.js代码
让我们将上面的所有代码整合一下:
// CustomPalette.js export default class CustomPalette { constructor(bpmnFactory, create, elementFactory, palette, translate) { this.bpmnFactory = bpmnFactory; this.create = create; this.elementFactory = elementFactory; this.translate = translate;
palette.registerProvider(this); }
getPaletteEntries(element) { const { bpmnFactory, create, elementFactory, translate } = this;
function createTask() { return function(event) { const businessObject = bpmnFactory.create('bpmn:Task'); // 其实这个也可以不要 const shape = elementFactory.createShape({ type: 'bpmn:Task', businessObject }); console.log(shape) // 只在拖动或者点击时触发 create.start(event, shape); } }
return { 'create.lindaidai-task': { group: 'model', className: 'icon-custom lindaidai-task', title: translate('创建一个类型为lindaidai-task的任务节点'), action: { dragstart: createTask(), click: createTask() } } } } }
CustomPalette.$inject = [ 'bpmnFactory', 'create', 'elementFactory', 'palette', 'translate' ]注意: 项目案例里我为了方便演示, 在custom-palette中引入的是ImportJS/onlyPalette.js, 而上面的案例是以引入custom/index.js为讲解的, 这个自己要明白如何区分.
完全自定义Palette
可以看到, 上面的那种实现方式实际上就是定义了一个CustomPalette然后在new BpmnModeler生成的对象中引用进去.
但是这样做有一点不好, 那就是如果你不想要它提供的默认的这些项, 比如开始节点、结束节点、任务节点, 而是全都是自己自定义的, 就不能满足了. 比如这样:
此时你就需要重写BpmnModeler这个类了, 实现自己独有的一套modeler.
前期准备
继续在上面的项目的基础上创建一个customModeler文件夹和一个custom-modeler.vue文件. 然后在customModeler中创建一个index.js和一个custom文件夹.
customModeler文件夹下的文件就是用来放自定义的modelercustom-modeler.vue作为页面展示(记得配置页面的路由)
此时项目结构变成了:
编写CustomPalette.js代码
这里的CustomPalette.js的编写方式就和第一种的有所不同了:
/** * A palette that allows you to create BPMN _and_ custom elements. */ export default function PaletteProvider(palette, create, elementFactory, globalConnect) { this.create = create this.elementFactory = elementFactory this.globalConnect = globalConnect
palette.registerProvider(this) }
PaletteProvider.$inject = [ 'palette', 'create', 'elementFactory', 'globalConnect' ]
PaletteProvider.prototype.getPaletteEntries = function(element) { // 此方法和上面案例的一样 const { create, elementFactory } = this; function createTask() { return function(event) { const shape = elementFactory.createShape({ type: 'bpmn:Task' }); console.log(shape) // 只在拖动或者点击时触发 create.start(event, shape); } } return { 'create.lindaidai-task': { group: 'model', className: 'icon-custom lindaidai-task', title: '创建一个类型为lindaidai-task的任务节点', action: { dragstart: createTask(), click: createTask() } } } }
在这里是直接重写了PaletteProvider这个类, 同时覆盖了其原型上的getPaletteEntries方法, 从而达到覆盖原有的工具栏的效果.
(别看上面写的东西好像很多的样子, 但是其实静下心来看发现也没啥)
编写custom/index.js代码
接下来还是和第一种方式一样, 需要将我们自定义的Palette导出:
// custom/index.js import CustomPalette from './CustomPalette' export default { __init__: ['paletteProvider'], paletteProvider: ['type', CustomPalette] }
这不过这里我们就不是用customPalette了, 而是直接用paletteProvider.
编写customModeler/index.js代码
最重要的一步, 就是编写CustomModeler这个类了:
import Modeler from 'bpmn-js/lib/Modeler' import inherits from 'inherits' import CustomModule from './custom' export default function CustomModeler(options) { Modeler.call(this, options) this._customElements = [] } inherits(CustomModeler, Modeler) CustomModeler.prototype._modules = [].concat( CustomModeler.prototype._modules, [ CustomModule ] )
导出的类继承了Modeler这个核心的类, 这样就保证了其他功能的实现.
在页面上引用
最后一步, 是需要将我们原本通过BpmnModeler创建的对象改为通过我们自定义的CustomModeler来创建, 编写custom-modeler.vue.
xml
复制代码
快来打开页面看看效果:
推荐阅读
发表评论
2024-06-16 13:19:27回复