为了简化流程引擎配置,支持用户自定义配置,针对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 复杂多选类型自定义属性23-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()">×</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);
}
...
}
待实现
- 指定岗位时,岗位下拉列表自动获取
- 指定人时,人员下拉列表自动获取
- 选择不同审批规则时,下拉获取不同的审批人列表
- 根据设置的审批方式实现任务监听,审批通过/拒绝时更新任务节点和流程实例状态
- 根据设置的审批规则,进入节点时,动态获取审批候选人
- 根据设置的自动抄送规则,节点审批通过/拒绝时,自动触发抄送
网友评论