Activiti Explorer定制

为了简化流程引擎配置,支持用户自定义配置,针对Activit Explorer进行定制化

简化侧边节点,重分类,重命名

简化前

编辑stencilset.json,在stencils数组中,移除不需要的节点,比如

{
      "type": "node",
      "id": "StartErrorEvent",
      "title": "异常事件",
      "description": "A start event that catches a thrown BPMN error",
      "view": "\n\n  \n  \n  \t\n  \n  \n  \n    \n    \n    \n\t\n  \n",
      "icon": "startevent/error.png",
      "groups": [
        "启动事件"
      ],
      "propertyPackages": [
        "overrideidpackage",
        "namepackage",
        "documentationpackage",
        "executionlistenerspackage",
        "errorrefpackage"
      ],
      "hiddenPropertyPackages": [],
      "roles": [
        "sequence_start",
        "Startevents_all",
        "StartEventsMorph",
        "all"
      ]
    },

同时,可以修改groups和title,进行自定义分组和命名

简化后

简化节点属性

简化前

编辑stencilset.json,找到对应的节点,比如"id": "UserTask",将不必要的properties从propertyPackages移动到hiddenPropertyPackages,例:

改动前

    {
      "type": "node",
      "id": "UserTask",
      "title": "用户活动",
      "description": "分配给特定人的任务 ",
      "view": "\n\n  \n  \n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \n  \n\t\n\t\n\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\n  \n\t\n\t\t\n\t\n\t\n\t\n\t\t\n\t\n\t\n\n\t\n\t\t\n\t\n  \n",
      "icon": "activity/list/type.user.png",
      "groups": [
        "活动列表"
      ],
      "propertyPackages": [
        "tasklistenerspackage",
        "overrideidpackage",
        "namepackage",
        "documentationpackage",
        "asynchronousdefinitionpackage",
        "exclusivedefinitionpackage",
        "executionlistenerspackage",
        "multiinstance_typepackage",
        "multiinstance_cardinalitypackage",
        "multiinstance_collectionpackage",
        "multiinstance_variablepackage",
        "multiinstance_conditionpackage",
        "isforcompensationpackage",
        "usertaskassignmentpackage",
        "formkeydefinitionpackage",
        "duedatedefinitionpackage",
        "prioritydefinitionpackage",
        "formpropertiespackage"
      ],
      "hiddenPropertyPackages": [],
      "roles": [
        "Activity",
        "sequence_start",
        "sequence_end",
        "ActivitiesMorph",
        "all"
      ]
    },

改动后

    {
      "type": "node",
      "id": "UserTask",
      "title": "人工任务",
      "description": "分配给特定人的任务 ",
      "view": "\n\n  \n  \n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \t\n  \n  \n\t\n\t\n\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\n  \n\t\n\t\t\n\t\n\t\n\t\n\t\t\n\t\n\t\n\n\t\n\t\t\n\t\n  \n",
      "icon": "activity/list/type.user.png",
      "groups": [
        "任务"
      ],
      "propertyPackages": [
        "namepackage",
        "approvalwaypackage",
        "approvalrulepackage",
        "autocopypackage",
        "isskipnobodypackage",
        "isskipRepeatpackage",
        "executionlistenerspackage",
        "multiinstance_typepackage",
        "isforcompensationpackage"
      ],
      "hiddenPropertyPackages": [
        "tasklistenerspackage",
        "overrideidpackage",
        "documentationpackage",
        "asynchronousdefinitionpackage",
        "exclusivedefinitionpackage",
        "multiinstance_cardinalitypackage",
        "multiinstance_collectionpackage",
        "multiinstance_variablepackage",
        "multiinstance_conditionpackage",
        "usertaskassignmentpackage",
        "formkeydefinitionpackage",
        "duedatedefinitionpackage",
        "prioritydefinitionpackage",
        "formpropertiespackage"
      ],
      "roles": [
        "Activity",
        "sequence_start",
        "sequence_end",
        "ActivitiesMorph",
        "all"
      ]
    },
简化后

增加自定义属性

上面的任务任务节点中,其中approvalwaypackage,approvalrulepackage, autocopypackage为自定义属性,在节点的propertyPackages进行引用外,还需要单独定义

{
      "name": "approvalwaypackage",
      "properties": [
        {
          "id": "approvalway",
          "type": "kisbpm-multiinstance-approvalway",
          "title": "审批方式",
          "value": "",
          "description": "一票通过或拒绝,全部通过",
          "popular": true,
          "refToView": "multiinstance"
        }
      ]
    },
    {
      "name": "approvalrulepackage",
      "properties": [
        {
          "id": "approvalrule",
          "type": "String",
          "title": "审批规则",
          "value": "",
          "description": "指定岗位,指定人,或者部门负责人",
          "popular": true
        }
      ]
    },
    {
      "name": "autocopypackage",
      "properties": [
        {
          "id": "autocopy",
          "type": "multiplecomplex",
          "title": "自动抄送",
          "value": "",
          "description": "指定岗位,指定人,或者部门负责人",
          "popular": true
        }
      ]
    },

