Activiti Explorer定制

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

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

简化前

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

{
      "type": "node",
      "id": "StartErrorEvent",
      "title": "异常事件",
      "description": "A start event that catches a thrown BPMN error",
      "view": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<svg\n   xmlns=\"http://www.w3.org/2000/svg\"\n   xmlns:oryx=\"http://www.b3mn.org/oryx\"\n   width=\"40\"\n   height=\"40\"\n   version=\"1.0\">\n  <defs></defs>\n  <oryx:magnets>\n  \t<oryx:magnet oryx:cx=\"16\" oryx:cy=\"16\" oryx:default=\"yes\" />\n  </oryx:magnets>\n  <oryx:docker oryx:cx=\"16\" oryx:cy=\"16\" />\n  <g pointer-events=\"fill\">\n    <circle id=\"bg_frame\" cx=\"16\" cy=\"16\" r=\"15\" stroke=\"#585858\" fill=\"#ffffff\" stroke-width=\"1\"/>\n    \n    <path\n         stroke=\"#585858\"\n         style=\"fill:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10\"\n         d=\"M 22.820839,11.171502 L 19.36734,24.58992 L 13.54138,14.281819 L 9.3386512,20.071607 L 13.048949,6.8323057 L 18.996148,16.132659 L 22.820839,11.171502 z\"\n         id=\"errorPolygon\" />\n\t<text font-size=\"11\" \n\t\tid=\"text_name\" \n\t\tx=\"16\" y=\"33\" \n\t\toryx:align=\"top center\" \n\t\tstroke=\"#373e48\"\n\t></text>\n  </g>\n</svg>",
      "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": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<svg\n   xmlns=\"http://www.w3.org/2000/svg\"\n   xmlns:svg=\"http://www.w3.org/2000/svg\"\n   xmlns:oryx=\"http://www.b3mn.org/oryx\"\n   xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n\n   width=\"102\"\n   height=\"82\"\n   version=\"1.0\">\n  <defs></defs>\n  <oryx:magnets>\n  \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"20\" oryx:anchors=\"left\" />\n  \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"40\" oryx:anchors=\"left\" />\n  \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"60\" oryx:anchors=\"left\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"25\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n  \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n  \t<oryx:magnet oryx:cx=\"75\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"20\" oryx:anchors=\"right\" />\n  \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"40\" oryx:anchors=\"right\" />\n  \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"60\" oryx:anchors=\"right\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"25\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n  \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n  \t<oryx:magnet oryx:cx=\"75\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"40\" oryx:default=\"yes\" />\n  </oryx:magnets>\n  <g pointer-events=\"fill\" oryx:minimumSize=\"50 40\">\n\t<rect id=\"text_frame\" oryx:anchors=\"bottom top right left\" x=\"1\" y=\"1\" width=\"94\" height=\"79\" rx=\"10\" ry=\"10\" stroke=\"none\" stroke-width=\"0\" fill=\"none\" />\n\t<rect id=\"bg_frame\" oryx:resize=\"vertical horizontal\" x=\"0\" y=\"0\" width=\"100\" height=\"80\" rx=\"10\" ry=\"10\" stroke=\"#bbbbbb\" stroke-width=\"1\" fill=\"#f9f9f9\" />\n\t\t<text \n\t\t\tfont-size=\"12\" \n\t\t\tid=\"text_name\" \n\t\t\tx=\"50\" \n\t\t\ty=\"40\" \n\t\t\toryx:align=\"middle center\"\n\t\t\toryx:fittoelem=\"text_frame\"\n\t\t\tstroke=\"#373e48\">\n\t\t</text>\n\t\n\t<g id=\"userTask\" transform=\"translate(3,3)\">\n\t\t<path oryx:anchors=\"top left\"\n       \t\tstyle=\"fill:#d1b575;stroke:none;\"\n       \t\t d=\"m 1,17 16,0 0,-1.7778 -5.333332,-3.5555 0,-1.7778 c 1.244444,0 1.244444,-2.3111 1.244444,-2.3111 l 0,-3.0222 C 12.555557,0.8221 9.0000001,1.0001 9.0000001,1.0001 c 0,0 -3.5555556,-0.178 -3.9111111,3.5555 l 0,3.0222 c 0,0 0,2.3111 1.2444443,2.3111 l 0,1.7778 L 1,15.2222 1,17 17,17\" \n         />\n\t\t\n\t</g>\n  \n\t<g id=\"parallel\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" d=\"M46 70 v8 M50 70 v8 M54 70 v8\" stroke-width=\"2\" />\n\t</g>\n\t\n\t<g id=\"sequential\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" stroke-width=\"2\" d=\"M46,76h10M46,72h10 M46,68h10\"/>\n\t</g>\n\t\n\n\t<g id=\"compensation\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" d=\"M 62 74 L 66 70 L 66 78 L 62 74 L 62 70 L 58 74 L 62 78 L 62 74\" stroke-width=\"1\" />\n\t</g>\n  </g>\n</svg>",
      "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": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<svg\n   xmlns=\"http://www.w3.org/2000/svg\"\n   xmlns:svg=\"http://www.w3.org/2000/svg\"\n   xmlns:oryx=\"http://www.b3mn.org/oryx\"\n   xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n\n   width=\"102\"\n   height=\"82\"\n   version=\"1.0\">\n  <defs></defs>\n  <oryx:magnets>\n  \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"20\" oryx:anchors=\"left\" />\n  \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"40\" oryx:anchors=\"left\" />\n  \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"60\" oryx:anchors=\"left\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"25\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n  \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n  \t<oryx:magnet oryx:cx=\"75\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"20\" oryx:anchors=\"right\" />\n  \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"40\" oryx:anchors=\"right\" />\n  \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"60\" oryx:anchors=\"right\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"25\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n  \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n  \t<oryx:magnet oryx:cx=\"75\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"40\" oryx:default=\"yes\" />\n  </oryx:magnets>\n  <g pointer-events=\"fill\" oryx:minimumSize=\"50 40\">\n\t<rect id=\"text_frame\" oryx:anchors=\"bottom top right left\" x=\"1\" y=\"1\" width=\"94\" height=\"79\" rx=\"10\" ry=\"10\" stroke=\"none\" stroke-width=\"0\" fill=\"none\" />\n\t<rect id=\"bg_frame\" oryx:resize=\"vertical horizontal\" x=\"0\" y=\"0\" width=\"100\" height=\"80\" rx=\"10\" ry=\"10\" stroke=\"#bbbbbb\" stroke-width=\"1\" fill=\"#f9f9f9\" />\n\t\t<text \n\t\t\tfont-size=\"12\" \n\t\t\tid=\"text_name\" \n\t\t\tx=\"50\" \n\t\t\ty=\"40\" \n\t\t\toryx:align=\"middle center\"\n\t\t\toryx:fittoelem=\"text_frame\"\n\t\t\tstroke=\"#373e48\">\n\t\t</text>\n\t\n\t<g id=\"userTask\" transform=\"translate(3,3)\">\n\t\t<path oryx:anchors=\"top left\"\n       \t\tstyle=\"fill:#d1b575;stroke:none;\"\n       \t\t d=\"m 1,17 16,0 0,-1.7778 -5.333332,-3.5555 0,-1.7778 c 1.244444,0 1.244444,-2.3111 1.244444,-2.3111 l 0,-3.0222 C 12.555557,0.8221 9.0000001,1.0001 9.0000001,1.0001 c 0,0 -3.5555556,-0.178 -3.9111111,3.5555 l 0,3.0222 c 0,0 0,2.3111 1.2444443,2.3111 l 0,1.7778 L 1,15.2222 1,17 17,17\" \n         />\n\t\t\n\t</g>\n  \n\t<g id=\"parallel\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" d=\"M46 70 v8 M50 70 v8 M54 70 v8\" stroke-width=\"2\" />\n\t</g>\n\t\n\t<g id=\"sequential\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" stroke-width=\"2\" d=\"M46,76h10M46,72h10 M46,68h10\"/>\n\t</g>\n\t\n\n\t<g id=\"compensation\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" d=\"M 62 74 L 66 70 L 66 78 L 62 74 L 62 70 L 58 74 L 62 78 L 62 74\" stroke-width=\"1\" />\n\t</g>\n  </g>\n</svg>",
      "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

<span ng-if="!property.noValue">
    <select ng-model="property.value">
        <option value="oneTicket">一票通过或拒绝</option>
        <option value="allTicket">全部通过</option>
    </select></span>
<span ng-if="property.noValue" translate>PROPERTY.EMPTY</span>

multiinstance-property-write-approvalway.html

<div ng-controller="KisBpmMultiInstanceCtrl">
    <select ng-model="property.value" ng-change="multiInstanceChanged()">
        <option value="oneTicket">一票通过或拒绝</option>
        <option value="allTicket">全部通过</option>
    </select>
</div>

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

<span ng-if="!property.noValue">{{'PROPERTY.AUTOCOPY.DISPLAY' | translate:property.value.autocopies}}</span>
<span ng-if="property.noValue" translate>PROPERTY.AUTOCOPY.EMPTY</span>

auto-copy-write-template.html

<!-- Just need to instantiate the controller, and it will take care of showing the modal dialog -->
<span ng-controller="KisBpmAutoCopyCtrl">
</span>

auto-copy-popup.html


<div class="modal" ng-controller="KisBpmAutoCopyPopupCtrl">
    <div class="modal-dialog modal-wide">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
                <h2>{{'PROPERTY.PROPERTY.EDIT.TITLE' | translate:property}}</h2>
            </div>
            <div class="modal-body">

                <div class="row row-no-gutter">
                    <div class="col-xs-6">
                        <div ng-if="translationsRetrieved" class="kis-listener-grid" ng-grid="gridOptions"></div>
                        <div class="pull-right">
                            <div class="btn-group">
                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.UP | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveListenerUp()"><i class="glyphicon glyphicon-arrow-up"></i></a>
                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.DOWN | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveListenerDown()"><i class="glyphicon glyphicon-arrow-down"></i></a>
                            </div>
                            <div class="btn-group">
                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.ADD | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewListener()"><i class="glyphicon glyphicon-plus"></i></a>
                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.REMOVE | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="removeListener()"><i class="glyphicon glyphicon-minus"></i></a>
                            </div>
                        </div>
                    </div>

                    <div class="col-xs-6">
                        <div ng-show="selectedListeners.length > 0">

                            <div class="form-group">
                                <label for="typeField">{{'PROPERTY.AUTOCOPY.TYPE' | translate}}</label>
                                <select id="typeField" class="form-control" ng-model="selectedListeners[0].type">
                                    <option value="station">指定岗位</option>
                                    <option value="person">指定人</option>
                                    <option value="deptHeader">部门负责人</option>
                                </select>
                            </div>

                            <div class="form-group">
                                <label for="assignerField">{{'PROPERTY.AUTOCOPY.ASSIGNER' | translate}}</label>
                                <select id="assignerField" class="form-control" ng-model="selectedListeners[0].assigner">
                                    <option>张三</option>
                                    <option>李四</option>
                                </select>
                            </div>

                            <div class="form-group">
                                <label for="settingField">{{'PROPERTY.AUTOCOPY.OTHERSETTING' | translate}}</label>
                                <select id="settingField" class="form-control" ng-model="selectedListeners[0].othersetting">
                                    <option>为空时自动寻找上级</option>
                                    <option>为空时不寻找上级</option>
                                </select>
                            </div>

                        </div>
                        <div ng-show="selectedListeners.length == 0" class="muted no-property-selected" translate>PROPERTY.AUTOCOPY.UNSELECTED</div>
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                <button ng-click="cancel()" class="btn btn-primary" translate>ACTION.CANCEL</button>
                <button ng-click="save()" class="btn btn-primary" translate>ACTION.SAVE</button>
            </div>
        </div>
    </div>
</div>

实现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

<script src="editor-app/configuration/properties-auto-copy-controller.js" type="text/javascript"></script>

多语言定义在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<String, JsonNode> shapeMap) {
         FlowElement flowElement = super.convertJsonToElement(elementNode, modelNode, shapeMap);
         UserTask userTask = (UserTask) flowElement;
         List<CustomProperty> 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);
        }
        ...
}

待实现

  • 指定岗位时,岗位下拉列表自动获取
  • 指定人时,人员下拉列表自动获取
  • 选择不同审批规则时,下拉获取不同的审批人列表
  • 根据设置的审批方式实现任务监听,审批通过/拒绝时更新任务节点和流程实例状态
  • 根据设置的审批规则,进入节点时,动态获取审批候选人
  • 根据设置的自动抄送规则,节点审批通过/拒绝时,自动触发抄送
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349