一、流程审批介绍

对审批表单进行改造,核心操作功能如下:
1、根据当前节点信息获取当前的formkey,通过key查询对应的自定义表单进行展示。
2、根据当前节点信息获取当前节点的隐藏和展示字段设置信息,根据设置信息设置表单。
image.png
image.png

二、流程审核前端功能改造

审核FormCheck改造

1、新增VDataCheck审核表单组件

  1. <VDataCheck
  2. ref="checkRef"
  3. :record="record"
  4. v-if="record.businessKey.indexOf('customize') != -1"
  5. @finishResponse="finishResponse"
  6. ></VDataCheck>

2、点击审核方法,调用VDataCheck组件中的onOk方法。
同理:表单驳回和任务委派同样增加对表单的编辑及改造。
image.png

新增Check审核表单组件

  1. <template>
  2. <div>
  3. <!-- <a-skeleton active v-show="!initStatus" :paragraph="{ rows: 10 }" /> -->
  4. <!-- <a-spin size="large" v-show="!initStatus" /> -->
  5. <k-form-build
  6. ref="kfb"
  7. :value="jsonData"
  8. :dynamicData="dynamicData"
  9. v-show="visable"
  10. />
  11. </div>
  12. </template>
  13. <script>
  14. import {
  15. findVFormInfo,
  16. saveOrUpdate,
  17. findVDataInfo,
  18. findVFormData,
  19. rejectAnyNod,
  20. delegateTask,
  21. } from "@/api/customize/vdata";
  22. import { findDictionaryList } from "@/api/system/dictionary";
  23. import { findFormParams } from "@/api/activiti/processTask";
  24. export default {
  25. // 声明当前子组件接收父组件传递的属性
  26. props: {
  27. record: {
  28. type: Object,
  29. default: null,
  30. },
  31. },
  32. data() {
  33. return {
  34. jsonData: {},
  35. form_data: {},
  36. dynamicData: {},
  37. form: {},
  38. initStatus: false,
  39. visable: false,
  40. menu_id: "",
  41. table_id: "",
  42. hideFieldKey: "",
  43. };
  44. },
  45. mounted() {
  46. //查询菜单配置信息
  47. let data = this.record.businessKey.split(":");
  48. this.initData(data[2]);
  49. this.menu_id = data[2];
  50. },
  51. updated() {
  52. //查询编辑项内容
  53. let data = this.record.businessKey.split(":");
  54. this.initFormData(data[2], data[3]);
  55. },
  56. methods: {
  57. initData(menu_id) {
  58. findVFormInfo({ menu_id: menu_id, formKey: this.record.formKey }).then(
  59. (response) => {
  60. this.form_data = response.data.data;
  61. this.table_id = this.form_data.id;
  62. this.dynamicData = response.data.object;
  63. this.jsonData = JSON.parse(this.form_data.table_content);
  64. }
  65. );
  66. },
  67. initFormData(menu_id, id) {
  68. findVDataInfo({
  69. table_id: this.record.formKey,
  70. menu_id: menu_id,
  71. id: id,
  72. }).then((response) => {
  73. this.form = response.data;
  74. this.$refs.kfb.setData(response.data);
  75. this.initStatus = true;
  76. this.$refs.kfb.disableAll();
  77. findFormParams({
  78. nodeType: "nodeType",
  79. taskId: this.record.id,
  80. }).then((response) => {
  81. this.visable = true;
  82. let data = response.data.data;
  83. if (data.writeFieldKey != undefined) {
  84. this.$refs.kfb.enable(data.writeFieldKey.split(","));
  85. }
  86. if (data.hideFieldKey != undefined) {
  87. this.$refs.kfb.hide(data.hideFieldKey.split(","));
  88. this.hideFieldKey = data.hideFieldKey;
  89. }
  90. });
  91. });
  92. },
  93. currentUserOrganize(value) {},
  94. onOk(data) {
  95. return new Promise((resolve) => {
  96. // 使用getData函数获取数据
  97. this.$refs.kfb
  98. .getData()
  99. .then((values) => {
  100. for (let k in values) {
  101. if (Array.isArray(values[k]) && values[k].length > 0) {
  102. if (
  103. Object.prototype.toString.call(values[k][0]) ==
  104. "[object String]"
  105. ) {
  106. values[k] = values[k].join(",");
  107. } else if (
  108. Object.prototype.toString.call(values[k][0]) ==
  109. "[object Object]"
  110. ) {
  111. for (let j in values[k]) {
  112. if (
  113. Array.isArray(values[k][j]) &&
  114. values[k][j].length > 0
  115. ) {
  116. values[k][j] = values[k][j].join(",");
  117. }
  118. }
  119. }
  120. }
  121. }
  122. saveOrUpdate({
  123. taskId: data.id,
  124. approve_opinion: data.approve_opinion,
  125. nodeData: data.nodeData,
  126. // businessKey: data.businessKey,
  127. // assignee: data.assignee,
  128. // assignee_name: data.assignee_name,
  129. // originalAssignee: data.originalAssignee,
  130. // executionId: data.executionId,
  131. // formKey: data.formKey,
  132. // procdef_name: data.procdef_name,
  133. // processDefinitionId: data.processDefinitionId,
  134. // processInstanceId: data.processInstanceId,
  135. // start_user_name: data.start_user_name,
  136. // suspensionState: data.suspensionState,
  137. // taskDefinitionKey: data.taskDefinitionKey,
  138. // tenantId: data.tenantId,
  139. submitType: "3",
  140. ...values,
  141. id: this.form.id,
  142. table_id: this.table_id,
  143. menu_id: this.menu_id,
  144. hideFieldKey: this.hideFieldKey,
  145. }).then((response) => {
  146. this.$emit("finishResponse", response);
  147. resolve(true);
  148. });
  149. })
  150. .catch(() => {});
  151. });
  152. },
  153. onRejectAnyNode(data) {
  154. return new Promise((resolve) => {
  155. // 使用getData函数获取数据
  156. this.$refs.kfb
  157. .getData()
  158. .then((values) => {
  159. for (let k in values) {
  160. if (Array.isArray(values[k]) && values[k].length > 0) {
  161. if (
  162. Object.prototype.toString.call(values[k][0]) ==
  163. "[object String]"
  164. ) {
  165. values[k] = values[k].join(",");
  166. } else if (
  167. Object.prototype.toString.call(values[k][0]) ==
  168. "[object Object]"
  169. ) {
  170. for (let j in values[k]) {
  171. if (
  172. Array.isArray(values[k][j]) &&
  173. values[k][j].length > 0
  174. ) {
  175. values[k][j] = values[k][j].join(",");
  176. }
  177. }
  178. }
  179. }
  180. }
  181. rejectAnyNod({
  182. taskId: data.id,
  183. flowElementId: data.flowElementId,
  184. ...values,
  185. id: this.form.id,
  186. table_id: this.table_id,
  187. menu_id: this.menu_id,
  188. hideFieldKey: this.hideFieldKey,
  189. }).then((response) => {
  190. this.$emit("finishResponse", response);
  191. resolve(true);
  192. });
  193. })
  194. .catch(() => {});
  195. });
  196. },
  197. onDelegateTask(data) {
  198. return new Promise((resolve) => {
  199. // 使用getData函数获取数据
  200. this.$refs.kfb
  201. .getData()
  202. .then((values) => {
  203. for (let k in values) {
  204. if (Array.isArray(values[k]) && values[k].length > 0) {
  205. if (
  206. Object.prototype.toString.call(values[k][0]) ==
  207. "[object String]"
  208. ) {
  209. values[k] = values[k].join(",");
  210. } else if (
  211. Object.prototype.toString.call(values[k][0]) ==
  212. "[object Object]"
  213. ) {
  214. for (let j in values[k]) {
  215. if (
  216. Array.isArray(values[k][j]) &&
  217. values[k][j].length > 0
  218. ) {
  219. values[k][j] = values[k][j].join(",");
  220. }
  221. }
  222. }
  223. }
  224. }
  225. delegateTask({
  226. taskId: data.taskId,
  227. userId: data.userId,
  228. userName: data.userName,
  229. ...values,
  230. id: this.form.id,
  231. table_id: this.table_id,
  232. menu_id: this.menu_id,
  233. hideFieldKey: this.hideFieldKey,
  234. }).then((response) => {
  235. this.$emit("finishResponse", response);
  236. resolve(true);
  237. });
  238. })
  239. .catch(() => {});
  240. });
  241. },
  242. onCancel() {
  243. return new Promise((resolve) => {
  244. resolve(true);
  245. });
  246. },
  247. },
  248. };
  249. </script>

