ProcessDefinitionDetail.vue 9.9 KB

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