react+bpmn-js+flowable问题解决记录

最近在做表单流程基础服务平台,流程设计器选用了react+bpmn-js实现,流程引擎采用flowable,实现自定义流程过程中遇到了不少坑,现将踩坑过程记录如下,供道友们参考。

实现过程中主要参考了以下几篇文章,在此表示感谢!
文章1:Bpmn.js 中文文档(一)
文章2:bpmn-js(五) 线条上添加决策表达式
文章3:全网最详bpmn.js教材-properties-panel篇(上)
文章4:前端vue+bpmn-js实现activiti的流程设计器,后端Springboot+Activiti开发工作流

一、属性面板无法显示问题

最初按照文章3配置,属性面板一直不显示,未提示任何错误,后来排查多次方才发现,additionalModules属性应为数组 [propertiesPanelModule,propertiesProviderModule],原先给写成了对象 {propertiesPanelModule,propertiesProviderModule},汗。。。。

二、自定义属性面板

bpmn-js官方示例中,按照文章3 引入'bpmn-js-properties-panel/lib/provider/camunda',添加 camunda-bpmn-moddle后,绘制流程图生成的xml标签都是类似"camunda:candidateUsers",是适配camunda流程引擎的,使用xml字符串在flowable流程引擎中虽然也能部署成功,但是存在代理人、候选用户等设置的变量${user},流程引擎无法自动将下一步人员更新到流程中,所以自己实现flowable属性。

1、先将需要引入的空xml模板调整为flowable定义的,我这里需要自定义流程id和名称,就采用了下边这种方式实现:

const xmlStr = '<?xml version="1.0" encoding="UTF-8"?>' +
    '<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" ' +
    'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
    'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' +
    'xmlns:flowable="http://flowable.org/bpmn" ' +
    'xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" ' +
    'xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" ' +
    'xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" ' +
    'typeLanguage="http://www.w3.org/2001/XMLSchema" ' +
    'expressionLanguage="http://www.w3.org/1999/XPath" ' +
    'targetNamespace="http://www.flowable.org/processdef">' +
    '<process id="$ProcessId$" name="$ProcessNm$" isExecutable="true">' +
    '</process>' +
    '<bpmndi:BPMNDiagram id="BpmnDiagram_$ProcessId$">' +
    '<bpmndi:BPMNPlane id="BpmnPlane_$ProcessId$" bpmnElement="$ProcessId$">' +
    '</bpmndi:BPMNPlane>' +
    '</bpmndi:BPMNDiagram>' +
    '</definitions>';

export const InitXml = (processId, processNm) => {
    return xmlStr.replace(/\$ProcessId\$/g, processId).replace(/\$ProcessNm\$/g, processNm);
}

2、自己写provider,替换原来的propertiesProviderModule,直接上代码

需要以下4个文件,BpmnPropertiesProvider.js 中定义了选项卡、属性组,具体属性在 AssignableProps.js 和 MultiInstanceProps.js中,对应我这里配置的用户分配和会签两个属性组。

文件结构

BpmnPropertiesProvider.js:

import inherits from 'inherits';
import {is} from 'bpmn-js/lib/util/ModelUtil';
import PropertiesActivator from 'bpmn-js-properties-panel/lib/PropertiesActivator';
import processProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/ProcessProps';
import eventProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/EventProps';
import linkProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/LinkProps';
import documentationProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/DocumentationProps';
import idProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/IdProps';
import nameProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/NameProps';
import executableProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/ExecutableProps';
import assignableProps from './Parts/AssignableProps';
import multiInstanceProps from './Parts/MultiInstanceProps';

function getIdOptions(element) {
    if (is(element, 'bpmn:Participant')) {
        return {id: 'participant-id', label: 'Participant Id'};
    }
}

function getNameOptions(element) {
    if (is(element, 'bpmn:Participant')) {
        return {id: 'participant-name', label: 'Participant Name'};
    }
}

function createGeneralTabGroups(element, canvas, bpmnFactory, elementRegistry, translate) {
    let generalGroup = {
        id: 'general',
        label: translate('General'),
        entries: []
    };
    idProps(generalGroup, element, translate, getIdOptions(element));
    nameProps(generalGroup, element, bpmnFactory, canvas, translate, getNameOptions(element));
    processProps(generalGroup, element, translate);
    // executableProps(generalGroup, element, translate);

    let detailsGroup = {
        id: 'details',
        label: translate('Details'),
        entries: []
    };
    linkProps(detailsGroup, element, translate);
    eventProps(detailsGroup, element, bpmnFactory, elementRegistry, translate);

    let documentationGroup = {
        id: 'documentation',
        label: translate('Documentation'),
        entries: []
    };
    documentationProps(documentationGroup, element, bpmnFactory, translate);

    let assignableGroup = {
        id: 'assignable',
        label: translate('task assignable'),
        entries: []
    }
    assignableProps(assignableGroup, element, translate);

    let multiInstanceGroup = {
        id: 'multiInstance',
        label: translate('multiInstance loopCharacteristics'),
        entries: []
    }
    multiInstanceProps(multiInstanceGroup, element, translate);

    return [
        generalGroup,
        detailsGroup,
        // documentationGroup
        assignableGroup,
        multiInstanceGroup
    ];
}