其中
1、approvalrule指定"type": "String",最为简单,效果如下


String类型自定义属性

2、approvalway指定"type": "kisbpm-multiinstance-approvalway","refToView": "multiinstance",表示为下拉列表,其中这里的kisbpm-multiinstance-approvalway需要额外在两处进行修改,效果如下


下拉类型自定义属性

Activiti explorer前端使用的是AngularJS

2-1、properties.js 定义显示和写入模板
增加

"kisbpm-multiinstance-approvalway" : {
        "readModeTemplateUrl": "editor-app/configuration/properties/multiinstance-property-display-approvalway.html",
        "writeModeTemplateUrl": "editor-app/configuration/properties/multiinstance-property-write-approvalway.html"
    },

2-2、增加定义的显示和写入模板, AngularJS语法
multiinstance-property-display-approvalway.html


    
PROPERTY.EMPTY

multiinstance-property-write-approvalway.html

3、autocopypackage指定"type": "multiplecomplex",需要额外增加读、写模板,并在properties.js中定义模板指向,效果如下

复杂多选类型自定义属性1
复杂多选类型自定义属性2

3-1、properties.js定义模板

"oryx-autocopy-multiplecomplex": {
        "readModeTemplateUrl": "editor-app/configuration/properties/auto-copy-display-template.html",
        "writeModeTemplateUrl": "editor-app/configuration/properties/auto-copy-write-template.html"
    },

3-2、实现模板
auto-copy-display-template.html

{{'PROPERTY.AUTOCOPY.DISPLAY' | translate:property.value.autocopies}}
PROPERTY.AUTOCOPY.EMPTY

auto-copy-write-template.html




auto-copy-popup.html



实现properties-auto-copy-controller.js,并在modler.html中进行引用

/*
 * Activiti Modeler component part of the Activiti project
 * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*
 * Auto copy
 */

var KisBpmAutoCopyCtrl = [ '$scope', '$modal', '$timeout', '$translate', function($scope, $modal, $timeout, $translate) {

    // Config for the modal window
    var opts = {
        template:  'editor-app/configuration/properties/auto-copy-popup.html?version=' + Date.now(),
        scope: $scope
    };

    // Open the dialog
    $modal(opts);
}];