businessKey内容分析:

business:customize:自定义模块menu_id:当前数据id

String businessKey = “business:customize:”+pd.get(“menu_id”)+”:”+id;

根据formKey查询当前自定义表单信息

form_data:查询当前自定义数据表的信息,含有k-form-design设计内容。
table_id:form_data自定义数据表的主键id。
dynamicData:动态字段-如下拉框、单选框、多选框绑定的数据字典信息,此处dynamicData需要在jsonData初始化之前渲染。

 findVFormInfo({ menu_id: menu_id, formKey: this.record.formKey }).then(
        (response) => {
          this.form_data = response.data.data;
          this.table_id = this.form_data.id;
          this.dynamicData = response.data.object;
          this.jsonData = JSON.parse(this.form_data.table_content);
        }
      );

对应后台方法如下:

/**
* @title findVFormInfo
* @description 查询自定义表单信息
* @author Administrator
* @updateTime 2021/10/3 0003 10:21
*/
@GetMapping("/findVFormInfo")
public void findVFormInfo(HttpServletRequest request, HttpServletResponse response) throws Exception {
    PageData pd = new PageData(request);
    Vform vform = null;
    if(Verify.verifyIsNotNull(pd.get("formKey"))){
        vform = vformService.getById(pd.get("formKey").toString());
    }else{
        Vmenu vmenu = vmenuService.getById(pd.get("menu_id").toString());
        vform = vformService.getById(vmenu.getTable_id());
    }
    //查询主表信息和主表字段信息-组织动态参数
    QueryWrapper queryWrapper = new QueryWrapper();
    queryWrapper.eq("table_id",vform.getId());
    List<Vfield> vfields = vfieldService.list(queryWrapper);
    PageData dynamicData = new PageData();
    for (Vfield vfield:vfields) {
        if("select,checkbox,radio".indexOf(vfield.getField_type())!=-1&&vfield.getDynamic().equals("true")){
            Dictionary dictionary = new Dictionary();
            dictionary.setParent_code(vfield.getDynamicKey());
            List<Dictionary> list = dictionaryService.findList(dictionary);
            List<PageData> dynamicList = new ArrayList<PageData>();
            list.forEach(item->{
                PageData p = new PageData();
                p.put("label",item.getName());
                p.put("value",item.getId());
                dynamicList.add(p);
            });
            dynamicData.put(vfield.getDynamicKey(),dynamicList);
        }
    }
    vform.setVfields(vfields);
    //查询组织关联子表的动态参数
    QueryWrapper formWrapper = new QueryWrapper();
    formWrapper.eq("main_id",vform.getId());
    List<Vform> listChild = vformService.list(formWrapper);
    for (Vform vformChild:listChild) {
        QueryWrapper qWrapper = new QueryWrapper();
        qWrapper.eq("table_id",vformChild.getId());
        List<Vfield> vfieldsChild = vfieldService.list(qWrapper);

        for (Vfield vfield:vfieldsChild) {
            if("select,checkbox,radio".indexOf(vfield.getField_type())!=-1&&vfield.getDynamic().equals("true")){
                Dictionary dictionary = new Dictionary();
                dictionary.setParent_code(vfield.getDynamicKey());
                List<Dictionary> list = dictionaryService.findList(dictionary);
                List<PageData> dynamicList = new ArrayList<PageData>();
                list.forEach(item->{
                    PageData p = new PageData();
                    p.put("label",item.getName());
                    p.put("value",item.getId());
                    dynamicList.add(p);
                });
                dynamicData.put(vfield.getDynamicKey(),dynamicList);
            }
        }
        vform.setVfields(vfieldsChild);
    }
    Json json = new Json();
    json.setData(vform);
    json.setObject(dynamicData);
    json.setSuccess(true);
    this.writeJson(response,json);
    }

