zoukankan      html  css  js  c++  java
  • 工作流二次开发之新增表单实践(二)

    再接上篇,目前基本已完成工作流表单属性的自增和页面调整工作;现将步骤和关键代码总结如下:

    官方文档及下载地址

    关于springboot结合使用的项目,有前辈已经写了帖子并且集成好了

    附上博客地址:Activiti工作流学习之SpringBoot整合Activiti5.22.0实现在线设计器(二)

    如果你看了官方文档,且也认真看了这篇博客,那么博客中的开源代码你拉下来后,我想基本的功能你已经实现了;表单设计器的页面,你已经能看到了;那么我们现在要做的是工作流的二次开发,即现有的节点表单不满足我们现有的业务需求,我们需要对其进行二次改造,增加我们自己的表单!

    新增自定义表单

    增加表单之前,我们还需要稍微了解一下angular.js的东西,因为本项目的前端数据绑定是angular模板语法来绑定的;废话不多说,我们来看下前台代码结构:

    UeoAmR.png

    解释下:properties文件夹下就是表单页面,write-template对应的是编辑页面,popup对应的是弹窗,display对应的是绑定字段属性的一个展示页面;且根据名字,你可以知道xxx-aaa-controller.js代表aaa相关页面的操作js;好,了解这些后你需要知道表单是如何增加的,以上这些只是表单属性操作的页面,我们来看下resources下的stencilset.json;
    UeoYAP.png

    因为我们是需要到对应的任务节点添加字段,即在用户活动中增加自定义的属性,那么我们找到对应的用户活动在propertypackages中添加我们自定义的package即可:
    Ueoa9S.png

    知道了大概模板结构和方法后,我们照猫画虎;也需要增加对应的三个模板页面和对应的controller.js;如果是弹窗,简单的页面我们直接写参照描述表单的写法,如果是比较复杂的表格筛选这样的,建议使用layui;具体页面可以参考我之前的博客关于layui表格的实践,在modal-body中引入自己写的页面即可,我这里做了个隐藏输入框是为了表单选中后的一个回显!

    <div class="modal-body">
         <iframe id="role_frame" src="../../../associateDeptDialog.html" frameborder="0" name="myframe" scrolling="yes" width="100%" height="400px"></iframe>
         <input style="display:none" type="text" ng-model="property.value" id="roles_id">
    </div>
    

    关于数据传值,特别说明下由于采用iframe标签,涉及到父子页面的传值问题,我们需要先在自定义的页面将值组装好,然后父页面调用子页面的方法拿到返回值;代码如下,在对应的controller中:

    $scope.save = function() {
        var selectUsers = myframe.window.canSave();//调用子页面的canSave方法拿到对应的值
        $scope.property.value = selectUsers;
        $scope.updatePropertyInModel($scope.property);
        $scope.close();
    };
    

    改造原有表单逻辑

    那么我们要对原有的表单进行改造呢?首先还是要清楚,数据怎么来的怎么绑定的,绑定的数据格式是怎么样的!

    特说明一点,此次工作流的二次开发过程中,发现节点代理有候选人候选组的属性,在节点流转的时候我们根据表act_ru_identitylink即可了解当前处理人;那么我们需要根据我们自己的用户角色来进行选择,则需要改造原有的表单逻辑。

    如下,我们修改代理人表单,以实现勾选的方式填充进关联用户和关联角色;需要注意的是,关联用户即候选人和候选组都是多个,改之前是多个input输入框,我们了解到对应的数据结构也是一个数组;我这里多个是以分号分隔,只是展示,真实到后台数据还是原有的数组结构(注意:以逗号隔开,数据保存时会被存为多条,所以这里用&符号隔开)

    废话不多说,我们还是来看下实现,对应的popu弹窗页面:

     <div class="row row-no-gutter">
         <div class="form-group">
             <!--<label for="userField">{{'PROPERTY.ASSIGNMENT.CANDIDATE_USERS' | translate}}</label>-->
             <label>关联用户(候选人)</label>
             <button ng-click="chooseUser()" class="btn btn-primary" style="margin-left: 10px;">选择用户</button>
             <input type="text" class="form-control" ng-model="property.selectUsers" id="users_id" readonly="readonly">
         <!--<div ng-repeat="candidateUser in assignment.candidateUsers">-->
         <!--input id="userField" class="form-control" type="text" ng-model="candidateUser.value" />-->
         <!--<i class="glyphicon glyphicon-minus clickable-property" ng-click="removeCandidateUserValue($index)"></i>-->
         <!--<i ng-if="$index == (assignment.candidateUsers.length - 1)" class="glyphicon glyphicon-plus clickable-property" ng-click="addCandidateUserValue($index)"></i>-->
          <!--</div>-->
         </div>
         <div class="form-group">
          <!--<label for="groupField">{{'PROPERTY.ASSIGNMENT.CANDIDATE_GROUPS' | translate}}</label>-->
             <label>关联角色(候选组)</label>
             <button ng-click="chooseRole()" class="btn btn-primary" style="margin-left: 10px;">选择角色</button>
             <input type="text" class="form-control" ng-model="property.selectRoles" id="roles_id" readonly="readonly">
         <!--<div ng-repeat="candidateGroup in assignment.candidateGroups">-->
        <!--<input id="groupField" class="form-control" type="text" ng-model="candidateGroup.value" />-->
        <!--<i class="glyphicon glyphicon-minus clickable-property" ng-click="removeCandidateGroupValue($index)"></i>-->
        <!--<i ng-if="$index == (assignment.candidateGroups.length - 1)" class="glyphicon glyphicon-plus clickable-property" ng-click="addCandidateGroupValue($index)"></i>-->
         <!-- </div>-->
         </div>
    </div>
    

    部分controller.js页面,因为两个选择按钮的弹窗一致,只po出一个代码即可,其余不重要的js可以屏蔽掉:

    if ($scope.assignment.candidateUsers == undefined || $scope.assignment.candidateUsers.length == 0){
        $scope.assignment.candidateUsers = [{value: ''}];
        $scope.property.selectUsers = "";
    } else {
        var candidateUsers = $scope.assignment.candidateUsers;
        var selectUsers = "";
        $scope.property.selectUsers = "";
        for(var i = 0;i< candidateUsers.length;i++){
            if(i < candidateUsers.length - 1){
                selectUsers += candidateUsers[i].value + ";";
            }else if(i == candidateUsers.length - 1){
                selectUsers += candidateUsers[i].value;
            }
        }
        $scope.property.selectUsers = selectUsers;
    }
    $scope.chooseUser = function () {
        layui.use(['layer'], function(){
            var layer = layui.layer;
            layer.open({
                type: 2,
                title: '关联用户选择',
                shadeClose: true,
                shade: 0,
                area: ['75%', '80%'],
                maxmin: true,
                content: ['../../../associateUserDialog.html', 'yes'],//iframe的url
                btn: ['确定', '取消'],
                yes: function(index, layer0){
                    var iframeWin0 = window[layer0.find('iframe')[0]['name']];
                    var selectUsers = iframeWin0.canSave();
                    console.log(selectUsers);
                    $scope.assignment.candidateUsers = [];
                    var candidateUsers = [];
                    if(selectUsers){
                        var users_list = selectUsers.split(";")
                        for(var i =0;i< users_list.length;i++){
                            var map = {};
                            var user = users_list[i];
                            map.value = user;
                            candidateUsers.push(map);
                        }
                    }
                    $scope.assignment.candidateUsers = candidateUsers;
                    $scope.property.selectUsers = selectUsers;//模板数据绑定
                    layer.close(index);
                },
                btn2: function(index, layero){
                    //按钮【按钮二】的回调
                }
            });
        });
    }
    

    因为此次修改涉及到二次数据回显:第一次点击代理数据回显到对应的第一层弹窗中,第二次回显到我们自己写的弹窗中,所以associateUserDialog.html页面中canSave方法需要修改一下:

    function canSave(){
        userStr = '';
        for(var i in selectUser){
            if(i == selectUser.length-1){
                userStr += selectUser[i].id+"&"+selectUser[i].roleName+"&"+selectUser[i].roleCode;
            }else{
                userStr += selectUser[i].id+"&"+selectUser[i].roleName+"&"+selectUser[i].roleCode+";";
            }
        }
        window.parent.document.getElementById("roles_id").value = userStr;// 增加此为了数据绑定
        return userStr;
    }
    

    好,自此我们完成了自定义表单和原有表单的改造;接下来我们来看下,自定义字段后台该怎么处理吧;因为原有表单的改造,我们只是改变了表单填充的方式并没有改变数据结构,所以不需要处理;而我们自定义的表单属性,原有的节点实体是无法识别的,我们需要自己处理下;

    后台自定义属性处理

    通过了解,我们知道我们自己增加的属性是以对应的json保存在表act_ge_bytearry中的bytes字段中的,那么我们还需要做的就是流程发布的时候,将对应的表单属性解析为对应的xml;我们来看下发布接口:deployment

    通过断点调试及源码了解,我们不难发现json 转xml的方法convertJsonToElement;那么关键就是重写它,然后在转换时使用我们自定义的转换器;DefinedBpmnJsonConverter 继承 UserTaskJsonConverter重写方法如下:

    @Override
    protected FlowElement convertJsonToElement(JsonNode elementNode, JsonNode modelNode, Map<String, JsonNode> shapeMap) {
        FlowElement flowElement = super.convertJsonToElement(elementNode,modelNode,shapeMap);
        UserTask userTask = (UserTask)flowElement;
    
        CustomProperty customProperty1= new CustomProperty();
        customProperty1.setName("associaterolestype");
        customProperty1.setSimpleValue(this.getPropertyValueAsString("associaterolestype",elementNode));
    
        CustomProperty customProperty2 = new CustomProperty();
        customProperty2.setName("associatedepts");
        customProperty2.setSimpleValue(this.getPropertyValueAsString("associatedepts",elementNode));
    
        //将自定义属性设置在CustomProperties中,这是很多博客没有写明的!
        userTask.getCustomProperties().add(customProperty1);
        userTask.getCustomProperties().add(customProperty2);
    
        return userTask;
    }
    /**
     * 自定义转换器
     */
    public static void definedBpmConverter(){
     fillTypes(ChildBpmnJsonConverter.getConvertersToBpmnMap(),ChildBpmnJsonConverter.getConvertersToJsonMap());
    }
    
    public static void fillTypes(Map<String, Class<? extends BaseBpmnJsonConverter>> convertersToBpmnMap, Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> convertersToJsonMap) {
        fillJsonTypes(convertersToBpmnMap);
        fillBpmnTypes(convertersToJsonMap);
    }
    public static void fillJsonTypes(Map<String, Class<? extends BaseBpmnJsonConverter>> convertersToBpmnMap) {
        convertersToBpmnMap.put("UserTask", DefinedBpmnJsonConverter.class);
    }
    public static void fillBpmnTypes(Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> convertersToJsonMap) {
        convertersToJsonMap.put(UserTask.class, DefinedBpmnJsonConverter.class);
    }
    

    ChildBpmnJsonConverter 代码如下:

    public class ChildBpmnJsonConverter extends BpmnJsonConverter {
        public static Map<String, Class<? extends BaseBpmnJsonConverter>> getConvertersToBpmnMap(){
            return convertersToBpmnMap;
        };
         public static Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> getConvertersToJsonMap(){
             return convertersToJsonMap;
         };
    }
    

    deployment接口中,我们需要json转换前调用自定义的转换代码如下:

    // 解析转换
    BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
    DefinedBpmnJsonConverter.definedBpmConverter();
    BpmnModel model = jsonConverter.convertToBpmnModel(modelNode);
    

    关于后端自定义属性的解析亦可参考博文:activiti modeler 任务节点自定义属性扩展,此处只是po出了原博客没有注明的代码。

    注意:补充一点,多个角色或人员采用#分割,因为后面发现&符号,流程发布后在标签中会被转义成&amp;而在其他标签值中不受影响,为了统一解析采用#分割更好!

    小结:关于工作流的二次开发,其实主要工作量还是前端页面的改造部分,因为后端对应的api已经有了,最多就是扩展重写一下;最后就是工作流结合业务场景怎么使用了,这个还是要看官方文档和api了,奥力给!

    余路那么长,还是得带着虔诚上路...
  • 相关阅读:
    MySQL 可重复读,差点就让我背上了一个 P0 事故
    Thread.sleep(0) 有什么用
    你不会还在用这8个错误的SQL写法吧?
    Spring事务失效的 8 大原因
    我说 SELECT COUNT(*) 会造成全表扫描,面试官让我回去等通知
    这么写参数校验(Validator)就不会被劝退了
    HyperLedger Fabric 1.4 基础环境搭建(7)
    HyperLedger Fabric 1.4 简介(6.1)
    HyperLedger Fabric 1.4 关键技术(6.4)
    HyperLedger Fabric 1.4 交易流程(6.3)
  • 原文地址:https://www.cnblogs.com/itiaotiao/p/13425448.html
Copyright © 2011-2022 走看看