ProcessDefinitionDetail.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <template>
  2. <ContentWrap :bodyStyle="{ padding: '10px 20px 0' }">
  3. <div class="processInstance-wrap-main">
  4. <el-scrollbar>
  5. <div class="text-#878c93 h-15px">流程:{{ selectProcessDefinition.name }}</div>
  6. <el-divider class="!my-8px" />
  7. <!-- 中间主要内容 tab 栏 -->
  8. <el-tabs v-model="activeTab">
  9. <!-- 表单信息 -->
  10. <el-tab-pane label="表单填写" name="form">
  11. <div class="form-scroll-area">
  12. <el-scrollbar>
  13. <el-row>
  14. <el-col :span="17">
  15. <form-create
  16. :rule="detailForm.rule"
  17. v-model:api="fApi"
  18. v-model="detailForm.value"
  19. :option="detailForm.option"
  20. @submit="submitForm"
  21. />
  22. </el-col>
  23. <el-col :span="6" :offset="1">
  24. <!-- 流程时间线 -->
  25. <ProcessInstanceTimeline
  26. ref="timelineRef"
  27. :activity-nodes="activityNodes"
  28. :show-status-icon="false"
  29. :startUserSelectTasks="startUserSelectTasks"
  30. :startUserSelectAssignees="startUserSelectAssignees"
  31. @select-user-confirm="selectUserConfirm"
  32. />
  33. </el-col>
  34. </el-row>
  35. </el-scrollbar>
  36. </div>
  37. </el-tab-pane>
  38. <!-- 流程图 -->
  39. <el-tab-pane label="流程图" name="diagram">
  40. <div class="form-scroll-area">
  41. <!-- 流程图预览 -->
  42. <ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML" />
  43. </div>
  44. </el-tab-pane>
  45. </el-tabs>
  46. <!-- 底部操作栏 -->
  47. <div class="b-t-solid border-t-1px border-[var(--el-border-color)]">
  48. <!-- 操作栏按钮 -->
  49. <div
  50. v-if="activeTab === 'form'"
  51. class="h-50px bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
  52. >
  53. <el-button plain type="success" @click="submitForm">
  54. <Icon icon="ep:select" />&nbsp; 发起
  55. </el-button>
  56. <el-button plain type="danger" @click="handleCancel">
  57. <Icon icon="ep:close" />&nbsp; 取消
  58. </el-button>
  59. </div>
  60. </div>
  61. </el-scrollbar>
  62. </div>
  63. </ContentWrap>
  64. </template>
  65. <script lang="ts" setup>
  66. import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
  67. import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
  68. import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
  69. import type { ApiAttrs } from '@form-create/element-ui/types/config'
  70. import { useTagsViewStore } from '@/store/modules/tagsView'
  71. import * as ProcessInstanceApi from '@/api/bpm/processInstance'
  72. import * as DefinitionApi from '@/api/bpm/definition'
  73. defineOptions({ name: 'ProcessDefinitionDetail' })
  74. const props = defineProps<{
  75. selectProcessDefinition: any
  76. }>()
  77. const { push, currentRoute } = useRouter() // 路由
  78. const message = useMessage() // 消息弹窗
  79. const { delView } = useTagsViewStore() // 视图操作
  80. const detailForm: any = ref({
  81. rule: [],
  82. option: {},
  83. value: {}
  84. }) // 流程表单详情
  85. const fApi = ref<ApiAttrs>()
  86. // 指定审批人
  87. const startUserSelectTasks: any = ref([]) // 发起人需要选择审批人的用户任务列表
  88. const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
  89. const bpmnXML: any = ref(null) // BPMN 数据
  90. /** 当前的Tab */
  91. const activeTab = ref('form')
  92. const emit = defineEmits(['cancel'])
  93. // 审批节点信息
  94. const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([])
  95. /** 设置表单信息、获取流程图数据 **/
  96. const initProcessInfo = async (row: any, formVariables?: any) => {
  97. // 重置指定审批人
  98. startUserSelectTasks.value = []
  99. startUserSelectAssignees.value = {}
  100. // 情况一:流程表单
  101. if (row.formType == 10) {
  102. // 设置表单
  103. // 注意:需要从 formVariables 中,移除不在 row.formFields 的值。
  104. // 原因是:后端返回的 formVariables 里面,会有一些非表单的信息。例如说,某个流程节点的审批人。
  105. // 这样,就可能导致一个流程被审批不通过后,重新发起时,会直接后端报错!!!
  106. const allowedFields = decodeFields(row.formFields).map((fieldObj: any) => fieldObj.field)
  107. for (const key in formVariables) {
  108. if (!allowedFields.includes(key)) {
  109. delete formVariables[key]
  110. }
  111. }
  112. setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
  113. await nextTick()
  114. fApi.value?.btn.show(false) // 隐藏提交按钮
  115. // 获取流程审批信息
  116. await getApprovalDetail(row)
  117. // 加载流程图
  118. const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
  119. if (processDefinitionDetail) {
  120. bpmnXML.value = processDefinitionDetail.bpmnXml
  121. startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
  122. // 设置指定审批人
  123. if (startUserSelectTasks.value?.length > 0) {
  124. for (const userTask of startUserSelectTasks.value) {
  125. // 初始化数据
  126. startUserSelectAssignees.value[userTask.id] = []
  127. }
  128. }
  129. }
  130. // 情况二:业务表单
  131. } else if (row.formCustomCreatePath) {
  132. await push({
  133. path: row.formCustomCreatePath
  134. })
  135. // 这里暂时无需加载流程图,因为跳出到另外个 Tab;
  136. }
  137. }
  138. /** 获取审批详情 */
  139. const getApprovalDetail = async (row: any) => {
  140. try {
  141. const param = {
  142. processDefinitionId: row.id
  143. }
  144. const data = await ProcessInstanceApi.getApprovalDetail(param)
  145. if (!data) {
  146. message.error('查询不到审批详情信息!')
  147. return
  148. }
  149. // 获取审批节点,显示 Timeline 的数据
  150. activityNodes.value = data.activityNodes
  151. } finally {
  152. }
  153. }
  154. /** 提交按钮 */
  155. const submitForm = async () => {
  156. if (!fApi.value || !props.selectProcessDefinition) {
  157. return
  158. }
  159. // 如果有指定审批人,需要校验
  160. if (startUserSelectTasks.value?.length > 0) {
  161. for (const userTask of startUserSelectTasks.value) {
  162. if (
  163. Array.isArray(startUserSelectAssignees.value[userTask.id]) &&
  164. startUserSelectAssignees.value[userTask.id].length === 0
  165. )
  166. return message.warning(`请选择${userTask.name}的审批人`)
  167. }
  168. }
  169. // 提交请求
  170. fApi.value.btn.loading(true)
  171. try {
  172. await ProcessInstanceApi.createProcessInstance({
  173. processDefinitionId: props.selectProcessDefinition.id,
  174. variables: detailForm.value.value,
  175. startUserSelectAssignees: startUserSelectAssignees.value
  176. })
  177. // 提示
  178. message.success('发起流程成功')
  179. // 跳转回去
  180. delView(unref(currentRoute))
  181. await push({
  182. name: 'BpmProcessInstanceMy'
  183. })
  184. } finally {
  185. fApi.value.btn.loading(false)
  186. }
  187. }
  188. const handleCancel = () => {
  189. emit('cancel')
  190. }
  191. const selectUserConfirm = (id, userList) => {
  192. startUserSelectAssignees.value[id] = userList?.map((item) => item.id)
  193. }
  194. defineExpose({ initProcessInfo })
  195. </script>
  196. <style lang="scss" scoped>
  197. $wrap-padding-height: 20px;
  198. $wrap-margin-height: 15px;
  199. $button-height: 51px;
  200. $process-header-height: 105px;
  201. .processInstance-wrap-main {
  202. height: calc(
  203. 100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
  204. );
  205. max-height: calc(
  206. 100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
  207. );
  208. overflow: auto;
  209. .form-scroll-area {
  210. height: calc(
  211. 100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
  212. $process-header-height - 40px
  213. );
  214. max-height: calc(
  215. 100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
  216. $process-header-height - 40px
  217. );
  218. overflow: auto;
  219. }
  220. }
  221. .form-box {
  222. :deep(.el-card) {
  223. border: none;
  224. }
  225. }
  226. </style>