Browse Source

添加流程管理模块

sunny 1 month ago
parent
commit
edaef959b7
82 changed files with 15419 additions and 0 deletions
  1. 2 0
      package.json
  2. 150 0
      src/api/flowable/definition.js
  3. 44 0
      src/api/flowable/expression.js
  4. 80 0
      src/api/flowable/finished.js
  5. 68 0
      src/api/flowable/form.js
  6. 44 0
      src/api/flowable/listener.js
  7. 106 0
      src/api/flowable/process.js
  8. 143 0
      src/api/flowable/todo.js
  9. 9 0
      src/components/Process/.bpmnlintrc
  10. 68 0
      src/components/Process/BpmData.js
  11. 166 0
      src/components/Process/PropertyPanel.vue
  12. 120 0
      src/components/Process/common/bpmnUtils.js
  13. 32 0
      src/components/Process/common/customTranslate.js
  14. 24 0
      src/components/Process/common/mixinExecutionListener.js
  15. 70 0
      src/components/Process/common/mixinPanel.js
  16. 22 0
      src/components/Process/common/mixinXcrud.js
  17. 55 0
      src/components/Process/common/parseElement.js
  18. 24 0
      src/components/Process/components/custom/customContextPad.vue
  19. 81 0
      src/components/Process/components/nodePanel/gateway.vue
  20. 113 0
      src/components/Process/components/nodePanel/process.vue
  21. 217 0
      src/components/Process/components/nodePanel/property/executionListener.vue
  22. 72 0
      src/components/Process/components/nodePanel/property/listenerList.vue
  23. 96 0
      src/components/Process/components/nodePanel/property/listenerParam.vue
  24. 133 0
      src/components/Process/components/nodePanel/property/multiInstance.vue
  25. 147 0
      src/components/Process/components/nodePanel/property/signal.vue
  26. 221 0
      src/components/Process/components/nodePanel/property/taskListener.vue
  27. 105 0
      src/components/Process/components/nodePanel/sequenceFlow.vue
  28. 102 0
      src/components/Process/components/nodePanel/startEnd.vue
  29. 638 0
      src/components/Process/components/nodePanel/task.vue
  30. 524 0
      src/components/Process/customPanel/CustomContextPad.js
  31. 142 0
      src/components/Process/customPanel/CustomPalette.js
  32. 8 0
      src/components/Process/customPanel/index.js
  33. 187 0
      src/components/Process/designer.vue
  34. 1195 0
      src/components/Process/flowable/flowable.json
  35. 33 0
      src/components/Process/flowable/init.js
  36. 51 0
      src/components/Process/flowable/showConfig.js
  37. 5 0
      src/components/Process/index.js
  38. 355 0
      src/components/Process/index.vue
  39. 353 0
      src/components/Process/index1.vue
  40. 232 0
      src/components/Process/lang/zh.js
  41. 140 0
      src/components/Process/panel/ButtonsPanel.vue
  42. 139 0
      src/components/Process/panel/PropertiesPanel2.vue
  43. 133 0
      src/components/Process/panel/commonPanel.vue
  44. 175 0
      src/components/Process/panel/conditionPanel.vue
  45. 472 0
      src/components/Process/panel/executionListener.vue
  46. 84 0
      src/components/Process/panel/formPanel.vue
  47. 236 0
      src/components/Process/panel/multiInstance.vue
  48. 65 0
      src/components/Process/panel/otherPanel.vue
  49. 529 0
      src/components/Process/panel/taskListener.vue
  50. 424 0
      src/components/Process/panel/taskPanel.vue
  51. 183 0
      src/components/Process/style/flow-viewer.scss
  52. 123 0
      src/components/Process/style/process-panel.scss
  53. 257 0
      src/components/parser/Parser.vue
  54. 17 0
      src/components/parser/README.md
  55. 324 0
      src/components/parser/example/Index.vue
  56. 3 0
      src/components/parser/index.js
  57. 25 0
      src/components/parser/package.json
  58. 600 0
      src/views/flowable/definition/index.vue
  59. 174 0
      src/views/flowable/definition/model.vue
  60. 272 0
      src/views/flowable/expression/index.vue
  61. 300 0
      src/views/flowable/listener/index.vue
  62. 24 0
      src/views/flowable/task/finished/detail/flow.vue
  63. 238 0
      src/views/flowable/task/finished/detail/flowview.vue
  64. 211 0
      src/views/flowable/task/finished/detail/index.vue
  65. 277 0
      src/views/flowable/task/finished/index.vue
  66. 123 0
      src/views/flowable/task/flowForm/index.vue
  67. 301 0
      src/views/flowable/task/form/index.vue
  68. 24 0
      src/views/flowable/task/myProcess/detail/flow.vue
  69. 238 0
      src/views/flowable/task/myProcess/detail/flowview.vue
  70. 235 0
      src/views/flowable/task/myProcess/detail/index.vue
  71. 360 0
      src/views/flowable/task/myProcess/index.vue
  72. 24 0
      src/views/flowable/task/myProcess/send/flow.vue
  73. 129 0
      src/views/flowable/task/myProcess/send/flowview.vue
  74. 376 0
      src/views/flowable/task/myProcess/send/index.vue
  75. 36 0
      src/views/flowable/task/record/flow.vue
  76. 239 0
      src/views/flowable/task/record/flowview.vue
  77. 608 0
      src/views/flowable/task/record/index.vue
  78. 24 0
      src/views/flowable/task/todo/detail/flow.vue
  79. 243 0
      src/views/flowable/task/todo/detail/flowview.vue
  80. 568 0
      src/views/flowable/task/todo/detail/index.vue
  81. 219 0
      src/views/flowable/task/todo/index.vue
  82. 10 0
      yarn.lock

+ 2 - 0
package.json

@@ -29,6 +29,8 @@
     "vue-router": "3.0.1",
     "vue-splitpane": "1.0.2",
     "vuex": "3.0.1",
+    "highlight.js": "9.18.5",
+    "vkbeautify": "^0.99.3",
     "xlsx": "^0.11.16"
   },
   "devDependencies": {

+ 150 - 0
src/api/flowable/definition.js

@@ -0,0 +1,150 @@
+import request from '@/utils/request'
+
+// 查询流程定义列表
+export function listDefinition(query) {
+  return request({
+    url: '/flowable/definition/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 部署流程实例
+export function definitionStart(procDefId, data) {
+  return request({
+    url: '/flowable/definition/start/' + procDefId,
+    method: 'post',
+    data: data
+  })
+}
+
+// 获取流程变量
+export function getProcessVariables(taskId) {
+  return request({
+    url: '/flowable/task/processVariables/' + taskId,
+    method: 'get'
+  })
+}
+
+// 激活/挂起流程
+export function updateState(params) {
+  return request({
+    url: '/flowable/definition/updateState',
+    method: 'put',
+    params: params
+  })
+}
+
+// 指定流程办理人员列表
+export function userList(query) {
+  return request({
+    url: '/flowable/definition/userList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 指定流程办理组列表
+export function roleList(query) {
+  return request({
+    url: '/flowable/definition/roleList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 指定流程表达式
+export function expList(query) {
+  return request({
+    url: '/flowable/definition/expList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 读取xml文件
+export function readXml(deployId) {
+  return request({
+    url: '/flowable/definition/readXml/' + deployId,
+    method: 'get'
+  })
+}
+
+// 读取image文件
+export function readImage(deployId) {
+  return request({
+    url: '/flowable/definition/readImage/' + deployId,
+    method: 'get'
+  })
+}
+
+// 获取流程执行节点
+export function getFlowViewer(procInsId, executionId) {
+  return request({
+    url: '/flowable/task/flowViewer/' + procInsId + '/' + executionId,
+    method: 'get'
+  })
+}
+
+// 流程节点数据
+export function flowXmlAndNode(query) {
+  return request({
+    url: '/flowable/task/flowXmlAndNode',
+    method: 'get',
+    params: query
+  })
+}
+
+// 读取xml文件
+export function saveXml(data) {
+  return request({
+    url: '/flowable/definition/save',
+    method: 'post',
+    data: data
+  })
+}
+
+// 新增流程定义
+export function addDeployment(data) {
+  return request({
+    url: '/system/deployment',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改流程定义
+export function updateDeployment(data) {
+  return request({
+    url: '/system/deployment',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除流程定义
+export function delDeployment(deployId) {
+  return request({
+    url: '/flowable/definition/' + deployId,
+    method: 'delete',
+  })
+}
+
+// 导出流程定义
+export function exportDeployment(query) {
+  return request({
+    url: '/system/deployment/export',
+    method: 'get',
+    params: query
+  })
+}
+
+
+// 查询最新流程版本
+export function selectByFlowKey(flowKey) {
+  return request({
+    url: '/flowable/definition/selectByFlowKey',
+    method: 'get',
+    params: { flowKey: flowKey }
+  })
+}

+ 44 - 0
src/api/flowable/expression.js

@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+
+// 查询流程达式列表
+export function listExpression(query) {
+  return request({
+    url: '/system/expression/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询流程达式详细
+export function getExpression(id) {
+  return request({
+    url: '/system/expression/' + id,
+    method: 'get'
+  })
+}
+
+// 新增流程达式
+export function addExpression(data) {
+  return request({
+    url: '/system/expression',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改流程达式
+export function updateExpression(data) {
+  return request({
+    url: '/system/expression',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除流程达式
+export function delExpression(id) {
+  return request({
+    url: '/system/expression/' + id,
+    method: 'delete'
+  })
+}

+ 80 - 0
src/api/flowable/finished.js

@@ -0,0 +1,80 @@
+import request from '@/utils/request'
+import da from "element-ui/src/locale/lang/da";
+
+// 查询已办任务列表
+export function finishedList(query) {
+  return request({
+    url: '/flowable/task/finishedList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 任务流转记录
+export function flowRecord(query) {
+  return request({
+    url: '/flowable/task/flowRecord',
+    method: 'get',
+    params: query
+  })
+}
+
+// 撤回任务
+export function revokeProcess(data) {
+  return request({
+    url: '/flowable/task/revokeProcess',
+    method: 'post',
+    data: data
+  })
+}
+
+// 部署流程实例
+export function deployStart(deployId) {
+  return request({
+    url: '/flowable/process/startFlow/' + deployId,
+    method: 'get',
+  })
+}
+
+// 查询流程定义详细
+export function getDeployment(id) {
+  return request({
+    url: '/system/deployment/' + id,
+    method: 'get'
+  })
+}
+
+// 新增流程定义
+export function addDeployment(data) {
+  return request({
+    url: '/system/deployment',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改流程定义
+export function updateDeployment(data) {
+  return request({
+    url: '/system/deployment',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除流程定义
+export function delDeployment(id) {
+  return request({
+    url: '/flowable/instance/delete/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出流程定义
+export function exportDeployment(query) {
+  return request({
+    url: '/system/deployment/export',
+    method: 'get',
+    params: query
+  })
+}

+ 68 - 0
src/api/flowable/form.js

@@ -0,0 +1,68 @@
+import request from '@/utils/request'
+
+// 查询流程表单列表
+export function listForm(query) {
+  return request({
+    url: '/flowable/form/list',
+    method: 'get',
+    params: query
+  })
+}
+export function listAllForm(query) {
+  return request({
+    url: '/flowable/form/formList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询流程表单详细
+export function getForm(formId) {
+  return request({
+    url: '/flowable/form/' + formId,
+    method: 'get'
+  })
+}
+
+// 新增流程表单
+export function addForm(data) {
+  return request({
+    url: '/flowable/form',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改流程表单
+export function updateForm(data) {
+  return request({
+    url: '/flowable/form',
+    method: 'put',
+    data: data
+  })
+}
+// 挂载表单
+export function addDeployForm(data) {
+  return request({
+    url: '/flowable/form/addDeployForm',
+    method: 'post',
+    data: data
+  })
+}
+
+// 删除流程表单
+export function delForm(formId) {
+  return request({
+    url: '/flowable/form/' + formId,
+    method: 'delete'
+  })
+}
+
+// 导出流程表单
+export function exportForm(query) {
+  return request({
+    url: '/flowable/form/export',
+    method: 'get',
+    params: query
+  })
+}

+ 44 - 0
src/api/flowable/listener.js

@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+
+// 查询流程监听列表
+export function listListener(query) {
+  return request({
+    url: '/system/listener/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询流程监听详细
+export function getListener(id) {
+  return request({
+    url: '/system/listener/' + id,
+    method: 'get'
+  })
+}
+
+// 新增流程监听
+export function addListener(data) {
+  return request({
+    url: '/system/listener',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改流程监听
+export function updateListener(data) {
+  return request({
+    url: '/system/listener',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除流程监听
+export function delListener(id) {
+  return request({
+    url: '/system/listener/' + id,
+    method: 'delete'
+  })
+}

+ 106 - 0
src/api/flowable/process.js

@@ -0,0 +1,106 @@
+import request from '@/utils/request'
+import da from "element-ui/src/locale/lang/da";
+
+// 我的发起的流程
+export function myProcessList(query) {
+  return request({
+    url: '/flowable/task/myProcess',
+    method: 'get',
+    params: query
+  })
+}
+
+export function flowFormData(query) {
+  return request({
+    url: '/flowable/task/flowFormData',
+    method: 'get',
+    params: query
+  })
+}
+
+// 完成任务
+export function complete(data) {
+  return request({
+    url: '/flowable/task/complete',
+    method: 'post',
+    data: data
+  })
+}
+
+// 取消申请
+export function stopProcess(data) {
+  return request({
+    url: '/flowable/task/stopProcess',
+    method: 'post',
+    data: data
+  })
+}
+
+// 驳回任务
+export function rejectTask(data) {
+  return request({
+    url: '/flowable/task/reject',
+    method: 'post',
+    data: data
+  })
+}
+
+// 可退回任务列表
+export function returnList(data) {
+  return request({
+    url: '/flowable/task/returnList',
+    method: 'post',
+    data: data
+  })
+}
+
+// 部署流程实例
+export function deployStart(deployId) {
+  return request({
+    url: '/flowable/process/startFlow/' + deployId,
+    method: 'get',
+  })
+}
+
+// 查询流程定义详细
+export function getDeployment(id) {
+  return request({
+    url: '/system/deployment/' + id,
+    method: 'get'
+  })
+}
+
+// 新增流程定义
+export function addDeployment(data) {
+  return request({
+    url: '/system/deployment',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改流程定义
+export function updateDeployment(data) {
+  return request({
+    url: '/system/deployment',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除流程定义
+export function delDeployment(id) {
+  return request({
+    url: '/system/deployment/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出流程定义
+export function exportDeployment(query) {
+  return request({
+    url: '/system/deployment/export',
+    method: 'get',
+    params: query
+  })
+}

+ 143 - 0
src/api/flowable/todo.js

@@ -0,0 +1,143 @@
+import request from '@/utils/request'
+import da from "element-ui/src/locale/lang/da";
+
+// 查询待办任务列表
+export function todoList(query) {
+  return request({
+    url: '/flowable/task/todoList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 完成任务
+export function complete(data) {
+  return request({
+    url: '/flowable/task/complete',
+    method: 'post',
+    data: data
+  })
+}
+
+// 委派任务
+export function delegate(data) {
+  return request({
+    url: '/flowable/task/delegate',
+    method: 'post',
+    data: data
+  })
+}
+
+// 退回任务
+export function returnTask(data) {
+  return request({
+    url: '/flowable/task/return',
+    method: 'post',
+    data: data
+  })
+}
+
+// 驳回任务
+export function rejectTask(data) {
+  return request({
+    url: '/flowable/task/reject',
+    method: 'post',
+    data: data
+  })
+}
+
+// 可退回任务列表
+export function returnList(data) {
+  return request({
+    url: '/flowable/task/returnList',
+    method: 'post',
+    data: data
+  })
+}
+
+// 下一节点
+export function getNextFlowNode(data) {
+  return request({
+    url: '/flowable/task/nextFlowNode',
+    method: 'post',
+    data: data
+  })
+}
+
+// 下一节点
+export function getNextFlowNodeByStart(data) {
+  return request({
+    url: '/flowable/task/nextFlowNodeByStart',
+    method: 'post',
+    data: data
+  })
+}
+
+// 部署流程实例
+export function deployStart(deployId) {
+  return request({
+    url: '/flowable/process/startFlow/' + deployId,
+    method: 'get',
+  })
+}
+
+// 查询流程定义详细
+export function getDeployment(id) {
+  return request({
+    url: '/system/deployment/' + id,
+    method: 'get'
+  })
+}
+
+// 新增流程定义
+export function addDeployment(data) {
+  return request({
+    url: '/system/deployment',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改流程定义
+export function updateDeployment(data) {
+  return request({
+    url: '/system/deployment',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除流程定义
+export function delDeployment(id) {
+  return request({
+    url: '/system/deployment/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出流程定义
+export function exportDeployment(query) {
+  return request({
+    url: '/system/deployment/export',
+    method: 'get',
+    params: query
+  })
+}
+// 流程节点表单
+export function flowTaskForm(query) {
+  return request({
+    url: '/flowable/task/flowTaskForm',
+    method: 'get',
+    params: query
+  })
+}
+// 调用签收API
+export function claimTask(taskId) {
+  return request({
+    url: '/flowable/task/claim',
+    method: 'post',
+    data: {
+      taskId: taskId
+    }
+  });
+}

+ 9 - 0
src/components/Process/.bpmnlintrc

@@ -0,0 +1,9 @@
+{
+  "extends": [
+    "bpmnlint:recommended",
+    "plugin:local/recommended"
+  ],
+  "rules": {
+    "local/no-manual-task": "warn"
+  }
+}

+ 68 - 0
src/components/Process/BpmData.js

@@ -0,0 +1,68 @@
+/**
+ * 存储流程设计相关参数
+ */
+export default class BpmData {
+  constructor() {
+    this.controls = [] // 设计器控件
+    this.init()
+  }
+
+  init() {
+    this.controls = [
+      {
+        action: 'create.start-event',
+        title: '开始'
+      },
+      {
+        action: 'create.intermediate-event',
+        title: '中间'
+      },
+      {
+        action: 'create.end-event',
+        title: '结束'
+      },
+      {
+        action: 'create.exclusive-gateway',
+        title: '网关'
+      },
+      {
+        action: 'create.task',
+        title: '任务'
+      },
+      {
+        action: 'create.user-task',
+        title: '用户任务'
+      },
+      {
+        action: 'create.user-sign-task',
+        title: '会签任务'
+      },
+      {
+        action: 'create.subprocess-expanded',
+        title: '子流程'
+      },
+      {
+        action: 'create.data-object',
+        title: '数据对象'
+      },
+      {
+        action: 'create.data-store',
+        title: '数据存储'
+      },
+      {
+        action: 'create.participant-expanded',
+        title: '扩展流程'
+      },
+      {
+        action: 'create.group',
+        title: '分组'
+      }
+    ]
+  }
+
+  //  获取控件配置信息
+  getControl(action) {
+    const result = this.controls.filter(item => item.action === action)
+    return result[0] || {}
+  }
+}

+ 166 - 0
src/components/Process/PropertyPanel.vue

@@ -0,0 +1,166 @@
+<template>
+  <div ref="propertyPanel" class="property-panel">
+    <div v-if="nodeName" class="node-name">{{ nodeName }}</div>
+    <component
+      :is="getComponent"
+      v-if="element"
+      :element="element"
+      :modeler="modeler"
+      :users="users"
+      :groups="groups"
+      :exps="exps"
+      :categorys="categorys"
+    />
+  </div>
+</template>
+
+<script>
+import taskPanel from './components/nodePanel/task'
+import startEndPanel from './components/nodePanel/startEnd'
+import processPanel from './components/nodePanel/process'
+import sequenceFlowPanel from './components/nodePanel/sequenceFlow'
+import gatewayPanel from './components/nodePanel/gateway'
+import { NodeName } from './lang/zh'
+
+export default {
+  name: 'PropertyPanel',
+  components: { processPanel, taskPanel, startEndPanel, sequenceFlowPanel, gatewayPanel },
+  props: {
+    users: {
+      type: Array,
+      required: true
+    },
+    groups: {
+      type: Array,
+      required: true
+    },
+    categorys: {
+      type: Array,
+      required: true
+    },
+    exps: {
+      type: Array,
+      default: () => []
+    },
+    modeler: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      element: null,
+      form: {
+        id: '',
+        name: '',
+        color: null
+      },
+    }
+  },
+  computed: {
+    getComponent() {
+      const type = this.element?.type
+      if (['bpmn:IntermediateThrowEvent', 'bpmn:StartEvent', 'bpmn:EndEvent'].includes(type)) {
+        return 'startEndPanel'
+      }
+      if ([
+        'bpmn:UserTask',
+        'bpmn:Task',
+        'bpmn:SendTask',
+        'bpmn:ReceiveTask',
+        'bpmn:ManualTask',
+        'bpmn:BusinessRuleTask',
+        'bpmn:ServiceTask',
+        'bpmn:ScriptTask'
+        // 'bpmn:CallActivity',
+        // 'bpmn:SubProcess'
+      ].includes(type)) {
+        return 'taskPanel'
+      }
+      if (type === 'bpmn:SequenceFlow') {
+        return 'sequenceFlowPanel'
+      }
+      if ([
+        'bpmn:InclusiveGateway',
+        'bpmn:ExclusiveGateway',
+        'bpmn:ParallelGateway',
+        'bpmn:EventBasedGateway'
+      ].includes(type)) {
+        return 'gatewayPanel'
+      }
+      if (type === 'bpmn:Process') {
+        return 'processPanel'
+      }
+      return null
+    },
+    nodeName() {
+      if (this.element) {
+        const bizObj = this.element.businessObject
+        const type = bizObj?.eventDefinitions
+          ? bizObj.eventDefinitions[0].$type
+          : bizObj.$type
+        return NodeName[type] || type
+      }
+      return ''
+    }
+  },
+  mounted() {
+    this.handleModeler()
+  },
+  methods: {
+    handleModeler() {
+      this.modeler.on('root.added', e => {
+        if (e.element.type === 'bpmn:Process') {
+          this.element = null
+          this.$nextTick().then(() => {
+            this.element = e.element
+          })
+        }
+      })
+      this.modeler.on('element.click', e => {
+        const { element } = e
+        if (element.type === 'bpmn:Process'
+          || element.type === 'bpmn:SequenceFlow'
+          || element.type === 'bpmn:EndEvent' ) {
+          this.element = element
+        }
+      })
+      this.modeler.on('selection.changed', e => {
+        // hack 同类型面板不刷新
+        this.element = null
+        const element = e.newSelection[0]
+        if (element) {
+          this.$nextTick().then(() => {
+            this.element = element
+          })
+        }
+      })
+    },
+  }
+}
+</script>
+
+<style lang="scss">
+.property-panel {
+  padding: 20px 20px;
+  // reset element css
+  .el-form--label-top .el-form-item__label {
+    padding: 0;
+  }
+  // 设置设计器右侧表单高度
+  .el-form-item {
+    margin-bottom: 6px;
+  }
+  .tab-table .el-form-item {
+    margin-bottom: 16px;
+  }
+  .node-name{
+    border-bottom: 1px solid #ccc;
+    padding: 0 0 10px 20px;
+    margin-bottom: 10px;
+    font-size: 16px;
+    font-weight: bold;
+    color: #444;
+  }
+}
+</style>

+ 120 - 0
src/components/Process/common/bpmnUtils.js

@@ -0,0 +1,120 @@
+import { NodeName } from '../lang/zh'
+
+// 创建监听器实例
+export function createListenerObject(moddle, options, isTask, prefix) {
+  const listenerObj = Object.create(null);
+  listenerObj.event = options.event;
+  isTask && (listenerObj.id = options.id); // 任务监听器特有的 id 字段
+  switch (options.listenerType) {
+    case "scriptListener":
+      listenerObj.script = createScriptObject(moddle, options, prefix);
+      break;
+    case "expressionListener":
+      listenerObj.expression = options.expression;
+      break;
+    case "delegateExpressionListener":
+      listenerObj.delegateExpression = options.delegateExpression;
+      break;
+    default:
+      listenerObj.class = options.class;
+  }
+  // 注入字段
+  if (options.fields) {
+    listenerObj.fields = options.fields.map(field => {
+      return createFieldObject(moddle, field, prefix);
+    });
+  }
+  // 任务监听器的 定时器 设置
+  if (isTask && options.event === "timeout" && !!options.eventDefinitionType) {
+    const timeDefinition = moddle.create("bpmn:FormalExpression", {
+      body: options.eventTimeDefinitions
+    });
+    const TimerEventDefinition = moddle.create("bpmn:TimerEventDefinition", {
+      id: `TimerEventDefinition_${uuid(8)}`,
+      [`time${options.eventDefinitionType.replace(/^\S/, s => s.toUpperCase())}`]: timeDefinition
+    });
+    listenerObj.eventDefinitions = [TimerEventDefinition];
+  }
+  return moddle.create(`${prefix}:${isTask ? "TaskListener" : "ExecutionListener"}`, listenerObj);
+}
+
+// 处理内置流程监听器
+export function createSystemListenerObject(moddle, options, isTask, prefix) {
+  const listenerObj = Object.create(null);
+  listenerObj.event = options.eventType;
+  listenerObj.listenerType = options.valueType;
+  switch (options.valueType) {
+    case "scriptListener":
+      listenerObj.script = createScriptObject(moddle, options, prefix);
+      break;
+    case "expressionListener":
+      listenerObj.expression = options.expression;
+      break;
+    case "delegateExpressionListener":
+      listenerObj.delegateExpression = options.delegateExpression;
+      break;
+    default:
+      listenerObj.class = options.value;
+  }
+  return moddle.create(`${prefix}:${isTask ? "TaskListener" : "ExecutionListener"}`, listenerObj);
+}
+
+// 转换成字段
+export function changeListenerObject(options) {
+  const listenerObj = Object.create(null);
+  listenerObj.event = options.eventType;
+  listenerObj.listenerType = options.valueType;
+  switch (options.valueType) {
+    case "scriptListener":
+      // listenerObj.script = createScriptObject(moddle, options, prefix);
+      break;
+    case "expressionListener":
+      listenerObj.expression = options.expression;
+      break;
+    case "delegateExpressionListener":
+      listenerObj.delegateExpression = options.delegateExpression;
+      break;
+    default:
+      listenerObj.class = options.value;
+  }
+  return listenerObj;
+}
+
+// 创建 监听器的注入字段 实例
+export function createFieldObject(moddle, option, prefix) {
+  const { name, fieldType, string, expression } = option;
+  const fieldConfig = fieldType === "string" ? { name, string } : { name, expression };
+  return moddle.create(`${prefix}:Field`, fieldConfig);
+}
+
+// 创建脚本实例
+export function createScriptObject(moddle, options, prefix) {
+  const { scriptType, scriptFormat, value, resource } = options;
+  const scriptConfig = scriptType === "inlineScript" ? { scriptFormat, value } : { scriptFormat, resource };
+  return moddle.create(`${prefix}:Script`, scriptConfig);
+}
+
+// 更新元素扩展属性
+export function updateElementExtensions(moddle, modeling, element, extensionList) {
+  const extensions = moddle.create("bpmn:ExtensionElements", {
+    values: extensionList
+  });
+  modeling.updateProperties(element, {
+    extensionElements: extensions
+  });
+}
+
+// 创建一个id
+export function uuid(length = 8, chars) {
+  let result = "";
+  let charsString = chars || "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+  for (let i = length; i > 0; --i) {
+    result += charsString[Math.floor(Math.random() * charsString.length)];
+  }
+  return result;
+}
+
+// 转换流程节点名称
+export function translateNodeName(node){
+  return NodeName[node];
+}

+ 32 - 0
src/components/Process/common/customTranslate.js

@@ -0,0 +1,32 @@
+import translations from '../lang/zh'
+
+// export default function customTranslate(template, replacements) {
+//   replacements = replacements || {}
+//
+//   // Translate
+//   template = translations[template] || template
+//
+//   // Replace
+//   return template.replace(/{([^}]+)}/g, function(_, key) {
+//     var str = replacements[key]
+//     if (
+//       translations[replacements[key]] !== null &&
+//       translations[replacements[key]] !== 'undefined'
+//     ) {
+//       str = translations[replacements[key]]
+//     }
+//     return str || '{' + key + '}'
+//   })
+// }
+
+export default function customTranslate(template, replacements) {
+  replacements = replacements || {};
+
+  // Translate
+  template = translations[template] || template;
+
+  // Replace
+  return template.replace(/{([^}]+)}/g, function(_, key) {
+    return replacements[key] || '{' + key + '}';
+  });
+}

+ 24 - 0
src/components/Process/common/mixinExecutionListener.js

@@ -0,0 +1,24 @@
+
+import executionListenerDialog from '../components/nodePanel/property/executionListener'
+export default {
+  components: {
+    executionListenerDialog
+  },
+  data() {
+    return {
+      executionListenerLength: 0,
+      dialogName: null
+    }
+  },
+  methods: {
+    computedExecutionListenerLength() {
+      this.executionListenerLength = this.element.businessObject.extensionElements?.values?.length ?? 0
+    },
+    finishExecutionListener() {
+      if (this.dialogName === 'executionListenerDialog') {
+        this.computedExecutionListenerLength()
+      }
+      this.dialogName = ''
+    }
+  }
+}

+ 70 - 0
src/components/Process/common/mixinPanel.js

@@ -0,0 +1,70 @@
+import xcrud from 'xcrud'
+import golbalConfig from 'xcrud/package/common/config'
+import showConfig from '../flowable/showConfig'
+golbalConfig.set({
+  input: {
+    // size: 'mini'
+  },
+  select: {
+    // size: 'mini'
+  },
+  colorPicker: {
+    showAlpha: true
+  },
+  xform: {
+    form: {
+      labelWidth: 'auto'
+      // size: 'mini'
+    }
+  }
+})
+export default {
+  components: { xForm: xcrud.xForm },
+  props: {
+    modeler: {
+      type: Object,
+      required: true
+    },
+    element: {
+      type: Object,
+      required: true
+    },
+    categorys: {
+      type: Array,
+      default: () => []
+    }
+  },
+  watch: {
+    'formData.id': function(val) {
+      this.updateProperties({ id: val })
+    },
+    'formData.name': function(val) {
+      this.updateProperties({ name: val })
+    },
+    'formData.documentation': function(val) {
+      if (!val) {
+        this.updateProperties({ documentation: [] })
+        return
+      }
+      const documentationElement = this.modeler.get('moddle').create('bpmn:Documentation', { text: val })
+      this.updateProperties({ documentation: [documentationElement] })
+    }
+  },
+  methods: {
+    updateProperties(properties) {
+      const modeling = this.modeler.get('modeling')
+      modeling.updateProperties(this.element, properties)
+    }
+  },
+  computed: {
+    elementType() {
+      const bizObj = this.element.businessObject
+      return bizObj.eventDefinitions
+        ? bizObj.eventDefinitions[0].$type
+        : bizObj.$type
+    },
+    showConfig() {
+      return showConfig[this.elementType] || {}
+    }
+  }
+}

+ 22 - 0
src/components/Process/common/mixinXcrud.js

@@ -0,0 +1,22 @@
+import xcrud from 'xcrud'
+import golbalConfig from 'xcrud/package/common/config'
+golbalConfig.set({
+  input: {
+    // size: 'mini'
+  },
+  select: {
+    // size: 'mini'
+  },
+  colorPicker: {
+    showAlpha: true
+  },
+  xform: {
+    form: {
+      labelWidth: 'auto'
+      // size: 'mini'
+    }
+  }
+})
+export default {
+  components: { xForm: xcrud.xForm }
+}

+ 55 - 0
src/components/Process/common/parseElement.js

@@ -0,0 +1,55 @@
+export function commonParse(element) {
+  const result = {
+    ...element.businessObject,
+    ...element.businessObject.$attrs
+  }
+  return formatJsonKeyValue(result)
+}
+
+export function formatJsonKeyValue(result) {
+  // 移除flowable前缀,格式化数组
+  for (const key in result) {
+    if (key.indexOf('flowable:') === 0) {
+      const newKey = key.replace('flowable:', '')
+      result[newKey] = result[key]
+      delete result[key]
+    }
+  }
+  result = documentationParse(result)
+  return result
+}
+
+export function documentationParse(obj) {
+  if ('documentation' in obj) {
+    let str = ''
+    obj.documentation.forEach(item => {
+      str += item.text
+    })
+    obj.documentation = str
+  }
+  return obj
+}
+
+export function conditionExpressionParse(obj) {
+  if ('conditionExpression' in obj) {
+    if (obj.conditionExpression) {
+      obj.conditionExpression = obj.conditionExpression.body
+    }
+  }
+  return obj
+}
+
+export function userTaskParse(obj) {
+  for (const key in obj) {
+    if (key === 'candidateUsers') {
+      obj.userType = 'candidateUsers'
+      obj[key] = obj[key]?.split(',') || []
+    } else if (key === 'candidateGroups') {
+      obj.userType = 'candidateGroups'
+      obj[key] = obj[key]?.split(',') || []
+    } else if (key === 'assignee') {
+      obj.userType = 'assignee'
+    }
+  }
+  return obj
+}

+ 24 - 0
src/components/Process/components/custom/customContextPad.vue

@@ -0,0 +1,24 @@
+export default class CustomContextPad {
+    constructor(config, contextPad, create, elementFactory, injector, translate) {
+        this.create = create;
+        this.elementFactory = elementFactory;
+        this.translate = translate;
+
+        if (config.autoPlace !== false) {
+            this.autoPlace = injector.get('autoPlace', false);
+        }
+
+        contextPad.registerProvider(this); // 定义这是一个contextPad
+    }
+
+    getContextPadEntries(element) {}
+}
+
+CustomContextPad.$inject = [
+    'config',
+    'contextPad',
+    'create',
+    'elementFactory',
+    'injector',
+    'translate'
+];

+ 81 - 0
src/components/Process/components/nodePanel/gateway.vue

@@ -0,0 +1,81 @@
+<template>
+  <div>
+    <x-form ref="xForm" v-model="formData" :config="formConfig">
+      <template #executionListener>
+        <el-badge :value="executionListenerLength">
+          <el-button size="small" @click="dialogName = 'executionListenerDialog'">修改</el-button>
+        </el-badge>
+      </template>
+    </x-form>
+    <executionListenerDialog
+      v-if="dialogName === 'executionListenerDialog'"
+      :element="element"
+      :modeler="modeler"
+      @close="finishExecutionListener"
+    />
+  </div>
+</template>
+
+<script>
+import mixinPanel from '../../common/mixinPanel'
+import mixinExecutionListener from '../../common/mixinExecutionListener'
+import { commonParse } from '../../common/parseElement'
+export default {
+  mixins: [mixinPanel, mixinExecutionListener],
+  data() {
+    return {
+      formData: {}
+    }
+  },
+  computed: {
+    formConfig() {
+      return {
+        inline: false,
+        item: [
+          {
+            xType: 'input',
+            name: 'id',
+            label: '节点 id',
+            rules: [{ required: true, message: 'Id 不能为空' }]
+          },
+          {
+            xType: 'input',
+            name: 'name',
+            label: '节点名称'
+          },
+          {
+            xType: 'input',
+            name: 'documentation',
+            label: '节点描述'
+          },
+          {
+            xType: 'slot',
+            name: 'executionListener',
+            label: '执行监听器'
+          },
+          {
+            xType: 'switch',
+            name: 'async',
+            label: '异步',
+            activeText: '是',
+            inactiveText: '否'
+          }
+        ]
+      }
+    }
+  },
+  watch: {
+    'formData.async': function(val) {
+      if (val === '') val = null
+      this.updateProperties({ 'flowable:async': val })
+    }
+  },
+  created() {
+    this.formData = commonParse(this.element)
+  }
+}
+</script>
+
+<style>
+
+</style>

+ 113 - 0
src/components/Process/components/nodePanel/process.vue

@@ -0,0 +1,113 @@
+<template>
+  <div>
+    <x-form ref="xForm" v-model="formData" :config="formConfig">
+      <template #executionListener>
+        <el-badge :value="executionListenerLength">
+          <el-button size="small" @click="dialogName = 'executionListenerDialog'">修改</el-button>
+        </el-badge>
+      </template>
+      <template #signal>
+        <el-badge :value="signalLength">
+          <el-button size="small" @click="dialogName = 'signalDialog'">修改</el-button>
+        </el-badge>
+      </template>
+    </x-form>
+    <executionListenerDialog
+      v-if="dialogName === 'executionListenerDialog'"
+      :element="element"
+      :modeler="modeler"
+      @close="finishExecutionListener"
+    />
+    <signalDialog
+      v-if="dialogName === 'signalDialog'"
+      :element="element"
+      :modeler="modeler"
+      @close="finishExecutionListener"
+    />
+  </div>
+</template>
+
+<script>
+import mixinPanel from '../../common/mixinPanel'
+import mixinExecutionListener from '../../common/mixinExecutionListener'
+import signalDialog from './property/signal'
+import { commonParse } from '../../common/parseElement'
+export default {
+  components: {
+    signalDialog
+  },
+  mixins: [mixinPanel, mixinExecutionListener],
+  data() {
+    return {
+      signalLength: 0,
+      formData: {}
+    }
+  },
+  computed: {
+    formConfig() {
+      const _this = this
+      return {
+        inline: false,
+        item: [
+          {
+            xType: 'select',
+            name: 'processCategory',
+            label: '流程分类',
+            dic: { data: _this.categorys, label: 'dictLabel', value: 'dictValue' }
+          },
+          {
+            xType: 'input',
+            name: 'id',
+            label: '流程标识key',
+            rules: [{ required: true, message: 'Id 不能为空' }]
+          },
+          {
+            xType: 'input',
+            name: 'name',
+            label: '流程名称'
+          },
+          {
+            xType: 'input',
+            name: 'documentation',
+            label: '节点描述'
+          },
+          {
+            xType: 'slot',
+            name: 'executionListener',
+            label: '执行监听器'
+          },
+          {
+            xType: 'slot',
+            name: 'signal',
+            label: '信号定义'
+          }
+        ]
+      }
+    }
+  },
+  watch: {
+    'formData.processCategory': function(val) {
+      if (val === '') val = null
+      this.updateProperties({ 'flowable:processCategory': val })
+    }
+  },
+  created() {
+    this.formData = commonParse(this.element)
+  },
+  methods: {
+    computedSignalLength() {
+      this.signalLength = this.element.businessObject.extensionElements?.values?.length ?? 0
+    },
+    finishSignal() {
+      if (this.dialogName === 'signalDialog') {
+        this.computedSignalLength()
+      }
+      this.dialogName = ''
+    }
+  }
+}
+</script>
+
+<style>
+
+</style>

+ 217 - 0
src/components/Process/components/nodePanel/property/executionListener.vue

@@ -0,0 +1,217 @@
+<template>
+  <div>
+    <el-dialog
+      title="执行监听器"
+      :visible.sync="dialogVisible"
+      width="900px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :show-close="false"
+      @closed="$emit('close')"
+    >
+      <x-form ref="xForm" v-model="formData" :config="formConfig">
+        <template #params="scope">
+          <el-badge :value="scope.row.params ? scope.row.params.length : 0" type="primary">
+            <el-button size="small" @click="configParam(scope.$index)">配置</el-button>
+          </el-badge>
+        </template>
+      </x-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" size="medium" @click="closeDialog">确 定</el-button>
+      </span>
+      <listener-list
+        :visible="listenerDialogVisible"
+        @close="() => this.listenerDialogVisible = false"
+        @submit="addListener"
+      />
+    </el-dialog>
+    <listenerParam v-if="showParamDialog" :value="formData.executionListener[nowIndex].params" @close="finishConfigParam" />
+  </div>
+</template>
+
+<script>
+import mixinPanel from '../../../common/mixinPanel'
+import listenerParam from './listenerParam'
+import FlowListener from '@/components/flow/Listener'
+import ListenerList from '@/components/Process/components/nodePanel/property/listenerList'
+export default {
+  components: { ListenerList, listenerParam, FlowListener },
+  mixins: [mixinPanel],
+  data() {
+    return {
+      dialogVisible: true,
+      listenerDialogVisible: false,
+      showParamDialog: false,
+      nowIndex: null,
+      formData: {
+        executionListener: []
+      }
+    }
+  },
+  computed: {
+    formConfig() {
+      //   const _this = this
+      return {
+        inline: false,
+        item: [
+          {
+            xType: 'tabs',
+            tabs: [
+              {
+                label: '执行监听器',
+                name: 'executionListener',
+                column: [
+                  {
+                    label: '事件',
+                    name: 'event',
+                    width: 180,
+                    rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
+                    xType: 'select',
+                    dic: [
+                      { label: 'start', value: 'start' },
+                      { label: 'end', value: 'end' },
+                      { label: 'take', value: 'take' }
+                    ]
+                  },
+                  {
+                    label: '类型',
+                    name: 'type',
+                    width: 180,
+                    rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
+                    xType: 'select',
+                    dic: [
+                      { label: '类', value: 'class' },
+                      { label: '表达式', value: 'expression' },
+                      { label: '委托表达式', value: 'delegateExpression' }
+                    ],
+                    tooltip: `类:示例 com.company.MyCustomListener,自定义类必须实现 org.flowable.engine.delegate.ExecutionListener 接口 <br />
+                              表达式:示例 \${myObject.callMethod(task, task.eventName)} <br />
+                              委托表达式:示例 \${myListenerSpringBean} ,该 springBean 需要实现 org.flowable.engine.delegate.ExecutionListener 接口
+                    `
+                  },
+                  {
+                    label: '值',
+                    name: 'className',
+                    xType: 'input',
+                    rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
+                  },
+                  {
+                    xType: 'slot',
+                    label: '参数',
+                    width: 120,
+                    slot: true,
+                    name: 'params'
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => this.addButton())
+    this.formData.executionListener = this.element.businessObject.extensionElements?.values
+      .filter(item => item.$type === 'flowable:ExecutionListener')
+      .map(item => {
+        let type
+        if ('class' in item) type = 'class'
+        if ('expression' in item) type = 'expression'
+        if ('delegateExpression' in item) type = 'delegateExpression'
+        return {
+          event: item.event,
+          type: type,
+          className: item[type],
+          params: item.fields?.map(field => {
+            let fieldType
+            if ('stringValue' in field) fieldType = 'stringValue'
+            if ('expression' in field) fieldType = 'expression'
+            return {
+              name: field.name,
+              type: fieldType,
+              value: field[fieldType]
+            }
+          }) ?? []
+        }
+      }) ?? []
+  },
+  methods: {
+    addButton() {
+      const button = document.createElement('button')
+      button.innerText = '内置监听器'
+      button.setAttribute('type', 'button')
+      button.setAttribute('class', 'el-button el-button--primary el-button--mini')
+      button.addEventListener('click', () => this.listenerDialogVisible = true)
+      const div = document.getElementById('pane-executionListener')
+      const table = div.getElementsByClassName('el-table')[0]
+      div.insertBefore(button, table)
+    },
+    configParam(index) {
+      this.nowIndex = index
+      const nowObj = this.formData.executionListener[index]
+      if (!nowObj.params) {
+        nowObj.params = []
+      }
+      this.showParamDialog = true
+    },
+    finishConfigParam(param) {
+      this.showParamDialog = false
+      // hack 数量不更新问题
+      const cache = this.formData.executionListener[this.nowIndex]
+      cache.params = param
+      this.$set(this.formData.executionListener[this.nowIndex], this.nowIndex, cache)
+      this.nowIndex = null
+    },
+    updateElement() {
+      if (this.formData.executionListener?.length) {
+        let extensionElements = this.element.businessObject.get('extensionElements')
+        if (!extensionElements) {
+          extensionElements = this.modeler.get('moddle').create('bpmn:ExtensionElements')
+        }
+        // 清除旧值
+        extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:ExecutionListener') ?? []
+        this.formData.executionListener.forEach(item => {
+          const executionListener = this.modeler.get('moddle').create('flowable:ExecutionListener')
+          executionListener['event'] = item.event
+          executionListener[item.type] = item.className
+          if (item.params && item.params.length) {
+            item.params.forEach(field => {
+              const fieldElement = this.modeler.get('moddle').create('flowable:Field')
+              fieldElement['name'] = field.name
+              fieldElement[field.type] = field.value
+              // 注意:flowable.json 中定义的string和expression类为小写,不然会和原生的String类冲突,此处为hack
+              // const valueElement = this.modeler.get('moddle').create(`flowable:${field.type}`, { body: field.value })
+              // fieldElement[field.type] = valueElement
+              executionListener.get('fields').push(fieldElement)
+            })
+          }
+          extensionElements.get('values').push(executionListener)
+        })
+        this.updateProperties({ extensionElements: extensionElements })
+      } else {
+        const extensionElements = this.element.businessObject[`extensionElements`]
+        if (extensionElements) {
+          extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:ExecutionListener') ?? []
+        }
+      }
+    },
+    closeDialog() {
+      console.log(this.formData)
+      this.$refs.xForm.validate().then(() => {
+        this.updateElement()
+        this.dialogVisible = false
+      }).catch(e => console.error(e))
+    },
+    addListener(data) {
+      this.formData.executionListener = this.formData.executionListener.concat(data)
+    }
+  }
+}
+</script>
+
+<style>
+.flow-containers  .el-badge__content.is-fixed {
+  top: 18px;
+}
+</style>

+ 72 - 0
src/components/Process/components/nodePanel/property/listenerList.vue

@@ -0,0 +1,72 @@
+<template>
+  <el-dialog title="内置监听器"
+             width="900px"
+             :visible.sync="dialogVisible"
+             append-to-body
+             :close-on-click-modal="false"
+             :close-on-press-escape="false"
+             :show-close="false"
+             :before-close="close"
+  >
+    <flow-listener @handleSelect="handleSelect"/>
+    <span slot="footer" class="dialog-footer">
+      <el-button @click="close">取 消</el-button>
+      <el-button type="primary" @click="checkComplete">确 定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+  import FlowListener from '@/components/flow/Listener'
+
+  export default {
+    name: 'ListentList',
+    components: { FlowListener },
+    props: {
+      visible: {
+        type: Boolean,
+        default: false
+      }
+    },
+    data() {
+      return {
+        dialogVisible: this.visible,
+        listenerList: []
+      }
+    },
+    watch: {
+      visible: {
+        handler(newVal) {
+          this.dialogVisible = newVal
+        },
+        immediate: true,
+        deep: true
+      }
+    },
+    methods: {
+      close() {
+        this.dialogVisible = false
+        this.$emit('close')
+      },
+      checkComplete() {
+        this.close()
+        this.$emit('submit', this.listenerList)
+      },
+      handleSelect(selection) {
+        const type = ['class', 'expression', 'delegateExpression']
+        let list = []
+        selection.forEach(data => {
+          const formData = {
+            event: data.eventType,
+            type: type[parseInt(data.valueType) - 1],
+            className: data.value
+          }
+          list.push(formData)
+        })
+        this.listenerList = list
+      }
+    }
+  }
+</script>
+
+<style></style>

+ 96 - 0
src/components/Process/components/nodePanel/property/listenerParam.vue

@@ -0,0 +1,96 @@
+<template>
+  <div>
+    <el-dialog
+      title="监听器参数"
+      :visible.sync="dialogVisible"
+      width="700px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :show-close="false"
+      @closed="$emit('close', formData.paramList)"
+    >
+      <x-form ref="xForm" v-model="formData" :config="formConfig" />
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" size="medium" @click="closeDialog">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import mixinXcrud from '../../../common/mixinXcrud'
+export default {
+  mixins: [mixinXcrud],
+  props: {
+    value: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      dialogVisible: true,
+      formData: {
+        paramList: this.value
+      }
+    }
+  },
+  computed: {
+    formConfig() {
+      return {
+        inline: false,
+        item: [
+          {
+            xType: 'tabs',
+            tabs: [
+              {
+                label: '监听器参数',
+                name: 'paramList',
+                column: [
+                  {
+                    label: '类型',
+                    name: 'type',
+                    width: 180,
+                    rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
+                    xType: 'select',
+                    dic: [
+                      { label: '字符串', value: 'stringValue' },
+                      { label: '表达式', value: 'expression' }
+                    ]
+                  },
+                  {
+                    label: '名称',
+                    name: 'name',
+                    width: 180,
+                    rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
+                    xType: 'input'
+                  },
+                  {
+                    label: '值',
+                    name: 'value',
+                    xType: 'input',
+                    rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    }
+  },
+  methods: {
+    closeDialog() {
+      this.$refs.xForm.validate().then(() => {
+        this.dialogVisible = false
+      }).catch(e => console.error(e))
+    }
+  }
+}
+</script>
+
+<style>
+.flow-containers  .el-badge__content.is-fixed {
+  top: 18px;
+}
+</style>

+ 133 - 0
src/components/Process/components/nodePanel/property/multiInstance.vue

@@ -0,0 +1,133 @@
+<template>
+  <div>
+    <el-dialog
+      title="多实例配置"
+      :visible.sync="dialogVisible"
+      width="600px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      class="muti-instance"
+      @closed="$emit('close')"
+    >
+      <el-descriptions :column="1" size="mini" border>
+        <el-descriptions-item label="使用说明">按照BPMN2.0规范的要求,用于为每个实例创建执行的父执行,会提供下列变量:</el-descriptions-item>
+        <el-descriptions-item label="nrOfInstances">实例总数。</el-descriptions-item>
+        <el-descriptions-item label="nrOfActiveInstances">当前活动的(即未完成的),实例数量。对于顺序多实例,这个值总为1。</el-descriptions-item>
+        <el-descriptions-item label="nrOfCompletedInstances">已完成的实例数量。</el-descriptions-item>
+        <el-descriptions-item label="loopCounter">给定实例在for-each循环中的index。</el-descriptions-item>
+      </el-descriptions>
+      <div class="app-container">
+       <x-form ref="xForm" v-model="formData" :config="formConfig" />
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import mixinPanel from '@/components/Process/common/mixinPanel'
+import {formatJsonKeyValue} from '@/components/Process/common/parseElement'
+
+export default {
+  mixins: [mixinPanel],
+  data() {
+    return {
+      dialogVisible: true,
+      formData: {},
+      prefix: 'flowable:',
+    }
+  },
+  computed: {
+    formConfig() {
+      const _this = this
+      return {
+        inline: false,
+        item: [
+          {
+            xType: 'input',
+            name: 'collection',
+            label: '集合',
+            tooltip: 'collection: 属性会作为表达式进行解析。如果表达式解析为字符串而不是一个集合,<br />不论是因为本身配置的就是静态字符串值,还是表达式计算结果为字符串,<br />这个字符串都会被当做变量名,并从流程变量中用于获取实际的集合。',
+            // rules: [{ required: true, message: '请输入集合名称', trigger: ['blur', 'change'] }]
+          },
+          {
+            xType: 'input',
+            name: 'elementVariable',
+            label: '元素变量',
+            tooltip: 'elementVariable: 每创建一个用户任务前,先以该元素变量为label,集合中的一项为value,<br />创建(局部)流程变量,该局部流程变量被用于指派用户任务。<br />一般来说,该字符串应与指定人员变量相同。',
+            // rules: [{ required: true, message: '请输入元素变量', trigger: ['blur', 'change'] }]
+          },
+          {
+            xType: 'select',
+            name: 'isSequential',
+            label: '执行方式',
+            tooltip: '并行会签(parallel)、串行会签(sequential),其中并行会签的意思是 多个人同时执行任务。串行会签是按顺序执行任务。',
+            dic: [{label: '串行', value: true}, {label: '并行', value: false}],
+            // rules: [{ required: true, message: '请选择执行方式', trigger: ['blur', 'change'] }]
+          },
+          {
+            xType: 'input',
+            name: 'completionCondition',
+            label: '完成条件',
+            tooltip: 'completionCondition: 多实例活动在所有实例都完成时结束,然而也可以指定一个表达式,在每个实例<br />结束时进行计算。当表达式计算为true时,将销毁所有剩余的实例,并结束多实例<br />活动,继续执行流程。例如 ${nrOfCompletedInstances/nrOfInstances >= 0.6 },<br />表示当任务完成60%时,该节点就算完成',
+            // rules: [{ required: true, message: '请输入完成条件', trigger: ['blur', 'change'] }]
+          }
+        ],
+        operate: [
+          { text: '确定', show: true, click: _this.save },
+          { text: '清空', show: true, click: () => { _this.formData = {} } }
+        ]
+      }
+    }
+  },
+  mounted() {
+    const cache = JSON.parse(JSON.stringify(this.element.businessObject.loopCharacteristics ?? {}))
+    cache.completionCondition = cache.completionCondition?.body
+    // 拼接多实例对象
+    if (this.element.businessObject.loopCharacteristics) {
+      Object.assign(cache, this.element.businessObject.loopCharacteristics.$attrs)
+    }
+    this.formData = formatJsonKeyValue(cache)
+  },
+  methods: {
+    updateElement() {
+      if (this.formData.isSequential !== null && this.formData.isSequential !== undefined) {
+        // const model = this.modeler.get('moddle');
+        let loopCharacteristics = this.element.businessObject.get('loopCharacteristics')
+        if (!loopCharacteristics) {
+          loopCharacteristics = this.modeler.get('moddle').create('bpmn:MultiInstanceLoopCharacteristics')
+        }
+        loopCharacteristics['isSequential'] = this.formData.isSequential
+        loopCharacteristics['collection'] = this.formData.collection
+        loopCharacteristics['elementVariable'] = this.formData.elementVariable
+        // let loopCardinality = model.create("bpmn:Expression",{
+        //   body: "4"
+        // });
+        // loopCharacteristics['loopCardinality'] = loopCardinality
+
+        loopCharacteristics.$attrs['isSequential'] = this.formData.isSequential
+        loopCharacteristics.$attrs[this.prefix + 'collection'] = this.formData.collection
+        loopCharacteristics.$attrs[this.prefix + 'elementVariable'] = this.formData.elementVariable
+
+        if (this.formData.completionCondition) {
+          loopCharacteristics['completionCondition'] = this.modeler.get('moddle').create('bpmn:Expression', {body: this.formData.completionCondition})
+        }
+        this.updateProperties({loopCharacteristics: loopCharacteristics})
+      } else {
+        delete this.element.businessObject.loopCharacteristics
+      }
+    },
+    save() {
+      this.$refs['xForm'].validate().then(() => {
+        this.updateElement()
+        this.dialogVisible = false
+      }).catch(e => console.error(e));
+    }
+  }
+}
+</script>
+
+<style>
+.muti-instance .el-form-item {
+  margin-bottom: 22px;
+}
+</style>

+ 147 - 0
src/components/Process/components/nodePanel/property/signal.vue

@@ -0,0 +1,147 @@
+<template>
+  <div>
+    <el-dialog
+      title="信号定义"
+      :visible.sync="dialogVisible"
+      width="700px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :show-close="false"
+      @closed="$emit('close')"
+    >
+      <x-form ref="xForm" v-model="formData" :config="formConfig" />
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" size="medium" @click="closeDialog">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import mixinPanel from '../../../common/mixinPanel'
+export default {
+  mixins: [mixinPanel],
+  data() {
+    return {
+      dialogVisible: true,
+      formData: {
+        signal: []
+      }
+    }
+  },
+  computed: {
+    formConfig() {
+    //   const _this = this
+      return {
+        inline: false,
+        item: [
+          {
+            xType: 'tabs',
+            tabs: [
+              {
+                label: '信号定义',
+                name: 'signal',
+                column: [
+                  {
+                    label: 'scope',
+                    name: 'scope',
+                    width: 180,
+                    rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
+                    xType: 'select',
+                    dic: [
+                      { label: '全局', value: 'start' },
+                      { label: '流程实例', value: 'end' }
+                    ]
+                  },
+                  {
+                    label: 'id',
+                    name: 'id',
+                    width: 200,
+                    rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }],
+                    xType: 'input'
+                  },
+                  {
+                    label: '名称',
+                    name: 'name',
+                    xType: 'input',
+                    rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    }
+  },
+  mounted() {
+    // this.formData.signal = this.element.businessObject.extensionElements?.values.map(item => {
+    this.formData.signal = this.element.businessObject.extensionElements?.values
+      .filter(item => item.$type === 'bpmn:Signal')
+      .map(item => {
+        return {
+          scope: item.scope,
+          id: item.id,
+          name: item.name
+        }
+      }) ?? []
+  },
+  methods: {
+    updateElement() {
+      // if (this.formData.signal?.length) {
+      //   let extensionElements = this.element.businessObject.get('extensionElements')
+      //   if (!extensionElements) {
+      //     console.log(this.modeler.get('moddle'),"this.modeler.get('moddle')")
+      //     extensionElements = this.modeler.get('moddle').create('bpmn:Signal')
+      //   }
+      //   extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'bpmn:Signal') ?? []
+      //   console.log(extensionElements,"extensionElements")
+      //   const length = extensionElements.get('values').length
+      //   for (let i = 0; i < length; i++) {
+      //     // 清除旧值
+      //     extensionElements.get('values').pop()
+      //   }
+      //   this.updateProperties({ extensionElements: extensionElements })
+      // } else {
+      //   const extensionElements = this.element.businessObject[`extensionElements`]
+      //   if (extensionElements) {
+      //     extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:ExecutionListener')
+      //   }
+      // }
+      if (this.formData.signal?.length) {
+        let extensionElements = this.element.businessObject.get('extensionElements')
+        if (!extensionElements) {
+          extensionElements = this.modeler.get('moddle').create('bpmn:ExtensionElements')
+        }
+        // 清除旧值
+        extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'bpmn:Signal') ?? []
+        this.formData.signal.forEach(item => {
+          const signal = this.modeler.get('moddle').create('bpmn:Signal')
+          signal['scope'] = item.scope
+          signal['id'] = item.id
+          signal['name'] = item.name
+          extensionElements.get('values').push(signal)
+        })
+        this.updateProperties({ extensionElements: extensionElements })
+      } else {
+        const extensionElements = this.element.businessObject[`extensionElements`]
+        if (extensionElements) {
+          extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'bpmn:Signal') ?? []
+        }
+      }
+    },
+    closeDialog() {
+      this.$refs.xForm.validate().then(() => {
+        this.updateElement()
+        this.dialogVisible = false
+      }).catch(e => console.error(e))
+    }
+  }
+}
+</script>
+
+<style>
+.flow-containers  .el-badge__content.is-fixed {
+    top: 18px;
+}
+</style>

+ 221 - 0
src/components/Process/components/nodePanel/property/taskListener.vue

@@ -0,0 +1,221 @@
+<template>
+  <div>
+    <el-dialog
+      title="任务监听器"
+      :visible.sync="dialogVisible"
+      width="900px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :show-close="false"
+      @closed="$emit('close')"
+    >
+      <x-form ref="xForm" v-model="formData" :config="formConfig">
+        <template #params="scope">
+          <el-badge :value="scope.row.params ? scope.row.params.length : 0" type="primary">
+            <el-button size="small" @click="configParam(scope.$index)">配置</el-button>
+          </el-badge>
+        </template>
+      </x-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" size="medium" @click="closeDialog">确 定</el-button>
+      </span>
+      <listener-list
+        :visible="listenerDialogVisible"
+        @close="() => this.listenerDialogVisible = false"
+        @submit="addListener"
+      />
+    </el-dialog>
+    <listenerParam v-if="showParamDialog" :value="formData.taskListener[nowIndex].params" @close="finishConfigParam" />
+  </div>
+</template>
+
+<script>
+import mixinPanel from '../../../common/mixinPanel'
+import listenerParam from './listenerParam'
+import ListenerList from '@/components/Process/components/nodePanel/property/listenerList'
+export default {
+  components: { listenerParam, ListenerList },
+  mixins: [mixinPanel],
+  data() {
+    return {
+      dialogVisible: true,
+      listenerDialogVisible: false,
+      showParamDialog: false,
+      nowIndex: null,
+      formData: {
+        taskListener: []
+      }
+    }
+  },
+  computed: {
+    formConfig() {
+      //   const _this = this
+      return {
+        inline: false,
+        item: [
+          {
+            xType: 'tabs',
+            tabs: [
+              {
+                label: '任务监听器',
+                name: 'taskListener',
+                column: [
+                  {
+                    label: '事件',
+                    name: 'event',
+                    width: 180,
+                    rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
+                    xType: 'select',
+                    dic: [
+                      { label: 'create', value: 'create' },
+                      { label: 'assignment', value: 'assignment' },
+                      { label: 'complete', value: 'complete' },
+                      { label: 'delete', value: 'delete' }
+                    ],
+                    tooltip: `create(创建):当任务已经创建,并且所有任务参数都已经设置时触发。<br />
+                              assignment(指派):当任务已经指派给某人时触发。请注意:当流程执行到达用户任务时,在触发create事件之前,会首先触发assignment事件。<br />
+                              complete(完成):当任务已经完成,从运行时数据中删除前触发。<br />
+                              delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发。
+                    `
+                  },
+                  {
+                    label: '类型',
+                    name: 'type',
+                    width: 180,
+                    rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
+                    xType: 'select',
+                    dic: [
+                      { label: '类', value: 'class' },
+                      { label: '表达式', value: 'expression' },
+                      { label: '委托表达式', value: 'delegateExpression' }
+                    ],
+                    tooltip: `类:示例 com.company.MyCustomListener,自定义类必须实现 org.flowable.engine.delegate.TaskListener 接口 <br />
+                              表达式:示例 \${myObject.callMethod(task, task.eventName)} <br />
+                              委托表达式:示例 \${myListenerSpringBean} ,该 springBean 需要实现 org.flowable.engine.delegate.TaskListener 接口
+                    `
+                  },
+                  {
+                    label: '值',
+                    name: 'className',
+                    xType: 'input',
+                    rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
+                  },
+                  // {
+                  //   xType: 'slot',
+                  //   label: '参数',
+                  //   width: 120,
+                  //   slot: true,
+                  //   name: 'params'
+                  // }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => this.addButton())
+    this.formData.taskListener = this.element.businessObject.extensionElements?.values
+      .filter(item => item.$type === 'flowable:TaskListener')
+      .map(item => {
+        let type
+        if ('class' in item) type = 'class'
+        if ('expression' in item) type = 'expression'
+        if ('delegateExpression' in item) type = 'delegateExpression'
+        return {
+          event: item.event,
+          type: type,
+          className: item[type],
+          params: item.fields?.map(field => {
+            let fieldType
+            if ('stringValue' in field) fieldType = 'stringValue'
+            if ('expression' in field) fieldType = 'expression'
+            return {
+              name: field.name,
+              type: fieldType,
+              value: field[fieldType]
+            }
+          }) ?? []
+        }
+      }) ?? []
+  },
+  methods: {
+    addButton() {
+      const button = document.createElement('button')
+      button.innerText = '内置监听器'
+      button.setAttribute('type', 'button')
+      button.setAttribute('class', 'el-button el-button--primary el-button--mini')
+      button.addEventListener('click', () => this.listenerDialogVisible = true)
+      const div = document.getElementById('pane-taskListener')
+      const table = div.getElementsByClassName('el-table')[0]
+      div.insertBefore(button, table)
+    },
+    configParam(index) {
+      this.nowIndex = index
+      const nowObj = this.formData.taskListener[index]
+      if (!nowObj.params) {
+        nowObj.params = []
+      }
+      this.showParamDialog = true
+    },
+    finishConfigParam(param) {
+      this.showParamDialog = false
+      // hack 数量不更新问题
+      const cache = this.formData.taskListener[this.nowIndex]
+      cache.params = param
+      this.$set(this.formData.taskListener[this.nowIndex], this.nowIndex, cache)
+      this.nowIndex = null
+    },
+    updateElement() {
+      if (this.formData.taskListener?.length) {
+        let extensionElements = this.element.businessObject.get('extensionElements')
+        if (!extensionElements) {
+          extensionElements = this.modeler.get('moddle').create('bpmn:ExtensionElements')
+        }
+        // 清除旧值
+        extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:TaskListener') ?? []
+        this.formData.taskListener.forEach(item => {
+          const taskListener = this.modeler.get('moddle').create('flowable:TaskListener')
+          taskListener['event'] = item.event
+          taskListener[item.type] = item.className
+          if (item.params && item.params.length) {
+            item.params.forEach(field => {
+              const fieldElement = this.modeler.get('moddle').create('flowable:Field')
+              fieldElement['name'] = field.name
+              fieldElement[field.type] = field.value
+              // 注意:flowable.json 中定义的string和expression类为小写,不然会和原生的String类冲突,此处为hack
+              // const valueElement = this.modeler.get('moddle').create(`flowable:${field.type}`, { body: field.value })
+              // fieldElement[field.type] = valueElement
+              taskListener.get('fields').push(fieldElement)
+            })
+          }
+          extensionElements.get('values').push(taskListener)
+        })
+        this.updateProperties({ extensionElements: extensionElements })
+      } else {
+        const extensionElements = this.element.businessObject[`extensionElements`]
+        if (extensionElements) {
+          extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:TaskListener') ?? []
+        }
+      }
+    },
+    closeDialog() {
+      this.$refs.xForm.validate().then(() => {
+        this.updateElement()
+        this.dialogVisible = false
+      }).catch(e => console.error(e))
+    },
+    addListener(data) {
+      this.formData.taskListener = this.formData.taskListener.concat(data)
+    }
+  }
+}
+</script>
+
+<style>
+.flow-containers  .el-badge__content.is-fixed {
+  top: 18px;
+}
+</style>

+ 105 - 0
src/components/Process/components/nodePanel/sequenceFlow.vue

@@ -0,0 +1,105 @@
+<template>
+  <div>
+    <x-form ref="xForm" v-model="formData" :config="formConfig">
+      <template #executionListener>
+        <el-badge :value="executionListenerLength">
+          <el-button size="small" @click="dialogName = 'executionListenerDialog'">修改</el-button>
+        </el-badge>
+      </template>
+    </x-form>
+    <executionListenerDialog
+      v-if="dialogName === 'executionListenerDialog'"
+      :element="element"
+      :modeler="modeler"
+      @close="finishExecutionListener"
+    />
+  </div>
+</template>
+
+<script>
+import mixinPanel from '../../common/mixinPanel'
+import {StrUtil} from '@/utils/StrUtil'
+import mixinExecutionListener from '../../common/mixinExecutionListener'
+import { commonParse, conditionExpressionParse } from '../../common/parseElement'
+export default {
+  mixins: [mixinPanel, mixinExecutionListener],
+  data() {
+    return {
+      formData: {},
+      executionListenerLength: 0
+    }
+  },
+  computed: {
+    formConfig() {
+      return {
+        inline: false,
+        item: [
+          {
+            xType: 'input',
+            name: 'id',
+            label: '节点 id',
+            rules: [{ required: true, message: 'Id 不能为空' }]
+          },
+          {
+            xType: 'input',
+            name: 'name',
+            label: '节点名称'
+          },
+          {
+            xType: 'input',
+            name: 'documentation',
+            label: '节点描述'
+          },
+          {
+            xType: 'slot',
+            name: 'executionListener',
+            label: '执行监听器'
+          },
+          {
+            xType: 'input',
+            name: 'conditionExpression',
+            label: '跳转条件'
+          },
+          {
+            xType: 'input',
+            name: 'skipExpression',
+            label: '跳过表达式'
+          }
+        ]
+      }
+    }
+  },
+  watch: {
+    'formData.conditionExpression': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        const newCondition = this.modeler.get('moddle').create('bpmn:FormalExpression', { body: val })
+        this.updateProperties({ conditionExpression: newCondition })
+      }
+      // else {
+      //   this.updateProperties({ conditionExpression: null })
+      // }
+    },
+    'formData.skipExpression': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:skipExpression': val})
+      } else {
+        delete this.element.businessObject.$attrs[`flowable:skipExpression`]
+      }
+    }
+  },
+  created() {
+    let cache = commonParse(this.element)
+    cache = conditionExpressionParse(cache)
+    this.formData = cache;
+    this.computedExecutionListenerLength();
+  },
+  methods:{
+    computedExecutionListenerLength() {
+      this.executionListenerLength = this.element.businessObject.extensionElements?.values
+        ?.filter(item => item.$type === 'flowable:ExecutionListener').length ?? 0
+    },
+  }
+}
+</script>
+
+<style></style>

+ 102 - 0
src/components/Process/components/nodePanel/startEnd.vue

@@ -0,0 +1,102 @@
+<template>
+  <div>
+    <x-form ref="xForm" v-model="formData" :config="formConfig">
+      <template #executionListener>
+        <el-badge :value="executionListenerLength">
+          <el-button size="small" @click="dialogName = 'executionListenerDialog'">修改</el-button>
+        </el-badge>
+      </template>
+    </x-form>
+    <executionListenerDialog
+      v-if="dialogName === 'executionListenerDialog'"
+      :element="element"
+      :modeler="modeler"
+      @close="finishExecutionListener"
+    />
+  </div>
+</template>
+
+<script>
+import mixinPanel from '../../common/mixinPanel'
+import mixinExecutionListener from '../../common/mixinExecutionListener'
+import { commonParse } from '../../common/parseElement'
+export default {
+  mixins: [mixinPanel, mixinExecutionListener],
+  data() {
+    return {
+      formData: {},
+      executionListenerLength: 0
+    }
+  },
+  computed: {
+    formConfig() {
+      const _this = this
+      return {
+        inline: false,
+        item: [
+          {
+            xType: 'input',
+            name: 'id',
+            label: '节点 id',
+            rules: [{ required: true, message: 'Id 不能为空' }]
+          },
+          {
+            xType: 'input',
+            name: 'name',
+            label: '节点名称'
+          },
+          {
+            xType: 'input',
+            name: 'documentation',
+            label: '节点描述'
+          },
+          {
+            xType: 'slot',
+            name: 'executionListener',
+            label: '执行监听器'
+          },
+          // {
+          //   xType: 'input',
+          //   name: 'initiator',
+          //   label: '发起人',
+          //   show: !!_this.showConfig.initiator
+          // },
+          // {
+          //   xType: 'input',
+          //   name: 'formKey',
+          //   label: '表单标识key',
+          //   show: !!_this.showConfig.formKey
+          // }
+        ]
+      }
+    }
+  },
+  watch: {
+    'formData.initiator': function(val) {
+      if (val === '') val = null
+      // 默认设置流程发起人
+      // if (val === '') val = 'INITIATOR'
+      this.updateProperties({ 'flowable:initiator': val })
+    },
+    'formData.formKey': function(val) {
+      if (val === '') val = null
+      this.updateProperties({ 'flowable:formKey': val })
+    }
+  },
+  created() {
+    // this.updateProperties({ 'flowable:initiator': 'INITIATOR' })
+    this.formData = commonParse(this.element)
+    this.computedExecutionListenerLength();
+  },
+  methods:{
+    computedExecutionListenerLength() {
+      this.executionListenerLength = this.element.businessObject.extensionElements?.values
+        ?.filter(item => item.$type === 'flowable:ExecutionListener').length ?? 0
+    },
+  }
+}
+</script>
+
+<style>
+
+</style>

+ 638 - 0
src/components/Process/components/nodePanel/task.vue

@@ -0,0 +1,638 @@
+<template>
+  <div>
+    <x-form ref="xForm" v-model="formData" :config="formConfig">
+      <template #executionListener>
+        <el-badge :value="executionListenerLength">
+          <el-button size="small" @click="dialogName = 'executionListenerDialog'">修改</el-button>
+        </el-badge>
+      </template>
+      <template #taskListener>
+        <el-badge :value="taskListenerLength">
+          <el-button size="small" @click="dialogName = 'taskListenerDialog'">修改</el-button>
+        </el-badge>
+      </template>
+      <template #multiInstance>
+        <el-badge :is-dot="hasMultiInstance">
+          <el-button size="small" @click="dialogName = 'multiInstanceDialog'">修改</el-button>
+        </el-badge>
+      </template>
+      <template #checkSingleUser>
+          <el-input placeholder="请选择人员" class="input-with-select" v-model="checkValues">
+              <template slot="append">
+                <!--指定用户-->
+                <el-button style="padding-left: 7px" icon="el-icon-user" @click="singleUserCheck"/>
+                <el-divider direction="vertical"></el-divider>
+                <!--选择表达式-->
+                <el-button style="padding-right: 7px" icon="el-icon-postcard" @click="singleExpCheck('assignee')"/>
+              </template>
+          </el-input>
+      </template>
+      <template #checkMultipleUser>
+          <el-input placeholder="请选择候选用户" class="input-with-select" v-model="checkValues">
+            <template slot="append">
+              <!--候选用户-->
+              <el-button style="padding-left: 7px" icon="el-icon-user" @click="multipleUserCheck"/>
+              <el-divider direction="vertical"></el-divider>
+              <!--选择表达式-->
+              <el-button style="padding-right: 7px" icon="el-icon-postcard" @click="singleExpCheck('candidateUsers')"/>
+            </template>
+          </el-input>
+      </template>
+      <template #checkRole>
+        <el-input placeholder="请选择候选角色" class="input-with-select" v-model="checkValues">
+          <template slot="append">
+          <!--候选角色-->
+            <el-button style="padding-left: 7px" icon="el-icon-user" @click="multipleRoleCheck"/>
+            <el-divider direction="vertical"></el-divider>
+              <!--选择表达式-->
+            <el-button style="padding-right: 7px" icon="el-icon-postcard" @click="singleExpCheck('candidateGroups')"/>
+          </template>
+        </el-input>
+      </template>
+    </x-form>
+    <executionListenerDialog
+      v-if="dialogName === 'executionListenerDialog'"
+      :element="element"
+      :modeler="modeler"
+      @close="finishExecutionListener"
+    />
+    <taskListenerDialog
+      v-if="dialogName === 'taskListenerDialog'"
+      :element="element"
+      :modeler="modeler"
+      @close="finishTaskListener"
+    />
+    <multiInstanceDialog
+      v-if="dialogName === 'multiInstanceDialog'"
+      :element="element"
+      :modeler="modeler"
+      @close="finishMultiInstance"
+    />
+    <!--选择人员-->
+    <el-dialog
+      title="选择人员"
+      :visible.sync="userVisible"
+      width="60%"
+      :close-on-press-escape="false"
+      :show-close="false"
+    >
+    <flow-user :checkType="checkType" :selectValues="selectValues" @handleUserSelect="handleUserSelect"></flow-user>
+    <span slot="footer" class="dialog-footer">
+      <el-button @click="userVisible = false">取 消</el-button>
+      <el-button type="primary" @click="checkUserComplete">确 定</el-button>
+    </span>
+    </el-dialog>
+    <!--选择表达式-->
+    <el-dialog
+      title="选择表达式"
+      :visible.sync="expVisible"
+      width="60%"
+      :close-on-press-escape="false"
+      :show-close="false"
+    >
+      <flow-exp :selectValues="selectValues" @handleSingleExpSelect="handleSingleExpSelect"></flow-exp>
+      <span slot="footer" class="dialog-footer">
+      <el-button @click="expVisible = false">取 消</el-button>
+      <el-button type="primary" @click="checkExpComplete">确 定</el-button>
+    </span>
+    </el-dialog>
+    <!--选择角色-->
+    <el-dialog
+      title="选择候选角色"
+      :visible.sync="roleVisible"
+      width="60%"
+      :close-on-press-escape="false"
+      :show-close="false"
+    >
+      <flow-role :checkType="checkType" :selectValues="selectValues" @handleRoleSelect="handleRoleSelect"></flow-role>
+      <span slot="footer" class="dialog-footer">
+      <el-button @click="roleVisible = false">取 消</el-button>
+      <el-button type="primary" @click="checkRoleComplete">确 定</el-button>
+    </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import mixinPanel from '../../common/mixinPanel'
+import executionListenerDialog from './property/executionListener'
+import taskListenerDialog from './property/taskListener'
+import multiInstanceDialog from './property/multiInstance'
+import { commonParse, userTaskParse } from '../../common/parseElement'
+import {StrUtil} from '@/utils/StrUtil'
+import FlowUser from '@/components/flow/User'
+import FlowRole from '@/components/flow/Role'
+import FlowExp from '@/components/flow/Expression'
+import { listAllForm } from '@/api/flowable/form'
+
+export default {
+  components: {
+    executionListenerDialog,
+    taskListenerDialog,
+    multiInstanceDialog,
+    FlowUser,
+    FlowRole,
+    FlowExp,
+  },
+  mixins: [mixinPanel],
+  props: {
+    users: {
+      type: Array,
+      required: true
+    },
+    groups: {
+      type: Array,
+      required: true
+    },
+    exps: {
+      type: Array,
+      required: true
+    }
+  },
+  data() {
+    return {
+      userTypeOption: [
+        { label: '指定人员', value: 'assignee' },
+        { label: '候选人员', value: 'candidateUsers' },
+        { label: '候选角色', value: 'candidateGroups' }
+      ],
+      dialogName: '',
+      executionListenerLength: 0,
+      taskListenerLength: 0,
+      hasMultiInstance: false,
+      userVisible: false,
+      roleVisible: false,
+      expVisible: false,
+      formData: {},
+      assignee: null,
+      candidateUsers: null,
+      candidateGroups: null,
+      // 选类型
+      checkType: 'single',
+      // 选中的值
+      checkValues: null,
+      // 数据回显
+      selectValues: null,
+      // 用户列表
+      userList: this.users,
+      // 角色列表
+      groupList: this.groups,
+      // 表达式列表
+      expList: this.exps,
+      // 表达式类型
+      expType: null,
+      // 表单列表
+      formList: [],
+    }
+  },
+  computed: {
+    formConfig() {
+      const _this = this
+      return {
+        inline: false,
+        item: [
+          {
+            xType: 'input',
+            name: 'id',
+            label: '节点 id',
+            rules: [{ required: true, message: 'Id 不能为空' }]
+          },
+          {
+            xType: 'input',
+            name: 'name',
+            label: '节点名称',
+            rules: [{ required: true, message: '节点名称不能为空' }]
+          },
+          {
+            xType: 'input',
+            name: 'documentation',
+            label: '节点描述'
+          },
+          {
+            xType: 'slot',
+            name: 'executionListener',
+            label: '执行监听器'
+          },
+          {
+            xType: 'slot',
+            name: 'taskListener',
+            label: '任务监听器',
+            show: !!_this.showConfig.taskListener
+          },
+          {
+            xType: 'select',
+            name: 'userType',
+            label: '用户类型',
+            // clearable: true,
+            dic: _this.userTypeOption,
+            // rules: [{ required: true, message: '用户类型不能为空' }],
+            show: !!_this.showConfig.userType
+          },
+          {
+            xType: 'slot',
+            name: 'checkSingleUser',
+            label: '指定人员',
+            // rules: [{ required: true, message: '指定人员不能为空' }],
+            show: !!_this.showConfig.assignee && _this.formData.userType === 'assignee'
+          },
+          {
+            xType: 'slot',
+            name: 'checkMultipleUser',
+            label: '候选人员',
+            // rules: [{ required: true, message: '候选人员不能为空' }],
+            show: !!_this.showConfig.candidateUsers && _this.formData.userType === 'candidateUsers'
+          },
+          {
+            xType: 'slot',
+            name: 'checkRole',
+            label: '候选角色',
+            // rules: [{ required: true, message: '候选角色不能为空' }],
+            show: !!_this.showConfig.candidateGroups && _this.formData.userType === 'candidateGroups'
+          },
+          {
+            xType: 'slot',
+            name: 'multiInstance',
+            label: '多实例'
+          },
+          {
+            xType: 'switch',
+            name: 'async',
+            label: '异步',
+            activeText: '是',
+            inactiveText: '否',
+            show: !!_this.showConfig.async
+          },
+          {
+            xType: 'input',
+            name: 'priority',
+            label: '优先级',
+            show: !!_this.showConfig.priority
+          },
+          // {
+          //   xType: 'input',
+          //   name: 'formKey',
+          //   label: '表单标识key',
+          //   show: !!_this.showConfig.formKey
+          // },
+          {
+            xType: 'select',
+            name: 'formKey',
+            label: '表单标识key',
+            clearable: true,
+            dic: { data: _this.formList, label: 'formName', value: 'formId' },
+            show: !!_this.showConfig.formKey
+          },
+          {
+            xType: 'input',
+            name: 'skipExpression',
+            label: '跳过表达式',
+            show: !!_this.showConfig.skipExpression
+          },
+          {
+            xType: 'switch',
+            name: 'isForCompensation',
+            label: '是否为补偿',
+            activeText: '是',
+            inactiveText: '否',
+            show: !!_this.showConfig.isForCompensation
+          },
+          {
+            xType: 'switch',
+            name: 'triggerable',
+            label: '服务任务可触发',
+            activeText: '是',
+            inactiveText: '否',
+            show: !!_this.showConfig.triggerable
+          },
+          {
+            xType: 'switch',
+            name: 'autoStoreVariables',
+            label: '自动存储变量',
+            activeText: '是',
+            inactiveText: '否',
+            show: !!_this.showConfig.autoStoreVariables
+          },
+          {
+            xType: 'input',
+            name: 'ruleVariablesInput',
+            label: '输入变量',
+            show: !!_this.showConfig.ruleVariablesInput
+          },
+          {
+            xType: 'input',
+            name: 'rules',
+            label: '规则',
+            show: !!_this.showConfig.rules
+          },
+          {
+            xType: 'input',
+            name: 'resultVariable',
+            label: '结果变量',
+            show: !!_this.showConfig.resultVariable
+          },
+          {
+            xType: 'switch',
+            name: 'exclude',
+            label: '排除',
+            activeText: '是',
+            inactiveText: '否',
+            show: !!_this.showConfig.exclude
+          },
+          {
+            xType: 'input',
+            name: 'class',
+            label: '类',
+            show: !!_this.showConfig.class
+          },
+          {
+            xType: 'datePicker',
+            type: 'datetime',
+            valueFormat: 'yyyy-MM-ddTHH:mm:ss',
+            name: 'dueDate',
+            label: '到期时间',
+            show: !!_this.showConfig.dueDate
+          }
+        ]
+      }
+    }
+  },
+  watch: {
+    'formData.userType': function(val, oldVal) {
+      if (StrUtil.isNotBlank(oldVal)) {
+          delete this.element.businessObject.$attrs[`flowable:${oldVal}`]
+          delete this.formData[oldVal]
+          // 清除已选人员数据
+          this.checkValues = '';
+          this.selectValues = null;
+          // 删除xml中已选择数据类型节点
+          delete this.element.businessObject.$attrs[`flowable:dataType`]
+      }
+      // 写入userType节点信息到xml
+      this.updateProperties({'flowable:userType': val})
+    },
+    'formData.async': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:async': val})
+      }
+    },
+    'formData.dueDate': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:dueDate': val})
+      }
+    },
+    'formData.formKey': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:formKey': val})
+      } else {
+        // 删除xml中已选择表单信息
+        delete this.element.businessObject[`formKey`]
+      }
+    },
+    'formData.priority': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:priority': val})
+      }
+    },
+    'formData.skipExpression': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:skipExpression': val})
+      } else {
+        delete this.element.businessObject.$attrs[`flowable:skipExpression`]
+      }
+    },
+    'formData.isForCompensation': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'isForCompensation': val})
+      }
+    },
+    'formData.triggerable': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:triggerable': val})
+      }
+    },
+    'formData.class': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:class': val})
+      }
+    },
+    'formData.autoStoreVariables': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:autoStoreVariables': val})
+      }
+    },
+    'formData.exclude': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:exclude': val})
+      }
+    },
+    'formData.ruleVariablesInput': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:ruleVariablesInput': val})
+      }
+    },
+    'formData.rules': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:rules': val})
+      }
+    },
+    'formData.resultVariable': function(val) {
+      if (StrUtil.isNotBlank(val)) {
+        this.updateProperties({'flowable:resultVariable': val})
+      }
+    }
+  },
+  created() {
+    let cache = commonParse(this.element)
+    cache = userTaskParse(cache)
+    this.formData = cache
+    this.computedExecutionListenerLength()
+    this.computedTaskListenerLength()
+    this.computedHasMultiInstance()
+    // 人员信息回显
+    this.checkValuesEcho();
+    // 加载表单列表
+    this.getListForm();
+  },
+  methods: {
+    computedExecutionListenerLength() {
+      this.executionListenerLength = this.element.businessObject.extensionElements?.values
+        ?.filter(item => item.$type === 'flowable:ExecutionListener').length ?? 0
+    },
+    computedTaskListenerLength() {
+      this.taskListenerLength = this.element.businessObject.extensionElements?.values
+        ?.filter(item => item.$type === 'flowable:TaskListener').length ?? 0
+    },
+    computedHasMultiInstance() {
+      if (this.element.businessObject.loopCharacteristics) {
+        this.hasMultiInstance = true
+      } else {
+        this.hasMultiInstance = false
+      }
+    },
+    // 获取表单信息
+    getListForm(){
+      listAllForm().then(res =>{
+        res.data.forEach(item =>{
+          item.formId = item.formId.toString();
+        })
+        this.formList = res.data;
+      })
+    },
+    // 设计器右侧表单数据回显
+    checkValuesEcho(){
+      const that = this;
+      const attrs = that.element.businessObject.$attrs;
+      const businessObject = that.element.businessObject;
+      console.log(that.element.businessObject,"this.element.businessObject")
+      // 指定用户
+      if (attrs.hasOwnProperty("flowable:assignee")) {
+        const val = attrs["flowable:assignee"];
+        // 查找是否动态指定人员(选中流程表达式)
+        if (attrs["flowable:dataType"] === "dynamic"||(attrs["flowable:dataType"] === "fixed"&&this.isFlowableExpression(attrs["flowable:assignee"]) == true)) {
+          this.checkValues = that.expList.find(item => item.expression == val).name;
+          this.selectValues = that.expList.find(item => item.expression == val).id;
+        } else {
+          this.checkValues = that.userList.find(item => item.userId == val).nickName;
+          this.selectValues = that.userList.find(item => item.userId == val).userId;
+        }
+        // 候选用户
+      } else if (attrs.hasOwnProperty("flowable:candidateUsers")) {
+        const val = attrs["flowable:candidateUsers"];
+        if (attrs["flowable:dataType"] === "dynamic") {
+          this.checkValues = that.expList.find(item => item.expression == val).name;
+          this.selectValues = that.expList.find(item => item.expression == val).id;
+        } else {
+          const newArr = that.userList.filter(i => val.split(',').includes(i.userId))
+          this.checkValues =  newArr.map(item => item.nickName).join(',');
+          this.selectValues = newArr.map(item => item.userId).join(',');
+        }
+      } else if (businessObject.hasOwnProperty("candidateGroups") || attrs.hasOwnProperty("flowable:candidateGroups") ) {
+        // 候选角色信息
+        const val = businessObject["candidateGroups"] || attrs["flowable:candidateGroups"];
+        if (attrs["flowable:dataType"] === "dynamic") {
+          this.checkValues = that.expList.find(item => item.expression == val).name;
+          this.selectValues = that.expList.find(item => item.expression == val).id;
+        } else {
+          const newArr = that.groupList.filter(i => val.split(',').includes(i.roleId))
+          this.checkValues =  newArr.map(item => item.roleName).join(',');
+          this.selectValues = newArr.map(item => item.roleId).join(',');
+        }
+      }
+    },
+    finishExecutionListener() {
+      if (this.dialogName === 'executionListenerDialog') {
+        this.computedExecutionListenerLength()
+      }
+      this.dialogName = ''
+    },
+    finishTaskListener() {
+      if (this.dialogName === 'taskListenerDialog') {
+        this.computedTaskListenerLength()
+      }
+      this.dialogName = ''
+    },
+    finishMultiInstance() {
+      if (this.dialogName === 'multiInstanceDialog') {
+        this.computedHasMultiInstance()
+      }
+      this.dialogName = ''
+    },
+    isFlowableExpression(value) {
+      // 正则:严格匹配 ${任意内容}
+      const expressionPattern = /^\$\{[\s\S]*\}$/;
+      return expressionPattern.test(value);
+    },
+
+// ------ 流程审批人员信息弹出框 start---------
+    /*单选人员*/
+    singleUserCheck(){
+      this.userVisible = true;
+      this.checkType = "single";
+    },
+    /*多选人员*/
+    multipleUserCheck(){
+      this.userVisible = true;
+      this.checkType = "multiple";
+    },
+    /*单选角色*/
+    singleRoleCheck(){
+      this.roleVisible = true;
+      this.checkType = "single";
+    },
+    /*多选角色*/
+    multipleRoleCheck(){
+      this.roleVisible = true;
+      this.checkType = "multiple";
+    },
+    /*单选表达式*/
+    singleExpCheck(expType){
+      this.expVisible = true;
+      this.expType = expType;
+    },
+    // 选中表达式
+    handleSingleExpSelect(selection) {
+      this.deleteFlowAttar();
+      this.updateProperties({'flowable:dataType':selection.dataType})
+      if ("assignee" === this.expType) {
+        this.updateProperties({'flowable:assignee': selection.expression});
+      } else if ("candidateUsers" === this.expType) {
+        this.updateProperties({'flowable:candidateUsers': selection.expression});
+      } else if ("candidateGroups" === this.expType) {
+        this.updateProperties({'flowable:candidateGroups': selection.expression});
+      }
+      this.checkValues = selection.name;
+      this.expType = null;
+    },
+    // 用户选中数据
+    handleUserSelect(selection) {
+      const that = this;
+      if (selection) {
+        that.deleteFlowAttar();
+        that.updateProperties({'flowable:dataType': 'fixed'})
+        if (selection instanceof Array) {
+          const userIds = selection.map(item => item.userId);
+          const nickName = selection.map(item => item.nickName);
+          that.updateProperties({'flowable:candidateUsers': userIds.join(',')})
+          that.checkValues = nickName.join(',');
+        } else {
+          that.updateProperties({'flowable:assignee': selection.userId})
+          that.checkValues = selection.nickName;
+        }
+      }
+    },
+    // 角色选中数据
+    handleRoleSelect(selection, name) {
+      const that = this;
+      if (selection && name) {
+        that.deleteFlowAttar();
+        that.updateProperties({'flowable:dataType': 'fixed'})
+        that.updateProperties({'flowable:candidateGroups': selection});
+        that.checkValues = name;
+      }
+    },
+    /*用户选中赋值*/
+    checkUserComplete(){
+      this.userVisible = false;
+      this.checkType = "";
+    },
+    /*候选角色选中赋值*/
+    checkRoleComplete(){
+      this.roleVisible = false;
+      this.checkType = "";
+      },
+    /*表达式选中赋值*/
+    checkExpComplete(){
+      this.expVisible = false;
+    },
+    // 删除节点
+    deleteFlowAttar(){
+      delete this.element.businessObject.$attrs[`flowable:dataType`]
+      delete this.element.businessObject.$attrs[`flowable:assignee`]
+      delete this.element.businessObject.$attrs[`flowable:candidateUsers`]
+      delete this.element.businessObject.$attrs[`flowable:candidateGroups`]
+    }
+  }
+}
+</script>
+
+<style></style>

+ 524 - 0
src/components/Process/customPanel/CustomContextPad.js

@@ -0,0 +1,524 @@
+import {
+  assign,
+  forEach,
+  isArray,
+  every
+} from 'min-dash';
+
+import {
+  is
+} from 'bpmn-js/lib/util/ModelUtil';
+
+import {
+  isExpanded,
+  isEventSubProcess
+} from 'bpmn-js/lib/util/DiUtil';
+
+import {
+  isAny
+} from 'bpmn-js/lib/features/modeling/util/ModelingUtil';
+
+import {
+  getChildLanes
+} from 'bpmn-js/lib/features/modeling/util/LaneUtil';
+
+import {
+  hasPrimaryModifier
+} from 'diagram-js/lib/util/Mouse';
+
+
+/**
+ * A provider for BPMN 2.0 elements context pad
+ */
+export default function ContextPadProvider(
+  config, injector, eventBus,
+  contextPad, modeling, elementFactory,
+  connect, create, popupMenu,
+  canvas, rules, translate) {
+
+  config = config || {};
+
+  contextPad.registerProvider(this);
+
+  this._contextPad = contextPad;
+
+  this._modeling = modeling;
+
+  this._elementFactory = elementFactory;
+  this._connect = connect;
+  this._create = create;
+  this._popupMenu = popupMenu;
+  this._canvas = canvas;
+  this._rules = rules;
+  this._translate = translate;
+
+  if (config.autoPlace !== false) {
+    this._autoPlace = injector.get('autoPlace', false);
+  }
+
+  eventBus.on('create.end', 250, function(event) {
+    var context = event.context,
+      shape = context.shape;
+
+    if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
+      return;
+    }
+
+    var entries = contextPad.getEntries(shape);
+
+    if (entries.replace) {
+      entries.replace.action.click(event, shape);
+    }
+  });
+}
+
+ContextPadProvider.$inject = [
+  'config.contextPad',
+  'injector',
+  'eventBus',
+  'contextPad',
+  'modeling',
+  'elementFactory',
+  'connect',
+  'create',
+  'popupMenu',
+  'canvas',
+  'rules',
+  'translate'
+];
+
+ContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) {
+  var modeling = this._modeling;
+
+  var actions = {};
+
+  if (this._isDeleteAllowed(elements)) {
+    assign(actions, {
+      'delete': {
+        group: 'edit',
+        className: 'bpmn-icon-trash',
+        title: this._translate('Remove'),
+        action: {
+          click: function(event, elements) {
+            modeling.removeElements(elements.slice());
+          }
+        }
+      }
+    });
+  }
+
+  return actions;
+};
+
+/**
+ * @param {djs.model.Base[]} elements
+ * @return {boolean}
+ */
+ContextPadProvider.prototype._isDeleteAllowed = function(elements) {
+
+  var baseAllowed = this._rules.allowed('elements.delete', {
+    elements: elements
+  });
+
+  if (isArray(baseAllowed)) {
+    return every(baseAllowed, function(element) {
+      return includes(baseAllowed, element);
+    });
+  }
+
+  return baseAllowed;
+};
+
+ContextPadProvider.prototype.getContextPadEntries = function(element) {
+  var contextPad = this._contextPad,
+    modeling = this._modeling,
+
+    elementFactory = this._elementFactory,
+    connect = this._connect,
+    create = this._create,
+    popupMenu = this._popupMenu,
+    rules = this._rules,
+    autoPlace = this._autoPlace,
+    translate = this._translate;
+
+  var actions = {};
+
+  if (element.type === 'label') {
+    return actions;
+  }
+
+  var businessObject = element.businessObject;
+
+  function startConnect(event, element) {
+    connect.start(event, element);
+  }
+
+  function removeElement(e, element) {
+    modeling.removeElements([ element ]);
+  }
+
+  function getReplaceMenuPosition(element) {
+
+    var Y_OFFSET = 5;
+
+    var pad = contextPad.getPad(element).html;
+
+    var padRect = pad.getBoundingClientRect();
+
+    var pos = {
+      x: padRect.left,
+      y: padRect.bottom + Y_OFFSET
+    };
+
+    return pos;
+  }
+
+
+  /**
+   * Create an append action
+   *
+   * @param {string} type
+   * @param {string} className
+   * @param {string} [title]
+   * @param {Object} [options]
+   *
+   * @return {Object} descriptor
+   */
+  function appendAction(type, className, title, options) {
+
+    if (typeof title !== 'string') {
+      options = title;
+      title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
+    }
+
+    function appendStart(event, element) {
+
+      var shape = elementFactory.createShape(assign({ type: type }, options));
+      create.start(event, shape, {
+        source: element
+      });
+    }
+
+
+    var append = autoPlace ? function(event, element) {
+      var shape = elementFactory.createShape(assign({ type: type }, options));
+
+      autoPlace.append(element, shape);
+    } : appendStart;
+
+
+    return {
+      group: 'model',
+      className: className,
+      title: title,
+      action: {
+        dragstart: appendStart,
+        click: append
+      }
+    };
+  }
+
+  function splitLaneHandler(count) {
+
+    return function(event, element) {
+
+      // actual split
+      modeling.splitLane(element, count);
+
+      // refresh context pad after split to
+      // get rid of split icons
+      contextPad.open(element, true);
+    };
+  }
+
+
+  if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ]) && isExpanded(element)) {
+
+    var childLanes = getChildLanes(element);
+
+    assign(actions, {
+      'lane-insert-above': {
+        group: 'lane-insert-above',
+        className: 'bpmn-icon-lane-insert-above',
+        title: translate('Add Lane above'),
+        action: {
+          click: function(event, element) {
+            modeling.addLane(element, 'top');
+          }
+        }
+      }
+    });
+
+    if (childLanes.length < 2) {
+
+      if (element.height >= 120) {
+        assign(actions, {
+          'lane-divide-two': {
+            group: 'lane-divide',
+            className: 'bpmn-icon-lane-divide-two',
+            title: translate('Divide into two Lanes'),
+            action: {
+              click: splitLaneHandler(2)
+            }
+          }
+        });
+      }
+
+      if (element.height >= 180) {
+        assign(actions, {
+          'lane-divide-three': {
+            group: 'lane-divide',
+            className: 'bpmn-icon-lane-divide-three',
+            title: translate('Divide into three Lanes'),
+            action: {
+              click: splitLaneHandler(3)
+            }
+          }
+        });
+      }
+    }
+
+    assign(actions, {
+      'lane-insert-below': {
+        group: 'lane-insert-below',
+        className: 'bpmn-icon-lane-insert-below',
+        title: translate('Add Lane below'),
+        action: {
+          click: function(event, element) {
+            modeling.addLane(element, 'bottom');
+          }
+        }
+      }
+    });
+
+  }
+
+  if (is(businessObject, 'bpmn:FlowNode')) {
+
+    if (is(businessObject, 'bpmn:EventBasedGateway')) {
+
+      assign(actions, {
+        'append.receive-task': appendAction(
+          'bpmn:ReceiveTask',
+          'bpmn-icon-receive-task',
+          translate('Append ReceiveTask')
+        ),
+        'append.message-intermediate-event': appendAction(
+          'bpmn:IntermediateCatchEvent',
+          'bpmn-icon-intermediate-event-catch-message',
+          translate('Append MessageIntermediateCatchEvent'),
+          { eventDefinitionType: 'bpmn:MessageEventDefinition' }
+        ),
+        'append.timer-intermediate-event': appendAction(
+          'bpmn:IntermediateCatchEvent',
+          'bpmn-icon-intermediate-event-catch-timer',
+          translate('Append TimerIntermediateCatchEvent'),
+          { eventDefinitionType: 'bpmn:TimerEventDefinition' }
+        ),
+        'append.condition-intermediate-event': appendAction(
+          'bpmn:IntermediateCatchEvent',
+          'bpmn-icon-intermediate-event-catch-condition',
+          translate('Append ConditionIntermediateCatchEvent'),
+          { eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
+        ),
+        'append.signal-intermediate-event': appendAction(
+          'bpmn:IntermediateCatchEvent',
+          'bpmn-icon-intermediate-event-catch-signal',
+          translate('Append SignalIntermediateCatchEvent'),
+          { eventDefinitionType: 'bpmn:SignalEventDefinition' }
+        )
+      });
+    } else
+
+    if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
+
+      assign(actions, {
+        'append.compensation-activity':
+          appendAction(
+            'bpmn:Task',
+            'bpmn-icon-task',
+            translate('Append compensation activity'),
+            {
+              isForCompensation: true
+            }
+          )
+      });
+    } else
+
+    if (!is(businessObject, 'bpmn:EndEvent') &&
+      !businessObject.isForCompensation &&
+      !isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
+      !isEventSubProcess(businessObject)) {
+
+      assign(actions, {
+        'append.end-event': appendAction(
+          'bpmn:EndEvent',
+          'bpmn-icon-end-event-none',
+          translate('Append EndEvent')
+        ),
+        'append.gateway': appendAction(
+          'bpmn:ExclusiveGateway',
+          'bpmn-icon-gateway-none',
+          translate('Append Gateway')
+        ),
+        'append.append-user-task': appendAction(
+          'bpmn:UserTask',
+          'bpmn-icon-user-task',
+          '添加用户任务'
+        ),
+        'append.intermediate-event': appendAction(
+          'bpmn:IntermediateThrowEvent',
+          'bpmn-icon-intermediate-event-none',
+          translate('Append Intermediate/Boundary Event')
+        )
+      });
+    }
+  }
+
+  if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
+
+    // Replace menu entry
+    assign(actions, {
+      'replace': {
+        group: 'edit',
+        className: 'bpmn-icon-screw-wrench',
+        title: translate('Change type'),
+        action: {
+          click: function(event, element) {
+
+            var position = assign(getReplaceMenuPosition(element), {
+              cursor: { x: event.x, y: event.y }
+            });
+
+            popupMenu.open(element, 'bpmn-replace', position, {
+              title: translate('Change element'),
+              width: 300,
+              search: true
+            });
+          }
+        }
+      }
+    });
+  }
+
+  if (is(businessObject, 'bpmn:SequenceFlow')) {
+    assign(actions, {
+      'append.text-annotation': appendAction(
+        'bpmn:TextAnnotation',
+        'bpmn-icon-text-annotation'
+      )
+    });
+  }
+
+  if (
+    isAny(businessObject, [
+      'bpmn:FlowNode',
+      'bpmn:InteractionNode',
+      'bpmn:DataObjectReference',
+      'bpmn:DataStoreReference',
+    ])
+  ) {
+    assign(actions, {
+      'append.text-annotation': appendAction(
+        'bpmn:TextAnnotation',
+        'bpmn-icon-text-annotation'
+      ),
+
+      'connect': {
+        group: 'connect',
+        className: 'bpmn-icon-connection-multi',
+        title: translate(
+          'Connect using ' +
+          (businessObject.isForCompensation
+            ? ''
+            : 'Sequence/MessageFlow or ') +
+          'Association'
+        ),
+        action: {
+          click: startConnect,
+          dragstart: startConnect,
+        },
+      },
+    });
+  }
+
+  if (is(businessObject, 'bpmn:TextAnnotation')) {
+    assign(actions, {
+      'connect': {
+        group: 'connect',
+        className: 'bpmn-icon-connection-multi',
+        title: translate('Connect using Association'),
+        action: {
+          click: startConnect,
+          dragstart: startConnect,
+        },
+      },
+    });
+  }
+
+  if (isAny(businessObject, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
+    assign(actions, {
+      'connect': {
+        group: 'connect',
+        className: 'bpmn-icon-connection-multi',
+        title: translate('Connect using DataInputAssociation'),
+        action: {
+          click: startConnect,
+          dragstart: startConnect
+        }
+      }
+    });
+  }
+
+  if (is(businessObject, 'bpmn:Group')) {
+    assign(actions, {
+      'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation')
+    });
+  }
+
+  // delete element entry, only show if allowed by rules
+  var deleteAllowed = rules.allowed('elements.delete', { elements: [ element ] });
+
+  if (isArray(deleteAllowed)) {
+
+    // was the element returned as a deletion candidate?
+    deleteAllowed = deleteAllowed[0] === element;
+  }
+
+  if (deleteAllowed) {
+    assign(actions, {
+      'delete': {
+        group: 'edit',
+        className: 'bpmn-icon-trash',
+        title: translate('Remove'),
+        action: {
+          click: removeElement
+        }
+      }
+    });
+  }
+
+  return actions;
+};
+
+
+// helpers /////////
+
+function isEventType(eventBo, type, definition) {
+
+  var isType = eventBo.$instanceOf(type);
+  var isDefinition = false;
+
+  var definitions = eventBo.eventDefinitions || [];
+  forEach(definitions, function(def) {
+    if (def.$type === definition) {
+      isDefinition = true;
+    }
+  });
+
+  return isType && isDefinition;
+}
+
+function includes(array, item) {
+  return array.indexOf(item) !== -1;
+}

+ 142 - 0
src/components/Process/customPanel/CustomPalette.js

@@ -0,0 +1,142 @@
+import { assign } from "min-dash";
+
+export default function CustomPalette(
+  palette,
+  create,
+  elementFactory,
+  handTool,
+  lassoTool,
+  spaceTool,
+  globalConnect,
+  translate
+) {
+  this.create = create;
+  this.elementFactory = elementFactory;
+  this.handTool = handTool;
+  this.lassoTool = lassoTool;
+  this.spaceTool = spaceTool;
+  this.globalConnect = globalConnect;
+  this.translate = translate;
+
+  palette.registerProvider(this);
+}
+
+CustomPalette.$inject = [
+  "palette",
+  "create",
+  "elementFactory",
+  "handTool",
+  "lassoTool",
+  "spaceTool",
+  "globalConnect",
+  "translate"
+];
+
+CustomPalette.prototype.getPaletteEntries = function (element) {
+  const {
+    create,
+    elementFactory,
+    handTool,
+    lassoTool,
+    spaceTool,
+    globalConnect,
+    translate
+  } = this;
+
+  function createAction(type, group, className, title, options) {
+    function createListener(event) {
+      var shape = elementFactory.createShape(assign({ type: type }, options));
+
+      if (options) {
+        shape.businessObject.di.isExpanded = options.isExpanded;
+      }
+
+      create.start(event, shape);
+    }
+
+    var shortType = type.replace(/^bpmn:/, "");
+
+    return {
+      group: group,
+      className: className,
+      title: title || translate("Create {type}", { type: shortType }),
+      action: {
+        dragstart: createListener,
+        click: createListener
+      }
+    };
+  }
+
+  return {
+    'hand-tool': {
+      group: 'tools',
+      className: 'bpmn-icon-hand-tool',
+      title: '激活抓手工具',
+      action: {
+        click: function(event) {
+          handTool.activateHand(event);
+        }
+      }
+    },
+    "lasso-tool": {
+      group: "tools",
+      className: "bpmn-icon-lasso-tool",
+      title: "激活套索工具",
+      action: {
+        click: function (event) {
+          lassoTool.activateSelection(event);
+        }
+      }
+    },
+    'space-tool': {
+      group: 'tools',
+      className: 'bpmn-icon-space-tool',
+      title: translate('Activate the create/remove space tool'),
+      action: {
+        click: function(event) {
+          spaceTool.activateSelection(event);
+        }
+      }
+    },
+    'global-connect-tool': {
+      group: 'tools',
+      className: 'bpmn-icon-connection-multi',
+      title: translate('Activate the global connect tool'),
+      action: {
+        click: function(event) {
+          globalConnect.start(event);
+        }
+      }
+    },
+
+    "tool-separator": {
+      group: "tools",
+      separator: true
+    },
+
+    "create.start-event": createAction(
+      "bpmn:StartEvent",
+      "event",
+      "bpmn-icon-start-event-none",
+      "创建开始节点"
+    ),
+    "create.end-event": createAction(
+      "bpmn:EndEvent",
+      "event",
+      "bpmn-icon-end-event-none",
+      "创建结束节点"
+    ),
+    "create.user-task": createAction(
+      "bpmn:UserTask",
+      "activity",
+      "bpmn-icon-user-task",
+      "创建用户任务"
+    ),
+    "create.exclusive-gateway": createAction(
+      "bpmn:ExclusiveGateway",
+      "gateway",
+      "bpmn-icon-gateway-xor",
+      "创建排他网关"
+    )
+  };
+};

+ 8 - 0
src/components/Process/customPanel/index.js

@@ -0,0 +1,8 @@
+import CustomContextPad from './CustomContextPad';
+import CustomPalette from "./CustomPalette";
+
+export default {
+  __init__: [ 'paletteProvider','contextPadProvider'],
+  paletteProvider: [ 'type', CustomPalette ],
+  contextPadProvider: [ 'type', CustomContextPad ],
+};

+ 187 - 0
src/components/Process/designer.vue

@@ -0,0 +1,187 @@
+<template>
+  <div>
+    <template slot="header">
+      <div class="card-header">
+        <span>{{ translateNodeName(elementType) }}</span>
+      </div>
+    </template>
+    <el-collapse v-model="activeName" >
+        <!--   常规信息     -->
+        <el-collapse-item name="common">
+          <template slot="title"><i class="el-icon-info"></i> 常规信息</template>
+          <common-panel :id="elementId"/>
+        </el-collapse-item>
+
+        <!--   任务信息     -->
+        <el-collapse-item name="Task" v-if="elementType.indexOf('Task') !== -1">
+          <template slot="title"><i class="el-icon-s-claim"></i> 任务配置</template>
+          <user-task-panel :id="elementId"/>
+        </el-collapse-item>
+
+        <!--   表单     -->
+        <el-collapse-item name="form" v-if="formVisible">
+          <template slot="title"><i class="el-icon-s-order"></i> 表单配置</template>
+          <form-panel :id="elementId"/>
+        </el-collapse-item>
+
+        <!--   执行监听器     -->
+        <el-collapse-item name="executionListener">
+          <template slot="title"><i class="el-icon-s-promotion"></i> 执行监听器
+             <el-badge :value="executionListenerCount" class="item" type="primary"/>
+           </template>
+          <execution-listener :id="elementId" @getExecutionListenerCount="getExecutionListenerCount"/>
+        </el-collapse-item>
+
+        <!--   任务监听器     -->
+        <el-collapse-item name="taskListener" v-if="elementType === 'UserTask'" >
+          <template slot="title"><i class="el-icon-s-flag"></i> 任务监听器
+            <el-badge :value="taskListenerCount" class="item" type="primary"/>
+          </template>
+          <task-listener :id="elementId" @getTaskListenerCount="getTaskListenerCount"/>
+        </el-collapse-item>
+
+        <!--   多实例     -->
+        <el-collapse-item name="multiInstance" v-if="elementType.indexOf('Task') !== -1" >
+          <template slot="title"><i class="el-icon-s-grid"></i> 多实例</template>
+          <multi-instance :id="elementId"/>
+        </el-collapse-item>
+        <!--   流转条件     -->
+        <el-collapse-item name="condition" v-if="conditionVisible" >
+          <template slot="title"><i class="el-icon-share"></i> 流转条件</template>
+          <condition-panel :id="elementId"/>
+        </el-collapse-item>
+
+        <!--   扩展属性     -->
+        <el-collapse-item name="properties" >
+          <template slot="title"><i class="el-icon-circle-plus"></i> 扩展属性</template>
+          <properties-panel :id="elementId"/>
+        </el-collapse-item>
+
+    </el-collapse>
+  </div>
+</template>
+
+<script>
+import ExecutionListener from './panel/executionListener'
+import TaskListener from './panel/taskListener'
+import MultiInstance from './panel/multiInstance'
+import CommonPanel from './panel/commonPanel'
+import UserTaskPanel from './panel/taskPanel'
+import ConditionPanel from './panel/conditionPanel'
+import FormPanel from './panel/formPanel'
+import OtherPanel from './panel/otherPanel'
+import PropertiesPanel from './panel/PropertiesPanel2.vue'
+
+import { translateNodeName } from "./common/bpmnUtils";
+import FlowUser from "@/components/flow/User/index.vue";
+import FlowRole from "@/components/flow/Role/index.vue";
+import FlowExp from "@/components/flow/Expression/index.vue";
+export default {
+  name: "Designer",
+  components: {
+    ExecutionListener,
+    TaskListener,
+    MultiInstance,
+    CommonPanel,
+    UserTaskPanel,
+    ConditionPanel,
+    FormPanel,
+    OtherPanel,
+    PropertiesPanel,
+    FlowUser,
+    FlowRole,
+    FlowExp,
+  },
+  data() {
+    return {
+      activeName : 'common',
+      executionListenerCount: 0,
+      taskListenerCount:0,
+      elementId:"",
+      elementType:"",
+      conditionVisible:false,// 流转条件设置
+      formVisible:false, // 表单配置
+      rules:{
+        id: [
+          { required: true, message: '节点Id 不能为空', trigger: 'blur' },
+        ],
+        name: [
+          { required: true, message: '节点名称不能为空', trigger: 'blur' },
+        ],
+      },
+    }
+  },
+
+  /** 传值监听 */
+  watch: {
+    elementId: {
+      handler() {
+        this.activeName = "common";
+      }
+    },
+  },
+  created() {
+    this.initModels();
+  },
+  methods: {
+    // 初始化流程设计器
+    initModels() {
+      this.getActiveElement();
+    },
+
+    // 注册节点事件
+    getActiveElement() {
+      // 初始第一个选中元素 bpmn:Process
+      this.initFormOnChanged(null);
+      this.modelerStore.modeler.on("import.done", e => {
+        this.initFormOnChanged(null);
+      });
+      // 监听选择事件,修改当前激活的元素以及表单
+      this.modelerStore.modeler.on("selection.changed", ({newSelection}) => {
+        this.initFormOnChanged(newSelection[0] || null);
+      });
+      this.modelerStore.modeler.on("element.changed", ({element}) => {
+        // 保证 修改 "默认流转路径" 类似需要修改多个元素的事件发生的时候,更新表单的元素与原选中元素不一致。
+        if (element && element.id === this.elementId) {
+          this.initFormOnChanged(element);
+        }
+      });
+    },
+
+    // 初始化数据
+    initFormOnChanged(element) {
+      let activatedElement = element;
+      if (!activatedElement) {
+        activatedElement =
+          this.modelerStore.elRegistry.find(el => el.type === "bpmn:Process") ??
+          this.modelerStore.elRegistry.find(el => el.type === "bpmn:Collaboration");
+      }
+      if (!activatedElement) return;
+      this.modelerStore.element = activatedElement;
+      this.elementId = activatedElement.id;
+      this.elementType = activatedElement.type.split(":")[1] || "";
+      this.conditionVisible = !!(
+        this.elementType === "SequenceFlow" &&
+        activatedElement.source &&
+        activatedElement.source.type.indexOf("StartEvent") === -1
+      );
+      this.formVisible = this.elementType === "UserTask" || this.elementType === "StartEvent";
+    },
+
+    /** 获取执行监听器数量 */
+    getExecutionListenerCount(value) {
+      this.executionListenerCount = value;
+    },
+    /** 获取任务监听器数量 */
+    getTaskListenerCount(value) {
+      this.taskListenerCount = value;
+    },
+    translateNodeName(val){
+      return translateNodeName(val);
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+</style>

File diff suppressed because it is too large
+ 1195 - 0
src/components/Process/flowable/flowable.json


+ 33 - 0
src/components/Process/flowable/init.js

@@ -0,0 +1,33 @@
+
+function randomStr() {
+  return Math.random().toString(36).slice(-8)
+}
+
+export default function() {
+  return `<?xml version="1.0" encoding="UTF-8"?>
+    <definitions
+      xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+      xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
+      xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
+      xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0"
+      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+      xmlns:flowable="http://flowable.org/bpmn"
+      targetNamespace="http://www.flowable.org/processdef"
+      >
+      <process id="flow_${randomStr()}" name="flow_${randomStr()}">
+        <startEvent id="start_event" name="开始" />
+      </process>
+      <bpmndi:BPMNDiagram id="BPMNDiagram_flow">
+        <bpmndi:BPMNPlane id="BPMNPlane_flow" bpmnElement="T-2d89e7a3-ba79-4abd-9f64-ea59621c258c">
+          <bpmndi:BPMNShape id="BPMNShape_start_event" bpmnElement="start_event" bioc:stroke="">
+            <omgdc:Bounds x="240" y="200" width="30" height="30" />
+            <bpmndi:BPMNLabel>
+              <omgdc:Bounds x="242" y="237" width="23" height="14" />
+            </bpmndi:BPMNLabel>
+          </bpmndi:BPMNShape>
+        </bpmndi:BPMNPlane>
+      </bpmndi:BPMNDiagram>
+    </definitions>
+    `
+}

+ 51 - 0
src/components/Process/flowable/showConfig.js

@@ -0,0 +1,51 @@
+export default {
+  'bpmn:EndEvent': {},
+  'bpmn:StartEvent': {
+    initiator: true,
+    formKey: true
+  },
+  'bpmn:UserTask': {
+    userType: true,
+    assignee: true,
+    candidateUsers: true,
+    candidateGroups: true,
+    async: true,
+    priority: true,
+    formKey: true,
+    skipExpression: true,
+    dueDate: true,
+    taskListener: true
+  },
+  'bpmn:ServiceTask': {
+    async: true,
+    skipExpression: true,
+    isForCompensation: true,
+    triggerable: true,
+    class: true
+  },
+  'bpmn:ScriptTask': {
+    async: true,
+    isForCompensation: true,
+    autoStoreVariables: true
+  },
+  'bpmn:ManualTask': {
+    async: true,
+    isForCompensation: true
+  },
+  'bpmn:ReceiveTask': {
+    async: true,
+    isForCompensation: true
+  },
+  'bpmn:SendTask': {
+    async: true,
+    isForCompensation: true
+  },
+  'bpmn:BusinessRuleTask': {
+    async: true,
+    isForCompensation: true,
+    ruleVariablesInput: true,
+    rules: true,
+    resultVariable: true,
+    exclude: true
+  }
+}

+ 5 - 0
src/components/Process/index.js

@@ -0,0 +1,5 @@
+import workflowBpmnModeler from './index.vue'
+
+workflowBpmnModeler.install = Vue => Vue.component(workflowBpmnModeler.name, workflowBpmnModeler) // 给组件配置install方法
+
+export default workflowBpmnModeler

File diff suppressed because it is too large
+ 355 - 0
src/components/Process/index.vue


File diff suppressed because it is too large
+ 353 - 0
src/components/Process/index1.vue


+ 232 - 0
src/components/Process/lang/zh.js

@@ -0,0 +1,232 @@
+export default {
+  // Labels
+  'Activate the global connect tool': '激活全局连接工具',
+  'Append {type}': '添加 {type}',
+  'Append Task': '添加任务',
+  'Append Gateway': '添加网关',
+  'Append EndEvent': '添加结束事件',
+  'Append StartEvent': '添加开始事件',
+  "Append Intermediate/Boundary Event": '添加边界事件',
+  'Add Lane above': '在上面添加道',
+  'Divide into two Lanes': '分割成两个道',
+  'Divide into three Lanes': '分割成三个道',
+  'Add Lane below': '在下面添加道',
+  'Append compensation activity': '追加补偿活动',
+  'Change type': '修改类型',
+  'Connect using Association': '使用关联连接',
+  'Connect using Sequence/MessageFlow or Association': '使用顺序/消息流或者关联连接',
+  'Connect using DataInputAssociation': '使用数据输入关联连接',
+  'Remove': '移除',
+  'Activate the hand tool': '激活抓手工具',
+  'Activate the lasso tool': '激活套索工具',
+  'Activate the create/remove space tool': '激活创建/删除空间工具',
+  'Create expanded SubProcess': '创建扩展子过程',
+  'Create IntermediateThrowEvent/BoundaryEvent': '创建中间抛出事件/边界事件',
+  'Create Pool/Participant': '创建池/参与者',
+  'Parallel Multi Instance': '并行多重事件',
+  'Sequential Multi Instance': '时序多重事件',
+  'DataObjectReference': '数据对象参考',
+  'DataStoreReference': '数据存储参考',
+  'Loop': '循环',
+  'Ad-hoc': '即席',
+  'Create {type}': '创建 {type}',
+  'Task': '任务',
+  'Send Task': '发送任务',
+  'Receive Task': '接收任务',
+  'User Task': '用户任务',
+  'Manual Task': '手工任务',
+  'Business Rule Task': '业务规则任务',
+  'Service Task': '服务任务',
+  'Script Task': '脚本任务',
+  'Call Activity': '调用活动',
+  'Sub Process (collapsed)': '子流程(折叠的)',
+  'Sub Process (expanded)': '子流程(展开的)',
+  'Start Event': '开始事件',
+  'StartEvent': '开始事件',
+  'Intermediate Throw Event': '中间事件',
+  'End Event': '结束事件',
+  'EndEvent': '结束事件',
+  'Create Gateway': '创建网关',
+  'Create Intermediate/Boundary Event': '创建中间/边界事件',
+  'Message Start Event': '消息开始事件',
+  'Timer Start Event': '定时开始事件',
+  'Conditional Start Event': '条件开始事件',
+  'Signal Start Event': '信号开始事件',
+  'Error Start Event': '错误开始事件',
+  'Escalation Start Event': '升级开始事件',
+  'Compensation Start Event': '补偿开始事件',
+  'Message Start Event (non-interrupting)': '消息开始事件(非中断)',
+  'Timer Start Event (non-interrupting)': '定时开始事件(非中断)',
+  'Conditional Start Event (non-interrupting)': '条件开始事件(非中断)',
+  'Signal Start Event (non-interrupting)': '信号开始事件(非中断)',
+  'Escalation Start Event (non-interrupting)': '升级开始事件(非中断)',
+  'Message Intermediate Catch Event': '消息中间捕获事件',
+  'Message Intermediate Throw Event': '消息中间抛出事件',
+  'Timer Intermediate Catch Event': '定时中间捕获事件',
+  'Escalation Intermediate Throw Event': '升级中间抛出事件',
+  'Conditional Intermediate Catch Event': '条件中间捕获事件',
+  'Link Intermediate Catch Event': '链接中间捕获事件',
+  'Link Intermediate Throw Event': '链接中间抛出事件',
+  'Compensation Intermediate Throw Event': '补偿中间抛出事件',
+  'Signal Intermediate Catch Event': '信号中间捕获事件',
+  'Signal Intermediate Throw Event': '信号中间抛出事件',
+  'Message End Event': '消息结束事件',
+  'Escalation End Event': '定时结束事件',
+  'Error End Event': '错误结束事件',
+  'Cancel End Event': '取消结束事件',
+  'Compensation End Event': '补偿结束事件',
+  'Signal End Event': '信号结束事件',
+  'Terminate End Event': '终止结束事件',
+  'Message Boundary Event': '消息边界事件',
+  'Message Boundary Event (non-interrupting)': '消息边界事件(非中断)',
+  'Timer Boundary Event': '定时边界事件',
+  'Timer Boundary Event (non-interrupting)': '定时边界事件(非中断)',
+  'Escalation Boundary Event': '升级边界事件',
+  'Escalation Boundary Event (non-interrupting)': '升级边界事件(非中断)',
+  'Conditional Boundary Event': '条件边界事件',
+  'Conditional Boundary Event (non-interrupting)': '条件边界事件(非中断)',
+  'Error Boundary Event': '错误边界事件',
+  'Cancel Boundary Event': '取消边界事件',
+  'Signal Boundary Event': '信号边界事件',
+  'Signal Boundary Event (non-interrupting)': '信号边界事件(非中断)',
+  'Compensation Boundary Event': '补偿边界事件',
+  'Exclusive Gateway': '互斥网关',
+  'Parallel Gateway': '并行网关',
+  'Inclusive Gateway': '相容网关',
+  'Complex Gateway': '复杂网关',
+  'Event based Gateway': '事件网关',
+  'Transaction': '转运',
+  'Sub Process': '子流程',
+  'Event Sub Process': '事件子流程',
+  'Collapsed Pool': '折叠池',
+  'Expanded Pool': '展开池',
+  // Errors
+  'no parent for {element} in {parent}': '在{parent}里,{element}没有父类',
+  'no shape type specified': '没有指定的形状类型',
+  'flow elements must be children of pools/participants': '流元素必须是池/参与者的子类',
+  'out of bounds release': 'out of bounds release',
+  'more than {count} child lanes': '子道大于{count} ',
+  'element required': '元素不能为空',
+  'diagram not part of bpmn:Definitions': '流程图不符合bpmn规范',
+  'no diagram to display': '没有可展示的流程图',
+  'no process or collaboration to display': '没有可展示的流程/协作',
+  'element {element} referenced by {referenced}#{property} not yet drawn': '由{referenced}#{property}引用的{element}元素仍未绘制',
+  'already rendered {element}': '{element} 已被渲染',
+  'failed to import {element}': '导入{element}失败',
+  // 属性面板的参数
+  'Id': '标识',
+  'Name': '名称',
+  'General': '常规',
+  'Details': '详情',
+  'Message Name': '消息名称',
+  'Message': '消息',
+  'Initiator': '创建者',
+  'Asynchronous Continuations': '持续异步',
+  'Asynchronous Before': '异步前',
+  'Asynchronous After': '异步后',
+  'Job Configuration': '工作配置',
+  'Exclusive': '排除',
+  'Job Priority': '工作优先级',
+  'Retry Time Cycle': '重试时间周期',
+  'Documentation': '文档',
+  'Element Documentation': '元素文档',
+  'History Configuration': '历史配置',
+  'History Time To Live': '历史的生存时间',
+  'Forms': '表单',
+  'Form Key': '表单key',
+  'Form Fields': '表单字段',
+  'Business Key': '业务key',
+  'Form Field': '表单字段',
+  'ID': '编号',
+  'Type': '类型',
+  'Label': '名称',
+  'Default Value': '默认值',
+  'Validation': '校验',
+  'Add Constraint': '添加约束',
+  'Config': '配置',
+  'Properties': '属性',
+  'Add Property': '添加属性',
+  'Value': '值',
+  'Listeners': '监听器',
+  'Execution Listener': '执行监听',
+  'Event Type': '事件类型',
+  'Listener Type': '监听器类型',
+  'Java Class': 'Java类',
+  'Expression': '表达式',
+  'Must provide a value': '必须提供一个值',
+  'Delegate Expression': '代理表达式',
+  'Script': '脚本',
+  'Script Format': '脚本格式',
+  'Script Type': '脚本类型',
+  'Inline Script': '内联脚本',
+  'External Script': '外部脚本',
+  'Resource': '资源',
+  'Field Injection': '字段注入',
+  'Extensions': '扩展',
+  'Input/Output': '输入/输出',
+  'Input Parameters': '输入参数',
+  'Output Parameters': '输出参数',
+  'Parameters': '参数',
+  'Output Parameter': '输出参数',
+  'Timer Definition Type': '定时器定义类型',
+  'Timer Definition': '定时器定义',
+  'Date': '日期',
+  'Duration': '持续',
+  'Cycle': '循环',
+  'Signal': '信号',
+  'Signal Name': '信号名称',
+  'Escalation': '升级',
+  'Error': '错误',
+  'Link Name': '链接名称',
+  'Condition': '条件名称',
+  'Variable Name': '变量名称',
+  'Variable Event': '变量事件',
+  'Specify more than one variable change event as a comma separated list.': '多个变量事件以逗号隔开',
+  'Wait for Completion': '等待完成',
+  'Activity Ref': '活动参考',
+  'Version Tag': '版本标签',
+  'Executable': '可执行文件',
+  'External Task Configuration': '扩展任务配置',
+  'Task Priority': '任务优先级',
+  'External': '外部',
+  'Connector': '连接器',
+  'Must configure Connector': '必须配置连接器',
+  'Connector Id': '连接器编号',
+  'Implementation': '实现方式',
+  'Field Injections': '字段注入',
+  'Fields': '字段',
+  'Result Variable': '结果变量',
+  'Topic': '主题',
+  'Configure Connector': '配置连接器',
+  'Input Parameter': '输入参数',
+  'Assignee': '代理人',
+  'Candidate Users': '候选用户',
+  'Candidate Groups': '候选组',
+  'Due Date': '到期时间',
+  'Follow Up Date': '跟踪日期',
+  'Priority': '优先级',
+  'The follow up date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)': '跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
+  'The due date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)': '跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
+  'Variables': '变量'
+}
+
+export const NodeName = {
+  'bpmn:Process': '流程',
+  'bpmn:StartEvent': '开始事件',
+  'bpmn:IntermediateThrowEvent': '中间事件',
+  'bpmn:Task': '任务',
+  'bpmn:SendTask': '发送任务',
+  'bpmn:ReceiveTask': '接收任务',
+  'bpmn:UserTask': '用户任务',
+  'bpmn:ManualTask': '手工任务',
+  'bpmn:BusinessRuleTask': '业务规则任务',
+  'bpmn:ServiceTask': '服务任务',
+  'bpmn:ScriptTask': '脚本任务',
+  'bpmn:EndEvent': '结束事件',
+  'bpmn:SequenceFlow': '流程线',
+  'bpmn:ExclusiveGateway': '互斥网关',
+  'bpmn:ParallelGateway': '并行网关',
+  'bpmn:InclusiveGateway': '相容网关',
+  'bpmn:ComplexGateway': '复杂网关',
+  'bpmn:EventBasedGateway': '事件网关'
+}

+ 140 - 0
src/components/Process/panel/ButtonsPanel.vue

@@ -0,0 +1,140 @@
+<template>
+  <div class="panel-tab__content">
+    <el-divider content-position="center">按钮设置</el-divider>
+    <el-table :data="elementButtonList" size="mini" max-height="240" border fit>
+      <el-table-column label="序号" width="50px" type="index" />
+      <el-table-column label="属性名" prop="name" min-width="100px" show-overflow-tooltip />
+      <el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />
+      <el-table-column label="操作" width="90px">
+        <template slot-scope="{ row, $index }">
+          <el-button size="mini" type="text" @click="openAttributesForm(row, $index)">修改</el-button>
+          <el-divider direction="vertical" />
+          <el-button size="mini" type="text" style="color: #ff4d4f" @click="removeAttributes(row, $index)">移除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="element-drawer__button_save">
+      <el-button size="mini" type="primary" icon="el-icon-plus" @click="openAttributesForm(null, -1)">添加按钮</el-button>
+    </div>
+
+    <el-dialog :visible.sync="buttonFormModelVisible" title="按钮配置" width="600px" append-to-body destroy-on-close>
+      <el-form :model="buttonForm" label-width="80px" size="mini" ref="attributeFormRef" @submit.native.prevent>
+        <el-form-item label="属性名:" prop="label">
+          <el-input v-model="buttonForm.label" clearable />
+        </el-form-item>
+        <el-form-item label="属性值:" prop="value">
+          <el-input v-model="buttonForm.value" clearable />
+        </el-form-item>
+      </el-form>
+      <template slot="footer">
+        <el-button size="mini" @click="buttonFormModelVisible = false">取 消</el-button>
+        <el-button size="mini" type="primary" @click="saveAttribute">确 定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {StrUtil} from "@/utils/StrUtil";
+
+export default {
+  name: "ButtonsPanel",
+  props: {
+    id: {
+      type: String,
+      required: true
+    },
+  },
+  data() {
+    return {
+      elementButtonList: [],
+      otherExtensionList: [],
+      buttonForm: {},
+      editingPropertyIndex: -1,
+      buttonFormModelVisible: false
+    };
+  },
+  watch: {
+    id: {
+      immediate: true,
+      handler(val) {
+        if (StrUtil.isNotBlank(val)) {
+          this.resetAttributesList();
+        }
+      }
+    }
+  },
+  methods: {
+    resetAttributesList() {
+      this.bpmnElement = this.modelerStore.element;
+      this.otherExtensionList = []; // 其他扩展配置
+      this.bpmnElementProperties =
+        this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => {
+          if (ex.$type !== `flowable:Buttons`) {
+            this.otherExtensionList.push(ex);
+          }
+          return ex.$type === `flowable:Buttons`;
+        }) ?? [];
+
+      // 保存所有的 扩展属性字段
+      this.bpmnElementButtonList = this.bpmnElementProperties.reduce((pre, current) => pre.concat(current.values), []);
+      // 复制 显示
+      this.elementButtonList = JSON.parse(JSON.stringify(this.bpmnElementButtonList ?? []));
+    },
+    openAttributesForm(attr, index) {
+      this.editingPropertyIndex = index;
+      this.buttonForm = index === -1 ? {} : JSON.parse(JSON.stringify(attr));
+      this.buttonFormModelVisible = true;
+      this.$nextTick(() => {
+        if (this.$refs["attributeFormRef"]) this.$refs["attributeFormRef"].clearValidate();
+      });
+    },
+    removeAttributes(attr, index) {
+      this.$confirm("确认移除该属性吗?", "提示", {
+        confirmButtonText: "确 认",
+        cancelButtonText: "取 消"
+      })
+        .then(() => {
+          this.elementButtonList.splice(index, 1);
+          this.bpmnElementButtonList.splice(index, 1);
+          // 新建一个属性字段的保存列表
+          const propertiesObject = this.modelerStore.moddle.create(`flowable:Properties`, {
+            values: this.bpmnElementButtonList
+          });
+          this.updateElementExtensions(propertiesObject);
+          this.resetAttributesList();
+        })
+        .catch(() => console.info("操作取消"));
+    },
+    saveAttribute() {
+      const { name, value } = this.buttonForm;
+      console.log(this.bpmnElementButtonList);
+      if (this.editingPropertyIndex !== -1) {
+        this.modelerStore.modeling.updateModdleProperties(this.bpmnElement, this.bpmnElementButtonList[this.editingPropertyIndex], {
+          name,
+          value
+        });
+      } else {
+        // 新建属性字段
+        const newPropertyObject = this.modelerStore.moddle.create(`flowable:Button`, { name, value });
+        // 新建一个属性字段的保存列表
+        const propertiesObject = this.modelerStore.moddle.create(`flowable:Buttons`, {
+          values: this.bpmnElementButtonList.concat([newPropertyObject])
+        });
+        this.updateElementExtensions(propertiesObject);
+      }
+      this.buttonFormModelVisible = false;
+      this.resetAttributesList();
+    },
+    updateElementExtensions(properties) {
+      const extensions = this.modelerStore.moddle.create("bpmn:ExtensionElements", {
+        values: this.otherExtensionList.concat([properties])
+      });
+
+      this.modelerStore.modeling.updateProperties(this.bpmnElement, {
+        extensionElements: extensions
+      });
+    }
+  }
+};
+</script>

+ 139 - 0
src/components/Process/panel/PropertiesPanel2.vue

@@ -0,0 +1,139 @@
+<template>
+  <div class="panel-tab__content">
+    <el-table :data="elementPropertyList" size="mini" max-height="240" border fit>
+      <el-table-column label="序号" width="50px" type="index" />
+      <el-table-column label="属性名" prop="name" min-width="100px" show-overflow-tooltip />
+      <el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />
+      <el-table-column label="操作" width="90px">
+        <template slot-scope="{ row, $index }">
+          <el-button size="mini" type="text" @click="openAttributesForm(row, $index)">修改</el-button>
+          <el-divider direction="vertical" />
+          <el-button size="mini" type="text" style="color: #ff4d4f" @click="removeAttributes(row, $index)">移除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="element-drawer__button">
+      <el-button size="mini" type="primary" icon="el-icon-plus" @click="openAttributesForm(null, -1)">添加属性</el-button>
+    </div>
+
+    <el-dialog :visible.sync="propertyFormModelVisible" title="属性配置" width="600px" append-to-body destroy-on-close>
+      <el-form :model="propertyForm" label-width="80px" size="mini" ref="attributeFormRef" @submit.native.prevent>
+        <el-form-item label="属性名:" prop="name">
+          <el-input v-model="propertyForm.name" clearable />
+        </el-form-item>
+        <el-form-item label="属性值:" prop="value">
+          <el-input v-model="propertyForm.value" clearable />
+        </el-form-item>
+      </el-form>
+      <template slot="footer">
+        <el-button size="mini" @click="propertyFormModelVisible = false">取 消</el-button>
+        <el-button size="mini" type="primary" @click="saveAttribute">确 定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {StrUtil} from "@/utils/StrUtil";
+
+export default {
+  name: "PropertiesPanel",
+  props: {
+    id: {
+      type: String,
+      required: true
+    },
+  },
+  data() {
+    return {
+      elementPropertyList: [],
+      otherExtensionList: [],
+      propertyForm: {},
+      editingPropertyIndex: -1,
+      propertyFormModelVisible: false
+    };
+  },
+  watch: {
+    id: {
+      immediate: true,
+      handler(val) {
+        if (StrUtil.isNotBlank(val)) {
+          this.resetAttributesList();
+        }
+      }
+    }
+  },
+  methods: {
+    resetAttributesList() {
+      this.bpmnElement = this.modelerStore.element;
+      this.otherExtensionList = []; // 其他扩展配置
+      this.bpmnElementProperties =
+        this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => {
+          if (ex.$type !== `flowable:Properties`) {
+            this.otherExtensionList.push(ex);
+          }
+          return ex.$type === `flowable:Properties`;
+        }) ?? [];
+
+      // 保存所有的 扩展属性字段
+      this.bpmnElementPropertyList = this.bpmnElementProperties.reduce((pre, current) => pre.concat(current.values), []);
+      // 复制 显示
+      this.elementPropertyList = JSON.parse(JSON.stringify(this.bpmnElementPropertyList ?? []));
+    },
+    openAttributesForm(attr, index) {
+      this.editingPropertyIndex = index;
+      this.propertyForm = index === -1 ? {} : JSON.parse(JSON.stringify(attr));
+      this.propertyFormModelVisible = true;
+      this.$nextTick(() => {
+        if (this.$refs["attributeFormRef"]) this.$refs["attributeFormRef"].clearValidate();
+      });
+    },
+    removeAttributes(attr, index) {
+      this.$confirm("确认移除该属性吗?", "提示", {
+        confirmButtonText: "确 认",
+        cancelButtonText: "取 消"
+      })
+        .then(() => {
+          this.elementPropertyList.splice(index, 1);
+          this.bpmnElementPropertyList.splice(index, 1);
+          // 新建一个属性字段的保存列表
+          const propertiesObject = this.modelerStore.moddle.create(`flowable:Properties`, {
+            values: this.bpmnElementPropertyList
+          });
+          this.updateElementExtensions(propertiesObject);
+          this.resetAttributesList();
+        })
+        .catch(() => console.info("操作取消"));
+    },
+    saveAttribute() {
+      const { name, value } = this.propertyForm;
+      console.log(this.bpmnElementPropertyList);
+      if (this.editingPropertyIndex !== -1) {
+        this.modelerStore.modeling.updateModdleProperties(this.bpmnElement, this.bpmnElementPropertyList[this.editingPropertyIndex], {
+          name,
+          value
+        });
+      } else {
+        // 新建属性字段
+        const newPropertyObject = this.modelerStore.moddle.create(`flowable:Property`, { name, value });
+        // 新建一个属性字段的保存列表
+        const propertiesObject = this.modelerStore.moddle.create(`flowable:Properties`, {
+          values: this.bpmnElementPropertyList.concat([newPropertyObject])
+        });
+        this.updateElementExtensions(propertiesObject);
+      }
+      this.propertyFormModelVisible = false;
+      this.resetAttributesList();
+    },
+    updateElementExtensions(properties) {
+      const extensions = this.modelerStore.moddle.create("bpmn:ExtensionElements", {
+        values: this.otherExtensionList.concat([properties])
+      });
+
+      this.modelerStore.modeling.updateProperties(this.bpmnElement, {
+        extensionElements: extensions
+      });
+    }
+  }
+};
+</script>

+ 133 - 0
src/components/Process/panel/commonPanel.vue

@@ -0,0 +1,133 @@
+<template>
+  <div>
+  <el-form :model="bpmnFormData" label-width="80px" :rules="rules"  size="small">
+      <el-form-item :label="bpmnFormData.$type === 'bpmn:Process'? '流程标识': '节点ID'" prop="id">
+        <el-input v-model="bpmnFormData.id" @change="updateElementTask('id')"/>
+      </el-form-item>
+      <el-form-item :label="bpmnFormData.$type === 'bpmn:Process'? '流程名称': '节点名称'" prop="name">
+        <el-input v-model="bpmnFormData.name"  @change="updateElementTask('name')"/>
+      </el-form-item>
+
+      <!--流程的基础属性-->
+      <template v-if="bpmnFormData.$type === 'bpmn:Process'">
+        <el-form-item label="流程分类" prop="processCategory">
+          <el-select v-model="bpmnFormData.processCategory" placeholder="请选择流程分类" @change="updateElementTask('processCategory')">
+            <el-option
+                v-for="dict in dict.type.sys_process_category"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+      </template>
+      <el-form-item v-if="bpmnFormData.$type === 'bpmn:SubProcess'" label="状态">
+        <el-switch v-model="bpmnFormData.isExpanded" active-text="展开" inactive-text="折叠" @change="updateElementTask('isExpanded')" />
+      </el-form-item>
+      <el-form-item label="节点描述">
+        <el-input
+          :rows="2"
+          type="textarea"
+          v-model="bpmnFormData.documentationValue"
+          @change="updateDocumentation"
+        />
+      </el-form-item>
+  </el-form>
+  </div>
+</template>
+
+<script>
+import {StrUtil} from '@/utils/StrUtil'
+
+export default {
+  name: "CommonPanel",
+  dicts: ['sys_process_category'],
+  /** 组件传值  */
+  props : {
+    id: {
+      type: String,
+      required: true
+    },
+  },
+  data() {
+    return {
+      rules:{
+        id: [
+          { required: true, message: '节点Id 不能为空', trigger: 'blur' },
+        ],
+        name: [
+          { required: true, message: '节点名称不能为空', trigger: 'blur' },
+        ],
+      },
+      bpmnFormData: {}
+    }
+  },
+  /** 传值监听 */
+  watch: {
+    id: {
+      handler(newVal) {
+        if (StrUtil.isNotBlank(newVal)) {
+          this.resetTaskForm();
+        }
+      },
+      immediate: true, // 立即生效
+    },
+  },
+
+  created() {
+  },
+  methods: {
+    resetTaskForm() {
+      // this.bpmnFormData = JSON.parse(JSON.stringify(this.modelerStore.element.businessObject));
+      this.bpmnFormData = Object.assign({}, this.modelerStore.element.businessObject);
+
+      // 使用 $set 确保 documentationValue 是响应式的
+      this.$set(this.bpmnFormData, 'documentationValue', this.modelerStore.element.businessObject.documentation?.[0]?.text || '');
+    },
+    updateElementTask(key) {
+      const taskAttr = Object.create(null);
+      taskAttr[key] = this.bpmnFormData[key] || null;
+      this.modelerStore.modeling.updateProperties(this.modelerStore.element, taskAttr);
+    },
+    updateDocumentation() {
+      // 确保 modelerStore 是 BPMN.js 的 Modeler 实例
+      const modeler = this.modelerStore.modeler; // 获取实际的 modeler 实例
+      const moddle = modeler.get('moddle');      // 通过 modeler 获取 moddle
+      const modeling = modeler.get('modeling');  // 通过 modeler 获取 modeling
+
+      // 创建新的文档对象
+      const documentation = moddle.create('bpmn:Documentation', {
+        text: this.bpmnFormData.documentationValue
+      });
+
+      // 获取当前元素的扩展元素
+      let extensionElements = this.modelerStore.element.businessObject.extensionElements;
+
+      if (!extensionElements) {
+        // 如果没有扩展元素,创建一个新的
+        extensionElements = moddle.create('bpmn:ExtensionElements', {
+          values: []
+        });
+      }
+
+      // 更新文档
+      modeling.updateProperties(this.modelerStore.element, {
+        documentation: [documentation],
+        extensionElements: extensionElements
+      });
+
+      // 强制更新模型
+      this.modelerStore.modeler.get('commandStack').execute('element.updateProperties', {
+        element: this.modelerStore.element,
+        properties: {
+          documentation: [documentation]
+        }
+      });
+
+      this.$emit('save');
+    }
+  }
+}
+
+
+</script>

+ 175 - 0
src/components/Process/panel/conditionPanel.vue

@@ -0,0 +1,175 @@
+<template>
+  <div>
+    <el-form label-width="100px" size="small" @submit.native.prevent>
+      <el-form-item>
+        <template slot="label">
+            <span>
+               流转类型
+               <el-tooltip placement="top">
+                  <template slot="content">
+                     <div>
+                              普通流转路径:流程执行过程中,一个元素被访问后,会沿着其所有出口顺序流继续执行。
+                        <br />默认流转路径:只有当没有其他顺序流可以选择时,才会选择默认顺序流作为活动的出口顺序流。流程会忽略默认顺序流上的条件。
+                        <br />条件流转路径:是计算其每个出口顺序流上的条件。当条件计算为true时,选择该出口顺序流。如果该方法选择了多条顺序流,则会生成多个执行,流程会以并行方式继续。
+                     </div>
+                  </template>
+                  <i class="el-icon-question" />
+               </el-tooltip>
+            </span>
+        </template>
+        <el-select v-model="bpmnFormData.type" @change="updateFlowType">
+          <el-option label="普通流转路径" value="normal" />
+          <el-option label="默认流转路径" value="default" />
+          <el-option label="条件流转路径" value="condition" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="条件格式" v-if="bpmnFormData.type === 'condition'" key="condition">
+        <el-select v-model="bpmnFormData.conditionType">
+          <el-option label="表达式" value="expression" />
+          <el-option label="脚本" value="script" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="表达式" v-if="bpmnFormData.conditionType && bpmnFormData.conditionType === 'expression'" key="express">
+        <el-input v-model="bpmnFormData.body" clearable @change="updateFlowCondition" />
+      </el-form-item>
+      <template v-if="bpmnFormData.conditionType && bpmnFormData.conditionType === 'script'">
+        <el-form-item label="脚本语言" key="language">
+          <el-input v-model="bpmnFormData.language" clearable @change="updateFlowCondition" />
+        </el-form-item>
+        <el-form-item label="脚本类型" key="scriptType">
+          <el-select v-model="bpmnFormData.scriptType">
+            <el-option label="内联脚本" value="inlineScript" />
+            <el-option label="外部脚本" value="externalScript" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="脚本" v-if="bpmnFormData.scriptType === 'inlineScript'" key="body">
+          <el-input v-model="bpmnFormData.body" type="textarea" clearable @change="updateFlowCondition" />
+        </el-form-item>
+        <el-form-item label="资源地址" v-if="bpmnFormData.scriptType === 'externalScript'" key="resource">
+          <el-input v-model="bpmnFormData.resource" clearable @change="updateFlowCondition" />
+        </el-form-item>
+      </template>
+    </el-form>
+  </div>
+</template>
+
+<script>
+
+import {StrUtil} from "@/utils/StrUtil";
+export default {
+  name: "BpmnModel",
+  /** 组件传值  */
+  props : {
+    id: {
+      type: String,
+      required: true
+    },
+  },
+  data() {
+    return {
+      bpmnElementSource: {},
+      bpmnElementSourceRef: {},
+      bpmnFormData: {}
+    }
+  },
+
+  /** 传值监听 */
+  watch: {
+    id: {
+      handler(newVal) {
+        if (StrUtil.isNotBlank(newVal)) {
+          this.resetFlowCondition();
+        }
+      },
+      immediate: true, // 立即生效
+    },
+  },
+  created() {
+
+  },
+  methods: {
+    // 方法区
+    resetFlowCondition() {
+      this.bpmnFormData = {
+        body: null
+      };
+      this.bpmnElementSource = this.modelerStore.element.source;
+      this.bpmnElementSourceRef = this.modelerStore.element.businessObject.sourceRef;
+      if (this.bpmnElementSourceRef && this.bpmnElementSourceRef.default && this.bpmnElementSourceRef.default.id === this.modelerStore.element.id) {
+        // 默认
+        this.$set(this.bpmnFormData, "type", "default");
+      } else if (!this.modelerStore.element.businessObject.conditionExpression) {
+        // 普通
+        this.$set(this.bpmnFormData, "type", "normal");
+      } else {
+        // 带条件
+        const conditionExpression = this.modelerStore.element.businessObject.conditionExpression;
+        this.bpmnFormData = {...conditionExpression, type: "condition"};
+        // resource 可直接标识 是否是外部资源脚本
+        if (this.bpmnFormData.resource) {
+          this.$set(this.bpmnFormData, "conditionType", "script");
+          this.$set(this.bpmnFormData, "scriptType", "externalScript");
+          return;
+        }
+        if (conditionExpression.language) {
+          this.$set(this.bpmnFormData, "conditionType", "script");
+          this.$set(this.bpmnFormData, "scriptType", "inlineScript");
+          return;
+        }
+        this.$set(this.bpmnFormData, "conditionType", "expression");
+      }
+    },
+
+    updateFlowType(flowType) {
+      // 正常条件类
+      if (flowType === "condition") {
+        const flowConditionRef = this.modelerStore.moddle.create("bpmn:FormalExpression");
+        this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
+          conditionExpression: flowConditionRef
+        });
+        return;
+      }
+      // 默认路径
+      if (flowType === "default") {
+        this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
+          conditionExpression: null
+        });
+        this.modelerStore.modeling.updateProperties(this.bpmnElementSource, {
+          default: this.modelerStore.element
+        });
+        // 清空条件格式
+        this.bpmnFormData.conditionType = null;
+        return;
+      }
+      // 清空条件格式
+      this.bpmnFormData.conditionType = null;
+      // 正常路径,如果来源节点的默认路径是当前连线时,清除父元素的默认路径配置
+      if (this.bpmnElementSourceRef.default && this.bpmnElementSourceRef.default.id === this.modelerStore.element.id) {
+        this.modelerStore.modeling.updateProperties(this.bpmnElementSource, {
+          default: null
+        });
+      }
+      this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
+        conditionExpression: null
+      });
+    },
+
+    updateFlowCondition() {
+      let {conditionType, scriptType, body, resource, language} = this.bpmnFormData;
+      let condition;
+      if (conditionType === "expression") {
+        condition = this.modelerStore.moddle.create("bpmn:FormalExpression", {body});
+      } else {
+        if (scriptType === "inlineScript") {
+          condition = this.modelerStore.moddle.create("bpmn:FormalExpression", {body, language});
+          this.$set(this.bpmnFormData, "resource", "");
+        } else {
+          this.$set(this.bpmnFormData, "body", "");
+          condition = this.modelerStore.moddle.create("bpmn:FormalExpression", {resource, language});
+        }
+      }
+      this.modelerStore.modeling.updateProperties(this.modelerStore.element, {conditionExpression: condition});
+    }
+  }
+}
+</script>

+ 472 - 0
src/components/Process/panel/executionListener.vue

@@ -0,0 +1,472 @@
+<template>
+  <div class="panel-tab__content">
+    <el-table :data="elementListenersList" size="mini" border>
+      <el-table-column label="序号" width="50px" type="index" />
+      <el-table-column label="类型" width="60px" prop="event" />
+      <el-table-column label="监听类型" width="80px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <el-button size="mini" type="primary" @click="openListenerForm(scope.row, scope.$index)">修改</el-button>
+          <el-divider direction="vertical" />
+          <el-button size="mini" type="danger" @click="removeListener(scope.row, scope.$index)">移除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="element-drawer__button_save">
+      <el-button type="primary" icon="el-icon-plus" size="small" @click="listenerSystemVisible = true">内置监听器</el-button>
+      <el-button type="primary" icon="el-icon-plus" size="small" @click="openListenerForm(null)" >自定义监听器</el-button>
+    </div>
+
+    <!-- 监听器 修改/创建 部分 -->
+    <el-drawer :visible.sync="listenerFormModelVisible" title="执行监听器" size="480px" append-to-body destroy-on-close>
+      <el-form :model="listenerForm" size="small" label-width="96px" ref="listenerFormRef" @submit.native.prevent>
+        <el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
+          <el-select v-model="listenerForm.event">
+            <el-option label="start" value="start" />
+            <el-option label="end" value="end" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
+          <el-select v-model="listenerForm.listenerType">
+            <el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
+          </el-select>
+        </el-form-item>
+        <el-form-item
+            v-if="listenerForm.listenerType === 'classListener'"
+            label="Java类"
+            prop="class"
+            key="listener-class"
+            :rules="{ required: true, trigger: ['blur', 'change'] }"
+        >
+          <el-input v-model="listenerForm.class" clearable />
+        </el-form-item>
+        <el-form-item
+            v-if="listenerForm.listenerType === 'expressionListener'"
+            label="表达式"
+            prop="expression"
+            key="listener-expression"
+            :rules="{ required: true, trigger: ['blur', 'change'] }"
+        >
+          <el-input v-model="listenerForm.expression" clearable />
+        </el-form-item>
+        <el-form-item
+            v-if="listenerForm.listenerType === 'delegateExpressionListener'"
+            label="代理表达式"
+            prop="delegateExpression"
+            key="listener-delegate"
+            :rules="{ required: true, trigger: ['blur', 'change'] }"
+        >
+          <el-input v-model="listenerForm.delegateExpression" clearable />
+        </el-form-item>
+        <template v-if="listenerForm.listenerType === 'scriptListener'">
+          <el-form-item
+              label="脚本格式"
+              prop="scriptFormat"
+              key="listener-script-format"
+              :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
+          >
+            <el-input v-model="listenerForm.scriptFormat" clearable />
+          </el-form-item>
+          <el-form-item
+              label="脚本类型"
+              prop="scriptType"
+              key="listener-script-type"
+              :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
+          >
+            <el-select v-model="listenerForm.scriptType">
+              <el-option label="内联脚本" value="inlineScript" />
+              <el-option label="外部脚本" value="externalScript" />
+            </el-select>
+          </el-form-item>
+          <el-form-item
+              v-if="listenerForm.scriptType === 'inlineScript'"
+              label="脚本内容"
+              prop="value"
+              key="listener-script"
+              :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
+          >
+            <el-input v-model="this.listenerForm" clearable />
+          </el-form-item>
+          <el-form-item
+              v-if="listenerForm.scriptType === 'externalScript'"
+              label="资源地址"
+              prop="resource"
+              key="listener-resource"
+              :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
+          >
+            <el-input v-model="listenerForm.resource" clearable />
+          </el-form-item>
+        </template>
+      </el-form>
+      <el-divider />
+      <p class="listener-filed__title">
+        <span><el-icon><BellFilled /></el-icon>注入字段:</span>
+        <el-button type="primary" size="mini" @click="openListenerFieldForm(null)">添加字段</el-button>
+      </p>
+      <el-table :data="fieldsListOfListener" size="mini" max-height="240" border fit style="flex: none">
+        <el-table-column label="序号" width="50px" type="index" />
+        <el-table-column label="字段名称" width="80px" prop="name" />
+        <el-table-column label="字段类型" width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
+        <el-table-column label="值内容" width="80px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
+        <el-table-column label="操作">
+          <template slot-scope="scope">
+            <el-button size="mini" type="primary" @click="openListenerFieldForm(scope.row, scope.$index)">修改</el-button>
+            <el-divider direction="vertical" />
+            <el-button size="mini" type="danger" @click="removeListenerField(scope.row, scope.$index)">移除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="element-drawer__button">
+        <el-button size="small" @click="listenerFormModelVisible = false">取 消</el-button>
+        <el-button size="small" type="primary" @click="saveListenerConfig">保 存</el-button>
+      </div>
+    </el-drawer>
+
+    <!-- 注入西段 修改/创建 部分 -->
+    <el-dialog title="字段配置" :visible.sync="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
+      <el-form :model="listenerFieldForm" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.native.prevent>
+        <el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
+          <el-input v-model="listenerFieldForm.name" clearable />
+        </el-form-item>
+        <el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
+          <el-select v-model="listenerFieldForm.fieldType">
+            <el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
+          </el-select>
+        </el-form-item>
+        <el-form-item
+            v-if="listenerFieldForm.fieldType === 'string'"
+            label="字段值:"
+            prop="string"
+            key="field-string"
+            :rules="{ required: true, trigger: ['blur', 'change'] }"
+        >
+          <el-input v-model="listenerFieldForm.string" clearable />
+        </el-form-item>
+        <el-form-item
+            v-if="listenerFieldForm.fieldType === 'expression'"
+            label="表达式:"
+            prop="expression"
+            key="field-expression"
+            :rules="{ required: true, trigger: ['blur', 'change'] }"
+        >
+          <el-input v-model="listenerFieldForm.expression" clearable />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+          <el-button size="small" @click="listenerFieldFormModelVisible= false">取 消</el-button>
+          <el-button size="small" type="primary" @click="saveListenerFiled">确 定</el-button>
+      </div>
+    </el-dialog>
+
+
+    <!-- 内置监听器 -->
+    <el-drawer :visible.sync="listenerSystemVisible" title="执行监听器" size="580px" append-to-body destroy-on-close>
+      <el-table v-loading="loading" :data="listenerList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="名称" align="center" prop="name" />
+        <el-table-column label="类型" align="center" prop="eventType"/>
+        <el-table-column label="监听类型" align="center" prop="valueType">
+          <template slot-scope="scope">
+            <dict-tag :options="dict.type.sys_listener_value_type" :value="scope.row.valueType"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="执行内容" align="center" prop="value" :show-overflow-tooltip="true"/>
+      </el-table>
+
+      <pagination
+          v-show="total>0"
+          :total="total"
+          layout="prev, pager, next"
+          :page.sync="queryParams.pageNum"
+          :limit.sync="queryParams.pageSize"
+          @pagination="getList"
+      />
+
+      <div class="element-drawer__button">
+        <el-button size="small" @click="listenerSystemVisible = false">取 消</el-button>
+        <el-button size="small" type="primary" :disabled="listenerSystemChecked" @click="saveSystemListener">保 存</el-button>
+      </div>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import { listListener } from "@/api/flowable/listener";
+import {
+  changeListenerObject,
+  createListenerObject,
+  createSystemListenerObject,
+  updateElementExtensions
+} from "../common/bpmnUtils";
+
+import {StrUtil} from "@/utils/StrUtil";
+
+export default {
+  name: "ExecutionListener",
+  // 内置监听器相关信息
+  dicts: ['sys_listener_value_type', 'sys_listener_event_type'],
+  /** 组件传值  */
+  props : {
+    id: {
+      type: String,
+      required: true
+    },
+  },
+  data() {
+    return {
+      elementListenersList: [], // 监听器列表
+      listenerForm: {},// 监听器详情表单
+      listenerFormModelVisible: false, // 监听器 修改 侧边栏显示状态
+      fieldsListOfListener: [],
+      bpmnElementListeners: [],
+      otherExtensionList: [],
+      listenerFieldForm: {}, // 监听器 注入字段 详情表单
+      listenerFieldFormModelVisible: false, // 监听器 注入字段表单弹窗 显示状态
+      editingListenerIndex: -1, // 监听器所在下标,-1 为新增
+      editingListenerFieldIndex: -1, // 字段所在下标,-1 为新增
+
+      listenerList: [],
+      checkedListenerData: [],
+      listenerSystemVisible: false,
+      listenerSystemChecked: true,
+      loading: true,
+      total: 0,
+      listenerTypeObject: {
+        classListener: "Java 类",
+        expressionListener: "表达式",
+        delegateExpressionListener: "代理表达式",
+        scriptListener: "脚本"
+      },
+      eventType: {
+        create: "创建",
+        assignment: "指派",
+        complete: "完成",
+        delete: "删除",
+        update: "更新",
+        timeout: "超时"
+      },
+      fieldTypeObject: {
+        string: "字符串",
+        expression: "表达式"
+      },
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        type: 2,
+      },
+    }
+  },
+
+  /** 传值监听 */
+  watch: {
+    id: {
+      handler(newVal) {
+        if (StrUtil.isNotBlank(newVal)) {
+          this.resetListenersList();
+        }
+      },
+      immediate: true, // 立即生效
+    },
+  },
+  created() {
+    this.getList();
+
+  },
+  methods: {
+    resetListenersList() {
+      this.bpmnElementListeners =
+        this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type === `flowable:ExecutionListener`) ?? [];
+      this.elementListenersList = this.bpmnElementListeners.map(listener => this.initListenerType(listener));
+      this.$emit('getExecutionListenerCount', this.elementListenersList.length)
+    },
+
+    // 打开 监听器详情 侧边栏
+    openListenerForm(listener, index) {
+      if (listener) {
+        this.listenerForm = this.initListenerForm(listener);
+        this.editingListenerIndex = index;
+      } else {
+        this.listenerForm = {};
+        this.editingListenerIndex = -1; // 标记为新增
+      }
+      if (listener && listener.fields) {
+        this.fieldsListOfListener = listener.fields.map(field => ({
+          ...field,
+          fieldType: field.string ? "string" : "expression"
+        }));
+      } else {
+        this.fieldsListOfListener = [];
+        this.$set(this.listenerForm, "fields", []);
+      }
+      // 打开侧边栏并清楚验证状态
+      this.listenerFormModelVisible = true;
+      this.$nextTick(() => {
+        if (this.$refs["listenerFormRef"]) this.$refs["listenerFormRef"].clearValidate();
+      });
+    },
+
+    // 打开监听器字段修改弹窗
+    openListenerFieldForm(field, index) {
+      this.listenerFieldForm = field ? JSON.parse(JSON.stringify(field)) : {};
+      this.editingListenerFieldIndex = field ? index : -1;
+      this.listenerFieldFormModelVisible = true;
+      this.$nextTick(() => {
+        if (this.$refs["listenerFieldFormRef"]) this.$refs["listenerFieldFormRef"].clearValidate();
+      });
+    },
+
+    // 保存监听器注入字段
+    async saveListenerFiled() {
+      let validateStatus = await this.$refs["listenerFieldFormRef"].validate();
+      if (!validateStatus) return; // 验证不通过直接返回
+      if (this.editingListenerFieldIndex === -1) {
+        this.fieldsListOfListener.push(this.listenerFieldForm);
+        this.listenerForm.fields.push(this.listenerFieldForm);
+      } else {
+        this.fieldsListOfListener.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
+        this.listenerForm.fields.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
+      }
+      this.listenerFieldFormModelVisible = false;
+      this.$nextTick(() => (this.listenerFieldForm = {}));
+    },
+
+    // 移除监听器字段
+    removeListenerField(field, index) {
+      this.$confirm("确认移除该字段吗?", "提示", {
+        confirmButtonText: "确 认",
+        cancelButtonText: "取 消"
+      }).then(() => {
+        this.fieldsListOfListener.splice(index, 1);
+        this.listenerForm.fields.splice(index, 1);
+      }).catch(() => console.info("操作取消"));
+    },
+
+    // 移除监听器
+    removeListener(listener, index) {
+      this.$confirm("确认移除该监听器吗?", "提示", {
+        confirmButtonText: "确 认",
+        cancelButtonText: "取 消"
+      }).then(() => {
+        this.bpmnElementListeners.splice(index, 1);
+        this.elementListenersList.splice(index, 1);
+        updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
+        this.$emit('getExecutionListenerCount', this.elementListenersList.length)
+      }).catch((r) => console.info(r, "操作取消"));
+    },
+
+    // 保存监听器配置
+    async saveListenerConfig() {
+      let validateStatus = await this.$refs["listenerFormRef"].validate();
+      if (!validateStatus) return; // 验证不通过直接返回
+      const listenerObject = createListenerObject(this.modelerStore.moddle, this.listenerForm, false, "flowable");
+      if (this.editingListenerIndex === -1) {
+        this.bpmnElementListeners.push(listenerObject);
+        this.elementListenersList.push(this.listenerForm);
+      } else {
+        this.bpmnElementListeners.splice(this.editingListenerIndex, 1, listenerObject);
+        this.elementListenersList.splice(this.editingListenerIndex, 1, this.listenerForm);
+      }
+      // 保存其他配置
+      this.otherExtensionList = this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `flowable:ExecutionListener`) ?? [];
+      updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
+      this.$emit('getExecutionListenerCount', this.elementListenersList.length)
+      // 4. 隐藏侧边栏
+      this.listenerFormModelVisible = false;
+      this.listenerForm = {};
+    },
+
+    initListenerType(listener) {
+      let listenerType;
+      if (listener.class) listenerType = "classListener";
+      if (listener.expression) listenerType = "expressionListener";
+      if (listener.delegateExpression) listenerType = "delegateExpressionListener";
+      if (listener.script) listenerType = "scriptListener";
+      return {
+        ...JSON.parse(JSON.stringify(listener)),
+        ...(listener.script ?? {}),
+        listenerType: listenerType
+      };
+    },
+
+    // 初始化表单数据
+    initListenerForm(listener) {
+      let self = {
+        ...listener
+      };
+      if (listener.script) {
+        self = {
+          ...listener,
+          ...listener.script,
+          scriptType: listener.script.resource ? "externalScript" : "inlineScript"
+        };
+      }
+      if (listener.event === "timeout" && listener.eventDefinitions) {
+        if (listener.eventDefinitions.length) {
+          let k = "";
+          for (let key in listener.eventDefinitions[0]) {
+            console.log(listener.eventDefinitions, key);
+            if (key.indexOf("time") !== -1) {
+              k = key;
+              self.eventDefinitionType = key.replace("time", "").toLowerCase();
+            }
+          }
+          console.log(k);
+          self.eventTimeDefinitions = listener.eventDefinitions[0][k].body;
+        }
+      }
+      return self;
+    },
+
+
+    /** 查询流程达式列表 */
+    getList() {
+      this.loading = true;
+      listListener(this.queryParams).then(response => {
+        this.listenerList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      // ids.value = selection.map(item => item.id);
+      // TODO 应该使用 push?
+      this.checkedListenerData = selection;
+      this.listenerSystemChecked = selection.length !== 1;
+    },
+
+    // 保存内置监听器
+    saveSystemListener() {
+      if (this.checkedListenerData.length > 0) {
+        this.checkedListenerData.forEach(value => {
+          // 保存其他配置
+          const listenerObject = createSystemListenerObject(this.modelerStore.moddle, value, false, "flowable");
+          this.bpmnElementListeners.push(listenerObject);
+          this.elementListenersList.push(changeListenerObject(value));
+          this.otherExtensionList = this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `flowable:TaskListener`) ?? [];
+          updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
+        })
+        // 回传显示数量
+        this.$emit('getExecutionListenerCount', this.elementListenersList.length)
+      }
+      this.checkedListenerData = [];
+      this.listenerSystemChecked = true;
+      // 隐藏侧边栏
+      this.listenerSystemVisible = false;
+    }
+  }
+}
+
+</script>
+
+<style lang="scss">
+@import '../style/process-panel';
+.flow-containers  .el-badge__content.is-fixed {
+  top: 18px;
+}
+.dialog-footer button:first-child {
+  margin-right: 10px;
+}
+</style>

+ 84 - 0
src/components/Process/panel/formPanel.vue

@@ -0,0 +1,84 @@
+<template>
+  <div>
+    <el-form label-width="80px" size="small" @submit.native.prevent>
+      <el-form-item label="流程表单">
+        <el-select v-model="bpmnFormData.formKey" clearable class="m-2" placeholder="挂载节点表单" @change="updateElementFormKey">
+          <el-option
+              v-for="item in formList"
+              :key="item.value"
+              :label="item.formName"
+              :value="item.formId"
+          />
+        </el-select>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+
+import { listAllForm } from '@/api/flowable/form'
+import {StrUtil} from "@/utils/StrUtil";
+export default {
+  name: "FormPanel",
+  /** 组件传值  */
+  props : {
+    id: {
+      type: String,
+      required: true
+    },
+  },
+  data() {
+    return {
+      formList: [], // 表单数据
+      bpmnFormData: {
+          formKey: ''
+      }
+    }
+  },
+
+  /** 传值监听 */
+  watch: {
+    id: {
+      handler(newVal) {
+        if (StrUtil.isNotBlank(newVal)) {
+          // 加载表单列表
+          this.getListForm();
+          this.resetFlowForm();
+        }
+      },
+      immediate: true, // 立即生效
+    },
+  },
+  created() {
+
+  },
+  methods: {
+
+    // 方法区
+    resetFlowForm() {
+      this.bpmnFormData.formKey = this.modelerStore.element.businessObject.formKey;
+    },
+
+    updateElementFormKey(val) {
+      if (StrUtil.isBlank(val)) {
+        delete this.modelerStore.element.businessObject[`formKey`]
+      } else {
+        this.modelerStore.modeling.updateProperties(this.modelerStore.element, {'formKey': val});
+      }
+    },
+
+    // 获取表单信息
+    getListForm() {
+      listAllForm().then(res => {
+        res.data.forEach(item => {
+          item.formId = item.formId.toString();
+        })
+        this.formList = res.data;
+      })
+    }
+  }
+}
+
+
+</script>

+ 236 - 0
src/components/Process/panel/multiInstance.vue

@@ -0,0 +1,236 @@
+<template>
+  <div class="panel-tab__content">
+    <el-form label-width="70px" @submit.native.prevent size="small">
+      <el-form-item label="参数说明">
+        <el-button size="small" type="primary" @click="dialogVisible = true">查看</el-button>
+      </el-form-item>
+      <el-form-item label="回路特性">
+        <el-select v-model="loopCharacteristics" @change="changeLoopCharacteristicsType">
+          <!--bpmn:MultiInstanceLoopCharacteristics-->
+          <el-option label="并行多重事件" value="ParallelMultiInstance" />
+          <el-option label="时序多重事件" value="SequentialMultiInstance" />
+          <!--bpmn:StandardLoopCharacteristics-->
+          <el-option label="循环事件" value="StandardLoop" />
+          <el-option label="无" value="Null" />
+        </el-select>
+      </el-form-item>
+      <template v-if="loopCharacteristics === 'ParallelMultiInstance' || loopCharacteristics === 'SequentialMultiInstance'">
+        <el-form-item label="循环基数" key="loopCardinality">
+          <el-input v-model="loopInstanceForm.loopCardinality" clearable @change="updateLoopCardinality" />
+        </el-form-item>
+        <el-form-item label="集合" key="collection">
+          <el-input v-model="loopInstanceForm.collection" clearable @change="updateLoopBase" />
+        </el-form-item>
+        <el-form-item label="元素变量" key="elementVariable">
+          <el-input v-model="loopInstanceForm.elementVariable" clearable @change="updateLoopBase" />
+        </el-form-item>
+        <el-form-item label="完成条件" key="completionCondition">
+          <el-input v-model="loopInstanceForm.completionCondition" clearable @change="updateLoopCondition" />
+        </el-form-item>
+        <el-form-item label="异步状态" key="async">
+          <el-checkbox v-model="loopInstanceForm.asyncBefore" label="异步前" @change="updateLoopAsync('asyncBefore')" />
+          <el-checkbox v-model="loopInstanceForm.asyncAfter" label="异步后" @change="updateLoopAsync('asyncAfter')" />
+          <el-checkbox
+              v-model="loopInstanceForm.exclusive"
+              v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore"
+              label="排除"
+              @change="updateLoopAsync('exclusive')"
+          />
+        </el-form-item>
+        <el-form-item label="重试周期" prop="timeCycle" v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore" key="timeCycle">
+          <el-input v-model="loopInstanceForm.timeCycle" clearable @change="updateLoopTimeCycle" />
+        </el-form-item>
+      </template>
+    </el-form>
+
+  <!-- 参数说明 -->
+  <el-dialog title="多实例参数" :visible.sync="dialogVisible" width="680px" @closed="$emit('close')">
+    <el-descriptions :column="1" border>
+      <el-descriptions-item label="使用说明">按照BPMN2.0规范的要求,用于为每个实例创建执行的父执行,会提供下列变量:</el-descriptions-item>
+      <el-descriptions-item label="collection(集合变量)">传入List参数, 一般为用户ID集合</el-descriptions-item>
+      <el-descriptions-item label="elementVariable(元素变量)">List中单个参数的名称</el-descriptions-item>
+      <el-descriptions-item label="loopCardinality(基数)">List循环次数</el-descriptions-item>
+      <el-descriptions-item label="isSequential(串并行)">Parallel: 并行多实例,Sequential: 串行多实例</el-descriptions-item>
+      <el-descriptions-item label="completionCondition(完成条件)">任务出口条件</el-descriptions-item>
+      <el-descriptions-item label="nrOfInstances(实例总数)">实例总数</el-descriptions-item>
+      <el-descriptions-item label="nrOfActiveInstances(未完成数)">当前活动的(即未完成的),实例数量。对于顺序多实例,这个值总为1</el-descriptions-item>
+      <el-descriptions-item label="nrOfCompletedInstances(已完成数)">已完成的实例数量</el-descriptions-item>
+      <el-descriptions-item label="loopCounter">给定实例在for-each循环中的index</el-descriptions-item>
+    </el-descriptions>
+  </el-dialog>
+  </div>
+</template>
+<script>
+import {StrUtil} from '@/utils/StrUtil'
+
+
+export default {
+  name: "MultiInstance",
+  /** 组件传值  */
+  props: {
+    id: {
+      type: String,
+      required: true
+    },
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      loopCharacteristics: "",
+      loopInstanceForm: {},
+      multiLoopInstance: {},
+      defaultLoopInstanceForm: {
+        completionCondition: "",
+        loopCardinality: "",
+        extensionElements: [],
+        asyncAfter: false,
+        asyncBefore: false,
+        exclusive: false
+      },
+    }
+  },
+
+  /** 传值监听 */
+  watch: {
+    id: {
+      handler(newVal) {
+        if (StrUtil.isNotBlank(newVal)) {
+          this.getElementLoop(this.modelerStore.element.businessObject);        }
+      },
+      immediate: true, // 立即生效
+    },
+  },
+  created() {
+
+  },
+  methods: {
+    // 方法区
+    getElementLoop(businessObject) {
+      if (!businessObject.loopCharacteristics) {
+        this.loopCharacteristics = "Null";
+        this.loopInstanceForm = {};
+        return;
+      }
+      if (businessObject.loopCharacteristics.$type === "bpmn:StandardLoopCharacteristics") {
+        this.loopCharacteristics = "StandardLoop";
+        this.loopInstanceForm = {};
+        return;
+      }
+      if (businessObject.loopCharacteristics.isSequential) {
+        this.loopCharacteristics = "SequentialMultiInstance";
+      } else {
+        this.loopCharacteristics = "ParallelMultiInstance";
+      }
+      // 合并配置
+      this.loopInstanceForm = {
+        ...this.defaultLoopInstanceForm,
+        ...businessObject.loopCharacteristics,
+        completionCondition: businessObject.loopCharacteristics?.completionCondition?.body ?? "",
+        loopCardinality: businessObject.loopCharacteristics?.loopCardinality?.body ?? ""
+      };
+      // 保留当前元素 businessObject 上的 loopCharacteristics 实例
+      this.multiLoopInstance = this.modelerStore.element.businessObject.loopCharacteristics;
+      // 更新表单
+      if (
+        businessObject.loopCharacteristics.extensionElements &&
+        businessObject.loopCharacteristics.extensionElements.values &&
+        businessObject.loopCharacteristics.extensionElements.values.length
+      ) {
+        this.$set(this.loopInstanceForm, "timeCycle", businessObject.loopCharacteristics.extensionElements.values[0].body);
+      }
+    },
+
+    changeLoopCharacteristicsType(type) {
+      // 切换类型取消原表单配置
+      this.loopInstanceForm = {...this.defaultLoopInstanceForm};
+      // 取消多实例配置
+      if (type === "Null") {
+        this.modelerStore.modeling.updateProperties(this.modelerStore.element, {loopCharacteristics: null});
+        return;
+      }
+      // 配置循环
+      if (type === "StandardLoop") {
+        const loopCharacteristicsObject = this.modelerStore.moddle.create("bpmn:StandardLoopCharacteristics");
+        this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
+          loopCharacteristics: loopCharacteristicsObject
+        });
+        this.multiLoopInstance = null;
+        return;
+      }
+      // 时序
+      if (type === "SequentialMultiInstance") {
+        this.multiLoopInstance = this.modelerStore.moddle.create("bpmn:MultiInstanceLoopCharacteristics", {
+          isSequential: true
+        });
+      } else {
+        this.multiLoopInstance = this.modelerStore.moddle.create("bpmn:MultiInstanceLoopCharacteristics");
+      }
+      this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
+        loopCharacteristics: this.multiLoopInstance
+      });
+    },
+
+    // 循环基数
+    updateLoopCardinality(cardinality) {
+      let loopCardinality = null;
+      if (cardinality && cardinality.length) {
+        loopCardinality = this.modelerStore.moddle.create("bpmn:FormalExpression", {body: cardinality});
+      }
+      this.modelerStore.modeling.updateModdleProperties(this.modelerStore.element, this.multiLoopInstance, {
+        loopCardinality
+      });
+    },
+
+    // 完成条件
+    updateLoopCondition(condition) {
+      let completionCondition = null;
+      if (condition && condition.length) {
+        completionCondition = this.modelerStore.moddle.create("bpmn:FormalExpression", {body: condition});
+      }
+      this.modelerStore.modeling.updateModdleProperties(this.modelerStore.element, this.multiLoopInstance, {
+        completionCondition
+      });
+    },
+
+    // 重试周期
+    updateLoopTimeCycle(timeCycle) {
+      const extensionElements = this.modelerStore.moddle.create("bpmn:ExtensionElements", {
+        values: [
+          this.modelerStore.moddle.create(`flowable:FailedJobRetryTimeCycle`, {
+            body: timeCycle
+          })
+        ]
+      });
+      this.modelerStore.modeling.updateModdleProperties(this.modelerStore.element, this.multiLoopInstance, {
+        extensionElements
+      });
+    },
+
+    // 直接更新的基础信息
+    updateLoopBase() {
+      this.modelerStore.modeling.updateModdleProperties(this.modelerStore.element, this.multiLoopInstance, {
+        collection: this.loopInstanceForm.collection || null,
+        elementVariable: this.loopInstanceForm.elementVariable || null
+      });
+    },
+
+    // 各异步状态
+    updateLoopAsync(key) {
+      const {asyncBefore, asyncAfter} = this.loopInstanceForm;
+      let asyncAttr = Object.create(null);
+      if (!asyncBefore && !asyncAfter) {
+        this.$set(this.loopInstanceForm, "exclusive", false);
+        asyncAttr = {asyncBefore: false, asyncAfter: false, exclusive: false, extensionElements: null};
+      } else {
+        asyncAttr[key] = this.loopInstanceForm[key];
+      }
+      this.modelerStore.modeling.updateModdleProperties(this.modelerStore.element, this.multiLoopInstance, asyncAttr);
+    }
+  }
+}
+
+</script>
+<style lang="scss">
+@import '../style/process-panel';
+</style>
+

+ 65 - 0
src/components/Process/panel/otherPanel.vue

@@ -0,0 +1,65 @@
+<template>
+  <div>
+  <el-form label-width="80px" size="small" @submit.native.prevent>
+    <el-form-item label="跳过表达式">
+      <el-input v-model="bpmnFormData.skipExpression" @change="updateElementTask('skipExpression')"/>
+    </el-form-item>
+    <el-form-item label="是否为补偿">
+      <el-input v-model="bpmnFormData.isForCompensation" @change="updateElementTask('isForCompensation')"/>
+    </el-form-item>
+    <el-form-item label="服务任务可触发">
+      <el-input v-model="bpmnFormData.triggerable" @change="updateElementTask('triggerable')"/>
+    </el-form-item>
+  </el-form>
+  </div>
+</template>
+
+<script>
+
+import {StrUtil} from "@/utils/StrUtil";
+export default {
+  name: "OtherPanel",
+  /** 组件传值  */
+  props : {
+    id: {
+      type: String,
+      required: true
+    },
+  },
+  data() {
+    return {
+      bpmnFormData: {}
+    }
+  },
+
+  /** 传值监听 */
+  watch: {
+    id: {
+      handler(newVal) {
+        if (StrUtil.isNotBlank(newVal)) {
+          this.resetTaskForm();
+        }
+      },
+      immediate: true, // 立即生效
+    },
+  },
+  created() {
+
+  },
+  methods: {
+    // 方法区
+    resetFlowForm() {
+      this.bpmnFormData = JSON.parse(JSON.stringify(this.modelerStore.element.businessObject));
+    },
+
+    updateElementTask(key) {
+      const taskAttr = Object.create(null);
+      taskAttr[key] = this.bpmnFormData[key] || null;
+      this.modelerStore.modeling.updateProperties(this.modelerStore.element, taskAttr);
+    }
+  }
+}
+
+
+
+</script>

+ 529 - 0
src/components/Process/panel/taskListener.vue

@@ -0,0 +1,529 @@
+<template>
+  <div class="panel-tab__content">
+    <el-table :data="elementListenersList" border size="mini">
+      <el-table-column label="序号" width="50px" type="index" />
+      <el-table-column label="类型" width="60px" show-overflow-tooltip :formatter="row => listenerEventTypeObject[row.event]" />
+<!--      <el-table-column label="事件id" min-width="70px" prop="id" show-overflow-tooltip />-->
+      <el-table-column label="监听类型" width="85px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
+      <el-table-column label="操作" >
+        <template  slot-scope="scope">
+          <el-button size="mini" type="primary" @click="openListenerForm(scope.row, scope.$index)">修改</el-button>
+          <el-divider direction="vertical" />
+          <el-button size="mini" type="danger" @click="removeListener(scope.row, scope.$index)">移除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="element-drawer__button_save">
+      <el-button type="primary" icon="el-icon-plus" size="small" @click="listenerSystemVisible = true">内置监听器</el-button>
+      <el-button type="primary" icon="el-icon-plus" size="small" @click="openListenerForm(null)">自定义监听器</el-button>
+    </div>
+
+    <!-- 监听器 修改/创建 部分 -->
+    <el-drawer :visible.sync="listenerFormModelVisible" title="任务监听器" size="480px" append-to-body destroy-on-close>
+      <el-form :model="listenerForm" size="small" label-width="110px" ref="listenerFormRef" @submit.native.prevent>
+        <el-form-item prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
+          <template slot="label">
+            <span>
+               事件类型
+               <el-tooltip placement="top">
+                  <template slot="content">
+                     <div>
+                              create(创建):当任务已经创建,并且所有任务参数都已经设置时触发。
+                        <br />assignment(指派):当任务已经指派给某人时触发。请注意:当流程执行到达用户任务时,
+                        <br />在触发create事件之前,会首先触发assignment事件。这顺序看起来不太自然,
+                        <br />但是有实际原因的:当收到create事件时,我们通常希望能看到任务的所有参数,包括办理人。
+                        <br />complete(完成):当任务已经完成,从运行时数据中删除前触发。
+                        <br />delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发。
+                     </div>
+                  </template>
+                  <i class="el-icon-question" />
+               </el-tooltip>
+            </span>
+          </template>
+          <el-select v-model="listenerForm.event">
+            <el-option v-for="i in Object.keys(listenerEventTypeObject)" :key="i" :label="listenerEventTypeObject[i]" :value="i" />
+          </el-select>
+        </el-form-item>
+<!--        <el-form-item label="监听器ID" prop="id" :rules="{ required: true, trigger: ['blur', 'change'] }">-->
+<!--          <el-input v-model="listenerForm.id" clearable />-->
+<!--        </el-form-item>-->
+        <el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
+          <template slot="label">
+            <span>
+               监听类型
+               <el-tooltip placement="top">
+                  <template slot="content">
+                     <div>
+                              class:需要调用的委托类。这个类必须实现org.flowable.engine.delegate.TaskListener接口。
+                        <br />assignment(指派):当任务已经指派给某人时触发。请注意:当流程执行到达用户任务时,
+                        <br />  在触发create事件之前,会首先触发assignment事件。这顺序看起来不太自然,
+                        <br />  但是有实际原因的:当收到create事件时,我们通常希望能看到任务的所有参数,包括办理人。
+                        <br />complete(完成):当任务已经完成,从运行时数据中删除前触发。
+                        <br />delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发。
+                     </div>
+                  </template>
+                  <i class="el-icon-question" />
+               </el-tooltip>
+            </span>
+          </template>
+          <el-select v-model="listenerForm.listenerType">
+            <el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
+          </el-select>
+        </el-form-item>
+        <el-form-item
+            v-if="listenerForm.listenerType === 'classListener'"
+            label="Java类"
+            prop="class"
+            key="listener-class"
+            :rules="{ required: true, trigger: ['blur', 'change'] }"
+        >
+          <el-input v-model="listenerForm.class" clearable />
+        </el-form-item>
+        <el-form-item
+            v-if="listenerForm.listenerType === 'expressionListener'"
+            label="表达式"
+            prop="expression"
+            key="listener-expression"
+            :rules="{ required: true, trigger: ['blur', 'change'] }"
+        >
+          <el-input v-model="listenerForm.expression" clearable />
+        </el-form-item>
+        <el-form-item
+            v-if="listenerForm.listenerType === 'delegateExpressionListener'"
+            label="代理表达式"
+            prop="delegateExpression"
+            key="listener-delegate"
+            :rules="{ required: true, trigger: ['blur', 'change'] }"
+        >
+          <el-input v-model="listenerForm.delegateExpression" clearable />
+        </el-form-item>
+        <template v-if="listenerForm.listenerType === 'scriptListener'">
+          <el-form-item
+              label="脚本格式"
+              prop="scriptFormat"
+              key="listener-script-format"
+              :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
+          >
+            <el-input v-model="listenerForm.scriptFormat" clearable />
+          </el-form-item>
+          <el-form-item
+              label="脚本类型"
+              prop="scriptType"
+              key="listener-script-type"
+              :rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
+          >
+            <el-select v-model="listenerForm.scriptType">
+              <el-option label="内联脚本" value="inlineScript" />
+              <el-option label="外部脚本" value="externalScript" />
+            </el-select>
+          </el-form-item>
+          <el-form-item
+              v-if="listenerForm.scriptType === 'inlineScript'"
+              label="脚本内容"
+              prop="value"
+              key="listener-script"
+              :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
+          >
+            <el-input v-model="listenerForm.value" clearable />
+          </el-form-item>
+          <el-form-item
+              v-if="listenerForm.scriptType === 'externalScript'"
+              label="资源地址"
+              prop="resource"
+              key="listener-resource"
+              :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
+          >
+            <el-input v-model="listenerForm.resource" clearable />
+          </el-form-item>
+        </template>
+
+        <template v-if="listenerForm.event === 'timeout'">
+          <el-form-item label="定时器类型" prop="eventDefinitionType" key="eventDefinitionType">
+            <el-select v-model="listenerForm.eventDefinitionType">
+              <el-option label="日期" value="date" />
+              <el-option label="持续时长" value="duration" />
+              <el-option label="循环" value="cycle" />
+              <el-option label="无" value="null" />
+            </el-select>
+          </el-form-item>
+          <el-form-item
+              v-if="!!listenerForm.eventDefinitionType && listenerForm.eventDefinitionType !== 'null'"
+              label="定时器"
+              prop="eventTimeDefinitions"
+              key="eventTimeDefinitions"
+              :rules="{ required: true, trigger: ['blur', 'change'], message: '请填写定时器配置' }"
+          >
+            <el-input v-model="listenerForm.eventTimeDefinitions" clearable />
+          </el-form-item>
+        </template>
+      </el-form>
+
+      <el-divider />
+      <p class="listener-filed__title">
+        <span><i class="el-icon-menu"></i>注入字段:</span>
+        <el-button size="small" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
+      </p>
+      <el-table :data="fieldsListOfListener" size="mini" max-height="240" border fit style="flex: none">
+        <el-table-column label="序号" width="50px" type="index" />
+        <el-table-column label="字段名称" width="80px" prop="name" />
+        <el-table-column label="字段类型" width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
+        <el-table-column label="值内容" width="80px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
+        <el-table-column label="操作">
+          <template slot-scope="scope">
+            <el-button size="mini" type="primary"  @click="openListenerFieldForm(scope.row, scope.$index)">修改</el-button>
+            <el-divider direction="vertical" />
+            <el-button size="mini" type="danger" @click="removeListenerField(scope.row, scope.$index)">移除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="element-drawer__button">
+        <el-button size="small" @click="listenerFormModelVisible = false">取 消</el-button>
+        <el-button size="small" type="primary" @click="saveListenerConfig">保 存</el-button>
+      </div>
+    </el-drawer>
+
+    <!-- 注入西段 修改/创建 部分 -->
+    <el-dialog title="字段配置" :visible.sync="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
+      <el-form :model="listenerFieldForm"  label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.native.prevent>
+        <el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
+          <el-input v-model="listenerFieldForm.name" clearable />
+        </el-form-item>
+        <el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
+          <el-select v-model="listenerFieldForm.fieldType">
+            <el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
+          </el-select>
+        </el-form-item>
+        <el-form-item
+            v-if="listenerFieldForm.fieldType === 'string'"
+            label="字段值:"
+            prop="string"
+            key="field-string"
+            :rules="{ required: true, trigger: ['blur', 'change'] }"
+        >
+          <el-input v-model="listenerFieldForm.string" clearable />
+        </el-form-item>
+        <el-form-item
+            v-if="listenerFieldForm.fieldType === 'expression'"
+            label="表达式:"
+            prop="expression"
+            key="field-expression"
+            :rules="{ required: true, trigger: ['blur', 'change'] }"
+        >
+          <el-input v-model="listenerFieldForm.expression" clearable />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+          <el-button size="small" @click="listenerFieldFormModelVisible= false">取 消</el-button>
+          <el-button size="small" type="primary" @click="saveListenerFiled">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 内置监听器 -->
+    <el-drawer :visible.sync="listenerSystemVisible" title="任务监听器" size="580px" append-to-body destroy-on-close>
+      <el-table v-loading="loading" :data="listenerList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="名称" align="center" prop="name" />
+        <el-table-column label="类型" align="center" prop="eventType"/>
+        <el-table-column label="监听类型" align="center" prop="valueType">
+          <template slot-scope="scope">
+            <dict-tag :options="dict.type.sys_listener_value_type" :value="scope.row.valueType"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="执行内容" align="center" prop="value" :show-overflow-tooltip="true"/>
+      </el-table>
+
+      <pagination
+          v-show="total>0"
+          :total="total"
+          layout="prev, pager, next"
+          :page.sync="queryParams.pageNum"
+          :limit.sync="queryParams.pageSize"
+          @pagination="getList"
+      />
+
+      <div class="element-drawer__button">
+        <el-button size="small" @click="listenerSystemVisible = false">取 消</el-button>
+        <el-button size="small" type="primary" :disabled="listenerSystemChecked" @click="saveSystemListener">保 存</el-button>
+      </div>
+    </el-drawer>
+  </div>
+</template>
+<script>
+import { listListener } from "@/api/flowable/listener";
+import {
+  changeListenerObject,
+  createListenerObject,
+  createSystemListenerObject,
+  updateElementExtensions
+} from "../common/bpmnUtils";
+
+import {StrUtil} from "@/utils/StrUtil";
+
+export default {
+  name: "TaskListener",
+  // 内置监听器相关信息
+  dicts: ['sys_listener_value_type', 'sys_listener_event_type'],
+  /** 组件传值  */
+  props : {
+    id: {
+      type: String,
+      required: true
+    },
+  },
+  data() {
+    return {
+      elementListenersList: [], // 监听器列表
+      listenerForm: {},// 监听器详情表单
+      listenerFormModelVisible: false, // 监听器 修改 侧边栏显示状态
+      fieldsListOfListener: [],
+      bpmnElementListeners: [],
+      otherExtensionList: [],
+      listenerFieldForm: {}, // 监听器 注入字段 详情表单
+      listenerFieldFormModelVisible: false, // 监听器 注入字段表单弹窗 显示状态
+      editingListenerIndex: -1, // 监听器所在下标,-1 为新增
+      editingListenerFieldIndex: -1, // 字段所在下标,-1 为新增
+
+      listenerList: [],
+      checkedListenerData: [],
+      listenerSystemVisible: false,
+      listenerSystemChecked: true,
+      loading: true,
+      total: 0,
+      listenerTypeObject: {
+        classListener: "Java 类",
+        expressionListener: "表达式",
+        delegateExpressionListener: "代理表达式",
+        scriptListener: "脚本"
+      },
+      listenerEventTypeObject:{
+        create: "创建",
+        assignment: "指派",
+        complete: "完成",
+        delete: "删除",
+        // update: "更新",
+        // timeout: "超时"
+      },
+      fieldTypeObject:{
+        string: "字符串",
+        expression: "表达式"
+      },
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        type: 1,
+      },
+    }
+  },
+
+  /** 传值监听 */
+  watch: {
+    id: {
+      handler(newVal) {
+        if (StrUtil.isNotBlank(newVal)) {
+          this.resetListenersList();
+        }
+      },
+      immediate: true, // 立即生效
+    },
+  },
+  created() {
+    this.getList();
+
+  },
+  methods: {
+    resetListenersList() {
+      this.bpmnElementListeners =
+        this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type === `flowable:TaskListener`) ?? [];
+      this.elementListenersList = this.bpmnElementListeners.map(listener => this.initListenerType(listener));
+      this.$emit('getTaskListenerCount', this.elementListenersList.length)
+    },
+
+    // 打开 监听器详情 侧边栏
+    openListenerForm(listener, index) {
+      if (listener) {
+        this.listenerForm = this.initListenerForm(listener);
+        this.editingListenerIndex = index;
+      } else {
+        this.listenerForm = {};
+        this.editingListenerIndex = -1; // 标记为新增
+      }
+      if (listener && listener.fields) {
+        this.fieldsListOfListener = listener.fields.map(field => ({
+          ...field,
+          fieldType: field.string ? "string" : "expression"
+        }));
+      } else {
+        this.fieldsListOfListener = [];
+        this.$set(this.listenerForm, "fields", []);
+      }
+      // 打开侧边栏并清楚验证状态
+      this.listenerFormModelVisible = true;
+      this.$nextTick(() => {
+        if (this.$refs["listenerFormRef"]) this.$refs["listenerFormRef"].clearValidate();
+      });
+    },
+
+    // 打开监听器字段修改弹窗
+    openListenerFieldForm(field, index) {
+      this.listenerFieldForm = field ? JSON.parse(JSON.stringify(field)) : {};
+      this.editingListenerFieldIndex = field ? index : -1;
+      this.listenerFieldFormModelVisible = true;
+      this.$nextTick(() => {
+        if (this.$refs["listenerFieldFormRef"]) this.$refs["listenerFieldFormRef"].clearValidate();
+      });
+    },
+
+    // 保存监听器注入字段
+    async saveListenerFiled() {
+      let validateStatus = await this.$refs["listenerFieldFormRef"].validate();
+      if (!validateStatus) return; // 验证不通过直接返回
+      if (this.editingListenerFieldIndex === -1) {
+        this.fieldsListOfListener.push(this.listenerFieldForm);
+        this.listenerForm.fields.push(this.listenerFieldForm);
+      } else {
+        this.fieldsListOfListener.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
+        this.listenerForm.fields.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
+      }
+      this.listenerFieldFormModelVisible = false;
+      this.$nextTick(() => (this.listenerFieldForm = {}));
+    },
+
+    // 移除监听器字段
+    removeListenerField(field, index) {
+      this.$confirm("确认移除该字段吗?", "提示", {
+        confirmButtonText: "确 认",
+        cancelButtonText: "取 消"
+      }).then(() => {
+        this.fieldsListOfListener.splice(index, 1);
+        this.listenerForm.fields.splice(index, 1);
+      }).catch(() => console.info("操作取消"));
+    },
+
+    // 移除监听器
+    removeListener(listener, index) {
+      this.$confirm("确认移除该监听器吗?", "提示", {
+        confirmButtonText: "确 认",
+        cancelButtonText: "取 消"
+      }).then(() => {
+        this.bpmnElementListeners.splice(index, 1);
+        this.elementListenersList.splice(index, 1);
+        updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
+        this.$emit('getTaskListenerCount', this.elementListenersList.length)
+      }).catch((r) => console.info(r, "操作取消"));
+    },
+
+    // 保存监听器配置
+    async saveListenerConfig() {
+      let validateStatus = await this.$refs["listenerFormRef"].validate();
+      if (!validateStatus) return; // 验证不通过直接返回
+      const listenerObject = createListenerObject(this.modelerStore.moddle, this.listenerForm, false, "flowable");
+      if (this.editingListenerIndex === -1) {
+        this.bpmnElementListeners.push(listenerObject);
+        this.elementListenersList.push(this.listenerForm);
+      } else {
+        this.bpmnElementListeners.splice(this.editingListenerIndex, 1, listenerObject);
+        this.elementListenersList.splice(this.editingListenerIndex, 1, this.listenerForm);
+      }
+      // 保存其他配置
+      this.otherExtensionList = this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `flowable:TaskListener`) ?? [];
+      updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
+      this.$emit('getTaskListenerCount', this.elementListenersList.length)
+      // 4. 隐藏侧边栏
+      this.listenerFormModelVisible = false;
+      this.listenerForm = {};
+    },
+
+    initListenerType(listener) {
+      let listenerType;
+      if (listener.class) listenerType = "classListener";
+      if (listener.expression) listenerType = "expressionListener";
+      if (listener.delegateExpression) listenerType = "delegateExpressionListener";
+      if (listener.script) listenerType = "scriptListener";
+      return {
+        ...JSON.parse(JSON.stringify(listener)),
+        ...(listener.script ?? {}),
+        listenerType: listenerType
+      };
+    },
+
+    // 初始化表单数据
+    initListenerForm(listener) {
+      let self = {
+        ...listener
+      };
+      if (listener.script) {
+        self = {
+          ...listener,
+          ...listener.script,
+          scriptType: listener.script.resource ? "externalScript" : "inlineScript"
+        };
+      }
+      if (listener.event === "timeout" && listener.eventDefinitions) {
+        if (listener.eventDefinitions.length) {
+          let k = "";
+          for (let key in listener.eventDefinitions[0]) {
+            console.log(listener.eventDefinitions, key);
+            if (key.indexOf("time") !== -1) {
+              k = key;
+              self.eventDefinitionType = key.replace("time", "").toLowerCase();
+            }
+          }
+          console.log(k);
+          self.eventTimeDefinitions = listener.eventDefinitions[0][k].body;
+        }
+      }
+      return self;
+    },
+
+
+    /** 查询流程达式列表 */
+    getList() {
+      this.loading = true;
+      listListener(this.queryParams).then(response => {
+        this.listenerList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      // ids.value = selection.map(item => item.id);
+      // TODO 应该使用 push?
+      this.checkedListenerData = selection;
+      this.listenerSystemChecked = selection.length !== 1;
+    },
+
+    // 保存内置监听器
+    saveSystemListener() {
+      if (this.checkedListenerData.length > 0) {
+        this.checkedListenerData.forEach(value => {
+          // 保存其他配置
+          const listenerObject = createSystemListenerObject(this.modelerStore.moddle, value, true, "flowable");
+          this.bpmnElementListeners.push(listenerObject);
+          this.elementListenersList.push(changeListenerObject(value));
+          this.otherExtensionList = this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `flowable:TaskListener`) ?? [];
+          updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
+        })
+        // 回传显示数量
+        this.$emit('getTaskListenerCount', this.elementListenersList.length)
+      }
+      this.checkedListenerData = [];
+      this.listenerSystemChecked = true;
+      // 隐藏侧边栏
+      this.listenerSystemVisible = false;
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+@import '../style/process-panel';
+.flow-containers  .el-badge__content.is-fixed {
+  top: 18px;
+}
+.dialog-footer button:first-child {
+  margin-right: 10px;
+}
+</style>

+ 424 - 0
src/components/Process/panel/taskPanel.vue

@@ -0,0 +1,424 @@
+<template>
+  <div>
+    <el-form label-width="80px" size="small">
+      <el-form-item label="异步">
+        <el-switch v-model="bpmnFormData.async" active-text="是" inactive-text="否" @change="updateElementTask('async')"/>
+      </el-form-item>
+      <el-form-item label="用户类型">
+        <el-select v-model="bpmnFormData.userType" placeholder="选择人员" @change="updateUserType">
+          <el-option
+            v-for="item in userTypeOption"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="指定人员" v-if="bpmnFormData.userType === 'assignee'">
+        <el-input-tag v-model="bpmnFormData.assignee" :value="bpmnFormData.assignee"/>
+        <el-button-group class="ml-4" style="margin-top: 4px">
+          <!--指定人员-->
+          <el-tooltip class="box-item" effect="dark" content="指定人员" placement="bottom">
+            <el-button size="mini" type="primary" icon="el-icon-user" @click="singleUserCheck"/>
+          </el-tooltip>
+          <!--选择表达式-->
+          <el-tooltip class="box-item" effect="dark" content="选择表达式" placement="bottom">
+            <el-button size="mini" type="warning" icon="el-icon-postcard" @click="singleExpCheck"/>
+          </el-tooltip>
+        </el-button-group>
+      </el-form-item>
+
+      <el-form-item label="候选人员" v-else-if="bpmnFormData.userType === 'candidateUsers'">
+        <el-input-tag v-model="bpmnFormData.candidateUsers" :value="bpmnFormData.candidateUsers"/>
+        <el-button-group class="ml-4" style="margin-top: 4px">
+          <!--候选人员-->
+          <el-tooltip class="box-item" effect="dark" content="候选人员" placement="bottom">
+            <el-button size="mini" type="primary" icon="el-icon-user" @click="multipleUserCheck"/>
+          </el-tooltip>
+          <!--选择表达式-->
+          <el-tooltip class="box-item" effect="dark" content="选择表达式" placement="bottom">
+            <el-button size="mini" type="warning" icon="el-icon-postcard" @click="singleExpCheck"/>
+          </el-tooltip>
+        </el-button-group>
+      </el-form-item>
+
+      <el-form-item label="候选角色" v-else>
+        <el-input-tag v-model="bpmnFormData.candidateGroups" :value="bpmnFormData.candidateGroups"/>
+        <el-button-group class="ml-4" style="margin-top: 4px">
+          <!--候选角色-->
+          <el-tooltip class="box-item" effect="dark" content="候选角色" placement="bottom">
+            <el-button size="mini" type="primary" icon="el-icon-user"  @click="multipleRoleCheck"/>
+          </el-tooltip>
+          <!--选择表达式-->
+          <el-tooltip class="box-item" effect="dark" content="选择表达式" placement="bottom">
+            <el-button size="mini" type="warning" icon="el-icon-postcard" @click="singleExpCheck"/>
+          </el-tooltip>
+        </el-button-group>
+      </el-form-item>
+
+      <el-form-item label="优先级">
+        <el-input v-model="bpmnFormData.priority" @change="updateElementTask('priority')"/>
+      </el-form-item>
+      <el-form-item label="到期时间">
+        <el-input v-model="bpmnFormData.dueDate" @change="updateElementTask('dueDate')"/>
+      </el-form-item>
+    </el-form>
+
+    <!--选择人员-->
+    <el-dialog
+      title="选择人员"
+      :visible.sync="userVisible"
+      width="60%"
+      :close-on-press-escape="false"
+      :show-close="false"
+    >
+      <flow-user v-if="userVisible" :checkType="checkType" :selectValues="selectData.assignee || selectData.candidateUsers" @handleUserSelect="userSelect"></flow-user>
+      <div slot="footer" class="dialog-footer">
+        <el-button size="small" @click="userVisible = false">取 消</el-button>
+        <el-button size="small" type="primary" @click="checkUserComplete">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <!--选择角色-->
+    <el-dialog
+      title="选择候选角色"
+      :visible.sync="roleVisible"
+      width="60%"
+      :close-on-press-escape="false"
+      :show-close="false"
+    >
+      <flow-role v-if="roleVisible" :selectValues="selectData.candidateGroups" @handleRoleSelect="roleSelect"></flow-role>
+      <div slot="footer" class="dialog-footer">
+        <el-button size="small" @click="roleVisible = false">取 消</el-button>
+        <el-button size="small" type="primary" @click="checkRoleComplete">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <!--选择表达式-->
+    <el-dialog
+      title="选择表达式"
+      :visible.sync="expVisible"
+      width="60%"
+      :close-on-press-escape="false"
+      :show-close="false"
+    >
+      <flow-exp v-if="expVisible" :selectValues="selectData.exp" @handleSingleExpSelect="expSelect"></flow-exp>
+      <div slot="footer" class="dialog-footer">
+        <el-button size="small" @click="expVisible = false">取 消</el-button>
+        <el-button size="small" type="primary" @click="checkExpComplete">确 定</el-button>
+      </div>
+    </el-dialog>
+
+
+  </div>
+</template>
+
+<script>
+import FlowUser from '@/components/flow/User'
+import FlowRole from '@/components/flow/Role'
+import FlowExp from '@/components/flow/Expression'
+import ElInputTag from '@/components/flow/ElInputTag'
+import {StrUtil} from '@/utils/StrUtil'
+
+export default {
+  name: "TaskPanel",
+  components: {
+    FlowUser,
+    FlowRole,
+    FlowExp,
+    ElInputTag
+  },
+  /** 组件传值  */
+  props : {
+    id: {
+      type: String,
+      required: true
+    },
+  },
+  data() {
+    return {
+      userVisible: false,
+      roleVisible: false,
+      expVisible: false,
+      isIndeterminate: true,
+      checkType: 'single', // 选类
+      userType: '',
+      userTypeOption: [
+        {label: '指定人员', value: 'assignee'},
+        {label: '候选人员', value: 'candidateUsers'},
+        {label: '候选角色', value: 'candidateGroups'}
+      ],
+      checkAll: false,
+      bpmnFormData: {
+        userType: "",
+        assignee: "",
+        candidateUsers: "",
+        candidateGroups: "",
+        dueDate: "",
+        priority: "",
+        dataType: "",
+        expId: "",
+      },
+      // 数据回显
+      selectData: {
+        assignee: null,
+        candidateUsers: null,
+        candidateGroups: null,
+        exp: null,
+      },
+      otherExtensionList:[],
+    }
+  },
+
+  /** 传值监听 */
+  watch: {
+    id: {
+      handler(newVal) {
+        if (StrUtil.isNotBlank(newVal)) {
+          this.resetTaskForm();
+        }
+      },
+      immediate: true, // 立即生效
+    },
+  },
+  created() {
+
+  },
+  methods: {
+    // 初始化表单
+    resetTaskForm() {
+      // 初始化设为空值
+      this.bpmnFormData = {
+        userType: "",
+        assignee: "",
+        candidateUsers: "",
+        candidateGroups: "",
+        dueDate: "",
+        priority: "",
+        dataType: "",
+        expId: "",
+      }
+      this.selectData = {
+        assignee: null,
+        candidateUsers: null,
+        candidateGroups: null,
+        exp: null,
+      }
+      // 流程节点信息上取值
+      for (let key in this.bpmnFormData) {
+        const value = this.modelerStore.element?.businessObject[key] || this.bpmnFormData[key];
+        this.$set(this.bpmnFormData, key, value);
+      }
+      // 人员信息回显
+      this.checkValuesEcho(this.bpmnFormData);
+    },
+
+    // 更新节点信息
+    updateElementTask(key) {
+      const taskAttr = Object.create(null);
+      taskAttr[key] = this.bpmnFormData[key] || "";
+      this.modelerStore.modeling.updateProperties(this.modelerStore.element, taskAttr);
+    },
+
+    // 更新自定义流程节点/参数信息
+    updateCustomElement(key, value) {
+      const taskAttr = Object.create(null);
+      taskAttr[key] = value;
+      this.modelerStore.modeling.updateProperties(this.modelerStore.element, taskAttr);
+    },
+
+    // 更新人员类型
+    updateUserType(val) {
+      // 删除xml中已选择数据类型节点
+      this.deleteFlowAttar();
+      delete this.modelerStore.element.businessObject[`userType`]
+      // 清除已选人员数据
+      this.bpmnFormData[val] = null;
+      this.selectData = {
+        assignee: null,
+        candidateUsers: null,
+        candidateGroups: null,
+        exp: null,
+      }
+      // 写入userType节点信息到xml
+      this.updateCustomElement('userType', val);
+    },
+
+    // 设计器右侧表单数据回显
+    checkValuesEcho(formData) {
+      if (StrUtil.isNotBlank(formData.expId)) {
+        this.getExpList(formData.expId, formData.userType);
+      } else {
+        if ("candidateGroups" === formData.userType) {
+          this.getRoleList(formData[formData.userType], formData.userType);
+        } else {
+          this.getUserList(formData[formData.userType], formData.userType);
+        }
+      }
+    },
+
+    // 获取表达式信息
+    getExpList(val, key) {
+      if (StrUtil.isNotBlank(val)) {
+        this.bpmnFormData[key] = this.modelerStore.expList?.find(item => item.id.toString() === val).name;
+        this.selectData.exp = this.modelerStore.expList?.find(item => item.id.toString() === val).id;
+      }
+    },
+
+    // 获取人员信息
+    getUserList(val, key) {
+      if (StrUtil.isNotBlank(val)) {
+        const newArr = this.modelerStore.userList?.filter(i => val.toString().split(',').includes(i.userId.toString()))
+        this.bpmnFormData[key] = newArr.map(item => item.nickName).join(',');
+        if ('assignee' === key) {
+          this.selectData[key] = newArr.find(item => item.userId.toString() === val.toString()).userId;
+        } else {
+          this.selectData[key] = newArr.map(item => item.userId);
+        }
+      }
+    },
+
+    // 获取角色信息
+    getRoleList(val, key) {
+      if (StrUtil.isNotBlank(val)) {
+        const newArr = this.modelerStore.roleList?.filter(i => val.split(',').includes(i.roleId.toString()))
+        this.bpmnFormData[key] = newArr.map(item => item.roleName).join(',');
+        if ('assignee' === key) {
+          this.selectData[key] = newArr.find(item => item.roleId.toString() === val).roleId;
+        } else {
+          this.selectData[key] = newArr.map(item => item.roleId);
+        }
+      }
+    },
+
+    // ------ 流程审批人员信息弹出框 start---------
+
+    /*单选人员*/
+    singleUserCheck() {
+      this.userVisible = true;
+      this.checkType = "single";
+    },
+
+    /*多选人员*/
+    multipleUserCheck() {
+      this.userVisible = true;
+      this.checkType = "multiple";
+    },
+
+    /*单选角色*/
+    singleRoleCheck() {
+      this.roleVisible = true;
+      this.checkType = "single";
+    },
+
+    /*多选角色*/
+    multipleRoleCheck() {
+      this.roleVisible = true;
+    },
+
+    /*单选表达式*/
+    singleExpCheck() {
+      this.expVisible = true;
+    },
+
+    // 表达式选中数据
+    expSelect(selection) {
+      if (selection) {
+        this.deleteFlowAttar();
+        this.bpmnFormData[this.bpmnFormData.userType] = selection.name;
+        this.updateCustomElement('dataType', selection.dataType);
+        this.updateCustomElement('expId', selection.id.toString());
+        this.updateCustomElement(this.bpmnFormData.userType, selection.expression);
+        this.handleSelectData("exp", selection.id);
+      }
+    },
+
+    // 用户选中数据 TODO: 后面更改为 点击确认按钮再赋值人员信息
+    userSelect(selection) {
+      if (selection) {
+        this.deleteFlowAttar();
+        this.updateCustomElement('dataType', 'fixed');
+        if (selection instanceof Array) {
+          const userIds = selection.map(item => item.userId);
+          const nickName = selection.map(item => item.nickName);
+          // userType = candidateUsers
+          this.bpmnFormData[this.bpmnFormData.userType] = nickName.join(',');
+          this.updateCustomElement(this.bpmnFormData.userType, userIds.join(','));
+          this.handleSelectData(this.bpmnFormData.userType, userIds);
+        } else {
+          // userType = assignee
+          this.bpmnFormData[this.bpmnFormData.userType] = selection.nickName;
+          this.updateCustomElement(this.bpmnFormData.userType, selection.userId);
+          this.handleSelectData(this.bpmnFormData.userType, selection.userId);
+        }
+      }
+    },
+
+    // 角色选中数据
+    roleSelect(selection, name) {
+      if (selection && name) {
+        this.deleteFlowAttar();
+        this.bpmnFormData[this.bpmnFormData.userType] = name;
+        this.updateCustomElement('dataType', 'fixed');
+        // userType = candidateGroups
+        this.updateCustomElement(this.bpmnFormData.userType, selection);
+        this.handleSelectData(this.bpmnFormData.userType, selection);
+      }
+    },
+
+    // 处理人员回显
+    handleSelectData(key, value) {
+      for (let oldKey in this.selectData) {
+        if (key !== oldKey) {
+          this.$set(this.selectData, oldKey, null);
+        } else {
+          this.$set(this.selectData, oldKey, value);
+        }
+      }
+    },
+
+    /*用户选中赋值*/
+    checkUserComplete() {
+      this.userVisible = false;
+      this.checkType = "";
+    },
+
+    /*候选角色选中赋值*/
+    checkRoleComplete() {
+      this.roleVisible = false;
+    },
+
+    /*表达式选中赋值*/
+    checkExpComplete() {
+      this.expVisible = false;
+    },
+
+    // 删除节点
+    deleteFlowAttar() {
+      delete this.modelerStore.element.businessObject[`dataType`]
+      delete this.modelerStore.element.businessObject[`expId`]
+      delete this.modelerStore.element.businessObject[`assignee`]
+      delete this.modelerStore.element.businessObject[`candidateUsers`]
+      delete this.modelerStore.element.businessObject[`candidateGroups`]
+    },
+
+    // 去重数据
+    unique(arr, code) {
+      const res = new Map();
+      return arr.filter((item) => !res.has(item[code]) && res.set(item[code], 1));
+    },
+
+    // 更新扩展属性信息
+    updateElementExtensions(properties) {
+      const extensions = this.modelerStore.moddle.create("bpmn:ExtensionElements", {
+        values: this.otherExtensionList.concat([properties])
+      });
+
+      this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
+        extensionElements: extensions
+      });
+    }
+  }
+}
+</script>

File diff suppressed because it is too large
+ 183 - 0
src/components/Process/style/flow-viewer.scss


+ 123 - 0
src/components/Process/style/process-panel.scss

@@ -0,0 +1,123 @@
+//.process-panel__container {
+//  box-sizing: border-box;
+//  padding: 0 8px;
+//  border-left: 1px solid #eeeeee;
+//  box-shadow: 0 0 8px #cccccc;
+//  max-height: 100%;
+//  overflow-y: scroll;
+//}
+.panel-tab__title {
+  font-weight: 600;
+  padding: 0 8px;
+  font-size: 1.1em;
+  line-height: 1.2em;
+  i {
+    margin-right: 8px;
+    font-size: 1.2em;
+  }
+}
+.panel-tab__content {
+  width: 100%;
+  box-sizing: border-box;
+  //border-top: 1px solid #eeeeee;
+  padding: 8px 16px;
+  .panel-tab__content--title {
+    display: flex;
+    justify-content: space-between;
+    padding-bottom: 8px;
+    span {
+      flex: 1;
+      text-align: left;
+    }
+  }
+}
+.element-property {
+  width: 100%;
+  display: flex;
+  align-items: flex-start;
+  margin: 8px 0;
+  .element-property__label {
+    display: block;
+    width: 90px;
+    text-align: right;
+    overflow: hidden;
+    padding-right: 12px;
+    line-height: 32px;
+    font-size: 14px;
+    box-sizing: border-box;
+  }
+  .element-property__value {
+    flex: 1;
+    line-height: 32px;
+  }
+  .el-form-item {
+    width: 100%;
+    margin-bottom: 0;
+    padding-bottom: 18px;
+  }
+}
+.list-property {
+  flex-direction: column;
+  .element-listener-item {
+    width: 100%;
+    display: inline-grid;
+    grid-template-columns: 16px auto 32px 32px;
+    grid-column-gap: 8px;
+  }
+  .element-listener-item + .element-listener-item {
+    margin-top: 8px;
+  }
+}
+.listener-filed__title {
+  width: 100%;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 0;
+  span {
+    font-size: 14px;
+  }
+  i {
+    margin-right: 8px;
+  }
+}
+.element-drawer__button {
+  margin-top: 8px;
+  display: inline-flex;
+  justify-content: space-around;
+}
+.element-drawer__button > .el-button {
+  width: 100%;
+}
+
+.element-drawer__button_save {
+  margin-top: 8px;
+  width: 100%;
+  display: inline-flex;
+  justify-content: space-around;
+}
+.element-drawer__button_save > .el-button {
+  width: 100%;
+}
+
+.el-collapse-item__content {
+  padding-bottom: 0;
+}
+.el-input.is-disabled .el-input__inner {
+  color: #999999;
+}
+.el-form-item.el-form-item--mini {
+  margin-bottom: 0;
+  & + .el-form-item {
+    margin-top: 16px;
+  }
+}
+.el-drawer__header{
+  margin-bottom: 0;
+  border-bottom: 1px solid #e8e8e8;
+  padding: 16px 16px 8px 16px;
+  font-size: 18px;
+  color: #303133;
+}
+.el-drawer__body{
+  padding: 10px;
+}

+ 257 - 0
src/components/parser/Parser.vue

@@ -0,0 +1,257 @@
+<script>
+import { deepClone } from '../../utils/index'
+import render from '../render/render.js'
+
+const ruleTrigger = {
+  'el-input': 'blur',
+  'el-input-number': 'blur',
+  'el-select': 'change',
+  'el-radio-group': 'change',
+  'el-checkbox-group': 'change',
+  'el-cascader': 'change',
+  'el-time-picker': 'change',
+  'el-date-picker': 'change',
+  'el-rate': 'change'
+}
+
+const layouts = {
+  colFormItem(h, scheme) {
+    const config = scheme.__config__
+    const listeners = buildListeners.call(this, scheme)
+
+    let labelWidth = config.labelWidth ? `${config.labelWidth}px` : null
+    if (config.showLabel === false) labelWidth = '0'
+    return (
+      <el-col span={config.span}>
+        <el-form-item label-width={labelWidth} prop={scheme.__vModel__}
+          label={config.showLabel ? config.label : ''}>
+          <render conf={scheme} on={listeners} />
+        </el-form-item>
+      </el-col>
+    )
+  },
+  rowFormItem(h, scheme) {
+    let child = renderChildren.apply(this, arguments)
+    if (scheme.type === 'flex') {
+      child = <el-row type={scheme.type} justify={scheme.justify} align={scheme.align}>
+              {child}
+            </el-row>
+    }
+    return (
+      <el-col span={scheme.span}>
+        <el-row gutter={scheme.gutter}>
+          {child}
+        </el-row>
+      </el-col>
+    )
+  }
+}
+
+function renderFrom(h) {
+  const { formConfCopy } = this
+
+  return (
+    <el-row gutter={formConfCopy.gutter}>
+      <el-form
+        size={formConfCopy.size}
+        label-position={formConfCopy.labelPosition}
+        disabled={formConfCopy.disabled}
+        label-width={`${formConfCopy.labelWidth}px`}
+        ref={formConfCopy.formRef}
+        // model不能直接赋值 https://github.com/vuejs/jsx/issues/49#issuecomment-472013664
+        props={{ model: this[formConfCopy.formModel] }}
+        rules={this[formConfCopy.formRules]}
+      >
+        {renderFormItem.call(this, h, formConfCopy.fields)}
+        {formConfCopy.formBtns && formBtns.call(this, h)}
+      </el-form>
+    </el-row>
+  )
+}
+
+function formBtns(h) {
+  return <el-col>
+    <el-form-item size="large">
+      <el-button type="primary" onClick={this.submitForm}>提交</el-button>
+      <el-button onClick={this.resetForm}>重置</el-button>
+    </el-form-item>
+  </el-col>
+}
+
+function renderFormItem(h, elementList) {
+  if (elementList) {
+    return elementList.map(scheme => {
+      const config = scheme.__config__
+      const layout = layouts[config.layout]
+
+      if (layout) {
+        return layout.call(this, h, scheme)
+      }
+      throw new Error(`没有与${config.layout}匹配的layout`)
+    })
+  }
+}
+
+function renderChildren(h, scheme) {
+  const config = scheme.__config__
+  if (!Array.isArray(config.children)) return null
+  return renderFormItem.call(this, h, config.children)
+}
+
+function setValue(event, config, scheme) {
+  this.$set(config, 'defaultValue', event)
+  this.$set(this[this.formConf.formModel], scheme.__vModel__, event)
+}
+
+function buildListeners(scheme) {
+  const config = scheme.__config__;
+  const methods = this.formConf.__methods__ || {};
+  const listeners = {};
+
+  // 给__methods__中的方法绑定this和event
+  Object.keys(methods).forEach((key) => {
+    listeners[key] = (event) => methods[key].call(this, event);
+  });
+
+  // 响应 render.js 中的 vModel $emit('input', val)
+  listeners.input = (event) => setValue.call(this, event, config, scheme);
+
+  // 上传表单元素组件的成功、移除和预览事件
+  if (config.tag === "el-upload") {
+    listeners.upload = (response, file, fileList) =>
+      setUpload.call(this, config, scheme, response, file, fileList);
+
+    listeners.deleteUpload = (file, fileList) =>
+      deleteUpload.call(this, config, scheme, file, fileList);
+
+    listeners.previewUpload = (file, fileList) =>
+      window.open(file.url, "_blank");
+  }
+
+  return listeners;
+}
+
+//获取上传表单元素组件 上传的文件
+function setUpload(config, scheme, response, file, fileList) {
+  //response: 上传接口返回的数据
+  let fileObj = {
+    name: response.fileName,
+    url: response.url,
+    fileType: response.contentType,
+  };
+  let oldValue = "";
+  try {
+    oldValue = JSON.parse(this[this.formConf.formModel][scheme.__vModel__]);
+  } catch (err) {
+    console.warn(err);
+  }
+
+  if (oldValue) {
+    oldValue.push(fileObj);
+  } else {
+    oldValue = [fileObj];
+  }
+
+  this.$set(config, "defaultValue", JSON.stringify(oldValue));
+  this.$set(
+    this[this.formConf.formModel],
+    scheme.__vModel__,
+    JSON.stringify(oldValue)
+  );
+}
+
+//获取上传表单元素组件 删除文件后的文件列表
+function deleteUpload(config, scheme, file, fileList) {
+  let oldValue = JSON.parse(this[this.formConf.formModel][scheme.__vModel__]);
+
+  //file 删除的文件
+  //过滤掉删除的文件
+  let newValue = oldValue.filter((item) => item.name !== file.name);
+  this.$set(config, "defaultValue", JSON.stringify(newValue));
+  this.$set(
+    this[this.formConf.formModel],
+    scheme.__vModel__,
+    JSON.stringify(newValue)
+  );
+}
+
+export default {
+  components: {
+    render
+  },
+  props: {
+    formConf: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    const data = {
+      formConfCopy: deepClone(this.formConf),
+      [this.formConf.formModel]: {},
+      [this.formConf.formRules]: {}
+    }
+    if (data.formConfCopy.fields) {
+      this.initFormData(data.formConfCopy.fields, data[this.formConf.formModel])
+      this.buildRules(data.formConfCopy.fields, data[this.formConf.formRules])
+    }
+    return data
+  },
+  methods: {
+    initFormData(componentList, formData) {
+      componentList.forEach(cur => {
+        const config = cur.__config__
+        if (cur.__vModel__) formData[cur.__vModel__] = config.defaultValue
+        if (config.children) this.initFormData(config.children, formData)
+      })
+    },
+    buildRules(componentList, rules) {
+      componentList.forEach(cur => {
+        const config = cur.__config__
+        if (Array.isArray(config.regList)) {
+          if (config.required) {
+            const required = { required: config.required, message: cur.placeholder }
+            if (Array.isArray(config.defaultValue)) {
+              required.type = 'array'
+              required.message = `请至少选择一个${config.label}`
+            }
+            required.message === undefined && (required.message = `${config.label}不能为空`)
+            config.regList.push(required)
+          }
+          rules[cur.__vModel__] = config.regList.map(item => {
+            item.pattern && (item.pattern = eval(item.pattern))
+            item.trigger = ruleTrigger && ruleTrigger[config.tag]
+            return item
+          })
+        }
+        if (config.children) this.buildRules(config.children, rules)
+      })
+    },
+    resetForm() {
+      this.formConfCopy = deepClone(this.formConf)
+      this.$refs[this.formConf.formRef].resetFields()
+    },
+    submitForm() {
+      this.$refs[this.formConf.formRef].validate(valid => {
+        if (!valid) return false
+        // 触发sumit事件
+        // this.$emit('submit', this[this.formConf.formModel])
+        const params = {
+          formData: this.formConfCopy,
+          valData: this[this.formConf.formModel]
+        }
+        this.$emit('submit', params)
+        return true
+      })
+    },
+    // 传值给父组件
+    getData(){
+      this.$emit('getData', this[this.formConf.formModel])
+      // this.$emit('getData',this.formConfCopy)
+    }
+  },
+  render(h) {
+    return renderFrom.call(this, h)
+  }
+}
+</script>

+ 17 - 0
src/components/parser/README.md

@@ -0,0 +1,17 @@
+## form-generator JSON 解析器
+>用于将form-generator导出的JSON解析成一个表单。
+
+### 安装组件
+```
+npm i form-gen-parser
+```
+或者
+```
+yarn add form-gen-parser
+```
+
+### 使用示例
+> [查看在线示例](https://mrhj.gitee.io/form-generator/#/parser)  
+
+示例代码:  
+> [src\components\parser\example\Index.vue](https://github.com/JakHuang/form-generator/blob/dev/src/components/parser/example/Index.vue)

+ 324 - 0
src/components/parser/example/Index.vue

@@ -0,0 +1,324 @@
+<template>
+  <div class="test-form">
+    <parser :form-conf="formConf" @submit="sumbitForm1" />
+    <parser :key="key2" :form-conf="formConf" @submit="sumbitForm2" />
+    <el-button @click="change">
+      change
+    </el-button>
+  </div>
+</template>
+
+<script>
+import Parser from '../Parser'
+
+// 若parser是通过安装npm方式集成到项目中的,使用此行引入
+// import Parser from 'form-gen-parser'
+
+export default {
+  components: {
+    Parser
+  },
+  props: {},
+  data() {
+    return {
+      key2: +new Date(),
+      formConf: {
+        fields: [
+          {
+            __config__: {
+              label: '单行文本',
+              labelWidth: null,
+              showLabel: true,
+              changeTag: true,
+              tag: 'el-input',
+              tagIcon: 'input',
+              required: true,
+              layout: 'colFormItem',
+              span: 24,
+              document: 'https://element.eleme.cn/#/zh-CN/component/input',
+              regList: [
+                {
+                  pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
+                  message: '手机号格式错误'
+                }
+              ]
+            },
+            __slot__: {
+              prepend: '',
+              append: ''
+            },
+            __vModel__: 'mobile',
+            placeholder: '请输入手机号',
+            style: {
+              width: '100%'
+            },
+            clearable: true,
+            'prefix-icon': 'el-icon-mobile',
+            'suffix-icon': '',
+            maxlength: 11,
+            'show-word-limit': true,
+            readonly: false,
+            disabled: false
+          },
+          {
+            __config__: {
+              label: '日期范围',
+              tag: 'el-date-picker',
+              tagIcon: 'date-range',
+              defaultValue: null,
+              span: 24,
+              showLabel: true,
+              labelWidth: null,
+              required: true,
+              layout: 'colFormItem',
+              regList: [],
+              changeTag: true,
+              document:
+                'https://element.eleme.cn/#/zh-CN/component/date-picker',
+              formId: 101,
+              renderKey: 1585980082729
+            },
+            style: {
+              width: '100%'
+            },
+            type: 'daterange',
+            'range-separator': '至',
+            'start-placeholder': '开始日期',
+            'end-placeholder': '结束日期',
+            disabled: false,
+            clearable: true,
+            format: 'yyyy-MM-dd',
+            'value-format': 'yyyy-MM-dd',
+            readonly: false,
+            __vModel__: 'field101'
+          },
+          {
+            __config__: {
+              layout: 'rowFormItem',
+              tagIcon: 'row',
+              label: '行容器',
+              layoutTree: true,
+              children: [
+                {
+                  __config__: {
+                    label: '评分',
+                    tag: 'el-rate',
+                    tagIcon: 'rate',
+                    defaultValue: 0,
+                    span: 24,
+                    showLabel: true,
+                    labelWidth: null,
+                    layout: 'colFormItem',
+                    required: true,
+                    regList: [],
+                    changeTag: true,
+                    document: 'https://element.eleme.cn/#/zh-CN/component/rate',
+                    formId: 102,
+                    renderKey: 1586839671259
+                  },
+                  style: {},
+                  max: 5,
+                  'allow-half': false,
+                  'show-text': false,
+                  'show-score': false,
+                  disabled: false,
+                  __vModel__: 'field102'
+                }
+              ],
+              document: 'https://element.eleme.cn/#/zh-CN/component/layout',
+              formId: 101,
+              span: 24,
+              renderKey: 1586839668999,
+              componentName: 'row101',
+              gutter: 15
+            },
+            type: 'default',
+            justify: 'start',
+            align: 'top'
+          },
+          {
+            __config__: {
+              label: '按钮',
+              showLabel: true,
+              changeTag: true,
+              labelWidth: null,
+              tag: 'el-button',
+              tagIcon: 'button',
+              span: 24,
+              layout: 'colFormItem',
+              document: 'https://element.eleme.cn/#/zh-CN/component/button',
+              renderKey: 1594288459289
+            },
+            __slot__: {
+              default: '测试按钮1'
+            },
+            type: 'primary',
+            icon: 'el-icon-search',
+            round: false,
+            size: 'medium',
+            plain: false,
+            circle: false,
+            disabled: false,
+            on: {
+              click: 'clickTestButton1'
+            }
+          }
+        ],
+        __methods__: {
+          clickTestButton1() {
+            console.log(
+              `%c【测试按钮1】点击事件里可以访问当前表单:
+                1) formModel='formData', 所以this.formData可以拿到当前表单的model
+                2) formRef='elForm', 所以this.$refs.elForm可以拿到当前表单的ref(vue组件)
+              `,
+              'color:#409EFF;font-size: 15px'
+            )
+            console.log('表单的Model:', this.formData)
+            console.log('表单的ref:', this.$refs.elForm)
+          }
+        },
+        formRef: 'elForm',
+        formModel: 'formData',
+        size: 'small',
+        labelPosition: 'right',
+        labelWidth: 100,
+        formRules: 'rules',
+        gutter: 15,
+        disabled: false,
+        span: 24,
+        formBtns: true,
+        unFocusedComponentBorder: false
+      },
+      formConf2: {
+        fields: [
+          {
+            __config__: {
+              label: '单行文本',
+              labelWidth: null,
+              showLabel: true,
+              changeTag: true,
+              tag: 'el-input',
+              tagIcon: 'input',
+              required: true,
+              layout: 'colFormItem',
+              span: 24,
+              document: 'https://element.eleme.cn/#/zh-CN/component/input',
+              regList: [
+                {
+                  pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
+                  message: '手机号格式错误'
+                }
+              ]
+            },
+            __slot__: {
+              prepend: '',
+              append: ''
+            },
+            __vModel__: 'mobile',
+            placeholder: '请输入手机号',
+            style: {
+              width: '100%'
+            },
+            clearable: true,
+            'prefix-icon': 'el-icon-mobile',
+            'suffix-icon': '',
+            maxlength: 11,
+            'show-word-limit': true,
+            readonly: false,
+            disabled: false
+          },
+          {
+            __config__: {
+              label: '日期范围',
+              tag: 'el-date-picker',
+              tagIcon: 'date-range',
+              defaultValue: null,
+              span: 24,
+              showLabel: true,
+              labelWidth: null,
+              required: true,
+              layout: 'colFormItem',
+              regList: [],
+              changeTag: true,
+              document:
+                'https://element.eleme.cn/#/zh-CN/component/date-picker',
+              formId: 101,
+              renderKey: 1585980082729
+            },
+            style: {
+              width: '100%'
+            },
+            type: 'daterange',
+            'range-separator': '至',
+            'start-placeholder': '开始日期',
+            'end-placeholder': '结束日期',
+            disabled: false,
+            clearable: true,
+            format: 'yyyy-MM-dd',
+            'value-format': 'yyyy-MM-dd',
+            readonly: false,
+            __vModel__: 'field101'
+          }
+        ],
+        formRef: 'elForm',
+        formModel: 'formData',
+        size: 'small',
+        labelPosition: 'right',
+        labelWidth: 100,
+        formRules: 'rules',
+        gutter: 15,
+        disabled: false,
+        span: 24,
+        formBtns: true,
+        unFocusedComponentBorder: false
+      }
+    }
+  },
+  computed: {},
+  watch: {},
+  created() {},
+  mounted() {
+    // 表单数据回填,模拟异步请求场景
+    setTimeout(() => {
+      // 请求回来的表单数据
+      const data = {
+        mobile: '18836662555'
+      }
+      // 回填数据
+      this.fillFormData(this.formConf, data)
+      // 更新表单
+      this.key2 = +new Date()
+    }, 2000)
+  },
+  methods: {
+    fillFormData(form, data) {
+      form.fields.forEach(item => {
+        const val = data[item.__vModel__]
+        if (val) {
+          item.__config__.defaultValue = val
+        }
+      })
+    },
+    change() {
+      this.key2 = +new Date()
+      const t = this.formConf
+      this.formConf = this.formConf2
+      this.formConf2 = t
+    },
+    sumbitForm1(data) {
+      console.log('sumbitForm1提交数据:', data)
+    },
+    sumbitForm2(data) {
+      console.log('sumbitForm2提交数据:', data)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.test-form {
+  margin: 15px auto;
+  width: 800px;
+  padding: 15px;
+}
+</style>

+ 3 - 0
src/components/parser/index.js

@@ -0,0 +1,3 @@
+import Parser from './Parser'
+
+export default Parser

+ 25 - 0
src/components/parser/package.json

@@ -0,0 +1,25 @@
+{
+  "name": "form-gen-parser",
+  "version": "1.0.3",
+  "description": "表单json解析器",
+  "main": "lib/form-gen-parser.umd.js",
+  "directories": {
+    "example": "example"
+  },
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/JakHuang/form-generator.git"
+  },
+  "dependencies": {
+    "form-gen-render": "^1.0.0"
+  },
+  "author": "jakHuang",
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/JakHuang/form-generator/issues"
+  },
+  "homepage": "https://github.com/JakHuang/form-generator/blob/dev/src/components/parser"
+}

+ 600 - 0
src/views/flowable/definition/index.vue

@@ -0,0 +1,600 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-position="top">
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="名称" prop="name">
+            <el-input v-model="queryParams.name" placeholder="请输入名称" clearable size="small"
+              @keyup.enter.native="handleQuery" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="开始时间" prop="deployTime">
+            <el-date-picker clearable size="small" v-model="queryParams.deployTime" type="date"
+              value-format="yyyy-MM-dd" placeholder="选择时间" style="width: 100%;">
+            </el-date-picker>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6"></el-col>
+        <el-col :span="6"></el-col>
+      </el-row>
+
+
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="搜索">
+            <el-button style="width: 100%;" type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="重置">
+            <el-button style="width: 100%;" icon="el-icon-refresh" type="primary" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <!--      <el-col :span="1.5">-->
+      <!--        <el-button-->
+      <!--          type="primary"-->
+      <!--          icon="el-icon-upload"-->
+      <!--          size="small"-->
+      <!--          @click="handleImport"-->
+      <!--        >导入</el-button>-->
+      <!--      </el-col>-->
+      <el-col :span="1.5">
+        <el-button type="success" class="editButton" icon="el-icon-plus" size="small" @click="handleLoadXml">新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" icon="el-icon-delete" size="small" :disabled="multiple" @click="handleDelete"
+          v-hasPermi="['system:deployment:remove']">删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+    <el-alert title="流程设计说明" type="success">
+      <template slot='title'>
+        <p>流程设计说明:</p>
+        <div>1、XML文件中的流程定义id属性用作流程定义的key参数。</div>
+        <div>2、XML文件中的流程定义name属性用作流程定义的name参数。如果未给定name属性,会使用id作为name。</div>
+        <div>3、当每个唯一key的流程第一次部署时,指定版本为1。对其后所有使用相同key的流程定义,部署时版本会在该key当前已部署的最高版本号基础上加1。key参数用于区分流程定义。</div>
+        <div>
+          4、id参数设置为{processDefinitionKey}:{processDefinitionVersion}:{generated-id},其中generated-id是一个唯一数字,用以保证在集群环境下,流程定义缓存中,流程id的唯一性。
+        </div>
+      </template>
+    </el-alert>
+    <div class="border-card">
+      <el-table height="450" v-loading="loading" fit :data="definitionList" border
+        @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="流程编号" align="center" prop="deploymentId" :show-overflow-tooltip="true" />
+        <el-table-column label="流程标识" align="center" prop="flowKey" :show-overflow-tooltip="true" />
+        <el-table-column label="流程分类" align="center" prop="category" />
+        <el-table-column label="流程名称" align="center" width="120" :show-overflow-tooltip="true">
+          <template slot-scope="scope">
+            <el-button type="text" @click="handleReadImage(scope.row.deploymentId)">
+              <span>{{ scope.row.name }}</span>
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column label="业务表单" align="center" :show-overflow-tooltip="true">
+          <template slot-scope="scope">
+            <el-button v-if="scope.row.formId" type="text" @click="handleForm(scope.row.formId)">
+              <span>{{ scope.row.formName }}</span>
+            </el-button>
+            <label v-else>暂无表单</label>
+          </template>
+        </el-table-column>
+        <el-table-column label="流程版本" align="center">
+          <template slot-scope="scope">
+            <el-tag size="medium">v{{ scope.row.version }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center">
+          <template slot-scope="scope">
+            <el-tag type="success" v-if="scope.row.suspensionState === 1">激活</el-tag>
+            <el-tag type="warning" v-if="scope.row.suspensionState === 2">挂起</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="部署时间" align="center" prop="deploymentTime" width="180" />
+        <el-table-column label="操作" width="250"  class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button @click="handleLoadXml(scope.row)" icon="el-icon-edit-outline" type="text"
+              size="small">设计</el-button>
+            <el-button @click="handleAddForm(scope.row)" icon="el-icon-edit-el-icon-s-promotion" type="text"
+              size="small" v-if="scope.row.formId == null">配置主表单</el-button>
+            <el-button @click="handleUpdateSuspensionState(scope.row)" icon="el-icon-video-pause" type="text"
+              size="small" v-if="scope.row.suspensionState === 1">挂起</el-button>
+            <el-button @click="handleUpdateSuspensionState(scope.row)" icon="el-icon-video-play" type="text"
+              size="small" v-if="scope.row.suspensionState === 2">激活</el-button>
+            <el-button class="custom-red-color" @click="handleDelete(scope.row)" icon="el-icon-delete" type="text"
+              size="small" v-hasPermi="['system:deployment:remove']">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
+        @pagination="getList" />
+    </div>
+
+
+    <!-- 添加或修改流程定义对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="看看" prop="name">
+          <el-input v-model="form.name" placeholder="请输入看看" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- bpmn20.xml导入对话框 -->
+    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
+      <el-upload ref="upload" :limit="1" accept=".xml" :headers="upload.headers"
+        :action="upload.url + '?name=' + upload.name + '&category=' + upload.category" :disabled="upload.isUploading"
+        :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">
+          将文件拖到此处,或
+          <em>点击上传</em>
+        </div>
+        <div class="el-upload__tip" slot="tip">
+          流程名称:<el-input v-model="upload.name" />
+          流程分类:
+          <div>
+            <!--          <el-input v-model="upload.category"/>-->
+            <el-select v-model="upload.category" placeholder="请选择流程分类">
+              <el-option v-for="dict in sys_process_category" :key="dict.value" :label="dict.label"
+                :value="dict.value"></el-option>
+            </el-select>
+          </div>
+        </div>
+        <div class="el-upload__tip" style="color:red" slot="tip">提示:仅允许导入“bpmn20.xml”格式文件!</div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitFileForm">确 定</el-button>
+        <el-button @click="upload.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 流程图 -->
+    <el-dialog :title="readImage.title" :visible.sync="readImage.open" width="70%" append-to-body>
+      <!-- <el-image :src="readImage.src"></el-image> -->
+      <flow :flowData="flowData" />
+    </el-dialog>
+
+    <!--表单配置详情-->
+    <!--    <el-dialog :title="formTitle" :visible.sync="formConfOpen" width="50%" append-to-body>
+      <div class="test-form">
+        <parser :key="new Date().getTime()"  :form-conf="formConf" />
+      </div>
+    </el-dialog>-->
+    <el-dialog :title="formTitle" :visible.sync="formConfOpen" width="50%" append-to-body>
+      <div class="test-form">
+        <v-form-render :form-data="formData" ref="vFormRef" />
+      </div>
+    </el-dialog>
+
+    <!--挂载表单-->
+    <el-dialog :title="formDeployTitle" :visible.sync="formDeployOpen" width="60%" append-to-body>
+      <el-row :gutter="24">
+        <el-col :span="10" :xs="24">
+          <el-table ref="singleTable" :data="formList" border highlight-current-row
+            @current-change="handleCurrentChange" style="width: 100%">
+            <el-table-column label="表单编号" align="center" prop="formId" />
+            <el-table-column label="表单名称" align="center" prop="formName" />
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+              <template slot-scope="scope">
+                <el-button size="small" type="text" @click="submitFormDeploy(scope.row)">确定</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination small layout="prev, pager, next" v-show="formTotal > 0" :total="formTotal"
+            :page.sync="formQueryParams.pageNum" :limit.sync="formQueryParams.pageSize" @pagination="ListFormDeploy" />
+        </el-col>
+        <!--        <el-col :span="14" :xs="24">
+          <div v-if="currentRow">
+            <parser :key="new Date().getTime()" :form-conf="currentRow" />
+          </div>
+        </el-col>-->
+        <el-col :span="14" :xs="24">
+          <div class="test-form">
+            <v-form-render :form-data="formData" ref="vFormCurrentRowRef" />
+          </div>
+        </el-col>
+      </el-row>
+    </el-dialog>
+
+    <!--    &lt;!&ndash;流程设计器&ndash;&gt;-->
+    <!--    <el-dialog-->
+    <!--      title="流程配置"-->
+    <!--      :visible.sync="dialogVisible"-->
+    <!--      :close-on-press-escape="false"-->
+    <!--      :fullscreen=true-->
+    <!--      :before-close="handleClose"-->
+    <!--      append-to-body>-->
+    <!--      <Model :deployId="deployId"/>-->
+    <!--    </el-dialog>-->
+  </div>
+</template>
+
+<script>
+import {
+  listDefinition,
+  updateState,
+  delDeployment,
+  addDeployment,
+  updateDeployment,
+  exportDeployment,
+  definitionStart,
+  flowXmlAndNode
+} from "@/api/flowable/definition";
+import { getToken } from "@/utils/auth";
+import { getForm, addDeployForm, listForm } from "@/api/flowable/form";
+import Parser from '@/components/parser/Parser'
+import flow from '@/views/flowable/task/myProcess/send/flow'
+import Model from './model';
+import { dataTypeList } from '@/api/public'
+export default {
+  name: "Definition",
+  components: {
+    Parser,
+    flow,
+    Model
+  },
+  data() {
+    return {
+      sys_process_category: [],
+      // 遮罩层
+      loading: true,
+      dialogVisible: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 流程定义表格数据
+      definitionList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      formConfOpen: false,
+      formTitle: "",
+      formDeployOpen: false,
+      formDeployTitle: "",
+      formList: [],
+      formTotal: 0,
+      formConf: {}, // 默认表单数据
+      formData: {}, // 默认表单数据
+      readImage: {
+        open: false,
+        src: "",
+      },
+      // bpmn.xml 导入
+      upload: {
+        // 是否显示弹出层(xml导入)
+        open: false,
+        // 弹出层标题(xml导入)
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        name: null,
+        category: null,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + "/flowable/definition/import"
+      },
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        category: null,
+        key: null,
+        tenantId: null,
+        deployTime: null,
+        derivedFrom: null,
+        derivedFromRoot: null,
+        parentDeploymentId: null,
+        engineVersion: null
+      },
+      formQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+      },
+      // 挂载表单到流程实例
+      formDeployParam: {
+        formId: null,
+        deployId: null
+      },
+      deployId: '',
+      currentRow: null,
+      // xml
+      flowData: {},
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getDictType();
+  },
+  activated() {
+    const time = this.$route.query.t;
+    if (time != null) {
+      this.getList();
+    }
+  },
+  methods: {
+     getDictType() {
+      dataTypeList({ dictType: 'sys_process_category' }).then(response => {
+        this.sys_process_category = response.data.data
+      })
+    },
+    /** 查询流程定义列表 */
+    getList() {
+      this.loading = true;
+      listDefinition(this.queryParams).then(response => {
+        this.definitionList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    handleClose(done) {
+      this.$confirm('确定要关闭吗?关闭未保存的修改都会丢失?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        done();
+      }).catch(() => { });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        category: null,
+        key: null,
+        tenantId: null,
+        deployTime: null,
+        derivedFrom: null,
+        derivedFromRoot: null,
+        parentDeploymentId: null,
+        engineVersion: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.deploymentId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加流程定义";
+    },
+    /** 跳转到流程设计页面 */
+    handleLoadXml(row) {
+      // this.dialogVisible = true;
+      // this.deployId = row.deploymentId;
+      this.$router.push({ path: '/flowable/definition/model', query: { deployId: row.deploymentId } })
+    },
+    /** 流程图查看 */
+    handleReadImage(deployId) {
+      this.readImage.title = "流程图";
+      this.readImage.open = true;
+      // this.readImage.src = process.env.VUE_APP_BASE_API + "/flowable/definition/readImage/" + deploymentId;
+      flowXmlAndNode({ deployId: deployId }).then(res => {
+        this.flowData = res.data;
+      })
+    },
+    /** 表单查看 */
+    /*handleForm(formId){
+      getForm(formId).then(res =>{
+        this.formTitle = "表单详情";
+        this.formConfOpen = true;
+        this.formConf = JSON.parse(res.data.formContent)
+      })
+    },*/
+    handleForm(formId) {
+      getForm(formId).then(res => {
+        this.formTitle = "表单详情";
+        this.formConfOpen = true;
+        this.$nextTick(() => {
+          // 回显数据
+          this.$refs.vFormRef.setFormJson(JSON.parse(res.data.formContent))
+          this.$nextTick(() => {
+            // 表单禁用
+            this.$refs.vFormRef.disableForm();
+          })
+        })
+      })
+    },
+    /** 启动流程 */
+    handleDefinitionStart(row) {
+      definitionStart(row.id).then(res => {
+        this.$modal.msgSuccess(res.msg);
+      })
+    },
+    /** 挂载表单弹框 */
+    handleAddForm(row) {
+      this.formDeployParam.deployId = row.deploymentId
+      this.ListFormDeploy()
+    },
+    /** 挂载表单列表 */
+    ListFormDeploy() {
+      listForm(this.formQueryParams).then(res => {
+        this.formList = res.rows;
+        this.formTotal = res.total;
+        this.formDeployOpen = true;
+        this.formDeployTitle = "挂载表单";
+      })
+    },
+    // /** 更改挂载表单弹框 */
+    // handleEditForm(row){
+    //   this.formDeployParam.deployId = row.deploymentId
+    //   const queryParams = {
+    //     pageNum: 1,
+    //     pageSize: 10
+    //   }
+    //   listForm(queryParams).then(res =>{
+    //     this.formList = res.rows;
+    //     this.formDeployOpen = true;
+    //     this.formDeployTitle = "挂载表单";
+    //   })
+    // },
+    /** 挂载表单 */
+    submitFormDeploy(row) {
+      this.formDeployParam.formId = row.formId;
+      addDeployForm(this.formDeployParam).then(res => {
+        this.$modal.msgSuccess(res.msg);
+        this.formDeployOpen = false;
+        this.getList();
+      })
+    },
+    /* handleCurrentChange(data) {
+       if (data) {
+         this.currentRow = JSON.parse(data.formContent);
+       }
+     },*/
+    handleCurrentChange(data) {
+      if (data) {
+        this.$nextTick(() => {
+          // 回显数据
+          this.$refs.vFormCurrentRowRef.setFormJson(JSON.parse(data.formContent))
+          this.$nextTick(() => {
+            // 表单禁用
+            this.$refs.vFormCurrentRowRef.disableForm();
+          })
+        })
+      }
+    },
+    /** 挂起/激活流程 */
+    handleUpdateSuspensionState(row) {
+      let state = 1;
+      if (row.suspensionState === 1) {
+        state = 2
+      }
+      const params = {
+        deployId: row.deploymentId,
+        state: state
+      }
+      updateState(params).then(res => {
+        this.$modal.msgSuccess(res.msg);
+        this.getList();
+      });
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.deploymentId || this.ids
+      getDeployment(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改流程定义";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateDeployment(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addDeployment(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const deploymentIds = row.deploymentId || this.ids;
+      this.$confirm('是否确认删除流程定义编号为"' + deploymentIds + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delDeployment(deploymentIds);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      })
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有流程定义数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return exportDeployment(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+      })
+    },
+    /** 导入bpmn.xml文件 */
+    handleImport() {
+      this.upload.title = "bpmn20.xml文件导入";
+      this.upload.open = true;
+    },
+    // 文件上传中处理
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true;
+    },
+    // 文件上传成功处理
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false;
+      this.upload.isUploading = false;
+      this.$refs.upload.clearFiles();
+      this.$message(response.msg);
+      this.getList();
+    },
+    // 提交上传文件
+    submitFileForm() {
+      this.$refs.upload.submit();
+    }
+  }
+};
+</script>

+ 174 - 0
src/views/flowable/definition/model.vue

@@ -0,0 +1,174 @@
+<template>
+  <div>
+    <bpmn-modeler
+      ref="refNode"
+      :xml="xml"
+      :users="users"
+      :groups="groups"
+      :categorys="categorys"
+      :exps="exps"
+      :is-view="false"
+      @save="save"
+      @showXML="showXML"
+    />
+    <!--在线查看xml-->
+    <el-drawer :title="xmlTitle" :modal="false" direction="rtl" :visible.sync="xmlOpen" size="60%">
+      <!-- 设置对话框内容高度 -->
+        <el-scrollbar>
+            <pre v-highlight="xmlData"><code class="xml"></code></pre>
+        </el-scrollbar>
+    </el-drawer>
+  </div>
+</template>
+<script>
+import {readXml, roleList, saveXml, userList,expList} from "@/api/flowable/definition";
+import bpmnModeler from '@/components/Process/index'
+import vkBeautify from 'vkbeautify'
+import hljs from 'highlight.js'
+import 'highlight.js/styles/atelier-savanna-dark.css'
+import { dataTypeList } from '@/api/public'
+export default {
+  name: "Model",
+  components: {
+    bpmnModeler,
+    vkBeautify
+  },
+  // 自定义指令
+  directives: {
+    deep: true,
+    highlight:{
+      deep: true,
+      bind: function bind(el, binding) {
+        const targets = el.querySelectorAll('code');
+        let target;
+        let i;
+        for (i = 0; i < targets.length; i += 1) {
+          target = targets[i];
+          if (typeof binding.value === 'string') {
+            target.textContent = binding.value;
+          }
+          hljs.highlightBlock(target);
+        }
+      },
+      componentUpdated: function componentUpdated(el, binding) {
+        const targets = el.querySelectorAll('code');
+        let target;
+        let i;
+        for (i = 0; i < targets.length; i += 1) {
+          target = targets[i];
+          if (typeof binding.value === 'string') {
+            target.textContent = binding.value;
+            hljs.highlightBlock(target);
+          }
+        }
+      },
+    }
+  },
+  data() {
+    return {
+      xml: "", // 后端查询到的xml
+      modeler:"",
+      xmlOpen: false,
+      xmlTitle: '',
+      xmlData: '',
+      users: [],
+      groups: [],
+      categorys: [],
+      exps: [],
+
+    };
+  },
+  created () {
+    const deployId = this.$route.query && this.$route.query.deployId;
+    //  查询流程xml
+    if (deployId) {
+      this.getXmlData(deployId);
+    }
+    dataTypeList({ dictType: 'sys_process_category' }).then(response => {
+        this.categorys = response.data.data
+    })
+    this.getDataList()
+  },
+  methods: {
+    /** xml 文件 */
+    getXmlData(deployId) {
+      // 发送请求,获取xml
+      readXml(deployId).then(res =>{
+        this.xml = res.data;
+        this.modeler = res.data
+      })
+    },
+    /** 保存xml */
+    save(data) {
+      const params = {
+        name: data.process.name,
+        category: data.process.category,
+        xml: data.xml
+      }
+      saveXml(params).then(res => {
+        this.$modal.msgSuccess(res.msg)
+        // 关闭当前标签页并返回上个页面
+        const obj = { path: "/flowable/definition", query: { t: Date.now()} };
+        this.$tab.closeOpenPage(obj);
+      })
+    },
+    /** 指定流程办理人员列表 */
+    getDataList() {
+      userList().then(res =>{
+        res.data.forEach(val =>{
+          val.userId = val.userId.toString();
+        })
+        this.users = res.data;
+        // let arr = {nickName: "流程发起人", userId: "${INITIATOR}"}
+        // this.users.push(arr)
+      });
+      roleList().then(res =>{
+        res.data.forEach(val =>{
+          val.roleId = val.roleId.toString();
+        })
+        this.groups = res.data;
+      });
+      expList().then(res =>{
+        this.exps = res.data;
+      });
+    },
+    /** 展示xml */
+    showXML(xmlData){
+      this.xmlTitle = 'xml查看';
+      this.xmlOpen = true;
+      this.xmlData = vkBeautify.xml(xmlData);
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.content-box{
+  line-height: 10px;
+}
+// 修改对话框高度
+.showAll_dialog {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  overflow: hidden;
+  ::v-deep .el-dialog {
+    margin: 0 auto !important;
+    height: 80%;
+    overflow: hidden;
+    background-color: #ffffff;
+    .el-dialog__body {
+      position: absolute;
+      left: 0;
+      top: 54px;
+      bottom: 0;
+      right: 0;
+      z-index: 1;
+      overflow: hidden;
+      overflow-y: auto;
+      // 下边设置字体,我的需求是黑底白字
+      color: #ffffff;
+      padding: 0 15px;
+    }
+  }
+}
+</style>

+ 272 - 0
src/views/flowable/expression/index.vue

@@ -0,0 +1,272 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-position="top">
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="名称" prop="name">
+            <el-input v-model="queryParams.name" placeholder="请输入表达式名称" clearable @keyup.enter.native="handleQuery" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="状态" prop="status">
+            <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 100%;">
+              <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label"
+                :value="dict.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6"></el-col>
+        <el-col :span="6"></el-col>
+      </el-row>
+
+
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="搜索">
+            <el-button style="width: 100%;" type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="重置">
+            <el-button style="width: 100%;" icon="el-icon-refresh" type="primary" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" icon="el-icon-plus" size="small" @click="handleAdd"
+          v-hasPermi="['system:expression:add']">新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="success" class="editButton" icon="el-icon-edit" size="small" :disabled="single" @click="handleUpdate"
+          v-hasPermi="['system:expression:edit']">修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" icon="el-icon-delete" size="small" :disabled="multiple" @click="handleDelete"
+          v-hasPermi="['system:expression:remove']">删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" icon="el-icon-download" size="small" @click="handleExport"
+          v-hasPermi="['system:expression:export']">导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+    <div class="border-card">
+      <el-table height="450" v-loading="loading" border :data="expressionList"
+        @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="主键" align="center" prop="id" />
+        <el-table-column label="名称" align="center" prop="name" />
+        <el-table-column label="表达式内容" align="center" prop="expression" />
+        <el-table-column label="指定类型" align="center" prop="dataType">
+          <template slot-scope="scope">
+            <dict-tag :options="exp_data_type" :value="scope.row.dataType" />
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button size="small" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+              v-hasPermi="['system:expression:edit']">修改</el-button>
+            <el-button class="custom-red-color" size="small" type="text" icon="el-icon-delete"
+              @click="handleDelete(scope.row)" v-hasPermi="['system:expression:remove']">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
+        @pagination="getList" />
+    </div>
+
+
+    <!-- 添加或修改流程达式对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入表达式名称" />
+        </el-form-item>
+        <el-form-item label="内容" prop="expression">
+          <el-input v-model="form.expression" placeholder="请输入表达式内容" />
+        </el-form-item>
+        <el-form-item label="指定类型" prop="dataType">
+          <el-radio-group v-model="form.dataType">
+            <el-radio v-for="dict in exp_data_type" :key="dict.value"
+              :label="dict.value">{{ dict.label }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio v-for="dict in sys_common_status" :key="dict.value"
+              :label="parseInt(dict.value)">{{ dict.label }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listExpression, getExpression, delExpression, addExpression, updateExpression } from "@/api/flowable/expression";
+import { dataTypeList } from '@/api/public'
+export default {
+  name: "FlowExp",
+  data() {
+    return {
+      sys_common_status: [],
+      exp_data_type: [],
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 流程达式表格数据
+      expressionList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        expression: null,
+        status: null,
+      },
+      // 表单参数
+      form: {
+        dataType: 'fixed'
+      },
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getDictType();
+  },
+  methods: {
+     getDictType() {
+      dataTypeList({ dictType: 'sys_common_status' }).then(response => {
+        this.sys_common_status = response.data.data
+      })
+      dataTypeList({ dictType: 'exp_data_type' }).then(response => {
+        this.exp_data_type = response.data.data
+      })
+    },
+    /** 查询流程达式列表 */
+    getList() {
+      this.loading = true;
+      listExpression(this.queryParams).then(response => {
+        this.expressionList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        expression: null,
+        createTime: null,
+        updateTime: null,
+        createBy: null,
+        updateBy: null,
+        status: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加流程达式";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getExpression(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改流程达式";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateExpression(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addExpression(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$modal.confirm('是否确认删除流程达式编号为"' + ids + '"的数据项?').then(function () {
+        return delExpression(ids);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => { });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      this.download('system/expression/export', {
+        ...this.queryParams
+      }, `expression_${new Date().getTime()}.xlsx`)
+    }
+  }
+};
+</script>

+ 300 - 0
src/views/flowable/listener/index.vue

@@ -0,0 +1,300 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-position="top">
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="名称" prop="name">
+            <el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter.native="handleQuery" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="监听类型" prop="type">
+            <el-select v-model="queryParams.type" placeholder="请选择监听类型" clearable style="width: 100%;">
+              <el-option v-for="dict in sys_listener_type" :key="dict.value" :label="dict.label"
+                :value="dict.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6"></el-col>
+        <el-col :span="6"></el-col>
+      </el-row>
+
+
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="搜索">
+            <el-button style="width: 100%;" type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="重置">
+            <el-button style="width: 100%;" icon="el-icon-refresh" type="primary" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" icon="el-icon-plus" size="small" @click="handleAdd"
+          v-hasPermi="['system:listener:add']">新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="success" class="editButton" icon="el-icon-edit" size="small" :disabled="single" @click="handleUpdate"
+          v-hasPermi="['system:listener:edit']">修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" icon="el-icon-delete" size="small" :disabled="multiple" @click="handleDelete"
+          v-hasPermi="['system:listener:remove']">删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" icon="el-icon-download" size="small" @click="handleExport"
+          v-hasPermi="['system:listener:export']">导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+    <div class="border-card">
+      <el-table height="450" v-loading="loading" border :data="listenerList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="名称" align="center" prop="name" />
+        <el-table-column label="监听类型" align="center" prop="type">
+          <template slot-scope="scope">
+            <dict-tag :options="sys_listener_type" :value="scope.row.type" />
+          </template>
+        </el-table-column>
+        <el-table-column label="事件类型" align="center" prop="eventType" />
+        <el-table-column label="值类型" align="center" prop="valueType">
+          <template slot-scope="scope">
+            <dict-tag :options="sys_listener_value_type" :value="scope.row.valueType" />
+          </template>
+        </el-table-column>
+        <el-table-column label="执行内容" align="center" prop="value" />
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button size="small" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+              v-hasPermi="['system:listener:edit']">修改</el-button>
+            <el-button class="custom-red-color" size="small" type="text" icon="el-icon-delete"
+              @click="handleDelete(scope.row)" v-hasPermi="['system:listener:remove']">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
+        @pagination="getList" />
+    </div>
+
+
+    <!-- 添加或修改流程监听对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入名称" />
+        </el-form-item>
+        <el-form-item label="监听类型" prop="type">
+          <el-select v-model="form.type" placeholder="请选择监听类型">
+            <el-option v-for="dict in sys_listener_type" :key="dict.value" :label="dict.label"
+              :value="dict.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="事件类型" prop="eventType" v-if="form.type === '1'">
+          <el-select v-model="form.eventType" placeholder="请选择事件类型">
+            <el-option v-for="dict in taskListenerEventList" :key="dict.value" :label="dict.label"
+              :value="dict.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="事件类型" prop="eventType" v-else>
+          <el-select v-model="form.eventType" placeholder="请选择事件类型">
+            <el-option v-for="dict in executionListenerEventList" :key="dict.value" :label="dict.label"
+              :value="dict.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="值类型" prop="valueType">
+          <el-radio-group v-model="form.valueType">
+            <el-radio v-for="dict in sys_listener_value_type" :key="dict.value"
+              :label="dict.value">{{ dict.label }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="执行内容" prop="value">
+          <el-input v-model="form.value" placeholder="请输入执行内容" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listListener, getListener, delListener, addListener, updateListener } from "@/api/flowable/listener";
+import { dataTypeList } from '@/api/public'
+export default {
+  name: "Listener",
+  data() {
+    return {
+      sys_listener_value_type: [],
+      sys_listener_type: [],
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 流程监听表格数据
+      listenerList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        type: null,
+        eventType: null,
+        valueType: null,
+        value: null,
+        status: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      taskListenerEventList: [
+        { label: 'create', value: 'create' },
+        { label: 'assignment', value: 'assignment' },
+        { label: 'complete', value: 'complete' },
+        { label: 'delete', value: 'delete' },
+      ],
+      executionListenerEventList: [
+        { label: 'start', value: 'start' },
+        { label: 'end', value: 'end' },
+        { label: 'take', value: 'take' },
+      ],
+    };
+  },
+  created() {
+    this.getList();
+    this.getDictType();
+  },
+  methods: {
+    getDictType() {
+      dataTypeList({ dictType: 'sys_listener_value_type' }).then(response => {
+        this.sys_listener_value_type = response.data.data
+      })
+      dataTypeList({ dictType: 'sys_listener_type' }).then(response => {
+        this.sys_listener_type = response.data.data
+      })
+    },
+    /** 查询流程监听列表 */
+    getList() {
+      this.loading = true;
+      listListener(this.queryParams).then(response => {
+        this.listenerList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        type: null,
+        eventType: null,
+        valueType: null,
+        value: null,
+        createTime: null,
+        updateTime: null,
+        createBy: null,
+        updateBy: null,
+        status: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加流程监听";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getListener(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改流程监听";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateListener(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addListener(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$modal.confirm('是否确认删除流程监听编号为"' + ids + '"的数据项?').then(function () {
+        return delListener(ids);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => { });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      this.download('system/listener/export', {
+        ...this.queryParams
+      }, `listener_${new Date().getTime()}.xlsx`)
+    }
+  }
+};
+</script>

+ 24 - 0
src/views/flowable/task/finished/detail/flow.vue

@@ -0,0 +1,24 @@
+<template>
+  <div>
+    <flow-view :flowData="flowData"/>
+  </div>
+</template>
+<script>
+import FlowView from './flowview'
+
+export default {
+  name: "Flow",
+  components: {
+    FlowView
+  },
+  props: {
+    flowData: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  data() {
+    return {};
+  }
+};
+</script>

+ 238 - 0
src/views/flowable/task/finished/detail/flowview.vue

@@ -0,0 +1,238 @@
+<template>
+  <div class="containers main-box">
+    <el-button type="success" class="editButton"
+               size="small"
+               icon="el-icon-zoom-in"
+               @click="zoomViewport(true)">放大</el-button>
+    <el-button type="warning"
+               size="small"
+               icon="el-icon-zoom-out"
+               @click="zoomViewport(false)">缩小</el-button>
+    <el-button type="info"
+               size="small"
+               icon="el-icon-rank"
+               @click="fitViewport">适中</el-button>
+    <div class="canvas" ref="flowCanvas"></div>
+  </div>
+</template>
+<script>
+import { CustomViewer as BpmnViewer } from "@/components/customBpmn";
+
+export default {
+  name: "FlowView",
+  props: {
+    flowData: {
+      type: Object,
+      default: () => {}
+    },
+  },
+  data() {
+    return {
+      bpmnViewer: null
+    };
+  },
+  watch: {
+    flowData: {
+      handler(newVal) {
+        if (Object.keys(newVal).length > 0) {
+          // 生成实例
+          this.bpmnViewer && this.bpmnViewer.destroy();
+          this.bpmnViewer = new BpmnViewer({
+            container: this.$refs.flowCanvas,
+            height: 'calc(100vh - 200px)',
+          });
+          this.loadFlowCanvas(newVal)
+        }
+      },
+      immediate: true, // 立即生效
+      deep: true  //监听对象或数组的时候,要用到深度监听
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    // 加载流程
+    async loadFlowCanvas(flowData) {
+      const self = this
+      try {
+        await self.bpmnViewer.importXML(flowData.xmlData);
+        self.fitViewport()
+        if (flowData.nodeData !==undefined && flowData.nodeData.length > 0 ) {
+          self.fillColor(flowData.nodeData)
+        }
+      } catch (err) {
+        console.error(err.message, err.warnings)
+      }
+    },
+    // 设置高亮颜色的class
+    setNodeColor(nodeCodes, colorClass, canvas) {
+      for (let i = 0; i < nodeCodes.length; i++) {
+        canvas.addMarker(nodeCodes[i], colorClass);
+      }
+    },
+    // 让图能自适应屏幕
+    fitViewport() {
+      this.zoom = this.bpmnViewer.get('canvas').zoom("fit-viewport", "auto")
+    },
+    // 放大缩小
+    zoomViewport(zoomIn = true) {
+      this.zoom = this.bpmnViewer.get('canvas').zoom()
+      this.zoom += (zoomIn ? 0.1 : -0.1)
+      if(this.zoom >= 0.2) this.bpmnViewer.get('canvas').zoom(this.zoom)
+    },
+
+    // 设置高亮颜色的
+    fillColor(nodeData) {
+      const canvas = this.bpmnViewer.get('canvas')
+      this.bpmnViewer.getDefinitions().rootElements[0].flowElements.forEach(n => {
+        const completeTask = nodeData.find(m => m.key === n.id)
+        const todoTask = nodeData.find(m => !m.completed)
+        const endTask = nodeData[nodeData.length - 1]
+        if (n.$type === 'bpmn:UserTask') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+                if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
+                  canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
+                  canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
+                }else {
+                  canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                  canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                }
+              }
+            })
+          }
+        }
+        // 排他网关
+        else if (n.$type === 'bpmn:ExclusiveGateway') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+
+                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+              }
+
+            })
+          }
+
+        }
+        // 并行网关
+        else if (n.$type === 'bpmn:ParallelGateway') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+              }
+            })
+          }
+        }
+        else if (n.$type === 'bpmn:StartEvent') {
+          n.outgoing.forEach(nn => {
+            const completeTask = nodeData.find(m => m.key === nn.targetRef.id)
+            if (completeTask) {
+              canvas.addMarker(nn.id, 'highlight')
+              canvas.addMarker(n.id, 'highlight')
+              return
+            }
+          })
+        }
+        else if (n.$type === 'bpmn:EndEvent') {
+          if (endTask.key === n.id && endTask.completed) {
+            canvas.addMarker(n.id, 'highlight')
+            return
+          }
+        }
+      })
+    },
+  }
+};
+</script>
+<style lang="scss">
+  .bjs-powered-by {
+    display: none;
+  }
+  .view-mode {
+    .el-header, .el-aside, .djs-palette, .bjs-powered-by {
+      display: none;
+    }
+    .el-loading-mask {
+      background-color: initial;
+    }
+    .el-loading-spinner {
+      display: none;
+    }
+  }
+  .containers {
+    // background-color: #ffffff;
+    width: 100%;
+    height: 100%;
+    .canvas {
+      width: 100%;
+      height: 100%;
+    }
+    .panel {
+      position: absolute;
+      right: 0;
+      top: 50px;
+      width: 300px;
+    }
+    .load {
+      margin-right: 10px;
+    }
+    .el-form-item__label{
+      font-size: 13px;
+    }
+
+    .djs-palette{
+      left: 0px!important;
+      top: 0px;
+      border-top: none;
+    }
+
+    .djs-container svg {
+      min-height: 650px;
+    }
+
+    .highlight.djs-shape .djs-visual > :nth-child(1) {
+      fill: green !important;
+      stroke: green !important;
+      fill-opacity: 0.2 !important;
+    }
+    .highlight.djs-shape .djs-visual > :nth-child(2) {
+      fill: green !important;
+    }
+    .highlight.djs-shape .djs-visual > path {
+      fill: green !important;
+      fill-opacity: 0.2 !important;
+      stroke: green !important;
+    }
+    .highlight.djs-connection > .djs-visual > path {
+      stroke: green !important;
+    }
+    .highlight-todo.djs-connection > .djs-visual > path {
+      stroke: orange !important;
+      stroke-dasharray: 4px !important;
+      fill-opacity: 0.2 !important;
+    }
+    .highlight-todo.djs-shape .djs-visual > :nth-child(1) {
+      fill: orange !important;
+      stroke: orange !important;
+      stroke-dasharray: 4px !important;
+      fill-opacity: 0.2 !important;
+    }
+    .overlays-div {
+      font-size: 10px;
+      color: red;
+      width: 100px;
+      top: -20px !important;
+    }
+  }
+</style>

+ 211 - 0
src/views/flowable/task/finished/detail/index.vue

@@ -0,0 +1,211 @@
+<template>
+  <div class="app-container">
+    <el-card class="box-card" >
+      <div slot="header" class="clearfix">
+        <span class="el-icon-document">已办任务</span>
+        <el-button style="float: right;" size="mini" type="danger" @click="goBack">关闭</el-button>
+      </div>
+      <el-tabs  tab-position="top" v-model="activeName" @tab-click="handleClick">
+        <!--表单信息-->
+        <el-tab-pane label="表单信息" name="1">
+          <el-col :span="16" :offset="4" v-if="variableOpen">
+            <div class="test-form">
+              <parser :key="new Date().getTime()" :form-conf="variablesData" />
+            </div>
+          </el-col>
+        </el-tab-pane>
+        <!--流程流转记录-->
+        <el-tab-pane label="流转记录" name="2">
+          <el-col :span="16" :offset="4" >
+            <div class="block">
+              <el-timeline>
+                <el-timeline-item
+                  v-for="(item,index ) in flowRecordList"
+                  :key="index"
+                  :icon="setIcon(item.finishTime)"
+                  :color="setColor(item.finishTime)"
+                >
+                  <p style="font-weight: 700">{{item.taskName}}</p>
+                  <el-card :body-style="{ padding: '10px' }">
+                    <el-descriptions class="margin-top" :column="1" size="small" border>
+                      <el-descriptions-item v-if="item.assigneeName" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-user"></i>办理人</template>
+                        {{item.assigneeName}}
+                        <el-tag type="info" size="mini">{{item.deptName}}</el-tag>
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.candidate" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-user"></i>候选办理</template>
+                        {{item.candidate}}
+                      </el-descriptions-item>
+                      <el-descriptions-item label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-date"></i>接收时间</template>
+                        {{item.createTime}}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.finishTime" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-date"></i>处理时间</template>
+                        {{item.finishTime}}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.duration"  label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-time"></i>耗时</template>
+                        {{item.duration}}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.comment" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-tickets"></i>处理意见</template>
+                        {{item.comment.comment}}
+                      </el-descriptions-item>
+                    </el-descriptions>
+                  </el-card>
+                </el-timeline-item>
+              </el-timeline>
+            </div>
+          </el-col>
+        </el-tab-pane>
+        <el-tab-pane label="流程图" name="3">
+          <flow :flowData="flowData"/>
+        </el-tab-pane>
+      </el-tabs>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import {flowRecord} from "@/api/flowable/finished";
+import Parser from '@/components/parser/Parser'
+import {getProcessVariables, flowXmlAndNode} from "@/api/flowable/definition";
+import flow from '@/views/flowable/task/finished/detail/flow'
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+
+export default {
+  name: "Record",
+  components: {
+    Parser,
+    flow,
+  },
+  props: {},
+  data() {
+    return {
+      // 模型xml数据
+      flowData: {},
+      activeName: '1',
+      // 用户表格数据
+      userList: null,
+      defaultProps: {
+        children: "children",
+        label: "label"
+      },
+      // 查询参数
+      queryParams: {
+        deptId: undefined
+      },
+      // 遮罩层
+      loading: true,
+      flowRecordList: [], // 流程流转数据
+      formConfCopy: {},
+      src: null,
+      taskForm:{
+        multiple: false,
+        comment:"", // 意见内容
+        procInsId: "", // 流程实例编号
+        instanceId: "", // 流程实例编号
+        deployId: "",  // 流程定义编号
+        taskId: "" ,// 流程任务编号
+        procDefId: "",  // 流程编号
+        vars: "",
+        targetKey:""
+      },
+      variables: [], // 流程变量数据
+      variablesData: {}, // 流程变量数据
+      variableOpen: false, // 是否加载流程变量数据
+    };
+  },
+  created() {
+    this.taskForm.deployId = this.$route.query && this.$route.query.deployId;
+    this.taskForm.taskId  = this.$route.query && this.$route.query.taskId;
+    this.taskForm.procInsId = this.$route.query && this.$route.query.procInsId;
+    // 回显流程记录
+    // 流程任务重获取变量表单
+    if (this.taskForm.taskId){
+      this.processVariables( this.taskForm.taskId)
+    }
+    this.getFlowRecordList( this.taskForm.procInsId, this.taskForm.deployId);
+  },
+  methods: {
+    handleClick(tab, event) {
+      if (tab.name === '3') {
+        flowXmlAndNode({procInsId: this.taskForm.procInsId, deployId: this.taskForm.deployId}).then(res => {
+          this.flowData = res.data;
+        })
+      }
+    },
+    setIcon(val) {
+      if (val) {
+        return "el-icon-check";
+      } else {
+        return "el-icon-time";
+      }
+    },
+    setColor(val) {
+      if (val) {
+        return "#2bc418";
+      } else {
+        return "#b3bdbb";
+      }
+    },
+    /** 流程流转记录 */
+    getFlowRecordList(procInsId, deployId) {
+      const that = this
+      const params = {procInsId: procInsId, deployId: deployId}
+      flowRecord(params).then(res => {
+        that.flowRecordList = res.data.flowList;
+      }).catch(res => {
+        this.goBack();
+      })
+    },
+    /** 获取流程变量内容 */
+    processVariables(taskId) {
+      if (taskId) {
+        // 提交流程申请时填写的表单存入了流程变量中后续任务处理时需要展示
+        getProcessVariables(taskId).then(res => {
+          this.variablesData = res.data.variables;
+          this.variableOpen = true
+        });
+      }
+    },
+    /** 返回页面 */
+    goBack() {
+      // 关闭当前标签页并返回上个页面
+      const obj = { path: "/task/finished", query: { t: Date.now()} };
+      this.$tab.closeOpenPage(obj);
+    },
+  }
+};
+</script>
+<style lang="scss" scoped>
+.test-form {
+  margin: 15px auto;
+  width: 800px;
+  padding: 15px;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+.clearfix:after {
+  clear: both
+}
+
+.box-card {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.el-tag + .el-tag {
+  margin-left: 10px;
+}
+
+.my-label {
+  background: #E1F3D8;
+}
+</style>

+ 277 - 0
src/views/flowable/task/finished/index.vue

@@ -0,0 +1,277 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-position="top">
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="名称" prop="name">
+            <el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter.native="handleQuery" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="开始时间" prop="deployTime">
+            <el-date-picker style="width: 100%;" clearable v-model="queryParams.deployTime" type="date"
+              value-format="yyyy-MM-dd" placeholder="选择时间">
+            </el-date-picker>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6"></el-col>
+        <el-col :span="6"></el-col>
+      </el-row>
+
+
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="搜索">
+            <el-button style="width: 100%;" type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="重置">
+            <el-button style="width: 100%;" icon="el-icon-refresh" type="primary" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="danger" icon="el-icon-delete" size="small" :disabled="multiple" @click="handleDelete"
+          v-hasPermi="['system:deployment:remove']">删除
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+    <div class="border-card">
+      <el-table height="450" v-loading="loading" :data="finishedList" border @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="任务编号" align="center" prop="taskId" :show-overflow-tooltip="true" />
+        <el-table-column label="流程名称" align="center" prop="procDefName" :show-overflow-tooltip="true" />
+        <el-table-column label="任务节点" align="center" prop="taskName" />
+        <el-table-column label="流程发起人" align="center">
+          <template slot-scope="scope">
+            <label>{{ scope.row.startUserName }} <el-tag type="info"
+                size="small">{{ scope.row.startDeptName }}</el-tag></label>
+          </template>
+        </el-table-column>
+        <el-table-column label="接收时间" align="center" prop="createTime" width="180" />
+        <el-table-column label="审批时间" align="center" prop="finishTime" width="180" />
+        <el-table-column label="耗时" align="center" prop="duration" width="180" />
+        <el-table-column label="操作" align="center" width="150"  class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button style="color:#1890ff" size="small" type="text" icon="el-icon-tickets"
+              @click="handleFlowRecord(scope.row)">流转记录</el-button>
+            <el-button size="small" type="text" icon="el-icon-refresh-left" @click="handleRevoke(scope.row)">撤回
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
+        @pagination="getList" />
+    </div>
+
+  </div>
+</template>
+
+<script>
+import { finishedList, getDeployment, delDeployment, addDeployment, updateDeployment, exportDeployment, revokeProcess } from "@/api/flowable/finished";
+
+export default {
+  name: "Deploy",
+  components: {
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 已办任务列表数据
+      finishedList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      src: "",
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        category: null,
+        key: null,
+        tenantId: null,
+        deployTime: null,
+        derivedFrom: null,
+        derivedFromRoot: null,
+        parentDeploymentId: null,
+        engineVersion: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询流程定义列表 */
+    getList() {
+      this.loading = true;
+      finishedList(this.queryParams).then(response => {
+        this.finishedList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        category: null,
+        key: null,
+        tenantId: null,
+        deployTime: null,
+        derivedFrom: null,
+        derivedFromRoot: null,
+        parentDeploymentId: null,
+        engineVersion: null
+      };
+      this.resetForm("form");
+    },
+    setIcon(val) {
+      if (val) {
+        return "el-icon-check";
+      } else {
+        return "el-icon-time";
+      }
+
+    },
+    setColor(val) {
+      if (val) {
+        return "#2bc418";
+      } else {
+        return "#b3bdbb";
+      }
+
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加流程定义";
+    },
+    /** 流程流转记录 */
+    handleFlowRecord(row) {
+      this.$router.push({
+        path: '/flowable/task/finished/detail/index',
+        query: {
+          procInsId: row.procInsId,
+          deployId: row.deployId,
+          taskId: row.taskId,
+        }
+      })
+    },
+    /** 撤回任务 */
+    handleRevoke(row) {
+      const params = {
+        instanceId: row.procInsId
+      }
+      revokeProcess(params).then(res => {
+        this.$modal.msgSuccess(res.msg);
+        this.getList();
+      });
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getDeployment(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改流程定义";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateDeployment(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addDeployment(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除流程定义编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delDeployment(ids);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      })
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有流程定义数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return exportDeployment(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+      })
+    }
+  }
+};
+</script>

+ 123 - 0
src/views/flowable/task/flowForm/index.vue

@@ -0,0 +1,123 @@
+<template>
+  <div>
+  <v-form-designer ref="vfDesigner" :designer-config="designerConfig">
+    <!-- 保存按钮 -->
+    <template #customSaveButton>
+      <el-button type="text" @click="saveFormJson"><i class="el-icon-s-promotion" />保存</el-button>
+    </template>
+  </v-form-designer>
+
+  <!--系统表单信息-->
+  <el-dialog :title="formTitle" :visible.sync="formOpen" width="500px" append-to-body>
+    <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+      <el-form-item label="表单名称" prop="formName">
+        <el-input v-model="form.formName" placeholder="请输入表单名称" />
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="form.remark" placeholder="请输入备注" />
+      </el-form-item>
+    </el-form>
+    <div slot="footer" class="dialog-footer">
+      <el-button type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="cancel">取 消</el-button>
+    </div>
+  </el-dialog>
+  </div>
+</template>
+
+<script>
+import {addForm, getForm, updateForm} from "@/api/flowable/form";
+import { StrUtil } from '@/utils/StrUtil'
+
+export default {
+  name: "flowForm",
+  data() {
+    return {
+      formTitle: "",
+      formOpen: false,
+      // 表单校验
+      rules: {
+        formName: [
+          { required: true, message: "表单名称不能为空", trigger: "blur" }
+        ]
+      },
+      // 表单参数
+      form: {
+        formId: null,
+        formName: null,
+        formContent: null,
+        remark: null
+      },
+      designerConfig: {
+        generateSFCButton: false,
+        exportCodeButton: false,  //是否显示导出代码按钮
+        toolbarMaxWidth: 320,
+        toolbarMinWidth: 300,  //设计器工具按钮栏最小宽度(单位像素)
+        formHeader: false,
+      },
+    }
+  },
+  mounted() {
+    const formId = this.$route.query && this.$route.query.formId;
+    if (StrUtil.isNotBlank(formId)) {
+      getForm(formId).then(res => {
+        this.$nextTick(() => {
+          // 加载表单json数据
+          this.$refs.vfDesigner.setFormJson(JSON.parse(res.data.formContent))
+        })
+        this.form = res.data;
+      })
+    }else {
+      this.$nextTick(() => {
+        // 加载表单json数据
+        this.$refs.vfDesigner.setFormJson({"widgetList":[],"formConfig":{"modelName":"formData","refName":"vForm","rulesName":"rules","labelWidth":80,"labelPosition":"left","size":"","labelAlign":"label-left-align","cssCode":"","customClass":"","functions":"","layoutType":"PC","onFormCreated":"","onFormMounted":"","onFormDataChange":"","onFormValidate":""}})
+      })
+    }
+  },
+  methods:{
+    // 保存表单数据
+    saveFormJson() {
+      let formJson = this.$refs.vfDesigner.getFormJson()
+      this.form.formContent = JSON.stringify(formJson);
+      this.formOpen = true;
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.formId != null) {
+            updateForm(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.formOpen = false;
+            });
+          } else {
+            addForm(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.formOpen = false;
+            });
+          }
+          // 关闭当前标签页并返回上个页面
+          const obj = { path: "/flowable/form", query: { t: Date.now()} };
+          this.$tab.closeOpenPage(obj);
+        }
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.formOpen = false;
+      this.reset();
+    },
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+body {
+  margin: 0;  /* 如果页面出现垂直滚动条,则加入此行CSS以消除之 */
+}
+.el-container.main-container{
+  background: #fff;
+  margin-left: 0 !important;
+}
+
+</style>

+ 301 - 0
src/views/flowable/task/form/index.vue

@@ -0,0 +1,301 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-position="top">
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="表单名称" prop="formName">
+            <el-input v-model="queryParams.formName" placeholder="请输入表单名称" clearable size="small"
+              @keyup.enter.native="handleQuery" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+
+        </el-col>
+        <el-col :span="6">
+
+        </el-col>
+        <el-col :span="6">
+
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="搜索">
+            <el-button style="width: 100%;" type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="重置">
+            <el-button style="width: 100%;" icon="el-icon-refresh" type="primary" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" icon="el-icon-plus" size="small" @click="handleAdd"
+          v-hasPermi="['flowable:form:add']">新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="success" class="editButton" icon="el-icon-edit" size="small" :disabled="single" @click="handleUpdate"
+          v-hasPermi="['flowable:form:edit']">修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" icon="el-icon-delete" size="small" :disabled="multiple" @click="handleDelete"
+          v-hasPermi="['flowable:form:remove']">删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" icon="el-icon-download" size="small" @click="handleExport"
+          v-hasPermi="['flowable:form:export']">导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+    <div class="border-card">
+      <el-table height="450" v-loading="loading" border :data="formList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="表单主键" align="center" prop="formId" />
+        <el-table-column label="表单名称" align="center" prop="formName" />
+        <el-table-column label="备注" align="center" prop="remark" />
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button size="small" type="text" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button>
+            <el-button size="small" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+              v-hasPermi="['flowable:form:edit']">修改</el-button>
+            <el-button class="custom-red-color" size="small" type="text" icon="el-icon-delete"
+              @click="handleDelete(scope.row)" v-hasPermi="['flowable:form:remove']">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
+        @pagination="getList" />
+    </div>
+    < <!-- 添加或修改流程表单对话框 -->
+      <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+        <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+          <el-form-item label="表单名称" prop="formName">
+            <el-input v-model="form.formName" placeholder="请输入表单名称" />
+          </el-form-item>
+          <el-form-item label="表单内容">
+            <editor v-model="form.formContent" :min-height="192" />
+          </el-form-item>
+          <el-form-item label="备注" prop="remark">
+            <el-input v-model="form.remark" placeholder="请输入备注" />
+          </el-form-item>
+        </el-form>
+        <div slot="footer" class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </el-dialog>
+
+      <!--表单配置详情-->
+      <!--    <el-dialog :title="formTitle" :visible.sync="formConfOpen" width="60%" append-to-body>
+      <div class="test-form">
+        <parser :key="new Date().getTime()"  :form-conf="formConf" />
+      </div>
+    </el-dialog>-->
+      <!--表单详情-->
+      <el-dialog :title="formTitle" :visible.sync="formRenderOpen" width="60%" append-to-body>
+        <v-form-render :form-data="formData" ref="vFormRef" />
+      </el-dialog>
+  </div>
+</template>
+
+  <script>
+  import { listForm, getForm, delForm, addForm, updateForm, exportForm } from "@/api/flowable/form";
+  import Editor from '@/components/Editor';
+  import Parser from '@/components/parser/Parser'
+  export default {
+    name: "Form",
+    components: {
+      Editor,
+      Parser
+    },
+    data() {
+      return {
+        // 遮罩层
+        loading: true,
+        // 选中数组
+        ids: [],
+        // 非单个禁用
+        single: true,
+        // 非多个禁用
+        multiple: true,
+        // 显示搜索条件
+        showSearch: true,
+        // 总条数
+        total: 0,
+        // 流程表单表格数据
+        formList: [],
+        // 弹出层标题
+        title: "",
+        formConf: {}, // 默认表单数据
+        formConfOpen: false,
+        formTitle: "",// 对话框标题,用于绑定到 el-dialog 的 :title
+        formRenderOpen: false,// 控制对话框的显示与隐藏,绑定到 el-dialog 的 :visible.sync
+        // 是否显示弹出层
+        open: false,
+        // 查询参数
+        queryParams: {
+          pageNum: 1,
+          pageSize: 10,
+          formName: null,
+          formContent: null,
+        },
+        // 表单参数
+        form: {},
+        // 表单校验
+        rules: {
+        },
+        formData: {}// 表单数据,用于传递给 v-form-render 的 :form-data,存储表单的配置信息等
+      };
+    },
+    created() {
+      this.getList();
+    },
+    methods: {
+      /** 查询流程表单列表 */
+      getList() {
+        this.loading = true;
+        listForm(this.queryParams).then(response => {
+          this.formList = response.rows;
+          this.total = response.total;
+          this.loading = false;
+        });
+      },
+      // 取消按钮
+      cancel() {
+        this.open = false;
+        this.reset();
+      },
+      // 表单重置
+      reset() {
+        this.form = {
+          formId: null,
+          formName: null,
+          formContent: null,
+          createTime: null,
+          updateTime: null,
+          createBy: null,
+          updateBy: null,
+          remark: null
+        };
+        this.resetForm("form");
+      },
+      /** 搜索按钮操作 */
+      handleQuery() {
+        this.queryParams.pageNum = 1;
+        this.getList();
+      },
+      /** 重置按钮操作 */
+      resetQuery() {
+        this.resetForm("queryForm");
+        this.handleQuery();
+      },
+      // 多选框选中数据
+      handleSelectionChange(selection) {
+        this.ids = selection.map(item => item.formId)
+        this.single = selection.length !== 1
+        this.multiple = !selection.length
+      },
+      /** 表单配置信息 */
+      /* handleDetail(row){
+         this.formConfOpen = true;
+         this.formTitle = "流程表单配置详细";
+         this.formConf = JSON.parse(row.formContent)
+       },*/
+      /** 表单配置信息 */
+      handleDetail(row) {
+        this.formRenderOpen = true;
+        this.formTitle = "表单详情";
+
+        this.$nextTick(() => {
+          // 回显数据
+          this.$refs.vFormRef.setFormJson(JSON.parse(row.formContent))
+          this.$nextTick(() => {
+            // 表单禁用
+            this.$refs.vFormRef.disableForm();
+          })
+        })
+      },
+      /** 新增按钮操作 */
+      handleAdd() {
+        // this.reset();
+        // this.open = true;
+        // this.title = "添加流程表单";
+        /*this.$router.push({ path: '/tool/build/index', query: {formId: null }})*/
+        this.$router.push({ path: '/flowable/task/flowForm/index' })
+      },
+      /** 修改按钮操作 */
+      handleUpdate(row) {
+        // this.reset();
+        // const formId = row.formId || this.ids
+        // getForm(formId).then(response => {
+        //   this.form = response.data;
+        //   this.open = true;
+        //   this.title = "修改流程表单";
+        // });
+        //this.$router.push({ path: '/tool/build/index', query: {formId: row.formId }})
+        this.$router.push({ path: '/flowable/task/flowForm/index', query: { formId: row.formId } })
+      },
+      /** 提交按钮 */
+      submitForm() {
+        this.$refs["form"].validate(valid => {
+          if (valid) {
+            if (this.form.formId != null) {
+              updateForm(this.form).then(response => {
+                this.$modal.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              });
+            } else {
+              addForm(this.form).then(response => {
+                this.$modal.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              });
+            }
+          }
+        });
+      },
+      /** 删除按钮操作 */
+      handleDelete(row) {
+        const formIds = row.formId || this.ids;
+        this.$confirm('是否确认删除流程表单编号为"' + formIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function () {
+          return delForm(formIds);
+        }).then(() => {
+          this.getList();
+          this.$modal.msgSuccess("删除成功");
+        })
+      },
+      /** 导出按钮操作 */
+      handleExport() {
+        const queryParams = this.queryParams;
+        this.$confirm('是否确认导出所有流程表单数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function () {
+          return exportForm(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+        })
+      }
+    }
+  };
+</script>
+
+  <style lang="scss" scoped>
+  .test-form {
+    margin: 15px auto;
+    width: 800px;
+    padding: 15px;
+  }
+</style>

+ 24 - 0
src/views/flowable/task/myProcess/detail/flow.vue

@@ -0,0 +1,24 @@
+<template>
+  <div>
+    <flow-view :flowData="flowData"/>
+  </div>
+</template>
+<script>
+import FlowView from './flowview'
+
+export default {
+  name: "Flow",
+  components: {
+    FlowView
+  },
+  props: {
+    flowData: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  data() {
+    return {};
+  }
+};
+</script>

+ 238 - 0
src/views/flowable/task/myProcess/detail/flowview.vue

@@ -0,0 +1,238 @@
+<template>
+  <div class="containers main-box">
+    <el-button type="success" class="editButton"
+               size="small"
+               icon="el-icon-zoom-in"
+               @click="zoomViewport(true)">放大</el-button>
+    <el-button type="warning"
+               size="small"
+               icon="el-icon-zoom-out"
+               @click="zoomViewport(false)">缩小</el-button>
+    <el-button type="info"
+               size="small"
+               icon="el-icon-rank"
+               @click="fitViewport">适中</el-button>
+    <div class="canvas" ref="flowCanvas"></div>
+  </div>
+</template>
+<script>
+import { CustomViewer as BpmnViewer } from "@/components/customBpmn";
+
+export default {
+  name: "FlowView",
+  props: {
+    flowData: {
+      type: Object,
+      default: () => {}
+    },
+  },
+  data() {
+    return {
+      bpmnViewer: null
+    };
+  },
+  watch: {
+    flowData: {
+      handler(newVal) {
+        if (Object.keys(newVal).length > 0) {
+          // 生成实例
+          this.bpmnViewer && this.bpmnViewer.destroy();
+          this.bpmnViewer = new BpmnViewer({
+            container: this.$refs.flowCanvas,
+            height: 'calc(100vh - 200px)',
+          });
+          this.loadFlowCanvas(newVal);
+        }
+      },
+      immediate: true, // 立即生效
+      deep: true  //监听对象或数组的时候,要用到深度监听
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    // 加载流程
+    async loadFlowCanvas(flowData) {
+      const self = this
+      try {
+        await self.bpmnViewer.importXML(flowData.xmlData);
+        self.fitViewport()
+        if (flowData.nodeData !==undefined && flowData.nodeData.length > 0 ) {
+          self.fillColor(flowData.nodeData)
+        }
+      } catch (err) {
+        console.error(err.message, err.warnings)
+      }
+    },
+    // 设置高亮颜色的class
+    setNodeColor(nodeCodes, colorClass, canvas) {
+      for (let i = 0; i < nodeCodes.length; i++) {
+        canvas.addMarker(nodeCodes[i], colorClass);
+      }
+    },
+    // 让图能自适应屏幕
+    fitViewport() {
+      this.zoom = this.bpmnViewer.get('canvas').zoom("fit-viewport", "auto")
+    },
+    // 放大缩小
+    zoomViewport(zoomIn = true) {
+      this.zoom = this.bpmnViewer.get('canvas').zoom()
+      this.zoom += (zoomIn ? 0.1 : -0.1)
+      if(this.zoom >= 0.2) this.bpmnViewer.get('canvas').zoom(this.zoom)
+    },
+
+    // 设置高亮颜色的
+    fillColor(nodeData) {
+      const canvas = this.bpmnViewer.get('canvas')
+      this.bpmnViewer.getDefinitions().rootElements[0].flowElements.forEach(n => {
+        const completeTask = nodeData.find(m => m.key === n.id)
+        const todoTask = nodeData.find(m => !m.completed)
+        const endTask = nodeData[nodeData.length - 1]
+        if (n.$type === 'bpmn:UserTask') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+                if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
+                  canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
+                  canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
+                }else {
+                  canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                  canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                }
+              }
+            })
+          }
+        }
+        // 排他网关
+        else if (n.$type === 'bpmn:ExclusiveGateway') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+
+                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+              }
+
+            })
+          }
+
+        }
+        // 并行网关
+        else if (n.$type === 'bpmn:ParallelGateway') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+              }
+            })
+          }
+        }
+        else if (n.$type === 'bpmn:StartEvent') {
+          n.outgoing.forEach(nn => {
+            const completeTask = nodeData.find(m => m.key === nn.targetRef.id)
+            if (completeTask) {
+              canvas.addMarker(nn.id, 'highlight')
+              canvas.addMarker(n.id, 'highlight')
+              return
+            }
+          })
+        }
+        else if (n.$type === 'bpmn:EndEvent') {
+          if (endTask.key === n.id && endTask.completed) {
+            canvas.addMarker(n.id, 'highlight')
+            return
+          }
+        }
+      })
+    },
+  }
+};
+</script>
+<style lang="scss">
+  .bjs-powered-by {
+    display: none;
+  }
+  .view-mode {
+    .el-header, .el-aside, .djs-palette, .bjs-powered-by {
+      display: none;
+    }
+    .el-loading-mask {
+      background-color: initial;
+    }
+    .el-loading-spinner {
+      display: none;
+    }
+  }
+  .containers {
+    // background-color: #ffffff;
+    width: 100%;
+    height: 100%;
+    .canvas {
+      width: 100%;
+      height: 100%;
+    }
+    .panel {
+      position: absolute;
+      right: 0;
+      top: 50px;
+      width: 300px;
+    }
+    .load {
+      margin-right: 10px;
+    }
+    .el-form-item__label{
+      font-size: 13px;
+    }
+
+    .djs-palette{
+      left: 0px!important;
+      top: 0px;
+      border-top: none;
+    }
+
+    .djs-container svg {
+      min-height: 650px;
+    }
+
+    .highlight.djs-shape .djs-visual > :nth-child(1) {
+      fill: green !important;
+      stroke: green !important;
+      fill-opacity: 0.2 !important;
+    }
+    .highlight.djs-shape .djs-visual > :nth-child(2) {
+      fill: green !important;
+    }
+    .highlight.djs-shape .djs-visual > path {
+      fill: green !important;
+      fill-opacity: 0.2 !important;
+      stroke: green !important;
+    }
+    .highlight.djs-connection > .djs-visual > path {
+      stroke: green !important;
+    }
+    .highlight-todo.djs-connection > .djs-visual > path {
+      stroke: orange !important;
+      stroke-dasharray: 4px !important;
+      fill-opacity: 0.2 !important;
+    }
+    .highlight-todo.djs-shape .djs-visual > :nth-child(1) {
+      fill: orange !important;
+      stroke: orange !important;
+      stroke-dasharray: 4px !important;
+      fill-opacity: 0.2 !important;
+    }
+    .overlays-div {
+      font-size: 10px;
+      color: red;
+      width: 100px;
+      top: -20px !important;
+    }
+  }
+</style>

+ 235 - 0
src/views/flowable/task/myProcess/detail/index.vue

@@ -0,0 +1,235 @@
+<template>
+  <div class="app-container">
+    <el-card class="box-card" >
+      <div slot="header" class="clearfix">
+        <span class="el-icon-document">已发任务</span>
+        <el-button style="float: right;" size="mini" type="danger" @click="goBack">关闭</el-button>
+      </div>
+      <el-tabs  tab-position="top" v-model="activeName" @tab-click="handleClick">
+        <!--表单信息-->
+        <el-tab-pane label="表单信息" name="1">
+          <el-col :span="16" :offset="4">
+<!--            <div class="test-form">
+                <parser :key="new Date().getTime()" :form-conf="variablesData" />
+            </div>-->
+            <v-form-render ref="vFormRef"/>
+         </el-col>
+        </el-tab-pane>
+        <!--流程流转记录-->
+        <el-tab-pane label="流转记录" name="2">
+          <el-col :span="16" :offset="4" >
+            <div class="block">
+              <el-timeline>
+                <el-timeline-item
+                  v-for="(item,index ) in flowRecordList"
+                  :key="index"
+                  :icon="setIcon(item.finishTime)"
+                  :color="setColor(item.finishTime)"
+                >
+                  <p style="font-weight: 700">{{item.taskName}}</p>
+                  <el-card :body-style="{ padding: '10px' }">
+                    <el-descriptions class="margin-top" :column="1" size="small" border>
+                      <el-descriptions-item v-if="item.assigneeName" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-user"></i>办理人</template>
+                        {{item.assigneeName}}
+                        <el-tag type="info" size="mini">{{item.deptName}}</el-tag>
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.candidate" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-user"></i>候选办理</template>
+                        {{item.candidate}}
+                      </el-descriptions-item>
+                      <el-descriptions-item label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-date"></i>接收时间</template>
+                        {{item.createTime}}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.finishTime" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-date"></i>处理时间</template>
+                        {{item.finishTime}}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.duration"  label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-time"></i>耗时</template>
+                        {{item.duration}}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.comment" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-tickets"></i>处理意见</template>
+                        {{item.comment.comment}}
+                      </el-descriptions-item>
+                    </el-descriptions>
+                  </el-card>
+                </el-timeline-item>
+              </el-timeline>
+            </div>
+          </el-col>
+        </el-tab-pane>
+        <!--流程图-->
+        <el-tab-pane label="流程图" name="3">
+          <flow :flowData="flowData"/>
+<!--          <bpmn-viewer :flowData="flowData" :procInsId="taskForm.procInsId"/>-->
+        </el-tab-pane>
+    </el-tabs>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import {flowRecord} from "@/api/flowable/finished";
+import Parser from '@/components/parser/Parser'
+import {getProcessVariables, readXml, getFlowViewer, getHighlight, flowXmlAndNode} from "@/api/flowable/definition";
+import flow from '@/views/flowable/task/myProcess/detail/flow'
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+
+export default {
+  name: "Record",
+  components: {
+    Parser,
+    flow
+  },
+  props: {},
+  data() {
+    return {
+      // 模型xml数据
+      flowData: {},
+      activeName: '1',
+      // 查询参数
+      queryParams: {
+        deptId: undefined
+      },
+      // 遮罩层
+      loading: true,
+      flowRecordList: [], // 流程流转数据
+      taskForm:{
+        multiple: false,
+        comment:"", // 意见内容
+        procInsId: "", // 流程实例编号
+        instanceId: "", // 流程实例编号
+        deployId: "",  // 流程定义编号
+        taskId: "" ,// 流程任务编号
+        procDefId: "",  // 流程编号
+      },
+      variablesData: {}, // 流程变量数据
+    };
+  },
+  created() {
+    this.taskForm.deployId = this.$route.query && this.$route.query.deployId;
+    this.taskForm.taskId  = this.$route.query && this.$route.query.taskId;
+    this.taskForm.procInsId = this.$route.query && this.$route.query.procInsId;
+    // 回显流程记录
+    // 流程任务重获取变量表单
+    this.processVariables( this.taskForm.taskId)
+    this.getFlowRecordList(this.taskForm.procInsId, this.taskForm.deployId);
+  },
+  methods: {
+    handleClick(tab, event) {
+      if (tab.name === '3'){
+        flowXmlAndNode({procInsId:this.taskForm.procInsId,deployId:this.taskForm.deployId}).then(res => {
+          this.flowData = res.data;
+        })
+      }
+    },
+    getFlowViewer(procInsId,executionId) {
+      getFlowViewer(procInsId,executionId).then(res => {
+        this.taskList = res.data
+      })
+    },
+    setIcon(val) {
+      if (val) {
+        return "el-icon-check";
+      } else {
+        return "el-icon-time";
+      }
+    },
+    setColor(val) {
+      if (val) {
+        return "#2bc418";
+      } else {
+        return "#b3bdbb";
+      }
+    },
+    /** 流程流转记录 */
+    getFlowRecordList(procInsId, deployId) {
+      const that = this
+      const params = {procInsId: procInsId, deployId: deployId}
+      flowRecord(params).then(res => {
+        that.flowRecordList = res.data.flowList;
+      }).catch(res => {
+        this.goBack();
+      })
+    },
+    fillFormData(form, data) {
+      form.fields.forEach((item) => {
+        const vModel = item.__vModel__;
+        const val = data[item.__vModel__];
+
+        // 特殊处理el-upload,回显图片
+        if (item.__config__.tag === "el-upload") {
+          // 回显图片
+          item["file-list"] = (val || []).map((url) => ({
+            name: `${vModel}${i}`,
+            url,
+          }));
+        }
+
+        if (val) {
+          item.__config__.defaultValue = val;
+        }
+      });
+    },
+    /** 获取流程变量内容 */
+    processVariables(taskId) {
+      if (taskId) {
+        // 提交流程申请时填写的表单存入了流程变量中后续任务处理时需要展示
+        getProcessVariables(taskId).then(res => {
+          /*this.variablesData = res.data.variables;*/
+          this.$nextTick(() => {
+            // 回显表单
+            this.$refs.vFormRef.setFormJson(res.data.formJson);
+            this.$nextTick(() => {
+              // 加载表单填写的数据
+              this.$refs.vFormRef.setFormData(res.data);
+              this.$nextTick(() => {
+                // 表单禁用
+                this.$refs.vFormRef.disableForm();
+              })
+            })
+          })
+        });
+      }
+    },
+    /** 返回页面 */
+    goBack() {
+      // 关闭当前标签页并返回上个页面
+      const obj = { path: "/task/process", query: { t: Date.now()} };
+      this.$tab.closeOpenPage(obj);
+    },
+  }
+};
+</script>
+<style lang="scss" scoped>
+.test-form {
+  margin: 15px auto;
+  width: 800px;
+  padding: 15px;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+.clearfix:after {
+  clear: both
+}
+
+.box-card {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.el-tag + .el-tag {
+  margin-left: 10px;
+}
+
+.my-label {
+  background: #E1F3D8;
+}
+</style>

+ 360 - 0
src/views/flowable/task/myProcess/index.vue

@@ -0,0 +1,360 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-position="top">
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="名称" prop="name">
+            <el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter.native="handleQuery" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="开始时间" prop="deployTime">
+            <el-date-picker style="width: 100%;" clearable v-model="queryParams.deployTime" type="date" value-format="yyyy-MM-dd"
+              placeholder="选择时间">
+            </el-date-picker>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6"></el-col>
+        <el-col :span="6"></el-col>
+      </el-row>
+
+
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="搜索">
+            <el-button style="width: 100%;" type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="重置">
+            <el-button style="width: 100%;" icon="el-icon-refresh" type="primary" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" icon="el-icon-plus" size="small" @click="handleAdd"
+          v-hasPermi="['system:deployment:add']">新增流程</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" icon="el-icon-delete" size="small" :disabled="multiple" @click="handleDelete"
+          v-hasPermi="['system:deployment:remove']">删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+    <div class="border-card">
+      <el-table height="450" v-loading="loading" :data="myProcessList" border @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="流程编号" align="center" prop="procInsId" :show-overflow-tooltip="true" />
+        <el-table-column label="流程名称" align="center" prop="procDefName" :show-overflow-tooltip="true" />
+        <el-table-column label="流程类别" align="center" prop="category" width="100px" />
+        <el-table-column label="流程版本" align="center" width="80px">
+          <template slot-scope="scope">
+            <el-tag size="medium">v{{ scope.row.procDefVersion }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="提交时间" align="center" prop="createTime" width="180" />
+        <el-table-column label="流程状态" align="center" width="100">
+          <template slot-scope="scope">
+            <el-tag v-if="scope.row.finishTime == null" size="small">进行中</el-tag>
+            <el-tag type="success" class="editButton" v-if="scope.row.finishTime != null" size="small">已完成</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="耗时" align="center" prop="duration" width="180" />
+        <el-table-column label="当前节点" align="center" prop="taskName" />
+        <el-table-column label="办理人" align="center">
+          <template slot-scope="scope">
+            <label v-if="scope.row.assigneeName">{{ scope.row.assigneeName }} <el-tag type="info"
+                size="small">{{ scope.row.assigneeDeptName }}</el-tag></label>
+            <!--          <label v-if="scope.row.candidate">{{scope.row.candidate}}</label>-->
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="150"  class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button style="color:#1890ff" @click="handleFlowRecord(scope.row)" type="text" size="small">详情</el-button>
+            <el-button @click="handleStop(scope.row)" type="text" size="small">取消申请</el-button>
+            <el-button class="custom-red-color" @click="handleDelete(scope.row)" type="text" size="small"
+              v-hasPermi="['system:deployment:remove']">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
+        @pagination="getList" />
+    </div>
+
+
+    <!-- 发起流程 -->
+    <el-dialog :title="title" :visible.sync="open" width="60%" append-to-body>
+      <el-form :model="queryProcessParams" ref="queryProcessForm" :inline="true" v-show="showSearch" label-width="68px">
+        <el-form-item label="名称" prop="name">
+          <el-input v-model="queryProcessParams.name" placeholder="请输入名称" clearable size="small"
+            @keyup.enter.native="handleQuery" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" size="small" @click="handleProcessQuery">搜索</el-button>
+          <el-button icon="el-icon-refresh" size="small" @click="resetProcessQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table v-loading="processLoading" fit :data="definitionList" border>
+        <el-table-column label="流程名称" align="center" prop="name" />
+        <el-table-column label="流程版本" align="center">
+          <template slot-scope="scope">
+            <el-tag size="medium">v{{ scope.row.version }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="流程分类" align="center" prop="category" />
+        <el-table-column label="操作" align="center" width="300" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button size="small" type="text" icon="el-icon-edit-outline"
+              @click="handleStartProcess(scope.row)">发起流程</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination v-show="processTotal > 0" :total="processTotal" :page.sync="queryProcessParams.pageNum"
+        :limit.sync="queryProcessParams.pageSize" @pagination="listDefinition" />
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import {
+  getDeployment,
+  delDeployment,
+  addDeployment,
+  updateDeployment,
+  exportDeployment,
+  flowRecord
+} from "@/api/flowable/finished";
+import { myProcessList, stopProcess } from "@/api/flowable/process";
+import { listDefinition } from "@/api/flowable/definition";
+export default {
+  name: "Deploy",
+  components: {
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      processLoading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      processTotal: 0,
+      // 我发起的流程列表数据
+      myProcessList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      src: "",
+      definitionList: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        category: null,
+        key: null,
+        tenantId: null,
+        deployTime: null,
+        derivedFrom: null,
+        derivedFromRoot: null,
+        parentDeploymentId: null,
+        engineVersion: null
+      },
+      // 查询参数
+      queryProcessParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        category: null,
+        key: null,
+        tenantId: null,
+        deployTime: null,
+        derivedFrom: null,
+        derivedFromRoot: null,
+        parentDeploymentId: null,
+        engineVersion: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询流程定义列表 */
+    getList() {
+      this.loading = true;
+      myProcessList(this.queryParams).then(response => {
+        this.myProcessList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        category: null,
+        key: null,
+        tenantId: null,
+        deployTime: null,
+        derivedFrom: null,
+        derivedFromRoot: null,
+        parentDeploymentId: null,
+        engineVersion: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 搜索按钮操作 */
+    handleProcessQuery() {
+      this.queryProcessParams.pageNum = 1;
+      this.listDefinition();
+    },
+    /** 重置按钮操作 */
+    resetProcessQuery() {
+      this.resetForm("queryProcessForm");
+      this.handleProcessQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.procInsId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.open = true;
+      this.title = "发起流程";
+      this.listDefinition();
+    },
+    listDefinition() {
+      listDefinition(this.queryProcessParams).then(response => {
+        this.definitionList = response.data.records;
+        this.processTotal = response.data.total;
+        this.processLoading = false;
+      });
+    },
+    /**  发起流程申请 */
+    handleStartProcess(row) {
+      this.$router.push({
+        path: '/flowable/task/myProcess/send/index',
+        query: {
+          deployId: row.deploymentId,
+          procDefId: row.id
+        }
+      })
+    },
+    /**  取消流程申请 */
+    handleStop(row) {
+      const params = {
+        instanceId: row.procInsId
+      }
+      stopProcess(params).then(res => {
+        this.$modal.msgSuccess(res.msg);
+        this.getList();
+      });
+    },
+    /** 流程流转记录 */
+    handleFlowRecord(row) {
+      this.$router.push({
+        path: '/flowable/task/myProcess/detail/index',
+        query: {
+          procInsId: row.procInsId,
+          deployId: row.deployId,
+          taskId: row.taskId
+        }
+      })
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getDeployment(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改流程定义";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateDeployment(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addDeployment(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.procInsId || this.ids;// 暂不支持删除多个流程
+      this.$confirm('是否确认删除流程定义编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return delDeployment(ids);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      })
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有流程定义数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return exportDeployment(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+      })
+    }
+  }
+};
+</script>

+ 24 - 0
src/views/flowable/task/myProcess/send/flow.vue

@@ -0,0 +1,24 @@
+<template>
+  <div>
+    <flow-view :flowData="flowData"/>
+  </div>
+</template>
+<script>
+import FlowView from './flowview'
+
+export default {
+  name: "Flow",
+  components: {
+    FlowView
+  },
+  props: {
+    flowData: {
+      type: Object,
+      default: () => {}
+    },
+  },
+  data() {
+    return {};
+  }
+};
+</script>

+ 129 - 0
src/views/flowable/task/myProcess/send/flowview.vue

@@ -0,0 +1,129 @@
+<template>
+  <div class="containers main-box">
+    <el-button type="success" class="editButton"
+               size="small"
+               icon="el-icon-zoom-in"
+               @click="zoomViewport(true)">放大</el-button>
+    <el-button type="warning"
+               size="small"
+               icon="el-icon-zoom-out"
+               @click="zoomViewport(false)">缩小</el-button>
+    <el-button type="info"
+               size="small"
+               icon="el-icon-rank"
+               @click="fitViewport">适中</el-button>
+    <div class="canvas" ref="flowCanvas"></div>
+  </div>
+</template>
+<script>
+import { CustomViewer as BpmnViewer } from "@/components/customBpmn";
+
+export default {
+  name: "FlowView",
+  props: {
+    flowData: {
+      type: Object,
+      default: () => {}
+    },
+  },
+  data() {
+    return {
+      bpmnViewer: null
+    };
+  },
+  watch: {
+    flowData: {
+      handler(newVal) {
+        if (Object.keys(newVal).length > 0) {
+          // 生成实例
+          this.bpmnViewer && this.bpmnViewer.destroy();
+          this.bpmnViewer = new BpmnViewer({
+            container: this.$refs.flowCanvas,
+            height: 'calc(100vh - 200px)',
+          });
+          this.loadFlowCanvas(newVal)
+        }
+      },
+      immediate: true, // 立即生效
+      deep: true  //监听对象或数组的时候,要用到深度监听
+    }
+  },
+  mounted() {},
+  methods: {
+    // 加载流程图片
+    async loadFlowCanvas(flowData) {
+      const self = this
+      try {
+        await self.bpmnViewer.importXML(flowData.xmlData);
+        self.fitViewport()
+      } catch (err) {
+        console.error(err.message, err.warnings)
+      }
+    },
+    // 让图能自适应屏幕
+    fitViewport() {
+      this.zoom = this.bpmnViewer.get('canvas').zoom("fit-viewport", "auto")
+    },
+    // 放大缩小
+    zoomViewport(zoomIn = true) {
+      this.zoom = this.bpmnViewer.get('canvas').zoom()
+      this.zoom += (zoomIn ? 0.1 : -0.1)
+      if(this.zoom >= 0.2) this.bpmnViewer.get('canvas').zoom(this.zoom)
+    },
+  }
+};
+</script>
+<style lang="scss">
+  .bjs-powered-by {
+    display: none;
+  }
+  .view-mode {
+    .el-header, .el-aside, .djs-palette, .bjs-powered-by {
+      display: none;
+    }
+    .el-loading-mask {
+      background-color: initial;
+    }
+    .el-loading-spinner {
+      display: none;
+    }
+  }
+  .containers {
+    // background-color: #ffffff;
+    width: 100%;
+    height: 100%;
+    .canvas {
+      width: 100%;
+      height: 100%;
+    }
+    .panel {
+      position: absolute;
+      right: 0;
+      top: 50px;
+      width: 300px;
+    }
+    .load {
+      margin-right: 10px;
+    }
+    .el-form-item__label{
+      font-size: 13px;
+    }
+
+    .djs-palette{
+      left: 0px!important;
+      top: 0px;
+      border-top: none;
+    }
+
+    .djs-container svg {
+      min-height: 650px;
+    }
+
+    .overlays-div {
+      font-size: 10px;
+      color: red;
+      width: 100px;
+      top: -20px !important;
+    }
+  }
+</style>

+ 376 - 0
src/views/flowable/task/myProcess/send/index.vue

@@ -0,0 +1,376 @@
+<template>
+  <div class="app-container">
+    <el-card class="box-card" >
+      <div slot="header" class="clearfix">
+        <span class="el-icon-document">发起任务</span>
+        <el-button style="float: right;" size="mini" type="danger" @click="goBack">关闭</el-button>
+      </div>
+      <el-tabs  tab-position="top" v-model="activeName"  @tab-click="handleClick">
+        <!--表单信息-->
+        <el-tab-pane label="表单信息" name="1">
+            <!--初始化流程加载表单信息-->
+            <el-col :span="16" :offset="4">
+<!--              <div class="test-form">
+                <parser :key="new Date().getTime()" :form-conf="formConf" @submit="submitForm" ref="parser" @getData="getData" />
+              </div>-->
+              <v-form-render :form-data="formRenderData" ref="vFormRef"/>
+              <div style="margin-left:15%;margin-bottom: 20px;font-size: 14px;">
+<!--                <el-button type="primary" @click="submitForm">提 交</el-button>
+                <el-button type="primary" @click="resetForm">重 置</el-button>-->
+                <el-button @click="goBack">返 回</el-button>
+<!--                <el-button
+                  type="warning"
+                  @click="submitForm(1)"
+                  v-preventReClick
+                  v-if="!form.projectState || form.projectState == '1'"
+                >暂 存</el-button
+                >-->
+                <el-button type="warning" @click="submitForm(false)">暂 存</el-button>
+                <el-button type="primary" @click="submitForm(true)" v-preventReClick>提 交</el-button>
+              </div>
+            </el-col>
+        </el-tab-pane>
+        <!--流程图-->
+        <el-tab-pane label="流程图" name="2">
+           <flow :flowData="flowData"/>
+        </el-tab-pane>
+      </el-tabs>
+      <!--选择流程接收人-->
+      <el-dialog :title="taskTitle" :visible.sync="taskOpen" width="65%" append-to-body>
+        <flow-user v-if="checkSendUser" :checkType="checkType"  @handleUserSelect="handleUserSelect"/>
+        <flow-role v-if="checkSendRole" @handleRoleSelect="handleRoleSelect"/>
+        <span slot="footer" class="dialog-footer">
+          <el-button @click="taskOpen = false">取 消</el-button>
+          <el-button type="primary" @click="submitTask">提 交</el-button>
+        </span>
+      </el-dialog>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import Parser from '@/components/parser/Parser'
+import {definitionStart, flowXmlAndNode} from "@/api/flowable/definition";
+import flow from './flow'
+import {flowFormData} from "@/api/flowable/process";
+import {getNextFlowNodeByStart} from "@/api/flowable/todo";
+import FlowUser from '@/components/flow/User'
+import FlowRole from '@/components/flow/Role'
+
+export default {
+  name: "Record",
+  components: {
+    Parser,
+    flow,
+    FlowUser,
+    FlowRole,
+  },
+  props: {},
+  data() {
+    return {
+      // 模型xml数据
+      flowData: {},
+      activeName: '1', // 切换tab标签
+      defaultProps: {
+        children: "children",
+        label: "label"
+      },
+      // 查询参数
+      queryParams: {
+        deptId: undefined
+      },
+      // 遮罩层
+      loading: true,
+      deployId: "",  // 流程定义编号
+      procDefId: "",  // 流程实例编号
+      //formConf: {}, // 默认表单数据
+      formRenderData: {},// 默认表单数据
+      variables: [], // 流程变量数据
+      taskTitle: null,
+      taskOpen: false,
+      checkSendUser: false, // 是否展示人员选择模块
+      checkSendRole: false,// 是否展示角色选择模块
+      checkType: '', // 选择类型
+      checkValues: null, // 选中任务接收人员数据
+      formData: {}, // 填写的表单数据,
+      multiInstanceVars: '', // 会签节点
+      formJson: {} // 表单json
+    };
+  },
+  created() {
+    this.deployId = this.$route.query && this.$route.query.deployId;
+    // 初始化表单
+    this.procDefId  = this.$route.query && this.$route.query.procDefId;
+    // this.getNextFlowNodeByStart(this.deployId);
+    this.getFlowFormData(this.deployId);
+  },
+  methods: {
+    handleClick(tab, event) {
+      if (tab.name === '2'){
+        flowXmlAndNode({deployId:this.deployId}).then(res => {
+          this.flowData = res.data;
+        })
+      }
+    },
+    /** 流程表单数据 */
+    getFlowFormData(deployId) {
+      const that = this
+      const params = {deployId: deployId}
+      flowFormData(params).then(res => {
+        // 流程过程中不存在初始化表单 直接读取的流程变量中存储的表单值
+          //that.formConf = res.data;
+        this.$nextTick(() => {
+          // 回显数据
+          this.$refs.vFormRef.setFormJson(res.data);
+          this.formJson = res.data;
+        })
+      }).catch(res => {
+        this.goBack();
+      })
+    },
+    /** 返回页面 */
+    goBack() {
+      // 关闭当前标签页并返回上个页面
+      const obj = { path: "/task/process", query: { t: Date.now()} };
+      this.$tab.closeOpenPage(obj);
+    },
+    /** 接收子组件传的值 */
+    getData(data) {
+      if (data) {
+        const variables = [];
+        data.fields.forEach(item => {
+          let variableData = {};
+          variableData.label = item.__config__.label
+          // 表单值为多个选项时
+          if (item.__config__.defaultValue instanceof Array) {
+            const array = [];
+            item.__config__.defaultValue.forEach(val => {
+              array.push(val)
+            })
+            variableData.val = array;
+          } else {
+            variableData.val = item.__config__.defaultValue
+          }
+          variables.push(variableData)
+        })
+        this.variables = variables;
+      }
+    },
+    /** 申请流程表单数据提交 */
+    submitForm(flag) {
+      // 根据当前任务或者流程设计配置的下一步节点 todo 暂时未涉及到考虑网关、表达式和多节点情况
+     /* getNextFlowNodeByStart({deploymentId: this.deployId,variables:formData.valData}).then(res => {
+        const data = res.data;
+        if (data) {
+          this.formData = formData;
+          if (data.dataType === 'dynamic') {
+            if (data.type === 'assignee') { // 指定人员
+              this.checkSendUser = true;
+              this.checkType = "single";
+            } else if (data.type === 'candidateUsers') {  // 候选人员(多个)
+              this.checkSendUser = true;
+              this.checkType = "multiple";
+            } else if (data.type === 'candidateGroups') { // 指定组(所属角色接收任务)
+              this.checkSendRole = true;
+            } else { // 会签
+              // 流程设计指定的 elementVariable 作为会签人员列表
+              this.multiInstanceVars = data.vars;
+              this.checkSendUser = true;
+              this.checkType = "multiple";
+            }
+            this.taskOpen = true;
+            this.taskTitle = "选择任务接收";
+          } else {
+            const variables = this.formData.valData;
+            const formData = this.formData.formData;
+            formData.disabled = true;
+            formData.formBtns = false;
+            if (this.procDefId) {
+              variables.variables = formData;
+              // 启动流程并将表单数据加入流程变量
+              definitionStart(this.procDefId, JSON.stringify(variables)).then(res => {
+                this.$modal.msgSuccess(res.msg);
+                this.goBack();
+              })
+            }
+          }
+        }
+      })*/
+      this.$refs.vFormRef.getFormData().then(formData => {
+        formData.auditPass=flag;
+        // 根据当前任务或者流程设计配置的下一步节点 todo 暂时未涉及到考虑网关、表达式和多节点情况
+        getNextFlowNodeByStart({deploymentId: this.deployId, variables: formData}).then(res => {
+          const data = res.data;
+          debugger
+          if (data) {
+            this.formData = formData;
+            if (data.dataType === 'dynamic') {
+              if (data.type === 'assignee') { // 指定人员
+                this.checkSendUser = true;
+                this.checkType = "single";
+              } else if (data.type === 'candidateUsers') {  // 候选人员(多个)
+                this.checkSendUser = true;
+                this.checkType = "multiple";
+              } else if (data.type === 'candidateGroups') { // 指定组(所属角色接收任务)
+                this.checkSendRole = true;
+              } else { // 会签
+                // 流程设计指定的 elementVariable 作为会签人员列表
+                this.multiInstanceVars = data.vars;
+                this.checkSendUser = true;
+                this.checkType = "multiple";
+              }
+              this.taskOpen = true;
+              this.taskTitle = "选择任务接收";
+            } else {
+              if (this.procDefId) {
+                const param = {
+                  formJson:  this.formJson,
+                }
+                // 复制对象的属性值给新的对象
+                Object.assign(param, formData);
+                // 启动流程并将表单数据加入流程变量
+                definitionStart(this.procDefId, param).then(res => {
+                  this.$modal.msgSuccess(res.msg);
+                  this.goBack();
+                })
+              }
+            }
+          }
+        })
+      }).catch(error => {
+        // this.$modal.msgError(error)
+      })
+    },
+    /** 重置表单 */
+    resetForm() {
+      this.$refs.vFormRef.resetForm();
+    },
+    /** 提交流程 */
+    submitTask() {
+      if (!this.checkValues && this.checkSendUser){
+        this.$modal.msgError("请选择任务接收!");
+        return;
+      }
+      if (!this.checkValues && this.checkSendRole){
+        this.$modal.msgError("请选择流程接收角色组!");
+        return;
+      }
+      /*if (this.formData) {
+        const variables = this.formData.valData;
+        const formData = this.formData.formData;
+        // 表单是否禁用
+        formData.disabled = true;
+        // 是否显示按钮
+        formData.formBtns = false;
+        variables.variables = formData;
+        if (this.multiInstanceVars) {
+          this.$set(variables, this.multiInstanceVars, this.checkValues);
+        } else {
+          this.$set(variables, "approval", this.checkValues);
+        }
+        console.log(variables,"流程发起提交表单数据")
+        // 启动流程并将表单数据加入流程变量
+        definitionStart(this.procDefId, JSON.stringify(variables)).then(res => {
+          this.$modal.msgSuccess(res.msg);
+          this.goBack();
+        })
+      }*/
+      if (this.formData) {
+        const param = {
+          formJson:  this.formJson,
+        }
+        // 复制对象的属性值给新的对象
+        Object.assign(param, this.formData);
+        if (this.multiInstanceVars) {
+          this.$set(param, this.multiInstanceVars, this.checkValues);
+        } else {
+          this.$set(param, "approval", this.checkValues);
+        }
+        // 启动流程并将表单数据加入流程变量
+        definitionStart(this.procDefId, param).then(res => {
+          this.$modal.msgSuccess(res.msg);
+          this.goBack();
+        })
+      }
+    },
+    /** 根据当前任务获取流程设计配置的下一步节点 */
+    getNextFlowNodeByStart(deploymentId,variables) {
+      // 根据当前任务或者流程设计配置的下一步节点 todo 暂时未涉及到考虑网关、表达式和多节点情况
+      getNextFlowNodeByStart({deploymentId: deploymentId,variables:variables}).then(res => {
+        const data = res.data;
+        if (data) {
+          if (data.type === 'assignee') { // 指定人员
+            this.checkSendUser = true;
+            this.checkType = "single";
+          } else if (data.type === 'candidateUsers') {  // 候选人员(多个)
+            this.checkSendUser = true;
+            this.checkType = "multiple";
+          } else if (data.type === 'candidateGroups') { // 指定组(所属角色接收任务)
+            this.checkSendRole = true;
+          } else if (data.type === 'multiInstance') { // 会签?
+            // 流程设计指定的 elementVariable 作为会签人员列表
+            this.multiInstanceVars = data.vars;
+            this.checkSendUser = true;
+            this.checkType = "multiple";
+          }
+        }
+      })
+    },
+    // 用户信息选中数据
+    handleUserSelect(selection) {
+      if (selection) {
+        if (selection instanceof Array) {
+          const selectVal = selection.map(item => item.userId);
+          if (this.multiInstanceVars) {
+            this.checkValues = selectVal;
+          } else {
+            this.checkValues = selectVal.join(',');
+          }
+        } else {
+          this.checkValues = selection.userId;
+        }
+      }
+    },
+    // 角色信息选中数据
+    handleRoleSelect(selection) {
+      if (selection) {
+        if (selection instanceof Array) {
+          const selectVal = selection.map(item => item.roleId);
+          this.checkValues = selectVal.join(',')
+        } else {
+          this.checkValues = selection;
+        }
+      }
+    },
+  }
+};
+</script>
+<style lang="scss" scoped>
+.test-form {
+  margin: 15px auto;
+  width: 800px;
+  padding: 15px;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+.clearfix:after {
+  clear: both
+}
+
+.box-card {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.el-tag + .el-tag {
+  margin-left: 10px;
+}
+
+.my-label {
+  background: #E1F3D8;
+}
+</style>

+ 36 - 0
src/views/flowable/task/record/flow.vue

@@ -0,0 +1,36 @@
+<template>
+  <div>
+    <!--<bpmn-modeler
+      ref="refNode"
+      :xml="xmlData"
+      :is-view="true"
+      :taskList="taskData"
+    />-->
+    <flow-view :xmlData="xmlData" :taskList="taskData"/>
+  </div>
+</template>
+<script>
+import bpmnModeler from '@/components/Process/index'
+import FlowView from './flowview'
+
+export default {
+  name: "Flow",
+  components: {
+    bpmnModeler,
+    FlowView
+  },
+  props: {
+    xmlData: {
+      type: String,
+      default: ''
+    },
+    taskData: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {};
+  }
+};
+</script>

+ 239 - 0
src/views/flowable/task/record/flowview.vue

@@ -0,0 +1,239 @@
+<template>
+  <div class="containers main-box">
+    <el-button type="success" class="editButton"
+               size="small"
+               icon="el-icon-zoom-in"
+               @click="zoomViewport(true)">放大</el-button>
+    <el-button type="warning"
+               size="small"
+               icon="el-icon-zoom-out"
+               @click="zoomViewport(false)">缩小</el-button>
+    <el-button type="info"
+               size="small"
+               icon="el-icon-rank"
+               @click="fitViewport">适中</el-button>
+    <div class="canvas" ref="flowCanvas"></div>
+  </div>
+</template>
+<script>
+import { CustomViewer as BpmnViewer } from "@/components/customBpmn";
+
+export default {
+  name: "FlowView",
+  props: {
+    xmlData: {
+      type: String,
+      default: ''
+    },
+    taskList: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      bpmnViewer: null
+    };
+  },
+  watch: {
+    xmlData: function(val) {
+      if (val) {
+        this.getImg(val)
+      }
+    }
+  },
+  mounted() {
+    // 生成实例
+    this.bpmnViewer && this.bpmnViewer.destroy();
+    this.bpmnViewer = new BpmnViewer({
+      container: this.$refs.flowCanvas,
+      height: 'calc(100vh - 200px)',
+    });
+    // this.getImg(this.xmlData)
+  },
+  methods: {
+    // 获取流程图片
+    async getImg(xmlUrl) {
+      const self = this
+      try {
+        await self.bpmnViewer.importXML(xmlUrl);
+        self.fitViewport()
+        if (self.taskList !==undefined && self.taskList.length > 0 ) {
+          self.fillColor()
+        }
+      } catch (err) {
+        console.error(err.message, err.warnings)
+      }
+    },
+    // 设置高亮颜色的class
+    setNodeColor(nodeCodes, colorClass, canvas) {
+      for (let i = 0; i < nodeCodes.length; i++) {
+        canvas.addMarker(nodeCodes[i], colorClass);
+      }
+    },
+    // 让图能自适应屏幕
+    fitViewport() {
+      this.zoom = this.bpmnViewer.get('canvas').zoom("fit-viewport", "auto")
+    },
+    // 放大缩小
+    zoomViewport(zoomIn = true) {
+      this.zoom = this.bpmnViewer.get('canvas').zoom()
+      this.zoom += (zoomIn ? 0.1 : -0.1)
+      if(this.zoom >= 0.2) this.bpmnViewer.get('canvas').zoom(this.zoom)
+    },
+
+    // 设置高亮颜色的
+    fillColor() {
+      const canvas = this.bpmnViewer.get('canvas')
+      this.bpmnViewer.getDefinitions().rootElements[0].flowElements.forEach(n => {
+        const completeTask = this.taskList.find(m => m.key === n.id)
+        const todoTask = this.taskList.find(m => !m.completed)
+        const endTask = this.taskList[this.taskList.length - 1]
+        if (n.$type === 'bpmn:UserTask') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+                if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
+                  canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
+                  canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
+                }else {
+                  canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                  canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                }
+              }
+            })
+          }
+        }
+        // 排他网关
+        else if (n.$type === 'bpmn:ExclusiveGateway') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+
+                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+              }
+
+            })
+          }
+
+        }
+        // 并行网关
+        else if (n.$type === 'bpmn:ParallelGateway') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+              }
+            })
+          }
+        }
+        else if (n.$type === 'bpmn:StartEvent') {
+          n.outgoing.forEach(nn => {
+            const completeTask = this.taskList.find(m => m.key === nn.targetRef.id)
+            if (completeTask) {
+              canvas.addMarker(nn.id, 'highlight')
+              canvas.addMarker(n.id, 'highlight')
+              return
+            }
+          })
+        }
+        else if (n.$type === 'bpmn:EndEvent') {
+          if (endTask.key === n.id && endTask.completed) {
+            canvas.addMarker(n.id, 'highlight')
+            return
+          }
+        }
+      })
+    },
+  }
+};
+</script>
+<style lang="scss">
+  .bjs-powered-by {
+    display: none;
+  }
+  .view-mode {
+    .el-header, .el-aside, .djs-palette, .bjs-powered-by {
+      display: none;
+    }
+    .el-loading-mask {
+      background-color: initial;
+    }
+    .el-loading-spinner {
+      display: none;
+    }
+  }
+  .containers {
+    // background-color: #ffffff;
+    width: 100%;
+    height: 100%;
+    .canvas {
+      width: 100%;
+      height: 100%;
+    }
+    .panel {
+      position: absolute;
+      right: 0;
+      top: 50px;
+      width: 300px;
+    }
+    .load {
+      margin-right: 10px;
+    }
+    .el-form-item__label{
+      font-size: 13px;
+    }
+
+    .djs-palette{
+      left: 0px!important;
+      top: 0px;
+      border-top: none;
+    }
+
+    .djs-container svg {
+      min-height: 650px;
+    }
+
+    .highlight.djs-shape .djs-visual > :nth-child(1) {
+      fill: green !important;
+      stroke: green !important;
+      fill-opacity: 0.2 !important;
+    }
+    .highlight.djs-shape .djs-visual > :nth-child(2) {
+      fill: green !important;
+    }
+    .highlight.djs-shape .djs-visual > path {
+      fill: green !important;
+      fill-opacity: 0.2 !important;
+      stroke: green !important;
+    }
+    .highlight.djs-connection > .djs-visual > path {
+      stroke: green !important;
+    }
+    .highlight-todo.djs-connection > .djs-visual > path {
+      stroke: orange !important;
+      stroke-dasharray: 4px !important;
+      fill-opacity: 0.2 !important;
+    }
+    .highlight-todo.djs-shape .djs-visual > :nth-child(1) {
+      fill: orange !important;
+      stroke: orange !important;
+      stroke-dasharray: 4px !important;
+      fill-opacity: 0.2 !important;
+    }
+    .overlays-div {
+      font-size: 10px;
+      color: red;
+      width: 100px;
+      top: -20px !important;
+    }
+  }
+</style>

+ 608 - 0
src/views/flowable/task/record/index.vue

@@ -0,0 +1,608 @@
+<template>
+  <div class="app-container">
+    <el-card class="box-card" >
+        <div slot="header" class="clearfix">
+          <span class="el-icon-document">基础信息</span>
+          <el-button style="float: right;" type="primary" @click="goBack">关闭</el-button>
+        </div>
+
+      <!--流程处理表单模块-->
+      <el-col :span="16" :offset="6" v-if="variableOpen">
+          <div>
+            <parser :key="new Date().getTime()" :form-conf="variablesData" />
+          </div>
+          <!-- v-if="finished === 'true'" -->
+          <div style="margin-left:10%;margin-bottom: 20px;font-size: 14px;" > 
+            <el-button  icon="el-icon-edit-outline" type="success" class="editButton" size="mini" @click="handleComplete">审批</el-button>
+<!--                <el-button  icon="el-icon-edit-outline" type="primary" size="mini" @click="handleDelegate">委派</el-button>-->
+<!--                <el-button  icon="el-icon-edit-outline" type="primary" size="mini" @click="handleAssign">转办</el-button>-->
+<!--                <el-button  icon="el-icon-edit-outline" type="primary" size="mini" @click="handleDelegate">签收</el-button>-->
+            <el-button  icon="el-icon-refresh-left" type="warning" size="mini" @click="handleReturn">退回</el-button>
+            <el-button  icon="el-icon-circle-close" type="danger" size="mini" @click="handleReject">驳回</el-button>
+          </div>
+     </el-col>
+
+      <!--初始化流程加载表单信息-->
+      <el-col :span="16" :offset="4" v-if="formConfOpen">
+        <div class="test-form">
+          <parser :key="new Date().getTime()"  :form-conf="formConf" @submit="submitForm" ref="parser" @getData="getData" />
+        </div>
+      </el-col>
+    </el-card>
+
+    <!--流程流转记录-->
+    <el-card class="box-card" v-if="flowRecordList">
+          <div slot="header" class="clearfix">
+            <span class="el-icon-notebook-1">审批记录</span>
+          </div>
+          <el-col :span="16" :offset="4" >
+            <div class="block">
+              <el-timeline>
+                <el-timeline-item
+                  v-for="(item,index ) in flowRecordList"
+                  :key="index"
+                  :icon="setIcon(item.finishTime)"
+                  :color="setColor(item.finishTime)"
+                >
+                  <p style="font-weight: 700">{{item.taskName}}</p>
+                  <el-card :body-style="{ padding: '10px' }">
+                    <el-descriptions class="margin-top" :column="1" size="small" border>
+                      <el-descriptions-item v-if="item.assigneeName" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-user"></i>办理人</template>
+                        {{item.assigneeName}}
+                        <el-tag type="info" size="mini">{{item.deptName}}</el-tag>
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.candidate" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-user"></i>候选办理</template>
+                        {{item.candidate}}
+                      </el-descriptions-item>
+                      <el-descriptions-item label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-date"></i>接收时间</template>
+                        {{item.createTime}}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.finishTime" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-date"></i>处理时间</template>
+                        {{item.finishTime}}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.duration"  label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-time"></i>耗时</template>
+                        {{item.duration}}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.comment" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-tickets"></i>处理意见</template>
+                        {{item.comment.comment}}
+                      </el-descriptions-item>
+                    </el-descriptions>
+
+<!--                    <p  v-if="item.comment">-->
+<!--                      <el-tag type="success" class="editButton" v-if="item.comment.type === '1'">  {{item.comment.comment}}</el-tag>-->
+<!--                      <el-tag type="warning" v-if="item.comment.type === '2'">  {{item.comment.comment}}</el-tag>-->
+<!--                      <el-tag type="danger" v-if="item.comment.type === '3'">  {{item.comment.comment}}</el-tag>-->
+<!--                    </p>-->
+                  </el-card>
+                </el-timeline-item>
+              </el-timeline>
+            </div>
+          </el-col>
+      </el-card>
+    <el-card class="box-card">
+        <div slot="header" class="clearfix">
+          <span class="el-icon-picture-outline">流程图</span>
+        </div>
+        <flow :xmlData="xmlData" :taskData="taskList"></flow>
+    </el-card>
+
+    <!--审批正常流程-->
+    <el-dialog :title="completeTitle" :visible.sync="completeOpen" :width="checkSendUser? '60%':'40%'" append-to-body>
+      <el-form ref="taskForm" :model="taskForm" label-width="80px" >
+        <el-form-item  v-if="checkSendUser" prop="targetKey">
+          <el-row :gutter="20">
+            <!--部门数据-->
+            <el-col :span="6" :xs="24">
+              <h6>部门列表</h6>
+              <div class="head-container">
+                <el-input
+                  v-model="deptName"
+                  placeholder="请输入部门名称"
+                  clearable
+                  size="small"
+                  prefix-icon="el-icon-search"
+                  style="margin-bottom: 20px"
+                />
+              </div>
+              <div class="head-container">
+                <el-tree
+                  :data="deptOptions"
+                  :props="defaultProps"
+                  :expand-on-click-node="false"
+                  :filter-node-method="filterNode"
+                  ref="tree"
+                  default-expand-all
+                  @node-click="handleNodeClick"
+                />
+              </div>
+            </el-col>
+            <el-col :span="10" :xs="24">
+              <h6>待选人员</h6>
+              <el-table
+                ref="singleTable"
+                :data="userList"
+                border
+                style="width: 100%"
+                @selection-change="handleSelectionChange">
+                <el-table-column type="selection" width="50" align="center" />
+                <el-table-column label="用户名" align="center" prop="nickName" />
+                <el-table-column label="部门" align="center" prop="dept.deptName" />
+              </el-table>
+            </el-col>
+            <el-col :span="8" :xs="24">
+              <h6>已选人员</h6>
+              <el-tag
+                v-for="(user,index) in userData"
+                :key="index"
+                closable
+                @close="handleClose(user)">
+                {{user.nickName}} {{user.dept.deptName}}
+              </el-tag>
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="处理意见" prop="comment" :rules="[{ required: true, message: '请输入处理意见', trigger: 'blur' }]">
+          <el-input type="textarea" v-model="taskForm.comment" placeholder="请输入处理意见"/>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="completeOpen = false">取 消</el-button>
+        <el-button type="primary" @click="taskComplete">确 定</el-button>
+      </span>
+    </el-dialog>
+
+    <!--退回流程-->
+    <el-dialog :title="returnTitle" :visible.sync="returnOpen" width="40%" append-to-body>
+        <el-form ref="taskForm" :model="taskForm" label-width="80px" >
+            <el-form-item label="退回节点" prop="targetKey">
+              <el-radio-group v-model="taskForm.targetKey">
+                <el-radio-button
+                  v-for="item in returnTaskList"
+                  :key="item.id"
+                  :label="item.id"
+                >{{item.name}}</el-radio-button>
+              </el-radio-group>
+            </el-form-item>
+          <el-form-item label="退回意见" prop="comment" :rules="[{ required: true, message: '请输入意见', trigger: 'blur' }]">
+            <el-input style="width: 50%" type="textarea" v-model="taskForm.comment" placeholder="请输入意见"/>
+          </el-form-item>
+        </el-form>
+        <span slot="footer" class="dialog-footer">
+            <el-button @click="returnOpen = false">取 消</el-button>
+            <el-button type="primary" @click="taskReturn">确 定</el-button>
+        </span>
+    </el-dialog>
+
+    <!--驳回流程-->
+    <el-dialog :title="rejectTitle" :visible.sync="rejectOpen" width="40%" append-to-body>
+      <el-form ref="taskForm" :model="taskForm" label-width="80px" >
+        <el-form-item label="驳回意见" prop="comment" :rules="[{ required: true, message: '请输入意见', trigger: 'blur' }]">
+          <el-input style="width: 50%" type="textarea" v-model="taskForm.comment" placeholder="请输入意见"/>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+          <el-button @click="rejectOpen = false">取 消</el-button>
+          <el-button type="primary" @click="taskReject">确 定</el-button>
+        </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {flowRecord} from "@/api/flowable/finished";
+import Parser from '@/components/parser/Parser'
+import {definitionStart, getProcessVariables, readXml, getFlowViewer} from "@/api/flowable/definition";
+import {complete, rejectTask, returnList, returnTask, getNextFlowNode, delegate} from "@/api/flowable/todo";
+import flow from '@/views/flowable/task/record/flow'
+import {treeselect} from "@/api/system/dept";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import Treeselect from "@riophae/vue-treeselect";
+import {listUser} from "@/api/system/user";
+
+export default {
+  name: "Record",
+  components: {
+    Parser,
+    flow,
+    Treeselect
+  },
+  props: {},
+  data() {
+    return {
+      // 模型xml数据
+      xmlData: "",
+      taskList: [],
+      // 部门名称
+      deptName: undefined,
+      // 部门树选项
+      deptOptions: undefined,
+      // 用户表格数据
+      userList: null,
+      defaultProps: {
+        children: "children",
+        label: "label"
+      },
+      // 查询参数
+      queryParams: {
+        deptId: undefined
+      },
+      // 遮罩层
+      loading: true,
+      flowRecordList: [], // 流程流转数据
+      formConfCopy: {},
+      src: null,
+      rules: {}, // 表单校验
+      variablesForm: {}, // 流程变量数据
+      taskForm:{
+        returnTaskShow: false, // 是否展示回退表单
+        delegateTaskShow: false, // 是否展示回退表单
+        defaultTaskShow: true, // 默认处理
+        sendUserShow: false, // 审批用户
+        multiple: false,
+        comment:"", // 意见内容
+        procInsId: "", // 流程实例编号
+        instanceId: "", // 流程实例编号
+        deployId: "",  // 流程定义编号
+        taskId: "" ,// 流程任务编号
+        procDefId: "",  // 流程编号
+        vars: "",
+        targetKey:""
+      },
+      userDataList:[], // 流程候选人
+      assignee: null,
+      formConf: {}, // 默认表单数据
+      formConfOpen: false, // 是否加载默认表单数据
+      variables: [], // 流程变量数据
+      variablesData: {}, // 流程变量数据
+      variableOpen: false, // 是否加载流程变量数据
+      returnTaskList: [],  // 回退列表数据
+      finished: 'false',
+      completeTitle: null,
+      completeOpen: false,
+      returnTitle: null,
+      returnOpen: false,
+      rejectOpen: false,
+      rejectTitle: null,
+      userData:[],
+      checkSendUser: false // 是否展示选择人员模块
+    };
+  },
+  created() {
+    this.taskForm.deployId = this.$route.query && this.$route.query.deployId;
+    this.taskForm.taskId  = this.$route.query && this.$route.query.taskId;
+    this.taskForm.procInsId = this.$route.query && this.$route.query.procInsId;
+    this.taskForm.executionId = this.$route.query && this.$route.query.executionId;
+    this.taskForm.instanceId = this.$route.query && this.$route.query.procInsId;
+    // 初始化表单
+    this.taskForm.procDefId  = this.$route.query && this.$route.query.procDefId;
+    // 回显流程记录
+    this.getFlowViewer(this.taskForm.procInsId,this.taskForm.executionId);
+    this.getModelDetail(this.taskForm.deployId);
+    // 流程任务重获取变量表单
+    if (this.taskForm.taskId){
+      this.processVariables( this.taskForm.taskId)
+      this.getNextFlowNode(this.taskForm.taskId)
+      this.taskForm.deployId = null
+    }
+    this.getFlowRecordList( this.taskForm.procInsId, this.taskForm.deployId);
+    this.finished =  this.$route.query && this.$route.query.finished
+  },
+  methods: {
+    /** 查询部门下拉树结构 */
+    getTreeselect() {
+      treeselect().then(response => {
+        this.deptOptions = response.data;
+      });
+    },
+    /** 查询用户列表 */
+    getList() {
+      listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
+          this.userList = response.rows;
+          this.total = response.total;
+        }
+      );
+    },
+    // 筛选节点
+    filterNode(value, data) {
+      if (!value) return true;
+      return data.label.indexOf(value) !== -1;
+    },
+    // 节点单击事件
+    handleNodeClick(data) {
+      this.queryParams.deptId = data.id;
+      this.getList();
+    },
+    /** xml 文件 */
+    getModelDetail(deployId) {
+      // 发送请求,获取xml
+      readXml(deployId).then(res => {
+        this.xmlData = res.data
+      })
+    },
+    getFlowViewer(procInsId,executionId) {
+      getFlowViewer(procInsId,executionId).then(res => {
+        this.taskList = res.data
+      })
+    },
+    setIcon(val) {
+      if (val) {
+        return "el-icon-check";
+      } else {
+        return "el-icon-time";
+      }
+    },
+    setColor(val) {
+      if (val) {
+        return "#2bc418";
+      } else {
+        return "#b3bdbb";
+      }
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      if (selection) {
+        this.userData = selection
+        const selectVal = selection.map(item => item.userId);
+        if (selectVal instanceof Array) {
+          this.taskForm.values = {
+            "approval": selectVal.join(',')
+          }
+        } else {
+          this.taskForm.values = {
+            "approval": selectVal
+          }
+        }
+      }
+    },
+    // 关闭标签
+    handleClose(tag) {
+      this.userData.splice(this.userData.indexOf(tag), 1);
+      this.$refs.singleTable.toggleRowSelection(tag, false)
+    },
+    /** 流程变量赋值 */
+    handleCheckChange(val) {
+      if (val instanceof Array) {
+        this.taskForm.values = {
+          "approval": val.join(',')
+        }
+      } else {
+        this.taskForm.values = {
+          "approval": val
+        }
+      }
+    },
+    /** 流程流转记录 */
+    getFlowRecordList(procInsId, deployId) {
+      const that = this
+      const params = {procInsId: procInsId, deployId: deployId}
+      flowRecord(params).then(res => {
+        that.flowRecordList = res.data.flowList;
+        // 流程过程中不存在初始化表单 直接读取的流程变量中存储的表单值
+        if (res.data.formData) {
+          that.formConf = res.data.formData;
+          that.formConfOpen = true
+        }
+      }).catch(res => {
+        this.goBack();
+      })
+    },
+    fillFormData(form, data) {
+      form.fields.forEach(item => {
+        const val = data[item.__vModel__]
+        if (val) {
+          item.__config__.defaultValue = val
+        }
+      })
+    },
+    /** 获取流程变量内容 */
+    processVariables(taskId) {
+      if (taskId) {
+        // 提交流程申请时填写的表单存入了流程变量中后续任务处理时需要展示
+        getProcessVariables(taskId).then(res => {
+          this.variablesData = res.data.variables;
+          this.variableOpen = true
+        });
+      }
+    },
+    /** 根据当前任务或者流程设计配置的下一步节点 */
+    getNextFlowNode(taskId) {
+      // 根据当前任务或者流程设计配置的下一步节点 todo 暂时未涉及到考虑网关、表达式和多节点情况
+      const params = {taskId: taskId}
+      getNextFlowNode(params).then(res => {
+        const data = res.data;
+        if (data) {
+          this.checkSendUser = true
+          if (data.type === 'assignee') { // 指定人员
+            this.userDataList = res.data.userList;
+          } else if (data.type === 'candidateUsers') {  // 指定人员(多个)
+            this.userDataList = res.data.userList;
+            this.taskForm.multiple = true;
+          } else if (data.type === 'candidateGroups') { // 指定组(所属角色接收任务)
+            res.data.roleList.forEach(role => {
+              role.userId = role.roleId;
+              role.nickName = role.roleName;
+            })
+            this.userDataList = res.data.roleList;
+            this.taskForm.multiple = false;
+          } else if (data.type === 'multiInstance') { // 会签?
+            this.userDataList = res.data.userList;
+            this.taskForm.multiple = true;
+          }else if (data.type === 'fixed') { // 已经固定人员接收下一任务
+            this.checkSendUser = false;
+          }
+        }
+      })
+    },
+    /** 审批任务选择 */
+    handleComplete() {
+      this.completeOpen = true;
+      this.completeTitle = "审批流程";
+      this.getTreeselect();
+    },
+    /** 审批任务 */
+    taskComplete() {
+      if (!this.taskForm.values && this.checkSendUser){
+        this.msgError("请选择流程接收人员");
+        return;
+      }
+      if (!this.taskForm.comment){
+        this.msgError("请输入审批意见");
+        return;
+      }
+      complete(this.taskForm).then(response => {
+       this.$modal.msgSuccess(response.msg);
+        this.goBack();
+      });
+    },
+    /** 委派任务 */
+    handleDelegate() {
+      this.taskForm.delegateTaskShow = true;
+      this.taskForm.defaultTaskShow = false;
+    },
+    handleAssign(){
+
+    },
+    /** 返回页面 */
+    goBack() {
+      // 关闭当前标签页并返回上个页面
+      this.$store.dispatch("tagsView/delView", this.$route);
+      this.$router.go(-1)
+    },
+    /** 接收子组件传的值 */
+    getData(data) {
+      if (data) {
+        const variables = [];
+        data.fields.forEach(item => {
+          let variableData = {};
+          variableData.label = item.__config__.label
+          // 表单值为多个选项时
+          if (item.__config__.defaultValue instanceof Array) {
+            const array = [];
+            item.__config__.defaultValue.forEach(val => {
+              array.push(val)
+            })
+            variableData.val = array;
+          } else {
+            variableData.val = item.__config__.defaultValue
+          }
+          variables.push(variableData)
+        })
+        this.variables = variables;
+      }
+    },
+    /** 申请流程表单数据提交 */
+    submitForm(data) {
+      if (data) {
+        const variables = data.valData;
+        const formData = data.formData;
+        formData.disabled = true;
+        formData.formBtns = false;
+        if (this.taskForm.procDefId) {
+          variables.variables = formData;
+           // 启动流程并将表单数据加入流程变量
+          definitionStart(this.taskForm.procDefId, JSON.stringify(variables)).then(res => {
+           this.$modal.msgSuccess(res.msg);
+            this.goBack();
+          })
+        }
+      }
+    },
+    /** 驳回任务 */
+    handleReject() {
+      this.rejectOpen = true;
+      this.rejectTitle = "驳回流程";
+    },
+    /** 驳回任务 */
+    taskReject() {
+      this.$refs["taskForm"].validate(valid => {
+        if (valid) {
+          rejectTask(this.taskForm).then(res => {
+           this.$modal.msgSuccess(res.msg);
+            this.goBack();
+          });
+        }
+      });
+    },
+    /** 可退回任务列表 */
+    handleReturn() {
+      this.returnOpen = true;
+      this.returnTitle = "退回流程";
+      returnList(this.taskForm).then(res => {
+        this.returnTaskList = res.data;
+        this.taskForm.values = null;
+      })
+    },
+    /** 提交退回任务 */
+   taskReturn() {
+      this.$refs["taskForm"].validate(valid => {
+        if (valid) {
+          returnTask(this.taskForm).then(res => {
+           this.$modal.msgSuccess(res.msg);
+            this.goBack()
+          });
+        }
+      });
+    },
+    /** 取消回退任务按钮 */
+    cancelTask() {
+      this.taskForm.returnTaskShow = false;
+      this.taskForm.defaultTaskShow = true;
+      this.taskForm.sendUserShow = true;
+      this.returnTaskList = [];
+    },
+    /** 委派任务 */
+    submitDeleteTask() {
+      this.$refs["taskForm"].validate(valid => {
+        if (valid) {
+          delegate(this.taskForm).then(response => {
+           this.$modal.msgSuccess(response.msg);
+            this.goBack();
+          });
+        }
+      });
+    },
+    /** 取消回退任务按钮 */
+    cancelDelegateTask() {
+      this.taskForm.delegateTaskShow = false;
+      this.taskForm.defaultTaskShow = true;
+      this.taskForm.sendUserShow = true;
+      this.returnTaskList = [];
+    },
+  }
+};
+</script>
+<style lang="scss" scoped>
+.test-form {
+  margin: 15px auto;
+  width: 800px;
+  padding: 15px;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+.clearfix:after {
+  clear: both
+}
+
+.box-card {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.el-tag + .el-tag {
+  margin-left: 10px;
+}
+
+.my-label {
+  background: #E1F3D8;
+}
+</style>

+ 24 - 0
src/views/flowable/task/todo/detail/flow.vue

@@ -0,0 +1,24 @@
+<template>
+  <div>
+    <flow-view :flowData="flowData"/>
+  </div>
+</template>
+<script>
+import FlowView from './flowview'
+
+export default {
+  name: "Flow",
+  components: {
+    FlowView
+  },
+  props: {
+    flowData: {
+      type: Object,
+      default: () => {}
+    },
+  },
+  data() {
+    return {};
+  }
+};
+</script>

+ 243 - 0
src/views/flowable/task/todo/detail/flowview.vue

@@ -0,0 +1,243 @@
+<template>
+  <div class="containers main-box">
+    <el-button type="success" class="editButton"
+               size="small"
+               icon="el-icon-zoom-in"
+               @click="zoomViewport(true)">放大</el-button>
+    <el-button type="warning"
+               size="small"
+               icon="el-icon-zoom-out"
+               @click="zoomViewport(false)">缩小</el-button>
+    <el-button type="info"
+               size="small"
+               icon="el-icon-rank"
+               @click="fitViewport">适中</el-button>
+    <el-button type="primary"
+               size="small"
+               icon="el-icon">{{ processVersion }}</el-button>
+    <div class="canvas" ref="flowCanvas"></div>
+  </div>
+</template>
+<script>
+import { CustomViewer as BpmnViewer } from "@/components/customBpmn";
+
+export default {
+  name: "FlowView",
+  props: {
+    flowData: {
+      type: Object,
+      default: () => {}
+    },
+  },
+  data() {
+    return {
+      bpmnViewer: null,
+      processVersion: null
+    };
+  },
+  watch: {
+    flowData: {
+      handler(newVal) {
+        if (Object.keys(newVal).length > 0) {
+          // 生成实例
+          this.bpmnViewer && this.bpmnViewer.destroy();
+          this.bpmnViewer = new BpmnViewer({
+            container: this.$refs.flowCanvas,
+            height: 'calc(100vh - 200px)',
+          });
+          this.loadFlowCanvas(newVal);
+        }
+      },
+      immediate: true, // 立即生效
+      deep: true  //监听对象或数组的时候,要用到深度监听
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    // 加载流程
+    async loadFlowCanvas(flowData) {
+      const self = this
+      try {
+        await self.bpmnViewer.importXML(flowData.xmlData);
+        self.fitViewport()
+        if (flowData.nodeData !==undefined && flowData.nodeData.length > 0 ) {
+          self.fillColor(flowData.nodeData)
+        }
+        this.processVersion=flowData.processVersion;
+      } catch (err) {
+        console.error(err.message, err.warnings)
+      }
+    },
+    // 设置高亮颜色的class
+    setNodeColor(nodeCodes, colorClass, canvas) {
+      for (let i = 0; i < nodeCodes.length; i++) {
+        canvas.addMarker(nodeCodes[i], colorClass);
+      }
+    },
+    // 让图能自适应屏幕
+    fitViewport() {
+      this.zoom = this.bpmnViewer.get('canvas').zoom("fit-viewport", "auto")
+    },
+    // 放大缩小
+    zoomViewport(zoomIn = true) {
+      this.zoom = this.bpmnViewer.get('canvas').zoom()
+      this.zoom += (zoomIn ? 0.1 : -0.1)
+      if(this.zoom >= 0.2) this.bpmnViewer.get('canvas').zoom(this.zoom)
+    },
+
+    // 设置高亮颜色的
+    fillColor(nodeData) {
+      const canvas = this.bpmnViewer.get('canvas')
+      this.bpmnViewer.getDefinitions().rootElements[0].flowElements.forEach(n => {
+        const completeTask = nodeData.find(m => m.key === n.id)
+        const todoTask = nodeData.find(m => !m.completed)
+        const endTask = nodeData[nodeData.length - 1]
+        if (n.$type === 'bpmn:UserTask') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+                if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
+                  canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
+                  canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
+                }else {
+                  canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                  canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                }
+              }
+            })
+          }
+        }
+        // 排他网关
+        else if (n.$type === 'bpmn:ExclusiveGateway') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+
+                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+              }
+
+            })
+          }
+
+        }
+        // 并行网关
+        else if (n.$type === 'bpmn:ParallelGateway') {
+          if (completeTask) {
+            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+            n.outgoing?.forEach(nn => {
+              const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
+              if (targetTask) {
+                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+              }
+            })
+          }
+        }
+        else if (n.$type === 'bpmn:StartEvent') {
+          n.outgoing.forEach(nn => {
+            const completeTask = nodeData.find(m => m.key === nn.targetRef.id)
+            if (completeTask) {
+              canvas.addMarker(nn.id, 'highlight')
+              canvas.addMarker(n.id, 'highlight')
+              return
+            }
+          })
+        }
+        else if (n.$type === 'bpmn:EndEvent') {
+          if (endTask.key === n.id && endTask.completed) {
+            canvas.addMarker(n.id, 'highlight')
+            return
+          }
+        }
+      })
+    },
+  }
+};
+</script>
+<style lang="scss">
+  .bjs-powered-by {
+    display: none;
+  }
+  .view-mode {
+    .el-header, .el-aside, .djs-palette, .bjs-powered-by {
+      display: none;
+    }
+    .el-loading-mask {
+      background-color: initial;
+    }
+    .el-loading-spinner {
+      display: none;
+    }
+  }
+  .containers {
+    // background-color: #ffffff;
+    width: 100%;
+    height: 100%;
+    .canvas {
+      width: 100%;
+      height: 100%;
+    }
+    .panel {
+      position: absolute;
+      right: 0;
+      top: 50px;
+      width: 300px;
+    }
+    .load {
+      margin-right: 10px;
+    }
+    .el-form-item__label{
+      font-size: 13px;
+    }
+
+    .djs-palette{
+      left: 0px!important;
+      top: 0px;
+      border-top: none;
+    }
+
+    .djs-container svg {
+      min-height: 650px;
+    }
+
+    .highlight.djs-shape .djs-visual > :nth-child(1) {
+      fill: green !important;
+      stroke: green !important;
+      fill-opacity: 0.2 !important;
+    }
+    .highlight.djs-shape .djs-visual > :nth-child(2) {
+      fill: green !important;
+    }
+    .highlight.djs-shape .djs-visual > path {
+      fill: green !important;
+      fill-opacity: 0.2 !important;
+      stroke: green !important;
+    }
+    .highlight.djs-connection > .djs-visual > path {
+      stroke: green !important;
+    }
+    .highlight-todo.djs-connection > .djs-visual > path {
+      stroke: orange !important;
+      stroke-dasharray: 4px !important;
+      fill-opacity: 0.2 !important;
+    }
+    .highlight-todo.djs-shape .djs-visual > :nth-child(1) {
+      fill: orange !important;
+      stroke: orange !important;
+      stroke-dasharray: 4px !important;
+      fill-opacity: 0.2 !important;
+    }
+    .overlays-div {
+      font-size: 10px;
+      color: red;
+      width: 100px;
+      top: -20px !important;
+    }
+  }
+</style>

+ 568 - 0
src/views/flowable/task/todo/detail/index.vue

@@ -0,0 +1,568 @@
+<template>
+  <div class="app-container">
+    <el-card class="box-card">
+      <div slot="header" class="clearfix">
+        <span class="el-icon-document">待办任务</span>
+        <el-tag style="margin-left:10px">发起人:{{ startUser }}</el-tag>
+        <el-tag>任务节点:{{ taskName }}||{{ taskDefKey }}</el-tag>
+        <el-button style="float: right;" size="mini" type="danger" @click="goBack">关闭</el-button>
+      </div>
+      <el-tabs tab-position="top" v-model="activeName" @tab-click="handleClick">
+        <!--表单信息-->
+        <el-tab-pane label="表单信息" name="1">
+          <el-col :span="16" :offset="4">
+<!--            <div class="test-form">
+              &lt;!&ndash;              <parser :key="new Date().getTime()" :form-conf="variablesData"/>&ndash;&gt;
+              <parser :key="new Date().getTime()" :form-conf="variablesData" @submit="submitForm" ref="parser"/>
+
+            </div>
+            <div style="margin-left:15%;margin-bottom: 20px;font-size: 14px;">
+              <el-button v-if="!formKeyExist" icon="el-icon-edit-outline" type="success" class="editButton" size="mini"
+                         @click="handleComplete">审批
+              </el-button>
+              &lt;!&ndash;                <el-button  icon="el-icon-edit-outline" type="primary" size="mini" @click="handleDelegate">委派</el-button>&ndash;&gt;
+              &lt;!&ndash;                <el-button  icon="el-icon-edit-outline" type="primary" size="mini" @click="handleAssign">转办</el-button>&ndash;&gt;
+              &lt;!&ndash;                <el-button  icon="el-icon-edit-outline" type="primary" size="mini" @click="handleDelegate">签收</el-button>&ndash;&gt;
+              &lt;!&ndash;              <el-button icon="el-icon-refresh-left" type="warning" size="mini" @click="handleReturn">退回</el-button>&ndash;&gt;
+              &lt;!&ndash;              <el-button icon="el-icon-circle-close" type="danger" size="mini" @click="handleReject">驳回</el-button>&ndash;&gt;
+            </div>-->
+            <v-form-render ref="vFormRef"/>
+            <div style="margin-left:10%;margin-bottom: 20px;font-size: 14px;">
+              <!-- taskDefKey 为“暂存”时显示提交、关闭 -->
+              <template v-if="taskDefKey === 'modifyApply'||taskDefKey === 'Activity_0iaxnuk'">
+                <el-button type="primary" @click="handleComplete(true)">提交</el-button>
+                <el-button type="primary" @click="handleComplete(false)">关闭</el-button>
+              </template>
+              <!-- taskDefKey 不为“暂存”时显示审批、驳回 -->
+              <template v-else>
+                <el-button type="primary" @click="handleComplete(true)">审批</el-button>
+                <el-button type="primary" @click="handleComplete(false)">驳回</el-button>
+              </template>
+            </div>
+          </el-col>
+        </el-tab-pane>
+        <!--流程流转记录-->
+        <el-tab-pane label="流转记录" name="2">
+          <!--flowRecordList-->
+          <el-col :span="16" :offset="4">
+            <div class="block">
+              <el-timeline>
+                <el-timeline-item
+                  v-for="(item,index ) in flowRecordList"
+                  :key="index"
+                  :icon="setIcon(item.finishTime)"
+                  :color="setColor(item.finishTime)"
+                >
+                  <p style="font-weight: 700">{{ item.taskName }}</p>
+                  <el-card :body-style="{ padding: '10px' }">
+                    <el-descriptions class="margin-top" :column="1" size="small" border>
+                      <el-descriptions-item v-if="item.assigneeName" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-user"></i>办理人</template>
+                        {{ item.assigneeName }}
+                        <el-tag type="info" size="mini">{{ item.deptName }}</el-tag>
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.candidate" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-user"></i>候选办理</template>
+                        {{ item.candidate }}
+                      </el-descriptions-item>
+                      <el-descriptions-item label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-date"></i>接收时间</template>
+                        {{ item.createTime }}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.finishTime" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-date"></i>处理时间</template>
+                        {{ item.finishTime }}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.duration" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-time"></i>耗时</template>
+                        {{ item.duration }}
+                      </el-descriptions-item>
+                      <el-descriptions-item v-if="item.comment" label-class-name="my-label">
+                        <template slot="label"><i class="el-icon-tickets"></i>处理意见</template>
+                        {{ item.comment.comment }}
+                      </el-descriptions-item>
+                    </el-descriptions>
+                  </el-card>
+                </el-timeline-item>
+              </el-timeline>
+            </div>
+          </el-col>
+        </el-tab-pane>
+        <!--流程图-->
+        <el-tab-pane label="流程图" name="3">
+          <flow :flowData="flowData"/>
+        </el-tab-pane>
+      </el-tabs>
+      <!--审批任务-->
+      <el-dialog :title="completeTitle" :visible.sync="completeOpen" width="60%" append-to-body>
+        <el-form ref="taskForm" :model="taskForm">
+          <el-form-item prop="targetKey">
+            <flow-user v-if="checkSendUser" :checkType="checkType" @handleUserSelect="handleUserSelect"></flow-user>
+            <flow-role v-if="checkSendRole" @handleRoleSelect="handleRoleSelect"></flow-role>
+          </el-form-item>
+          <el-form-item label="处理意见" label-width="80px" prop="comment"
+                        :rules="[{ required: true, message: '请输入处理意见', trigger: 'blur' }]">
+            <el-input type="textarea" v-model="taskForm.comment" placeholder="请输入处理意见"/>
+          </el-form-item>
+        </el-form>
+        <span slot="footer" class="dialog-footer">
+          <el-button @click="completeOpen = false">取 消</el-button>
+          <el-button type="primary" @click="taskComplete">确 定</el-button>
+        </span>
+      </el-dialog>
+      <!--退回流程-->
+      <el-dialog :title="returnTitle" :visible.sync="returnOpen" width="40%" append-to-body>
+        <el-form ref="taskForm" :model="taskForm" label-width="80px">
+          <el-form-item label="退回节点" prop="targetKey">
+            <el-radio-group v-model="taskForm.targetKey">
+              <el-radio-button
+                v-for="item in returnTaskList"
+                :key="item.id"
+                :label="item.id"
+              >{{ item.name }}
+              </el-radio-button>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="退回意见" prop="comment"
+                        :rules="[{ required: true, message: '请输入意见', trigger: 'blur' }]">
+            <el-input style="width: 50%" type="textarea" v-model="taskForm.comment" placeholder="请输入意见"/>
+          </el-form-item>
+        </el-form>
+        <span slot="footer" class="dialog-footer">
+              <el-button @click="returnOpen = false">取 消</el-button>
+              <el-button type="primary" @click="taskReturn">确 定</el-button>
+          </span>
+      </el-dialog>
+      <!--驳回流程-->
+      <el-dialog :title="rejectTitle" :visible.sync="rejectOpen" width="40%" append-to-body>
+        <el-form ref="taskForm" :model="taskForm" label-width="80px">
+          <el-form-item label="驳回意见" prop="comment"
+                        :rules="[{ required: true, message: '请输入意见', trigger: 'blur' }]">
+            <el-input style="width: 50%" type="textarea" v-model="taskForm.comment" placeholder="请输入意见"/>
+          </el-form-item>
+        </el-form>
+        <span slot="footer" class="dialog-footer">
+            <el-button @click="rejectOpen = false">取 消</el-button>
+            <el-button type="primary" @click="taskReject">确 定</el-button>
+          </span>
+      </el-dialog>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import {flowRecord} from "@/api/flowable/finished";
+import FlowUser from '@/components/flow/User'
+import FlowRole from '@/components/flow/Role'
+import Parser from '@/components/parser/Parser'
+import {getProcessVariables, flowXmlAndNode, definitionStart} from "@/api/flowable/definition";
+import {
+  complete,
+  rejectTask,
+  returnList,
+  returnTask,
+  getNextFlowNode,
+  delegate,
+  flowTaskForm,
+} from "@/api/flowable/todo";
+import flow from '@/views/flowable/task/todo/detail/flow'
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {listUser} from "@/api/system/user";
+
+export default {
+  name: "Record",
+  components: {
+    Parser,
+    flow,
+    FlowUser,
+    FlowRole,
+  },
+  props: {},
+  data() {
+    return {
+      eventName: "click",
+      // 模型xml数据
+      xmlData: "",
+      flowData: {},
+      activeName: '1',
+      // 部门名称
+      deptName: undefined,
+      // 部门树选项
+      // 用户表格数据
+      userList: null,
+      defaultProps: {
+        children: "children",
+        label: "label"
+      },
+      // 查询参数
+      queryParams: {
+        deptId: undefined
+      },
+      // 遮罩层
+      loading: true,
+      flowRecordList: [], // 流程流转数据
+      formConfCopy: {},
+      src: null,
+      rules: {}, // 表单校验
+      variablesForm: {}, // 流程变量数据
+      taskForm: {
+        returnTaskShow: false, // 是否展示回退表单
+        delegateTaskShow: false, // 是否展示回退表单
+        defaultTaskShow: true, // 默认处理
+        comment: "", // 意见内容
+        procInsId: "", // 流程实例编号
+        instanceId: "", // 流程实例编号
+        deployId: "",  // 流程定义编号
+        taskId: "",// 流程任务编号
+        procDefId: "",  // 流程编号
+        targetKey: "",
+        variables: {
+          variables: {}
+        },
+      },
+      assignee: null,
+      formConf: {}, // 默认表单数据
+      variables: [], // 流程变量数据
+      variablesData: {}, // 流程变量数据
+      returnTaskList: [],  // 回退列表数据
+      completeTitle: null,
+      completeOpen: false,
+      returnTitle: null,
+      returnOpen: false,
+      rejectOpen: false,
+      rejectTitle: null,
+      userData: [],
+      checkSendUser: false, // 是否展示人员选择模块
+      checkSendRole: false,// 是否展示角色选择模块
+      checkType: 'single', // 选择类型
+      taskName: null, // 任务节点
+      taskDefKey:null,//节点Id
+      startUser: null, // 发起人信息,
+      multiInstanceVars: '', // 会签节点
+      formKeyExist: false, // 当前节点是否存在表单
+      formJson:{}
+    };
+  },
+  created() {
+    if (this.$route.query) {
+      this.taskName = this.$route.query.taskName;
+      this.taskDefKey = this.$route.query.taskDefKey;
+      this.startUser = this.$route.query.startUser;
+      this.taskForm.deployId = this.$route.query.deployId;
+      this.taskForm.taskId = this.$route.query.taskId;
+      this.taskForm.procInsId = this.$route.query.procInsId;
+      this.taskForm.executionId = this.$route.query.executionId;
+      this.taskForm.instanceId = this.$route.query.procInsId;
+      // 流程任务获取变量信息
+      if (this.taskForm.taskId) {
+        this.processVariables(this.taskForm.taskId)
+        this.getFlowTaskForm(this.taskForm.taskId)
+      }
+      this.getFlowRecordList(this.taskForm.procInsId, this.taskForm.deployId);
+    }
+  },
+  methods: {
+    handleClick(tab, event) {
+      if (tab.name === '3') {
+        flowXmlAndNode({procInsId: this.taskForm.procInsId, deployId: this.taskForm.deployId}).then(res => {
+          this.flowData = res.data;
+        })
+      }
+    },
+    setIcon(val) {
+      if (val) {
+        return "el-icon-check";
+      } else {
+        return "el-icon-time";
+      }
+    },
+    setColor(val) {
+      if (val) {
+        return "#2bc418";
+      } else {
+        return "#b3bdbb";
+      }
+    },
+    // 用户信息选中数据
+    handleUserSelect(selection) {
+      if (selection) {
+        if (selection instanceof Array) {
+          const selectVal = selection.map(item => item.userId);
+          if (this.multiInstanceVars) {
+            this.$set(this.taskForm.variables, this.multiInstanceVars, selectVal);
+          } else {
+            this.$set(this.taskForm.variables, "approval", selectVal.join(','));
+          }
+        } else {
+          this.$set(this.taskForm.variables, "approval", selection.userId.toString());
+        }
+      }
+    },
+    // 角色信息选中数据
+    handleRoleSelect(selection) {
+      if (selection) {
+        if (selection instanceof Array) {
+          const selectVal = selection.map(item => item.roleId);
+          this.$set(this.taskForm.variables, "approval", selectVal.join(','));
+        } else {
+          this.$set(this.taskForm.variables, "approval", selection);
+        }
+      }
+    },
+    /** 流程流转记录 */
+    getFlowRecordList(procInsId, deployId) {
+      const that = this
+      const params = {procInsId: procInsId, deployId: deployId}
+      flowRecord(params).then(res => {
+        that.flowRecordList = res.data.flowList;
+      }).catch(res => {
+        this.goBack();
+      })
+    },
+    fillFormData(form, data) {
+      form.fields.forEach(item => {
+        const val = data[item.__vModel__]
+        if (val) {
+          item.__config__.defaultValue = val
+        }
+      })
+    },
+    /** 获取流程变量内容 */
+    processVariables(taskId) {
+      if (taskId) {
+        // 提交流程申请时填写的表单存入了流程变量中后续任务处理时需要展示
+        getProcessVariables(taskId).then(res => {
+          // this.variablesData = res.data.variables;
+        });
+      }
+    },
+    /** 流程节点表单 */
+    getFlowTaskForm(taskId) {
+      if (taskId) {
+        // 提交流程申请时填写的表单存入了流程变量中后续任务处理时需要展示
+        flowTaskForm({taskId: taskId}).then(res => {
+          /*this.variablesData = res.data.formData;
+          this.taskForm.variables = res.data.formData;
+          this.formKeyExist = res.data.formKeyExist;*/
+          // 回显表单
+          this.$refs.vFormRef.setFormJson(res.data.formJson);
+          this.formJson = res.data.formJson;
+          this.$nextTick(() => {
+            // 加载表单填写的数据
+            this.$refs.vFormRef.setFormData(res.data);
+            // this.$nextTick(() => {
+            //   // 表单禁用
+            //   this.$refs.vFormRef.disableForm();
+            // })
+          })
+        });
+      }
+    },
+    /** 加载审批任务弹框 */
+    handleComplete(flag) {
+      // this.completeOpen = true;
+      // this.completeTitle = "流程审批";
+      //this.submitForm(null);
+      this.completeOpen = true;
+      this.completeTitle = "流程审批";
+      this.submitForm(flag);
+    },
+    /** 用户审批任务 */
+    taskComplete() {
+      if (!this.taskForm.variables && this.checkSendUser) {
+        this.$modal.msgError("请选择流程接收人员!");
+        return;
+      }
+      if (!this.taskForm.variables && this.checkSendRole) {
+        this.$modal.msgError("请选择流程接收角色组!");
+        return;
+      }
+      if (!this.taskForm.comment) {
+        this.$modal.msgError("请输入审批意见!");
+        return;
+      }
+      if (this.taskForm && this.formKeyExist) {
+        // 表单是否禁用
+        this.taskForm.formData.formData.disabled = true;
+        // 是否显示按钮
+        this.taskForm.formData.formData.formBtns = false;
+        this.taskForm.variables = Object.assign({}, this.taskForm.variables, this.taskForm.formData.valData);
+        this.taskForm.variables.variables = this.taskForm.formData.formData;
+        complete(this.taskForm).then(response => {
+          this.$modal.msgSuccess(response.msg);
+          this.goBack();
+        });
+      } else {
+        // 流程设计人员类型配置为固定人员接收任务时,直接提交任务到下一步
+        complete(this.taskForm).then(response => {
+          this.$modal.msgSuccess(response.msg);
+          this.goBack();
+        });
+      }
+    },
+    /** 委派任务 */
+    handleDelegate() {
+      this.taskForm.delegateTaskShow = true;
+      this.taskForm.defaultTaskShow = false;
+    },
+    handleAssign() {
+
+    },
+    /** 返回页面 */
+    goBack() {
+      // 关闭当前标签页并返回上个页面
+      const obj = { path: "/task/todo", query: { t: Date.now()} };
+      this.$tab.closeOpenPage(obj);
+    },
+    /** 驳回任务 */
+    handleReject() {
+      this.rejectOpen = true;
+      this.rejectTitle = "驳回流程";
+    },
+    /** 驳回任务 */
+    taskReject() {
+      this.$refs["taskForm"].validate(valid => {
+        if (valid) {
+          rejectTask(this.taskForm).then(res => {
+            this.$modal.msgSuccess(res.msg);
+            this.goBack();
+          });
+        }
+      });
+    },
+    /** 可退回任务列表 */
+    handleReturn() {
+      this.returnOpen = true;
+      this.returnTitle = "退回流程";
+      returnList(this.taskForm).then(res => {
+        this.returnTaskList = res.data;
+        this.taskForm.variables = null;
+      })
+    },
+    /** 提交退回任务 */
+    taskReturn() {
+      this.$refs["taskForm"].validate(valid => {
+        if (valid) {
+          returnTask(this.taskForm).then(res => {
+            this.$modal.msgSuccess(res.msg);
+            this.goBack()
+          });
+        }
+      });
+    },
+    /** 取消回退任务按钮 */
+    cancelTask() {
+      this.taskForm.returnTaskShow = false;
+      this.taskForm.defaultTaskShow = true;
+      this.returnTaskList = [];
+    },
+    /** 委派任务 */
+    submitDeleteTask() {
+      this.$refs["taskForm"].validate(valid => {
+        if (valid) {
+          delegate(this.taskForm).then(response => {
+            this.$modal.msgSuccess(response.msg);
+            this.goBack();
+          });
+        }
+      });
+    },
+    /** 取消回退任务按钮 */
+    cancelDelegateTask() {
+      this.taskForm.delegateTaskShow = false;
+      this.taskForm.defaultTaskShow = true;
+      this.returnTaskList = [];
+    },
+    /** 申请流程表单数据提交 */
+    submitForm(flag) {
+      // 根据当前任务或者流程设计配置的下一步节点 todo 暂时未涉及到考虑网关、表达式和多节点情况
+      const params = {taskId: this.taskForm.taskId}
+      getNextFlowNode(params).then(res => {
+        /*const data = res.data;
+        this.taskForm.formData = formData;
+        if (data) {
+          if (data.dataType === 'dynamic') {
+            if (data.type === 'assignee') { // 指定人员
+              this.checkSendUser = true;
+              this.checkType = "single";
+            } else if (data.type === 'candidateUsers') {  // 候选人员(多个)
+              this.checkSendUser = true;
+              this.checkType = "multiple";
+            } else if (data.type === 'candidateGroups') { // 指定组(所属角色接收任务)
+              this.checkSendRole = true;
+            } else { // 会签
+              // 流程设计指定的 elementVariable 作为会签人员列表
+              this.multiInstanceVars = data.vars;
+              this.checkSendUser = true;
+              this.checkType = "multiple";
+            }
+          }
+        }
+        this.completeOpen = true;
+        this.completeTitle = "流程审批";*/
+        this.$refs.vFormRef.getFormData().then(formData => {
+          formData.auditPass=flag;
+          Object.assign(this.taskForm.variables, formData);
+          this.taskForm.variables.formJson = this.formJson;
+          console.log(this.taskForm, "流程审批提交表单数据1")
+        }).catch(error => {
+          // this.$modal.msgError(error)
+        })
+        const data = res.data;
+        if (data) {
+          if (data.dataType === 'dynamic') {
+            if (data.type === 'assignee') { // 指定人员
+              this.checkSendUser = true;
+              this.checkType = "single";
+            } else if (data.type === 'candidateUsers') {  // 候选人员(多个)
+              this.checkSendUser = true;
+              this.checkType = "multiple";
+            } else if (data.type === 'candidateGroups') { // 指定组(所属角色接收任务)
+              this.checkSendRole = true;
+            } else { // 会签
+              // 流程设计指定的 elementVariable 作为会签人员列表
+              this.multiInstanceVars = data.vars;
+              this.checkSendUser = true;
+              this.checkType = "multiple";
+            }
+          }
+        }
+      })
+    },
+    // 动态绑定操作按钮的点击事件
+    handleButtonClick(method) {
+      this[method]();
+    }
+  },
+};
+</script>
+<style lang="scss" scoped>
+.test-form {
+  margin: 15px auto;
+  width: 800px;
+  padding: 15px;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both
+}
+
+.box-card {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.el-tag + .el-tag {
+  margin-left: 10px;
+}
+
+.my-label {
+  background: #E1F3D8;
+}
+</style>

+ 219 - 0
src/views/flowable/task/todo/index.vue

@@ -0,0 +1,219 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-position="top">
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="名称" prop="name">
+            <el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter.native="handleQuery" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="开始时间" prop="deployTime">
+            <el-date-picker style="width: 100%;" clearable v-model="queryParams.deployTime" type="date"
+              value-format="yyyy-MM-dd" placeholder="选择时间">
+            </el-date-picker>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6"></el-col>
+        <el-col :span="6"></el-col>
+      </el-row>
+
+
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="搜索">
+            <el-button style="width: 100%;" type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="重置">
+            <el-button style="width: 100%;" icon="el-icon-refresh" type="primary" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="danger" icon="el-icon-delete" size="small" :disabled="multiple" @click="handleDelete"
+          v-hasPermi="['system:deployment:remove']">删除
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+    <div class="border-card">
+      <el-table height="450" v-loading="loading" :data="todoList" border @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="任务编号" align="center" prop="taskId" :show-overflow-tooltip="true" />
+        <el-table-column label="流程名称" align="center" prop="procDefName" />
+        <el-table-column label="当前节点" align="center" prop="taskName" />
+        <el-table-column label="流程版本" align="center">
+          <template slot-scope="scope">
+            <el-tag size="medium">v{{ scope.row.procDefVersion }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="流程发起人" align="center">
+          <template slot-scope="scope">
+            <label>{{ scope.row.startUserName }} <el-tag type="info"
+                size="small">{{ scope.row.startDeptName }}</el-tag></label>
+          </template>
+        </el-table-column>
+        <el-table-column label="接收时间" align="center" prop="createTime" width="180" />
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <!-- 1. 签收按钮:无签收人时显示 -->
+            <el-button v-if="!scope.row.assigneeId" icon="el-icon-edit-outline" size="small" type="text"
+              @click="handleSign(scope.row)">签收</el-button>
+            <!-- 2. 处理按钮:有签收人时显示 -->
+            <!--          <el-button v-else size="small" type="text" icon="el-icon-edit-outline" @click="handleProcess(scope.row)">处理</el-button>-->
+            <el-button v-else size="small" type="text" icon="el-icon-edit-outline" @click="handleProcess(scope.row)">处理
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
+        @pagination="getList" />
+    </div>
+
+  </div>
+</template>
+
+<script>
+import {
+  todoList,
+  complete,
+  returnList,
+  returnTask,
+  rejectTask,
+  getDeployment,
+  delDeployment,
+  exportDeployment,
+  claimTask,
+} from "@/api/flowable/todo";
+
+export default {
+  name: "Deploy",
+  components: {},
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 流程待办任务表格数据
+      todoList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        category: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {}
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询流程定义列表 */
+    getList() {
+      this.loading = true;
+      todoList(this.queryParams).then(response => {
+        this.todoList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    // 跳转到处理页面
+    handleProcess(row) {
+      this.$router.push({
+        path: '/flowable/task/todo/detail/index',
+        query: {
+          procInsId: row.procInsId,
+          executionId: row.executionId,
+          deployId: row.deployId,
+          taskId: row.taskId,
+          taskName: row.taskName,
+          taskDefKey: row.taskDefKey,
+          startUser: row.startUserName + '-' + row.startDeptName,
+        }
+      })
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        category: null,
+        key: null,
+        tenantId: null,
+        deployTime: null,
+        derivedFrom: null,
+        derivedFromRoot: null,
+        parentDeploymentId: null,
+        engineVersion: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.taskId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.taskId || this.ids;
+      this.$confirm('是否确认删除流程定义编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delDeployment(ids);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      })
+    },
+    handleSign(row) {
+      const taskId = row.taskId;
+      claimTask(taskId).then(() => {
+        this.$modal.msgSuccess("签收成功");
+        this.getList();
+      });
+    }
+  }
+};
+</script>

+ 10 - 0
yarn.lock

@@ -4036,6 +4036,11 @@ he@1.2.x, he@^1.1.0, he@^1.1.1:
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
 
+highlight.js@9.18.5:
+  version "9.18.5"
+  resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825"
+  integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA==
+
 hmac-drbg@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -8583,6 +8588,11 @@ verror@1.10.0:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
+vkbeautify@^0.99.3:
+  version "0.99.3"
+  resolved "https://registry.npmmirror.com/vkbeautify/-/vkbeautify-0.99.3.tgz#4769b3238c5d772c6ea967545dd1df5582f23297"
+  integrity sha512-2ozZEFfmVvQcHWoHLNuiKlUfDKlhh4KGsy54U0UrlLMR1SO+XKAIDqBxtBwHgNrekurlJwE8A9K6L49T78ZQ9Q==
+
 vm-browserify@^1.0.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"