var KisBpmAutoCopyPopupCtrl = [ '$scope', '$q', '$translate', function($scope, $q, $translate) {

    // Put json representing form properties on scope
    if ($scope.property.value !== undefined && $scope.property.value !== null
        && $scope.property.value.autocopies !== undefined
        && $scope.property.value.autocopies !== null) {

        if ($scope.property.value.autocopies.constructor == String)
        {
            $scope.autocopies = JSON.parse($scope.property.value.autocopies);
        }
        else
        {
            // Note that we clone the json object rather then setting it directly,
            // this to cope with the fact that the user can click the cancel button and no changes should have happened
            $scope.autocopies = angular.copy($scope.property.value.autocopies);
        }
    } else {
        $scope.autocopies = [];
    }

    // Array to contain selected properties (yes - we only can select one, but ng-grid isn't smart enough)
    $scope.selectedListeners = [];
    $scope.selectedFields = [];
    $scope.translationsRetrieved = false;

    $scope.labels = {};

    var typePromise = $translate('PROPERTY.AUTOCOPY.TYPE');
    var assignerPromise = $translate('PROPERTY.AUTOCOPY.ASSIGNER');
    var othersettingPromise = $translate('PROPERTY.AUTOCOPY.OTHERSETTING');

    $q.all([typePromise, assignerPromise, othersettingPromise]).then(function(results) {
        $scope.labels.typeLabel = results[0];
        $scope.labels.assignerLabel = results[1];
        $scope.labels.othersettingLabel = results[2];
        $scope.translationsRetrieved = true;

        // Config for grid
        $scope.gridOptions = {
            data: 'autocopies',
            enableRowReordering: true,
            headerRowHeight: 28,
            multiSelect: false,
            keepLastSelected : false,
            selectedItems: $scope.selectedListeners,
            afterSelectionChange: function (rowItem, event) {
                $scope.selectedFields.length = 0;
                if ($scope.selectedListeners.length > 0)
                {
                    var fields = $scope.selectedListeners[0].fields;
                }
            },
            columnDefs: [{ field: 'type', displayName: $scope.labels.typeLabel },
                { field: 'assigner', displayName: $scope.labels.assignerLabel},
                { field: 'othersetting', displayName: $scope.labels.othersettingLabel }]
        };

        this.typeItems = [
            { text: "指定岗位", value: 'station' },
            { text: "指定人", value: 'person' },
            { text: "部门负责人", value: 'deptHeader' },
        ];

        // Config for field grid
        $scope.gridFieldOptions = {
            data: 'selectedListeners[0].fields',
            enableRowReordering: true,
            headerRowHeight: 28,
            multiSelect: false,
            keepLastSelected : false,
            selectedItems: $scope.selectedFields,
            columnDefs: [{ field: 'type', displayName: $scope.labels.typeLabel,
                editorType: DropList,
                editorSettings: {items: this.typeItems}},
                { field: 'assigner', displayName: $scope.labels.assignerLabel},
                { field: 'othersetting', displayName: $scope.labels.othersettingLabel }]
        };
    });
    
    $scope.listenerDetailsChanged = function() {
    };

    // Click handler for add button
    $scope.addNewListener = function() {
        $scope.autocopies.push({ type : 'station',
            assigner : '',
            othersetting : ''});
    };

    // Click handler for remove button
    $scope.removeListener = function() {
        if ($scope.selectedListeners.length > 0) {
            var index = $scope.autocopies.indexOf($scope.selectedListeners[0]);
            $scope.gridOptions.selectItem(index, false);
            $scope.autocopies.splice(index, 1);

            $scope.selectedListeners.length = 0;
            if (index < $scope.autocopies.length) {
                $scope.gridOptions.selectItem(index + 1, true);
            } else if ($scope.autocopies.length > 0) {
                $scope.gridOptions.selectItem(index - 1, true);
            }
        }
    };

    // Click handler for up button
    $scope.moveListenerUp = function() {
        if ($scope.selectedListeners.length > 0) {
            var index = $scope.autocopies.indexOf($scope.selectedListeners[0]);
            if (index != 0) { // If it's the first, no moving up of course
                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
                var temp = $scope.autocopies[index];
                $scope.autocopies.splice(index, 1);
                $timeout(function(){
                    $scope.autocopies.splice(index + -1, 0, temp);
                }, 100);

            }
        }
    };

    // Click handler for down button
    $scope.moveListenerDown = function() {
        if ($scope.selectedListeners.length > 0) {
            var index = $scope.autocopies.indexOf($scope.selectedListeners[0]);
            if (index != $scope.autocopies.length - 1) { // If it's the last element, no moving down of course
                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
                var temp = $scope.autocopies[index];
                $scope.autocopies.splice(index, 1);
                $timeout(function(){
                    $scope.autocopies.splice(index + 1, 0, temp);
                }, 100);

            }
        }
    };
    
    $scope.fieldDetailsChanged = function() {
    };

    // Click handler for add button
    $scope.addNewField = function() {
        if ($scope.selectedListeners.length > 0)
        {
            if ($scope.selectedListeners[0].fields == undefined)
            {
                $scope.selectedListeners[0].fields = [];
            }
            $scope.selectedListeners[0].fields.push({
                type : '',
                assigner : '',
                othersetting: ''});
        }
    };

    // Click handler for remove button
    $scope.removeField = function() {
        if ($scope.selectedFields.length > 0) {
            var index = $scope.selectedListeners[0].fields.indexOf($scope.selectedFields[0]);
            $scope.gridFieldOptions.selectItem(index, false);
            $scope.selectedListeners[0].fields.splice(index, 1);

            $scope.selectedFields.length = 0;
            if (index < $scope.selectedListeners[0].fields.length) {
                $scope.gridFieldOptions.selectItem(index + 1, true);
            } else if ($scope.selectedListeners[0].fields.length > 0) {
                $scope.gridFieldOptions.selectItem(index - 1, true);
            }
        }
    };

    // Click handler for up button
    $scope.moveFieldUp = function() {
        if ($scope.selectedFields.length > 0) {
            var index = $scope.selectedListeners[0].fields.indexOf($scope.selectedFields[0]);
            if (index != 0) { // If it's the first, no moving up of course
                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
                var temp = $scope.selectedListeners[0].fields[index];
                $scope.selectedListeners[0].fields.splice(index, 1);
                $timeout(function(){
                    $scope.selectedListeners[0].fields.splice(index + -1, 0, temp);
                }, 100);

            }
        }
    };

    // Click handler for down button
    $scope.moveFieldDown = function() {
        if ($scope.selectedFields.length > 0) {
            var index = $scope.selectedListeners[0].fields.indexOf($scope.selectedFields[0]);
            if (index != $scope.selectedListeners[0].fields.length - 1) { // If it's the last element, no moving down of course
                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
                var temp = $scope.selectedListeners[0].fields[index];
                $scope.selectedListeners[0].fields.splice(index, 1);
                $timeout(function(){
                    $scope.selectedListeners[0].fields.splice(index + 1, 0, temp);
                }, 100);

            }
        }
    };

    // Click handler for save button
    $scope.save = function() {

        if ($scope.autocopies.length > 0) {
            $scope.property.value = {};
            $scope.property.value.autocopies = $scope.autocopies;
        } else {
            $scope.property.value = null;
        }

        $scope.updatePropertyInModel($scope.property);
        $scope.close();
    };

    $scope.cancel = function() {
        $scope.$hide();
        $scope.property.mode = 'read';
    };

    // Close button handler
    $scope.close = function() {
        $scope.$hide();
        $scope.property.mode = 'read';
    };

}];