查询表单数据和writeFieldKey/hideFieldKey

1、数据渲染:this.$refs.kfb.setData(response.data);
2、设置表单不可编辑:this.$refs.kfb.disableAll(); 自己写的disableAll,默认组件是没有的,组件设置表单不可编辑模式如下:
image.png
如果设置::disabled=”true”,就无法控制可编辑项了。
3、查询writeFieldKey和hideFieldKey
设置表单可编辑项:this.$refs.kfb.enable(data.writeFieldKey.split(“,”));
设置表单隐藏项:this.$refs.kfb.hide(data.hideFieldKey.split(“,”));

findVDataInfo({
        table_id: this.record.formKey,
        menu_id: menu_id,
        id: id,
      }).then((response) => {
        this.form = response.data;
        this.$refs.kfb.setData(response.data);
        this.initStatus = true;
        this.$refs.kfb.disableAll();
        findFormParams({
          nodeType: "nodeType",
          taskId: this.record.id,
        }).then((response) => {
          this.visable = true;
          let data = response.data.data;
          if (data.writeFieldKey != undefined) {
            this.$refs.kfb.enable(data.writeFieldKey.split(","));
          }
          if (data.hideFieldKey != undefined) {
            this.$refs.kfb.hide(data.hideFieldKey.split(","));
            this.hideFieldKey = data.hideFieldKey;
          }
        });
      });