function createCustomizeTabGroups(element, canvas, bpmnFactory, elementRegistry, translate) {
    return [];
}

function BpmnPropertiesProvider(eventBus, canvas, bpmnFactory, elementRegistry, translate) {
    PropertiesActivator.call(this, eventBus);

    this.getTabs = function (element) {
        let generalTab = {
            id: 'general',
            label: translate('General'),
            groups: createGeneralTabGroups(element, canvas, bpmnFactory, elementRegistry, translate)
        };

/*
        let customizeTab = {
            id: 'customize',
            label: translate('customize'),
            groups: createCustomizeTabGroups(element, canvas, bpmnFactory, elementRegistry, translate)
        };
*/

        return [
            generalTab,
            // customizeTab
        ];
    };
}

BpmnPropertiesProvider.$inject = [
    'eventBus',
    'canvas',
    'bpmnFactory',
    'elementRegistry',
    'translate'
];

inherits(BpmnPropertiesProvider, PropertiesActivator);
export default BpmnPropertiesProvider;

AssignableProps.js:


import entryFactory from 'bpmn-js-properties-panel/lib/factory/EntryFactory';
import { is } from 'bpmn-js/lib/util/ModelUtil';

export default function(group, element, translate) {
    if (is(element, 'bpmn:UserTask')) {
        // 用户任务节点
        group.entries.push(entryFactory.textField(translate, {
            id : 'assignee',
            label : translate('Assignee'),
            modelProperty : 'flowable:assignee'
        }));

        group.entries.push(entryFactory.textField(translate, {
            id : 'candidateUsers',
            label : translate('Candidate Users'),
            modelProperty : 'flowable:candidateUsers'
        }));
    }
}

MultiInstanceProps.js:

import entryFactory from 'bpmn-js-properties-panel/lib/factory/EntryFactory';
import {is} from 'bpmn-js/lib/util/ModelUtil';

export default function (group, element, translate) {
    if (is(element, 'bpmn:UserTask')) {
        // 用户任务节点
        group.entries.push(entryFactory.selectBox(translate, {
            id: 'countersign',
            label: translate('countersign task'),
            selectOptions: [
                {value: 'false', name: translate('false')},
                {value: 'sequential', name: translate('sequential')},
                {value: 'synchronize', name: translate('synchronize')}
            ],
            modelProperty : 'flowable:countersign'
        }));
    }
}

由于设置会签节点属性设置比较复杂,为了减轻用户使用难度,我在MultiInstanceProps中自定义了flowable:countersign标签,通过监听此标签值的变化,自动设置取消会签属性,详见问题四。

index.js:

import BpmnPropertiesProvider from './BpmnPropertiesProvider';

export default {
    __init__: [ 'propertiesProvider' ],
    propertiesProvider: [ 'type', BpmnPropertiesProvider ]
};

3、最后在初始化BpmnModeler时引入。

import propertiesProviderModule from './Provider/bpmn';

this.bpmnModeler = new BpmnModeler({
    container: '#canvas',
    propertiesPanel: {
        parent: '#properties'
    },
    height: '100vh',
    additionalModules: [
        propertiesPanelModule,
        propertiesProviderModule //在此引入
    ]
})

三、删除属性问题

根据文章2,通过 modeling.updateProperties(element, { 'flowable:assignee': '${user01}'}) 可以设置元素属性,那么如何删除已设置的属性呢,多次尝试发现,更新为 undefined 即可删除:

const modeling = this.bpmnModeler.get('modeling');
let ele = internalEvent.element;
modeling.updateProperties(ele, { 'flowable:assignee': undefined})

四、会签属性配置问题

文章4中说明了如何设置多实例(会签),但是 collection 和 elementVariable 属性无法更新到xml中,flowable的属性都是在 $attrs 中,所以应该这么设置:

loopCharacteristics = bpmnFactory.create('bpmn:MultiInstanceLoopCharacteristics');
loopCharacteristics['isSequential'] = isSequential;
//flowable属性需要在$attrs中设置!!!
loopCharacteristics.$attrs['flowable:collection'] = collectionUsers;
loopCharacteristics.$attrs['flowable:elementVariable'] = elementVariable;
loopCharacteristics['completionCondition'] = lementHelper.createElement('bpmn:Expression', {body: '${nrOfCompletedInstances/nrOfInstances>=0.99}'}, loopCharacteristics, bpmnFactory);

modeling.updateProperties(ele, {
    'flowable:assignee': assignableUsers,
    'flowable:candidateUsers': undefined,
    loopCharacteristics: loopCharacteristics
});
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容