modeler.html


多语言定义在en.json中,这里直接修改了,也可以增加zh_cn.json,然后切换语言

    "PROPERTY.AUTOCOPY.DISPLAY" : "{{length}}自动抄送",
    "PROPERTY.AUTOCOPY.EMPTY" : "没有配置自动抄送",
    "PROPERTY.AUTOCOPY.TYPE" : "审批规则",
    "PROPERTY.AUTOCOPY.ASSIGNER" : "审批人",
    "PROPERTY.AUTOCOPY.OTHERSETTING" : "其他设置",
    "PROPERTY.AUTOCOPY.UNSELECTED" : "没有配置自动抄送",
    "PROPERTY.AUTOCOPY.FIELDS.TYPE" : "审批规则",
    "PROPERTY.AUTOCOPY.FIELDS.ASSIGNER" : "审批人",
    "PROPERTY.AUTOCOPY.FIELDS.OTHERSETTING" : "其他设置",
    "PROPERTY.AUTOCOPY.FIELDS.EMPTY" : "没有选择字段"

除了以上前端工作,自定义属性,还需要客制化流程图加载过程,以读取自定义属性

CustomUserTaskJsonConverter.java

public class CustomUserTaskJsonConverter extends UserTaskJsonConverter {
     @Override
     protected FlowElement convertJsonToElement(JsonNode elementNode, JsonNode modelNode, Map shapeMap) {
         FlowElement flowElement = super.convertJsonToElement(elementNode, modelNode, shapeMap);
         UserTask userTask = (UserTask) flowElement;
         List customProperties = new ArrayList<>();
         
         //添加扩展属性 审批方式
         String approvalWay = this.getPropertyValueAsString(CommonConstants.APPROVAL_WAY, elementNode);
         if (StringUtils.isNotEmpty(approvalWay)) {
             CustomProperty customProperty = new CustomProperty();
             customProperty.setName(CommonConstants.APPROVAL_WAY);
             customProperty.setSimpleValue(approvalWay);
             customProperties.add(customProperty);
         }

         //添加扩展属性 审批规则 
         String approvalRule = this.getPropertyValueAsString(CommonConstants.APPROVAL_RULE, elementNode);
         if (StringUtils.isNotEmpty(approvalRule)) {
             CustomProperty customProperty = new CustomProperty();
             customProperty.setName(CommonConstants.APPROVAL_RULE);
             customProperty.setSimpleValue(approvalRule);
             customProperties.add(customProperty);
         }

         //添加扩展属性 自动抄送
         JsonNode autoCopy = this.getProperty(CommonConstants.AUTO_COPY, elementNode);
         if (ObjectUtil.isNotEmpty(autoCopy)) {
             CustomProperty customProperty = new CustomProperty();
             customProperty.setName(CommonConstants.AUTO_COPY);
             customProperties.add(customProperty);
         }

         userTask.setCustomProperties(customProperties);
         return userTask;

     }
 }

ModelServiceImpl.java

public class ModelServiceImpl implements ModelService {

@Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deploy(String modelId) {
        try {
            // 获取模型
            Model model = repositoryService.getModel(modelId);
            ObjectNode objectNode = (ObjectNode) new ObjectMapper()
                    .readTree(repositoryService.getModelEditorSource(model.getId()));

            //从json 文件解析并添加自定义扩展属性到模型文件中
            CustomBpmnJsonConverter.getConvertersToBpmnMap().put("UserTask", CustomUserTaskJsonConverter.class);

            BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(objectNode);
        }
        ...
}

待实现

  • 指定岗位时,岗位下拉列表自动获取
  • 指定人时,人员下拉列表自动获取
  • 选择不同审批规则时,下拉获取不同的审批人列表
  • 根据设置的审批方式实现任务监听,审批通过/拒绝时更新任务节点和流程实例状态
  • 根据设置的审批规则,进入节点时,动态获取审批候选人
  • 根据设置的自动抄送规则,节点审批通过/拒绝时,自动触发抄送

你可能感兴趣的:(Activiti Explorer定制)