BPM插件增强:表单显示流程流转状态

这是一个日志贴

原始插件

https://www.cuba-platform.cn/marketplace/business-process-management/

有待增强部分

  1. 设计器没有汉化(已实现,参考
  2. 流程运行是在后台的,不能在表单中直观的看到流程状态(当前处理

需要做的工作或涉及到的问题

  1. 流程图的显示(bpmn.io
  2. 流程图高亮
  3. 找到并提取Activiti数据
  4. 自定义可视化组件
  5. 如何引入Js
  6. 浏览器打开静态资源

关键步骤记录

目前已经将流程图嵌入系统,当前需要做的是找到当前表单的流程描述文件已处理节点数据并高亮流程图。之前的步骤会补齐。
image

1赞

设计的流程

image

找到定义的bpmn文件

流程编辑器保存的时候是json串,引擎认识的却是符合bpmn2.0规范的xml,所以在首次的部署的时候要将json串转换为BpmnModel,再将BpmnModel转换成xml保存进数据库,以后每次使用就直接将xml转换成BpmnModel。

代码中找

image

界面中找

image

自定义组件加载显示

image

接下来

  1. 找到流程流经路径的数据
  2. 流程图中高亮流经的节点

节点高亮

高亮

js代码

com_enruipu_rfid_web_screens_BpmnViewer = function () {
const connector = this;
const element = connector.getElement();
element.innerHTML = "<div style='height: 900px' id=\"diagram\"></div>";

const viewer = new BpmnJS({container: '#diagram'});
// 流程描述文件,后面会通过Controller加载
$.get('/app/VAADIN/themes/helium/layouts/resources/pizza-collaboration.bpmn', showDiagram, 'text');

connector.highlight = function () {
    $.get('/app/VAADIN/themes/helium/layouts/resources/pizza-collaboration.bpmn', showDiagramHighlight, 'text');
}

/**
 * 普通加载
 * 
 * @param diagramXML
 * @returns {Promise<void>}
 */
async function showDiagram(diagramXML) {
    viewer.importXML(diagramXML);
}

/**
 * 高亮加载
 * 
 * @param diagramXML
 * @returns {Promise<void>}
 */
async function showDiagramHighlight(diagramXML) {

    await viewer.importXML(diagramXML);

    const canvas = viewer.get('canvas');
    // id、css样式
    canvas.addMarker('sid-9B186814-71C7-4E03-81C3-449836752A3E', 'primary');
    canvas.addMarker('sid-895E66D2-EA41-4A8E-90CE-3A60C3BC96E6', 'success');
    canvas.addMarker('sid-66D34D68-C943-417F-986A-124F15BDC913', 'danger');
    canvas.addMarker('sid-7E119504-A334-4964-A803-A6771E7ADA19', 'primary');
    canvas.addMarker('sid-94A54FD5-2A05-4250-B17E-A0D25C8EE53E', 'danger');

}
};

Controller

import com.haulmont.cuba.gui.components.Button;
import com.haulmont.cuba.gui.screen.Screen;
import com.haulmont.cuba.gui.screen.Subscribe;
import com.haulmont.cuba.gui.screen.UiController;
import com.haulmont.cuba.gui.screen.UiDescriptor;
import com.haulmont.cuba.web.gui.components.JavaScriptComponent;

import javax.inject.Inject;

@UiController(“demo_Sandbox”)
@UiDescriptor(“test-browse.xml”)
public class Sandbox extends Screen {

@Inject
private JavaScriptComponent bpmnComponent;

@Subscribe("highlight")
public void onHighlightClick(Button.ClickEvent event) {
    bpmnComponent.callFunction("highlight");
}

@Subscribe
protected void onInit(InitEvent event) {

}
}

css

.primary:not(.djs-connection) .djs-visual > :nth-child(1) {
    fill: rgba(64, 158, 255, 0.3) !important;
}

.success:not(.djs-connection) .djs-visual > :nth-child(1) {
    fill: rgba(103, 194, 58, 0.3) !important;
}

.danger:not(.djs-connection) .djs-visual > :nth-child(1) {
    fill: rgba(245, 108, 108, 0.3) !important;
}

用 canvas 不能实现连接线的高亮!

嵌入到表单

数据还没处理,先预览一下。 :grin:

image

:partying_face: 是不是顿时感觉系统又更高大上了些~

封装成 Fragment

image

使用

1. screens

<!--流程-->
        <groupBox id="procActionsBox"
                  caption="mainMsg://menu-config.bpm"
                  spacing="true"
                  width="1400px">

            <tabSheet id="procTabSheet" height="100%">
                <tab id="procActions" caption="审批" margin="true" spacing="true">
                    <fragment id="procActionsFragment"
                              screen="bpm_ProcActionsFragment" width="AUTO"/>
                </tab>
                <tab id="procView" caption="流程图" margin="true" spacing="true">
                    <fragment id="procViewActionFragment"
                              screen="bpm_ProcViewActionFragment" width="100%"/>
                </tab>
            </tabSheet>
        </groupBox>

2. controller

@Inject
protected ProcViewActionFragment procViewActionFragment;

@Subscribe
protected void onInit(InitEvent event) {
    procTabSheet.addSelectedTabChangeListener(selectedTabChangeEvent -> {
        if ("procView".equals(selectedTabChangeEvent.getSelectedTab().getName())) {
            procViewActionFragment.highlight();
        }
    });
}

完整代码

还有需要优化的地方,后续慢慢处理了。

后台文件

包路径:com.haulmont.bpm.gui.procactionsfragment

proc-view-action-fragment.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        messagesPack="com.haulmont.bpm.gui.procactionsfragment">

    <data readOnly="true">
        <collection id="procTasksDs"
                    class="com.haulmont.bpm.entity.ProcTask"
                    view="procTask-frame">
            <loader id="procTasksDl">
                <query>
                    <![CDATA[select e from bpm$ProcTask e
                where e.procInstance.id = :custom$procInstance
                order by e.createTs desc]]>
                </query>
            </loader>
        </collection>
    </data>

    <layout spacing="true" expand="bpmnComponent">

        <jsComponent id="bpmnComponent" width="100%"
                     initFunctionName="com_enruipu_rfid_web_screens_BpmnViewer">
            <dependencies>
                <dependency path="vaadin://bpmn/css/bpmn.css" type="STYLESHEET"/>
                <dependency path="vaadin://bpmn/js/jquery.js" type="JAVASCRIPT"/>
                <dependency path="vaadin://bpmn/js/bpmn-navigated-viewer.js" type="JAVASCRIPT"/>
                <dependency path="vaadin://bpmn/js/bpmn-connector.js" type="JAVASCRIPT"/>
            </dependencies>
        </jsComponent>
    </layout>
</window>

ProcViewActionFragment.java

package com.haulmont.bpm.gui.procactionsfragment;

import com.haulmont.bpm.BpmConstants;
import com.haulmont.bpm.entity.ProcDefinition;
import com.haulmont.bpm.entity.ProcInstance;
import com.haulmont.bpm.entity.ProcTask;
import com.haulmont.bpm.service.BpmEntitiesService;
import com.haulmont.bpm.service.ProcessRepositoryService;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.gui.model.CollectionLoader;
import com.haulmont.cuba.gui.screen.ScreenFragment;
import com.haulmont.cuba.gui.screen.UiController;
import com.haulmont.cuba.gui.screen.UiDescriptor;
import com.haulmont.cuba.web.gui.components.JavaScriptComponent;
import org.slf4j.Logger;

import javax.inject.Inject;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * @author Rubin
 * @version v1 2021/4/6 15:36
 */
@UiController("bpm_ProcViewActionFragment")
@UiDescriptor("proc-view-action-fragment.xml")
public class ProcViewActionFragment extends ScreenFragment {

    private static final Logger log = org.slf4j.LoggerFactory.getLogger(ProcViewActionFragment.class);

    protected ProcInstance procInstance;

    @Inject
    protected BpmEntitiesService bpmEntitiesService;


    @Inject
    protected ProcessRepositoryService processRepositoryService;

    @Inject
    private JavaScriptComponent bpmnComponent;

    /**
     * 获取 xml
     * 获取已经审批的节点
     * 获取最后一个节点的状态(审批中、拒绝)
     */

    @Inject
    protected CollectionLoader<ProcTask> procTasksDl;

    public void highlight(String procCode, Entity<?> entity) {

        ProcDefinition procDefinition = bpmEntitiesService.findProcDefinitionByCode(procCode, BpmConstants.Views.PROC_DEFINITION_WITH_ROLES);
        if (procDefinition == null) {
            log.debug("Process definition with code '{}' not found", procCode);
            return;
        }
        List<ProcInstance> procInstances = bpmEntitiesService.findActiveProcInstancesForEntity(procDefinition.getCode(), entity, BpmConstants.Views.PROC_INSTANCE_FULL);
        procInstance = procInstances.isEmpty() ? null : procInstances.get(0);

        if (procInstance != null) {
            String actId = procDefinition.getActId();

            // 获取XML
            String processXml = processRepositoryService.getProcessDefinitionXml(actId);

            // 获取已经审批的节点
            procTasksDl.setParameter("custom$procInstance", procInstance.getId());
            procTasksDl.load();

            List<ProcTask> items = procTasksDl.getContainer().getMutableItems();
            Collections.reverse(items);

            StringBuilder actTaskId = new StringBuilder();

            for (ProcTask task : items) {
                actTaskId.append(task.getActTaskDefinitionKey()).append(";");
            }

            // 最后一个节点判断
            // todo 拒绝时的判断
            ProcTask procTask = items.get(items.size() - 1);
            Date endDate = procTask.getEndDate();

            String marker = "primary";

            if (endDate != null) {
                marker = "danger";
            }

            String last = procTask.getActTaskDefinitionKey() + ";" + marker;

            bpmnComponent.callFunction("highlight", processXml, actTaskId.toString(), last);
        }


    }
}

前端文件

image

bpmn.css

.primary:not(.djs-connection) .djs-visual > :nth-child(1) {
  fill: rgba(64, 158, 255, 0.3) !important;
}

.success:not(.djs-connection) .djs-visual > :nth-child(1) {
fill: rgba(103, 194, 58, 0.3) !important;
}

.danger:not(.djs-connection) .djs-visual > :nth-child(1) {
   fill: rgba(245, 108, 108, 0.3) !important;
 }    

bpmn-navigated-viewer.js

https://unpkg.com/bpmn-js@8.2.2/dist/bpmn-navigated-viewer.development.js

bpmn-connector.js

com_enruipu_rfid_web_screens_BpmnViewer = function () {
const connector = this;
const element = connector.getElement();
element.innerHTML = "<div style='height: 500px' id=\"diagram\"></div>";

connector.highlight = async function (diagramXML, items, last) {
    items = items.substring(0, items.length - 1)
    const taskIds = items.split(";")
    const lastStatus = last.split(";")

    const viewer = new BpmnJS({container: '#diagram'});
    await viewer.importXML(diagramXML);

    const canvas = viewer.get('canvas');

    for (let i = 0; i < taskIds.length - 1; i++) {
        canvas.addMarker(taskIds[i], 'success');
    }

    // 最后一个节点状态
    canvas.addMarker(lastStatus[0], lastStatus[1]);

    // id、css样式
    // canvas.addMarker('sid-9B186814-71C7-4E03-81C3-449836752A3E', 'primary');
    // canvas.addMarker('sid-895E66D2-EA41-4A8E-90CE-3A60C3BC96E6', 'success');
    // canvas.addMarker('sid-66D34D68-C943-417F-986A-124F15BDC913', 'danger');
    // canvas.addMarker('sid-7E119504-A334-4964-A803-A6771E7ADA19', 'primary');
    // canvas.addMarker('sid-94A54FD5-2A05-4250-B17E-A0D25C8EE53E', 'danger');

}

};

jquery.js

https://unpkg.com/jquery@3.3.1/dist/jquery.js

使用

参考

image

本次优化还未处理的问题:

  1. 流程走完后它会复位,之前的数据没有加载出来。
  2. 审批不通过流程还会继续往下走,这个需要结合具体业务来处理。
 @Subscribe
protected void onInit(InitEvent event) {
    procTabSheet.addSelectedTabChangeListener(selectedTabChangeEvent -> {
        if ("procView".equals(selectedTabChangeEvent.getSelectedTab().getName())) {
            procViewActionFragment.highlight(PROCESS_CODE, getEditedEntity());
        }
    });
}