bpmn笔记

bpmn实现自定义流程构图

根据 bpmn文档 以及Bpmn.js基础 API 总结的随笔,可以做一些参考。

什么是流程引擎?

流程引擎总的来说就是用来驱动业务按照我设定的固定流程去流转的东西,在复杂多变的业务情况下,使用既定的流程能够大大降低我们设计业务的成本,并且保证了我们业务执行的准确性。(购物业务,中台支持)

1.初始化页面

1. 依赖安装

npm install bpmn-js -S

2. 页面接收内容

<div ref="canvas" class="canves"></div>

3.初始化页面

import Modeler from 'bpmn-js/lib/Modeler'
import 'bpmn-js/dist/assets/diagram-js.css' // 左边工具栏以及编辑节点的样式
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
import { xmlStr } from './xmlData.js'
export default {
  data() {
    return {
      bpmnModeler: null
    }
  },
  async mounted() {
    this.bpmnModeler = new Modeler({
      container: this.$refs.canvas
    })

    try {
      const { warnings } = await this.bpmnModeler.importXML(xmlStr)//xmlStr为导入的xml文件
      // 调整在正中间
      this.bpmnModeler.get('canvas').zoom('fit-viewport', 'auto')
      console.log('rendered')
    } catch (err) {
      console.log('error rendering', err)
    }
  }
}
xmlStr = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="sid-38422fae-e03e-43a3-bef4-bd33b32041b2" targetNamespace="http://bpmn.io/bpmn" exporter="bpmn-js (https://demo.bpmn.io)" exporterVersion="5.1.2">
<process id="Process_1" isExecutable="false">
    