查询findFormParams参数后台方法

此方法主要查询:writeFieldKey和hideFieldKey,如果需要查询参数,则需要解析xml,具体方法如下:
1、根据流程对象获取当前bpmnModel,再根据bpmnModel获取xmlContenxt,通过XmlUtil.readXML解析xml,再通过XmlUtil.getNodeListByXPath(“//*[name()=’bpmn2:userTask’]”, document);获取流程图中所有的userTask节点。

//根据流程定义id获取bpmnModel对象
            bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());

            //创建转换对象
            BpmnXMLConverter converter = new BpmnXMLConverter();
            //把bpmnModel对象转换成字符
            byte[] bytes = converter.convertToXML(bpmnModel);
            String xmlContenxt = new String(bytes);
            System.out.println(xmlContenxt);
            Document document = XmlUtil.readXML(xmlContenxt);
           NodeList node = XmlUtil.getNodeListByXPath("//*[name()='bpmn2:userTask']", document);

2、通过获取的节点信息,进一步对xml内容进行解析,获取对应的formKey和writeFieldKey/hideFieldKey
image.png
完整代码如下:

/**
     * @title findFormParams
     * @description 查询表单参数
     * @author Administrator
     * @updateTime 2022/5/9 0009 21:42
     */
    @PostMapping("/findFormParams")
    public void findFormParams(@RequestBody PageData pd,HttpServletRequest request,HttpServletResponse response) throws Exception {
        List<PageData> list = new ArrayList<PageData>();
        //获取当前节点信息
        BpmnModel bpmnModel = null;
        if (pd.get("nodeType").equals("startNode")) {//未启动
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(pd.getString("processDefinitionKey")).latestVersion().singleResult();
            //根据流程定义id获取bpmnModel对象
            bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());

            //创建转换对象
            BpmnXMLConverter converter = new BpmnXMLConverter();
            //把bpmnModel对象转换成字符
            byte[] bytes = converter.convertToXML(bpmnModel);
            String xmlContenxt = new String(bytes);
            System.out.println(xmlContenxt);
            Document document = XmlUtil.readXML(xmlContenxt);
            NodeList node = XmlUtil.getNodeListByXPath("//*[name()='findFormParams']", document);
            String formKey = "";
            String writeFieldKey = "";
            String hideFieldKey = "";
            for (int i = 0; i < node.getLength(); i++) {
                if(Verify.verifyIsNotNull(node.item(i).getAttributes().getNamedItem("activiti:formKey"))){
                    formKey = node.item(i).getAttributes().getNamedItem("activiti:formKey").getNodeValue();
                }
//                System.out.println(node.item(i).getNodeName() + "---" + node.item(i).getAttributes().getNamedItem("id") + "---" + node.item(i).getAttributes().getNamedItem("name") + "---" + node.item(i).getAttributes().getNamedItem("activiti:formKey").getNodeValue());
                NodeList childList = node.item(i).getChildNodes();
                for (int j = 0; j < childList.getLength(); j++) {
//                    System.out.println(childList.item(j).getNodeName());
                    if (childList.item(j).getNodeName() != "extensionElements") {
                        continue;
                    }
                    NodeList cls = childList.item(j).getChildNodes();
                    for (int m = 0; m < cls.getLength(); m++) {
                        if (cls.item(m).getNodeName() != "activiti:field") {
                            continue;
                        }
                        if(Verify.verifyIsNotNull(cls.item(m).getAttributes().getNamedItem("writeFieldKey"))){
                            writeFieldKey = cls.item(m).getAttributes().getNamedItem("writeFieldKey").getNodeValue();
                        }
                        if(Verify.verifyIsNotNull(cls.item(m).getAttributes().getNamedItem("hideFieldKey").getNodeValue())){
                            hideFieldKey = cls.item(m).getAttributes().getNamedItem("hideFieldKey").getNodeValue();
                        }
                        System.out.println(cls.item(m).getAttributes().getNamedItem("elementText"));
                    }
                }
            }
            pd.put("formKey",formKey);
            pd.put("writeFieldKey",writeFieldKey);
            pd.put("hideFieldKey",hideFieldKey);
        } else {//已启动
            Task task = taskService.createTaskQuery().taskId(pd.get("taskId").toString()).singleResult();

            pd.put("model_id", task.getProcessDefinitionId().split(":")[0]);
            //根据流程定义id获取bpmnModel对象
            bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());

            //创建转换对象
            BpmnXMLConverter converter = new BpmnXMLConverter();
            //把bpmnModel对象转换成字符
            byte[] bytes = converter.convertToXML(bpmnModel);
            String xmlContenxt = new String(bytes);
            System.out.println(xmlContenxt);
            Document document = XmlUtil.readXML(xmlContenxt);
            NodeList node = XmlUtil.getNodeListByXPath("//*[name()='bpmn2:userTask']", document);

            String formKey = "";
            String writeFieldKey = "";
            String hideFieldKey = "";
            for (int i = 0; i < node.getLength(); i++) {
                System.out.println(task.getTaskDefinitionKey()+":::"+node.item(i).getAttributes().getNamedItem("id").getNodeValue());
                if(task.getTaskDefinitionKey().equals(node.item(i).getAttributes().getNamedItem("id").getNodeValue())){
                    if(Verify.verifyIsNotNull(node.item(i).getAttributes().getNamedItem("activiti:formKey"))){
                        formKey = node.item(i).getAttributes().getNamedItem("activiti:formKey").getNodeValue();
                    }
//                    System.out.println(node.item(i).getNodeName() + "---" + node.item(i).getAttributes().getNamedItem("id") + "---" + node.item(i).getAttributes().getNamedItem("name") + "---" + node.item(i).getAttributes().getNamedItem("activiti:formKey").getNodeValue());
                    NodeList childList = node.item(i).getChildNodes();
                    for (int j = 0; j < childList.getLength(); j++) {
                        if (childList.item(j).getNodeName() != "extensionElements") {
                            continue;
                        }
                        NodeList cls = childList.item(j).getChildNodes();
                        for (int m = 0; m < cls.getLength(); m++) {
                            if (cls.item(m).getNodeName() != "activiti:field") {
                                continue;
                            }
                            if(Verify.verifyIsNotNull(cls.item(m).getAttributes().getNamedItem("writeFieldKey"))){
                                writeFieldKey = cls.item(m).getAttributes().getNamedItem("writeFieldKey").getNodeValue();
                            }
                            if(Verify.verifyIsNotNull(cls.item(m).getAttributes().getNamedItem("hideFieldKey"))){
                                hideFieldKey = cls.item(m).getAttributes().getNamedItem("hideFieldKey").getNodeValue();
                            }
//                        System.out.println(cls.item(m).getAttributes().getNamedItem("elementText"));
                        }
                    }
                }
            }
            pd.put("formKey",formKey);
            pd.put("writeFieldKey",writeFieldKey);
            pd.put("hideFieldKey",hideFieldKey);
        }
        Json json = new Json();
        json.setData(pd);
        json.setSuccess(true);
        json.setMsg("数据获取成功。");
        this.writeJson(response,json);
    }

新增流程审核、流程驳回、流程委派

为什么称为新增呢?因为之前的方法没有关联表单,仅仅是单纯的流程走向,本次因为设计到对表单的编辑,在执行流程操作之前需要先进行流程表单的存储。此处介绍流程审批,流程驳回和流程委派模式一致。

saveOrUpdate({
              taskId: data.id,
              approve_opinion: data.approve_opinion,
              nodeData: data.nodeData,
              submitType: "3",
              ...values,
              id: this.form.id,
              table_id: this.table_id,
              menu_id: this.menu_id,
              hideFieldKey: this.hideFieldKey,
            }).then((response) => {
              this.$emit("finishResponse", response);
              resolve(true);
            });
          })

后端代码位置,具体细节可以查看源码分析:
image.png