</process>
<bpmndi:BPMNDiagram id="BpmnDiagram_1">
    <bpmndi:BPMNPlane id="BpmnPlane_1" bpmnElement="Process_1">
    
    </bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>`

2.bpmn内部

包含了 diagram-js(用于图像、连线交互和建模)bpmn-moddle(读写 BPMN 元模型)

3.自定义 Palette (工具栏)

1.流程工具栏配置栏

新建配置文件 paletteEntries.js ,


	'hand-tool': {//抓手工具
            group: 'tools',
            className: 'bpmn-icon-hand-tool',
            title: translate('Activate the hand tool'),
            action: {
                click: function(event) {
                    handTool.activateHand(event);
                }
            }
        },
        'space-tool': {//创建/删除空间工具
            group: 'tools',
            className: 'bpmn-icon-space-tool',
            title: translate('Activate the create/remove space tool'),
            action: {
                click: function(event) {
                    spaceTool.activateSelection(event);
                }
            }
        },
        'lasso-tool': {//索套工具
            group: 'tools',
            className: 'bpmn-icon-lasso-tool',
            title: translate('Activate the lasso tool'),
            action: {
                click: function(event) {
                    lassoTool.activateSelection(event);
                }
            }
        },
        'global-connect-tool': {//全局连接工具
            group: 'tools',
            className: 'bpmn-icon-connection-multi',
            title: translate('Activate the global connect tool'),
            action: {
                click: function(event) {
                    globalConnect.toggle(event);
                }
            }
        },
        'tool-separator': {//工具分割线
            group: 'tools',
            separator: true
        },
        'create.start-event': createAction(//开始事件
            'bpmn:StartEvent', 'event', 'bpmn-icon-start-event-none',
            translate('Create StartEvent')
        ),
        'create.end-event': createAction(//结束事件
            'bpmn:EndEvent', 'event', 'bpmn-icon-end-event-none',
            translate('Create EndEvent')
        ),
        'create.intermediate-event': createAction(//中间/边界事件
            'bpmn:IntermediateThrowEvent', 'event', 'bpmn-icon-intermediate-event-none',
            translate('Create Intermediate/Boundary Event')
        ),
        'event-separator': {//事件分割线
            group: 'events',
            separator: true
        },
        'create.exclusive-gateway': createAction(//互斥网关
            'bpmn:ExclusiveGateway', 'gateway', 'bpmn-icon-gateway-xor',
            translate('Create ExclusiveGateway')
        ),
        'create.parallel-gateway': createAction(//并行网关
            'bpmn:ParallelGateway', 'gateway', 'bpmn-icon-gateway-parallel',
            translate('Create ParallelGateway')
        ),
        'create.inclusive-gateway': createAction(//相容网关
            'bpmn:InclusiveGateway', 'gateway', 'bpmn-icon-gateway-or',
            translate('Create InclusiveGateway')
        ),
        'create.complex-gateway': createAction(//复杂网关
            'bpmn:ComplexGateway', 'gateway', 'bpmn-icon-gateway-complex',
            translate('Create ComplexGateway')
        ),
        'create.event-based-gateway': createAction(//事件网关
            'bpmn:EventBasedGateway', 'gateway', 'bpmn-icon-gateway-eventbased',
            translate('Create EventbasedGateway')
        ),
        'gateway-separator': {//网关分割线
            group: 'gateways',
            separator: true
        },
        'create.task': createAction(//空白任务
            'bpmn:Task', 'activity', 'bpmn-icon-task',
            translate('Create Task')
        ),
        'create.user-task': createAction(//用户任务
            'bpmn:UserTask', 'activity', 'bpmn-icon-user-task',
            translate('Create UserTask')
        ),
        'create.send-task': createAction(//发送任务
            'bpmn:SendTask', 'activity', 'bpmn-icon-send-task',
            translate('Create SendTask')
        ),
        'create.receive-task': createAction(//接收任务
            'bpmn:ReceiveTask', 'activity', 'bpmn-icon-receive-task',
            translate('Create ReceiveTask')
        ),
        'create.business-rule-task': createAction(//业务规则任务
            'bpmn:BusinessRuleTask', 'activity', 'bpmn-icon-business-rule-task',
            translate('Create BusinessRuleTask')
        ),
        'create.service-task': createAction(//服务任务
            'bpmn:ServiceTask', 'activity', 'bpmn-icon-service-task',
            translate('Create ServiceTask')
        ),
        'create.script-task': createAction(//脚本任务
            'bpmn:ScriptTask', 'activity', 'bpmn-icon-script-task',
            translate('Create ScriptTask')
        ),
        'create.manual-task': createAction(//手工任务
            'bpmn:ManualTask', 'activity', 'bpmn-icon-manual-task',
            translate('Create ManualTask')
        ),
        'create.call-activity': createAction(//调用活动
            'bpmn:CallActivity', 'activity', 'bpmn-icon-call-activity',
            translate('Create CallActivityTask')
        ),
        'create.subprocess-expanded': {//创建子流程(展开的)
            group: 'activity',
            className: 'bpmn-icon-subprocess-expanded',
            title: translate('Create SubProcessExpanded'),
            action: {
                dragstart: createSubprocess,
                click: createSubprocess
            }
        },
        'task-separator': {//任务分割线
            group: 'tasks',
            separator: true
        },
        'create.data-object': createAction(//数据对象
            'bpmn:DataObjectReference', 'data-object', 'bpmn-icon-data-object',
            translate('Create DataObjectReference')
        ),
 
        'create.data-store': createAction(//数据存储引用
            'bpmn:DataStoreReference', 'data-store', 'bpmn-icon-data-store',
            translate('Create DataStoreReference')
        ),
 
        'create.participant-expanded': {//池/参与者
            group: 'collaboration',
            className: 'bpmn-icon-participant',
            title: translate('Create Pool/Participant'),
            action: {
                dragstart: createParticipant,
                click: createParticipant
            }
        },
        'create.group': createAction(//组
            'bpmn:Group', 'artifact', 'bpmn-icon-group',
            translate('Create Group')
        )
}

再使用 createActions 创建面板事件

function createAction(type, group, className, title, imageUrl) {

    // 还记得 CustomPalette.js 吗?便是这里回调 createListener 函数

    // if (action === 'click') {

    // handler(originalEvent, autoActivate, elementFactory, create)

    // }

    function createListener(event, autoActivate, elementFactory, create) {

        var shape = elementFactory.createShape({ type })
        create.start(event, shape)
    }
    
    return {

        group: group,

        className: className,

        title: title,

        imageUrl, // 📌

        action: {

            dragstart: createListener,

            click: createListener,

        },

    }

2.工具栏类名

查询配置文件 palrttteEntries.js 中需要的类名


.bpmn-icon-screw-wrench:before { content: '\e800'; } /* '' */
.bpmn-icon-trash:before { content: '\e801'; } /* '' */
.bpmn-icon-conditional-flow:before { content: '\e802'; } /* '' */
.bpmn-icon-default-flow:before { content: '\e803'; } /* '' */
.bpmn-icon-gateway-parallel:before { content: '\e804'; } /* '' */
.bpmn-icon-intermediate-event-catch-cancel:before { content: '\e805'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-message:before { content: '\e806'; } /* '' */
.bpmn-icon-start-event-compensation:before { content: '\e807'; } /* '' */
.bpmn-icon-start-event-non-interrupting-parallel-multiple:before { content: '\e808'; } /* '' */
.bpmn-icon-loop-marker:before { content: '\e809'; } /* '' */
.bpmn-icon-parallel-mi-marker:before { content: '\e80a'; } /* '' */
.bpmn-icon-start-event-non-interrupting-signal:before { content: '\e80b'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-timer:before { content: '\e80c'; } /* '' */
.bpmn-icon-intermediate-event-catch-parallel-multiple:before { content: '\e80d'; } /* '' */
.bpmn-icon-intermediate-event-catch-compensation:before { content: '\e80e'; } /* '' */
.bpmn-icon-gateway-xor:before { content: '\e80f'; } /* '' */
.bpmn-icon-connection:before { content: '\e810'; } /* '' */
.bpmn-icon-end-event-cancel:before { content: '\e811'; } /* '' */
.bpmn-icon-intermediate-event-catch-condition:before { content: '\e812'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-parallel-multiple:before { content: '\e813'; } /* '' */
.bpmn-icon-start-event-condition:before { content: '\e814'; } /* '' */
.bpmn-icon-start-event-non-interrupting-timer:before { content: '\e815'; } /* '' */
.bpmn-icon-sequential-mi-marker:before { content: '\e816'; } /* '' */
.bpmn-icon-user-task:before { content: '\e817'; } /* '' */
.bpmn-icon-business-rule:before { content: '\e818'; } /* '' */
.bpmn-icon-sub-process-marker:before { content: '\e819'; } /* '' */
.bpmn-icon-start-event-parallel-multiple:before { content: '\e81a'; } /* '' */
.bpmn-icon-start-event-error:before { content: '\e81b'; } /* '' */
.bpmn-icon-intermediate-event-catch-signal:before { content: '\e81c'; } /* '' */
.bpmn-icon-intermediate-event-catch-error:before { content: '\e81d'; } /* '' */
.bpmn-icon-end-event-compensation:before { content: '\e81e'; } /* '' */
.bpmn-icon-subprocess-collapsed:before { content: '\e81f'; } /* '' */
.bpmn-icon-subprocess-expanded:before { content: '\e820'; } /* '' */
.bpmn-icon-task:before { content: '\e821'; } /* '' */
.bpmn-icon-end-event-error:before { content: '\e822'; } /* '' */
.bpmn-icon-intermediate-event-catch-escalation:before { content: '\e823'; } /* '' */
.bpmn-icon-intermediate-event-catch-timer:before { content: '\e824'; } /* '' */
.bpmn-icon-start-event-escalation:before { content: '\e825'; } /* '' */
.bpmn-icon-start-event-signal:before { content: '\e826'; } /* '' */
.bpmn-icon-business-rule-task:before { content: '\e827'; } /* '' */
.bpmn-icon-manual:before { content: '\e828'; } /* '' */
.bpmn-icon-receive:before { content: '\e829'; } /* '' */
.bpmn-icon-call-activity:before { content: '\e82a'; } /* '' */
.bpmn-icon-start-event-timer:before { content: '\e82b'; } /* '' */
.bpmn-icon-start-event-message:before { content: '\e82c'; } /* '' */
.bpmn-icon-intermediate-event-none:before { content: '\e82d'; } /* '' */
.bpmn-icon-intermediate-event-catch-link:before { content: '\e82e'; } /* '' */
.bpmn-icon-end-event-escalation:before { content: '\e82f'; } /* '' */
.bpmn-icon-text-annotation:before { content: '\e830'; } /* '' */
.bpmn-icon-bpmn-io:before { content: '\e831'; } /* '' */
.bpmn-icon-gateway-complex:before { content: '\e832'; } /* '' */
.bpmn-icon-gateway-eventbased:before { content: '\e833'; } /* '' */
.bpmn-icon-gateway-none:before { content: '\e834'; } /* '' */
.bpmn-icon-gateway-or:before { content: '\e835'; } /* '' */
.bpmn-icon-end-event-terminate:before { content: '\e836'; } /* '' */
.bpmn-icon-end-event-signal:before { content: '\e837'; } /* '' */
.bpmn-icon-end-event-none:before { content: '\e838'; } /* '' */
.bpmn-icon-end-event-multiple:before { content: '\e839'; } /* '' */
.bpmn-icon-end-event-message:before { content: '\e83a'; } /* '' */
.bpmn-icon-end-event-link:before { content: '\e83b'; } /* '' */
.bpmn-icon-intermediate-event-catch-message:before { content: '\e83c'; } /* '' */
.bpmn-icon-intermediate-event-throw-compensation:before { content: '\e83d'; } /* '' */
.bpmn-icon-start-event-multiple:before { content: '\e83e'; } /* '' */
.bpmn-icon-script:before { content: '\e83f'; } /* '' */
.bpmn-icon-manual-task:before { content: '\e840'; } /* '' */
.bpmn-icon-send:before { content: '\e841'; } /* '' */
.bpmn-icon-service:before { content: '\e842'; } /* '' */
.bpmn-icon-receive-task:before { content: '\e843'; } /* '' */
.bpmn-icon-user:before { content: '\e844'; } /* '' */
.bpmn-icon-start-event-none:before { content: '\e845'; } /* '' */
.bpmn-icon-intermediate-event-throw-escalation:before { content: '\e846'; } /* '' */
.bpmn-icon-intermediate-event-catch-multiple:before { content: '\e847'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-escalation:before { content: '\e848'; } /* '' */
.bpmn-icon-intermediate-event-throw-link:before { content: '\e849'; } /* '' */
.bpmn-icon-start-event-non-interrupting-condition:before { content: '\e84a'; } /* '' */
.bpmn-icon-data-object:before { content: '\e84b'; } /* '' */
.bpmn-icon-script-task:before { content: '\e84c'; } /* '' */
.bpmn-icon-send-task:before { content: '\e84d'; } /* '' */
.bpmn-icon-data-store:before { content: '\e84e'; } /* '' */
.bpmn-icon-start-event-non-interrupting-escalation:before { content: '\e84f'; } /* '' */
.bpmn-icon-intermediate-event-throw-message:before { content: '\e850'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-multiple:before { content: '\e851'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-signal:before { content: '\e852'; } /* '' */
.bpmn-icon-intermediate-event-throw-multiple:before { content: '\e853'; } /* '' */
.bpmn-icon-start-event-non-interrupting-message:before { content: '\e854'; } /* '' */
.bpmn-icon-ad-hoc-marker:before { content: '\e855'; } /* '' */
.bpmn-icon-service-task:before { content: '\e856'; } /* '' */
.bpmn-icon-task-none:before { content: '\e857'; } /* '' */
.bpmn-icon-compensation-marker:before { content: '\e858'; } /* '' */
.bpmn-icon-start-event-non-interrupting-multiple:before { content: '\e859'; } /* '' */
.bpmn-icon-intermediate-event-throw-signal:before { content: '\e85a'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-condition:before { content: '\e85b'; } /* '' */
.bpmn-icon-participant:before { content: '\e85c'; } /* '' */
.bpmn-icon-event-subprocess-expanded:before { content: '\e85d'; } /* '' */
.bpmn-icon-lane-insert-below:before { content: '\e85e'; } /* '' */
.bpmn-icon-space-tool:before { content: '\e85f'; } /* '' */
.bpmn-icon-connection-multi:before { content: '\e860'; } /* '' */
.bpmn-icon-lane:before { content: '\e861'; } /* '' */
.bpmn-icon-lasso-tool:before { content: '\e862'; } /* '' */
.bpmn-icon-lane-insert-above:before { content: '\e863'; } /* '' */
.bpmn-icon-lane-divide-three:before { content: '\e864'; } /* '' */
.bpmn-icon-lane-divide-two:before { content: '\e865'; } /* '' */
.bpmn-icon-data-input:before { content: '\e866'; } /* '' */
.bpmn-icon-data-output:before { content: '\e867'; } /* '' */
.bpmn-icon-hand-tool:before { content: '\e868'; } /* '' */
.bpmn-icon-group:before { content: '\e869'; } /* '' */
.bpmn-icon-transaction:before { content: '\e8c4'; } /* '' */

3.CustomPalette.js

修改默认的面板,创建 CustomPalette.js

import { isArray, isFunction, forEach } from 'min-dash'

import {
    domify,
    query as domQuery,
    attr as domAttr,
    clear as domClear,
    classes as domClasses,
    matches as domMatches,
    delegate as domDelegate,
    event as domEvent,
} from 'min-dom'

var TOGGLE_SELECTOR = '.djs-palette-toggle'
var ENTRY_SELECTOR = '.entry'
var ELEMENT_SELECTOR = TOGGLE_SELECTOR + ', ' + ENTRY_SELECTOR

var PALETTE_OPEN_CLS = 'open'
var PALETTE_TWO_COLUMN_CLS = 'two-column'

var DEFAULT_PRIORITY = 1000

/**
 * A palette containing modeling elements.
 */
export default function Palette(
    eventBus,
    canvas,
    elementFactory,
    create,
    paletteContainer,
    paletteEntries
) {
    this._eventBus = eventBus
    this._canvas = canvas
    // 新增赋值
    this._entries = paletteEntries // 传入的工具栏数据
    this._paletteContainer = paletteContainer // 传入的工具栏容器
    this._elementFactory = elementFactory
    this._create = create

    var self = this
    eventBus.on('tool-manager.update', function (event) {
        var tool = event.tool

        self.updateToolHighlight(tool)
    })

    eventBus.on('i18n.changed', function () {
        self._update()
    })

    eventBus.on('diagram.init', function () {
        self._diagramInitialized = true

        self._rebuild()
    })
}

Palette.$inject = [
    'eventBus',
    'canvas',
    'elementFactory',
    'create',
    'config.paletteContainer',
    'config.paletteEntries',
]

/**
 * Register a provider with the palette
 *
 * @param  {number} [priority=1000]
 * @param  {PaletteProvider} provider
 *
 * @example
 * const paletteProvider = {
 *   getPaletteEntries: function() {
 *     return function(entries) {
 *       return {
 *         ...entries,
 *         'entry-1': {
 *           label: 'My Entry',
 *           action: function() { alert("I have been clicked!"); }
 *         }
 *       };
 *     }
 *   }
 * };
 *
 * palette.registerProvider(800, paletteProvider);
 */
Palette.prototype.registerProvider = function (priority, provider) {
    if (!provider) {
        provider = priority
        priority = DEFAULT_PRIORITY
    }

    this._eventBus.on('palette.getProviders', priority, function (event) {
        event.providers.push(provider)
    })

    this._rebuild()
}

/**
 * Returns the palette entries
 *
 * @return {Object<string, PaletteEntryDescriptor>} map of entries
 */
Palette.prototype.getEntries = function () {
    var providers = this._getProviders()

    return providers.reduce(addPaletteEntries, {})
}

Palette.prototype._rebuild = function () {
    if (!this._diagramInitialized) {
        return
    }

    var providers = this._getProviders()

    if (!providers.length) {
        return
    }

    if (!this._container) {
        this._init()
    }

    this._update()
}

/**
 * Initialize
 */
Palette.prototype._init = function () {
    var self = this

    var eventBus = this._eventBus

    var parentContainer = this._getParentContainer()

    var container = (this._container = domify(Palette.HTML_MARKUP))

    parentContainer.appendChild(container)

    domDelegate.bind(container, ELEMENT_SELECTOR, 'click', function (event) {
        var target = event.delegateTarget

        if (domMatches(target, TOGGLE_SELECTOR)) {
            return self.toggle()
        }

        self.trigger('click', event)
    })

    // prevent drag propagation
    domEvent.bind(container, 'mousedown', function (event) {
        event.stopPropagation()
    })

    // prevent drag propagation
    domDelegate.bind(container, ENTRY_SELECTOR, 'dragstart', function (event) {
        self.trigger('dragstart', event)
    })

    eventBus.on('canvas.resized', this._layoutChanged, this)

    eventBus.fire('palette.create', {
        container: container,
    })
}

Palette.prototype._getProviders = function (id) {
    var event = this._eventBus.createEvent({
        type: 'palette.getProviders',
        providers: [],
    })

    this._eventBus.fire(event)

    return event.providers
}

/**
 * Update palette state.
 *
 * @param  {Object} [state] { open, twoColumn }
 */
Palette.prototype._toggleState = function (state) {
    state = state || {}

    var parent = this._getParentContainer()
    var container = this._container

    var eventBus = this._eventBus

    var twoColumn

    var cls = domClasses(container)

    if ('twoColumn' in state) {
        twoColumn = state.twoColumn
    } else {
        twoColumn = this._needsCollapse(parent.clientHeight, this._entries || {})
    }

    // always update two column
    cls.toggle(PALETTE_TWO_COLUMN_CLS, twoColumn)

    if ('open' in state) {
        cls.toggle(PALETTE_OPEN_CLS, state.open)
    }

    eventBus.fire('palette.changed', {
        twoColumn: twoColumn,
        open: this.isOpen(),
    })
}

Palette.prototype._update = function () {
    var entriesContainer = domQuery('.djs-palette-entries', this._container)
    var entries = (this._entries = this.getEntries())

    domClear(entriesContainer)

    forEach(entries, function (entry, id) {
        var grouping = entry.group || 'default'

        var container = domQuery('[data-group=' + grouping + ']', entriesContainer)
        if (!container) {
            container = domify('<div class="group" data-group="' + grouping + '"></div>')
            entriesContainer.appendChild(container)
        }

        var html =
            entry.html ||
            (entry.separator
                ? '<hr class="separator" />'
                : '<div class="entry" draggable="true"></div>')

        var control = domify(html)
        container.appendChild(control)

        if (!entry.separator) {
            domAttr(control, 'data-action', id)

            if (entry.title) {
                domAttr(control, 'title', entry.title)
            }

            if (entry.className) {
                addClasses(control, entry.className)
            }

            if (entry.imageUrl) {
                control.appendChild(domify('<img src="' + entry.imageUrl + '">'))
            }
        }
    })

    // open after update
    this.open()
}

/**
 * Trigger an action available on the palette
 *
 * @param  {string} action
 * @param  {Event} event
 */
Palette.prototype.trigger = function (action, event, autoActivate) {
    var entries = this._entries
    var entry
    var handler
    var originalEvent
    var button = event.delegateTarget || event.target
    var elementFactory = this._elementFactory,
        create = this._create

    if (!button) {
        return event.preventDefault()
    }

    entry = entries[domAttr(button, 'data-action')]

    // when user clicks on the palette and not on an action
    if (!entry) {
        return
    }

    handler = entry.action

    originalEvent = event.originalEvent || event
    // simple action (via callback function)
    if (isFunction(handler)) {
        if (action === 'click') {
            handler(originalEvent, autoActivate, elementFactory, create)
        }
    } else {
        if (handler[action]) {
            handler[action](originalEvent, autoActivate, elementFactory, create)
        }
    }

    // silence other actions
    event.preventDefault()
}

Palette.prototype._layoutChanged = function () {
    this._toggleState({})
}

/**
 * Do we need to collapse to two columns?
 *
 * @param {number} availableHeight
 * @param {Object} entries
 *
 * @return {boolean}
 */
Palette.prototype._needsCollapse = function (availableHeight, entries) {
    // top margin + bottom toggle + bottom margin
    // implementors must override this method if they
    // change the palette styles
    var margin = 20 + 10 + 20

    var entriesHeight = Object.keys(entries).length * 46

    return availableHeight < entriesHeight + margin
}

/**
 * Close the palette
 */
Palette.prototype.close = function () {
    this._toggleState({
        open: false,
        twoColumn: false,
    })
}

/**
 * Open the palette
 */
Palette.prototype.open = function () {
    this._toggleState({ open: true })
}

Palette.prototype.toggle = function (open) {
    if (this.isOpen()) {
        this.close()
    } else {
        this.open()
    }
}

Palette.prototype.isActiveTool = function (tool) {
    return tool && this._activeTool === tool
}

Palette.prototype.updateToolHighlight = function (name) {
    var entriesContainer, toolsContainer

    if (!this._toolsContainer) {
        entriesContainer = domQuery('.djs-palette-entries', this._container)

        this._toolsContainer = domQuery('[data-group=tools]', entriesContainer)
    }

    toolsContainer = this._toolsContainer

    forEach(toolsContainer.children, function (tool) {
        var actionName = tool.getAttribute('data-action')

        if (!actionName) {
            return
        }

        var toolClasses = domClasses(tool)

        actionName = actionName.replace('-tool', '')

        if (toolClasses.contains('entry') && actionName === name) {
            toolClasses.add('highlighted-entry')
        } else {
            toolClasses.remove('highlighted-entry')
        }
    })
}

/**
 * Return true if the palette is opened.
 *
 * @example
 *
 * palette.open();
 *
 * if (palette.isOpen()) {
 *   // yes, we are open
 * }
 *
 * @return {boolean} true if palette is opened
 */
Palette.prototype.isOpen = function () {
    return domClasses(this._container).has(PALETTE_OPEN_CLS)
}

/**
 * Get container the palette lives in.
 *
 * @return {Element}
 */
Palette.prototype._getParentContainer = function () {
    return this._canvas.getContainer()
}

/* markup definition */

Palette.HTML_MARKUP =
    '<div class="djs-palette">' +
    '<div class="djs-palette-entries"></div>' +
    '<div class="djs-palette-toggle"></div>' +
    '</div>'

// helpers //////////////////////

function addClasses(element, classNames) {
    var classes = domClasses(element)

    var actualClassNames = isArray(classNames) ? classNames : classNames.split(/\s+/g)
    actualClassNames.forEach(function (cls) {
        classes.add(cls)
    })
}

function addPaletteEntries(entries, provider) {
    var entriesOrUpdater = provider.getPaletteEntries()

    if (isFunction(entriesOrUpdater)) {
        return entriesOrUpdater(entries)
    }

    forEach(entriesOrUpdater, function (entry, id) {
        entries[id] = entry
    })

    return entries
}

4.paletteEntries.js

面板控制配置:

  • 定义面板中是否有某个元素节点。

  • 导出新配置

export default {
    //工具栏配置项
    'event-separator': {
        //事件分割线
        group: 'events',
        separator: true,
    },
    'create.start-event': createAction(
        'bpmn:StartEvent',
        'event',
        'bpmn-icon-start-event-none',
        '开始任务'
    ),
    'create.end-event': createAction(
        'bpmn:EndEvent',
        'event',
        'bpmn-icon-end-event-none',
        '结束任务'
    ),
    'task-separator': {
        //任务分割线
        group: 'tasks',
        separator: true,
    },
    'create.task': createAction(
        'bpmn:Task',
        'activity',
        'bpmn-icon-task', // 🙋‍♂️ 使用图片后,记得修改成自己的类名
        '创建空白任务'
        // require('../img/task.png') // 📌
    ),
    'create.user-task': createAction(
        //用户任务
        'bpmn:UserTask',
        'activity',
        'bpmn-icon-user-task',
        '创建用户任务'
    ),
    'create.service-task': createAction(
        //服务任务
        'bpmn:ServiceTask',
        'activity',
        'bpmn-icon-service-task',
        '创建服务任务'
    ),

    'gateway-separator': {
        //网关分割线
        group: 'gateways',
        separator: true,
    },
    'create.exclusive-gateway': createAction(
        'bpmn:ExclusiveGateway',
        'gateway',
        'bpmn-icon-gateway-xor',
        '创建互斥网关'
    ),
    'create.parallel-gateway': createAction(
        'bpmn:ParallelGateway',
        'gateway',
        'bpmn-icon-gateway-parallel',
        '创建并行网关'
    ),
    'group-separator': {
        //网关分割线
        group: 'artifact',
        separator: true,
    },
    'create.group': createAction('bpmn:Group', 'artifact', 'bpmn-icon-group', '创建组'),
}

function createAction(type, group, className, title, imageUrl) {
    // 还记得 CustomPalette.js 吗?便是这里回调 createListener 函数
    // if (action === 'click') {
    // handler(originalEvent, autoActivate, elementFactory, create)
    // }
    function createListener(event, autoActivate, elementFactory, create) {
        var shape = elementFactory.createShape({ type })

        create.start(event, shape)
    }

    return {
        group: group,
        className: className,
        title: title,
        imageUrl, // 📌
        action: {
            dragstart: createListener,
            click: createListener,
        },
    }
}

5.CustomPaletteProvider.js

  • 面板注入,返回工具栏数据
  • 默认导出面板配置
  • 覆盖原面板,导入新配置

```js //先导入 //自定义面板 const modules = Modeler.prototype._modules const index = modules.findIndex((it: any) => it.paletteProvider) modules.splice(index, 1) bpmnModeler = new Modeler({ container: canvas.value, paletteEntries, additionalModules: [customPalette] })

4.自定义右侧属性栏

参考官方文档的总结

由于 0.x 版本和 1.x 版本 的包之间存在区别,此次的自定义的内容基于 1.9.0 版本。

1.自定义相关的自定义属性的 json 文件

注意:

  • 这里的 prefix 的值和 type 字段下面每一个对象(即每一个扩展属性)中的 name 需要拼接在一起,例如 “magic : spell”。通过如上格式实现对新增属性的 get 和 set
  • 每一条属性中的 extends 字段:
    • 不加该字段,并且 isAttr 为 false :则当前属性会变为当前流程节点的属性。
    • 加该字段,说明当前属性/标签是 extends 的子节点
      • 如果 isAttr 为 false ,则当前属性名字会变为子标签。
      • 如果 isAttr 为 true , 则当前属性会变为 (prefix):属性名,作为当前节点的属性。
{
    "name": "Magic",
    "prefix": "magic",
    "uri": "http://ntc",
    "xml": {
        "tagAlias": "lowerCase"
    },
    "associations": [],
    "types": [
        {
            "name": "OtherTaskProps",
            "extends": ["bpmn:Task"],
            "properties": [
                {
                    "name": "spell",
                    "isAttr": true,
                    "type": "String"
                },
                {
                    "name": "value",
                    "isAttr": true,
                    "type": "String"
                }
            ]
        },
        {
            "name": "TaskProps",
            "extends": ["bpmn:StartEvent"],
            "properties": [
                {
                    "name": "inputProp",
                    "isAttr": true,
                    "type": "String"
                },
                {
                    "name": "inputValue",
                    "isAttr": true,
                    "type": "String"
                }
            ]
        }
    ]
}

2.创建属性对应的 PropertiesProviderModule

1. 原生面板

2. 自定义面板

5. 常用 API

1.importXML()

在创建实例之后导入流程模板(即XML文件)

2.saveXML()

把流程内容导出为 XML 文件

3.saveSVG()

把流程实例保存为 SVG 文件

4.get(’eventBus’)