ProcessInstanceOperationButton.vue 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
  1. <template>
  2. <div
  3. class="h-50px bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
  4. >
  5. <!-- 【通过】按钮 -->
  6. <el-popover
  7. :visible="popOverVisible.approve"
  8. placement="top-end"
  9. :width="420"
  10. trigger="click"
  11. v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.APPROVE)"
  12. >
  13. <template #reference>
  14. <el-button plain type="success" @click="openPopover('approve')">
  15. <Icon icon="ep:select" />&nbsp; {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
  16. </el-button>
  17. </template>
  18. <!-- 审批表单 -->
  19. <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
  20. <el-form
  21. label-position="top"
  22. class="mb-auto"
  23. ref="approveFormRef"
  24. :model="approveReasonForm"
  25. :rules="approveReasonRule"
  26. label-width="100px"
  27. >
  28. <el-card v-if="runningTask?.formId > 0" class="mb-15px !-mt-10px">
  29. <template #header>
  30. <span class="el-icon-picture-outline"> 填写表单【{{ runningTask?.formName }}】 </span>
  31. </template>
  32. <form-create
  33. v-model="approveForm.value"
  34. v-model:api="approveFormFApi"
  35. :option="approveForm.option"
  36. :rule="approveForm.rule"
  37. />
  38. </el-card>
  39. <el-form-item :label="`${nodeTypeName}意见`" prop="reason">
  40. <el-input
  41. v-model="approveReasonForm.reason"
  42. :placeholder="`请输入${nodeTypeName}意见`"
  43. type="textarea"
  44. :rows="4"
  45. />
  46. </el-form-item>
  47. <el-form-item
  48. label="下一个节点的审批人"
  49. prop="nextAssignees"
  50. v-if="nextAssigneesActivityNode.length > 0"
  51. >
  52. <div class="ml-10px -mt-15px -mb-35px">
  53. <ProcessInstanceTimeline
  54. :activity-nodes="nextAssigneesActivityNode"
  55. :show-status-icon="false"
  56. @select-user-confirm="selectNextAssigneesConfirm"
  57. />
  58. </div>
  59. </el-form-item>
  60. <el-form-item
  61. v-if="runningTask.signEnable"
  62. label="签名"
  63. prop="signPicUrl"
  64. ref="approveSignFormRef"
  65. >
  66. <el-button @click="signRef.open()">点击签名</el-button>
  67. <el-image
  68. class="w-90px h-40px ml-5px"
  69. v-if="approveReasonForm.signPicUrl"
  70. :src="approveReasonForm.signPicUrl"
  71. :preview-src-list="[approveReasonForm.signPicUrl]"
  72. />
  73. </el-form-item>
  74. <el-form-item>
  75. <el-button
  76. :disabled="formLoading"
  77. type="success"
  78. @click="handleAudit(true, approveFormRef)"
  79. >
  80. {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
  81. </el-button>
  82. <el-button @click="closePopover('approve', approveFormRef)"> 取消 </el-button>
  83. </el-form-item>
  84. </el-form>
  85. </div>
  86. </el-popover>
  87. <!-- 【拒绝】按钮 -->
  88. <el-popover
  89. :visible="popOverVisible.reject"
  90. placement="top-end"
  91. :width="420"
  92. trigger="click"
  93. v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.REJECT)"
  94. >
  95. <template #reference>
  96. <el-button class="mr-20px" plain type="danger" @click="openPopover('reject')">
  97. <Icon icon="ep:close" />&nbsp; {{ getButtonDisplayName(OperationButtonType.REJECT) }}
  98. </el-button>
  99. </template>
  100. <!-- 审批表单 -->
  101. <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
  102. <el-form
  103. label-position="top"
  104. class="mb-auto"
  105. ref="rejectFormRef"
  106. :model="rejectReasonForm"
  107. :rules="rejectReasonRule"
  108. label-width="100px"
  109. >
  110. <el-form-item label="审批意见" prop="reason">
  111. <el-input
  112. v-model="rejectReasonForm.reason"
  113. placeholder="请输入审批意见"
  114. type="textarea"
  115. :rows="4"
  116. />
  117. </el-form-item>
  118. <el-form-item>
  119. <el-button
  120. :disabled="formLoading"
  121. type="danger"
  122. @click="handleAudit(false, rejectFormRef)"
  123. >
  124. {{ getButtonDisplayName(OperationButtonType.REJECT) }}
  125. </el-button>
  126. <el-button @click="closePopover('reject', rejectFormRef)"> 取消 </el-button>
  127. </el-form-item>
  128. </el-form>
  129. </div>
  130. </el-popover>
  131. <!-- 【抄送】按钮 -->
  132. <el-popover
  133. :visible="popOverVisible.copy"
  134. placement="top-start"
  135. :width="420"
  136. trigger="click"
  137. v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.COPY)"
  138. >
  139. <template #reference>
  140. <div @click="openPopover('copy')" class="hover-bg-gray-100 rounded-xl p-6px">
  141. <Icon :size="14" icon="svg-icon:send" />&nbsp;
  142. {{ getButtonDisplayName(OperationButtonType.COPY) }}
  143. </div>
  144. </template>
  145. <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
  146. <el-form
  147. label-position="top"
  148. class="mb-auto"
  149. ref="copyFormRef"
  150. :model="copyForm"
  151. :rules="copyFormRule"
  152. label-width="100px"
  153. >
  154. <el-form-item label="抄送人" prop="copyUserIds">
  155. <el-select
  156. v-model="copyForm.copyUserIds"
  157. clearable
  158. style="width: 100%"
  159. multiple
  160. placeholder="请选择抄送人"
  161. >
  162. <el-option
  163. v-for="item in userOptions"
  164. :key="item.id"
  165. :label="item.nickname"
  166. :value="item.id"
  167. />
  168. </el-select>
  169. </el-form-item>
  170. <el-form-item label="抄送意见" prop="copyReason">
  171. <el-input
  172. v-model="copyForm.copyReason"
  173. clearable
  174. placeholder="请输入抄送意见"
  175. type="textarea"
  176. :rows="3"
  177. />
  178. </el-form-item>
  179. <el-form-item>
  180. <el-button :disabled="formLoading" type="primary" @click="handleCopy">
  181. {{ getButtonDisplayName(OperationButtonType.COPY) }}
  182. </el-button>
  183. <el-button @click="closePopover('copy', copyFormRef)"> 取消 </el-button>
  184. </el-form-item>
  185. </el-form>
  186. </div>
  187. </el-popover>
  188. <!-- 【转办】按钮 -->
  189. <el-popover
  190. :visible="popOverVisible.transfer"
  191. placement="top-start"
  192. :width="420"
  193. trigger="click"
  194. v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.TRANSFER)"
  195. >
  196. <template #reference>
  197. <div @click="openPopover('transfer')" class="hover-bg-gray-100 rounded-xl p-6px">
  198. <Icon :size="14" icon="fa:share-square-o" />&nbsp;
  199. {{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
  200. </div>
  201. </template>
  202. <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
  203. <el-form
  204. label-position="top"
  205. class="mb-auto"
  206. ref="transferFormRef"
  207. :model="transferForm"
  208. :rules="transferFormRule"
  209. label-width="100px"
  210. >
  211. <el-form-item label="新审批人" prop="assigneeUserId">
  212. <el-select v-model="transferForm.assigneeUserId" clearable style="width: 100%">
  213. <el-option
  214. v-for="item in userOptions"
  215. :key="item.id"
  216. :label="item.nickname"
  217. :value="item.id"
  218. />
  219. </el-select>
  220. </el-form-item>
  221. <el-form-item label="审批意见" prop="reason">
  222. <el-input
  223. v-model="transferForm.reason"
  224. clearable
  225. placeholder="请输入审批意见"
  226. type="textarea"
  227. :rows="3"
  228. />
  229. </el-form-item>
  230. <el-form-item>
  231. <el-button :disabled="formLoading" type="primary" @click="handleTransfer()">
  232. {{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
  233. </el-button>
  234. <el-button @click="closePopover('transfer', transferFormRef)"> 取消 </el-button>
  235. </el-form-item>
  236. </el-form>
  237. </div>
  238. </el-popover>
  239. <!-- 【委派】按钮 -->
  240. <el-popover
  241. :visible="popOverVisible.delegate"
  242. placement="top-start"
  243. :width="420"
  244. trigger="click"
  245. v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.DELEGATE)"
  246. >
  247. <template #reference>
  248. <div @click="openPopover('delegate')" class="hover-bg-gray-100 rounded-xl p-6px">
  249. <Icon :size="14" icon="ep:position" />&nbsp;
  250. {{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
  251. </div>
  252. </template>
  253. <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
  254. <el-form
  255. label-position="top"
  256. class="mb-auto"
  257. ref="delegateFormRef"
  258. :model="delegateForm"
  259. :rules="delegateFormRule"
  260. label-width="100px"
  261. >
  262. <el-form-item label="接收人" prop="delegateUserId">
  263. <el-select v-model="delegateForm.delegateUserId" clearable style="width: 100%">
  264. <el-option
  265. v-for="item in userOptions"
  266. :key="item.id"
  267. :label="item.nickname"
  268. :value="item.id"
  269. />
  270. </el-select>
  271. </el-form-item>
  272. <el-form-item label="审批意见" prop="reason">
  273. <el-input
  274. v-model="delegateForm.reason"
  275. clearable
  276. placeholder="请输入审批意见"
  277. type="textarea"
  278. :rows="3"
  279. />
  280. </el-form-item>
  281. <el-form-item>
  282. <el-button :disabled="formLoading" type="primary" @click="handleDelegate()">
  283. {{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
  284. </el-button>
  285. <el-button @click="closePopover('delegate', delegateFormRef)"> 取消 </el-button>
  286. </el-form-item>
  287. </el-form>
  288. </div>
  289. </el-popover>
  290. <!-- 【加签】按钮 当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
  291. <el-popover
  292. :visible="popOverVisible.addSign"
  293. placement="top-start"
  294. :width="420"
  295. trigger="click"
  296. v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.ADD_SIGN)"
  297. >
  298. <template #reference>
  299. <div @click="openPopover('addSign')" class="hover-bg-gray-100 rounded-xl p-6px">
  300. <Icon :size="14" icon="ep:plus" />&nbsp;
  301. {{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
  302. </div>
  303. </template>
  304. <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
  305. <el-form
  306. label-position="top"
  307. class="mb-auto"
  308. ref="addSignFormRef"
  309. :model="addSignForm"
  310. :rules="addSignFormRule"
  311. label-width="100px"
  312. >
  313. <el-form-item label="加签处理人" prop="addSignUserIds">
  314. <el-select v-model="addSignForm.addSignUserIds" multiple clearable style="width: 100%">
  315. <el-option
  316. v-for="item in userOptions"
  317. :key="item.id"
  318. :label="item.nickname"
  319. :value="item.id"
  320. />
  321. </el-select>
  322. </el-form-item>
  323. <el-form-item label="审批意见" prop="reason">
  324. <el-input
  325. v-model="addSignForm.reason"
  326. clearable
  327. placeholder="请输入审批意见"
  328. type="textarea"
  329. :rows="3"
  330. />
  331. </el-form-item>
  332. <el-form-item>
  333. <el-button :disabled="formLoading" type="primary" @click="handlerAddSign('before')">
  334. 向前{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
  335. </el-button>
  336. <el-button :disabled="formLoading" type="primary" @click="handlerAddSign('after')">
  337. 向后{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
  338. </el-button>
  339. <el-button @click="closePopover('addSign', addSignFormRef)"> 取消 </el-button>
  340. </el-form-item>
  341. </el-form>
  342. </div>
  343. </el-popover>
  344. <!-- 【减签】按钮 -->
  345. <el-popover
  346. :visible="popOverVisible.deleteSign"
  347. placement="top-start"
  348. :width="420"
  349. trigger="click"
  350. v-if="runningTask?.children.length > 0"
  351. >
  352. <template #reference>
  353. <div @click="openPopover('deleteSign')" class="hover-bg-gray-100 rounded-xl p-6px">
  354. <Icon :size="14" icon="ep:semi-select" />&nbsp; 减签
  355. </div>
  356. </template>
  357. <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
  358. <el-form
  359. label-position="top"
  360. class="mb-auto"
  361. ref="deleteSignFormRef"
  362. :model="deleteSignForm"
  363. :rules="deleteSignFormRule"
  364. label-width="100px"
  365. >
  366. <el-form-item label="减签人员" prop="deleteSignTaskId">
  367. <el-select v-model="deleteSignForm.deleteSignTaskId" clearable style="width: 100%">
  368. <el-option
  369. v-for="item in runningTask.children"
  370. :key="item.id"
  371. :label="getDeleteSignUserLabel(item)"
  372. :value="item.id"
  373. />
  374. </el-select>
  375. </el-form-item>
  376. <el-form-item label="审批意见" prop="reason">
  377. <el-input
  378. v-model="deleteSignForm.reason"
  379. clearable
  380. placeholder="请输入审批意见"
  381. type="textarea"
  382. :rows="3"
  383. />
  384. </el-form-item>
  385. <el-form-item>
  386. <el-button :disabled="formLoading" type="primary" @click="handlerDeleteSign()">
  387. 减签
  388. </el-button>
  389. <el-button @click="closePopover('deleteSign', deleteSignFormRef)"> 取消 </el-button>
  390. </el-form-item>
  391. </el-form>
  392. </div>
  393. </el-popover>
  394. <!-- 【退回】按钮 -->
  395. <el-popover
  396. :visible="popOverVisible.return"
  397. placement="top-start"
  398. :width="420"
  399. trigger="click"
  400. v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.RETURN)"
  401. >
  402. <template #reference>
  403. <div @click="openPopover('return')" class="hover-bg-gray-100 rounded-xl p-6px">
  404. <Icon :size="14" icon="ep:back" />&nbsp;
  405. {{ getButtonDisplayName(OperationButtonType.RETURN) }}
  406. </div>
  407. </template>
  408. <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
  409. <el-form
  410. label-position="top"
  411. class="mb-auto"
  412. ref="returnFormRef"
  413. :model="returnForm"
  414. :rules="returnFormRule"
  415. label-width="100px"
  416. >
  417. <el-form-item label="退回节点" prop="targetTaskDefinitionKey">
  418. <el-select v-model="returnForm.targetTaskDefinitionKey" clearable style="width: 100%">
  419. <el-option
  420. v-for="item in returnList"
  421. :key="item.taskDefinitionKey"
  422. :label="item.name"
  423. :value="item.taskDefinitionKey"
  424. />
  425. </el-select>
  426. </el-form-item>
  427. <el-form-item label="退回理由" prop="returnReason">
  428. <el-input
  429. v-model="returnForm.returnReason"
  430. clearable
  431. placeholder="请输入退回理由"
  432. type="textarea"
  433. :rows="3"
  434. />
  435. </el-form-item>
  436. <el-form-item>
  437. <el-button :disabled="formLoading" type="primary" @click="handleReturn()">
  438. {{ getButtonDisplayName(OperationButtonType.RETURN) }}
  439. </el-button>
  440. <el-button @click="closePopover('return', returnFormRef)"> 取消 </el-button>
  441. </el-form-item>
  442. </el-form>
  443. </div>
  444. </el-popover>
  445. <!--【取消】按钮 这个对应发起人的取消, 只有发起人可以取消 -->
  446. <el-popover
  447. :visible="popOverVisible.cancel"
  448. placement="top-start"
  449. :width="420"
  450. trigger="click"
  451. v-if="
  452. userId === processInstance?.startUser?.id && !isEndProcessStatus(processInstance?.status)
  453. "
  454. >
  455. <template #reference>
  456. <div @click="openPopover('cancel')" class="hover-bg-gray-100 rounded-xl p-6px">
  457. <Icon :size="14" icon="fa:mail-reply" />&nbsp; 取消
  458. </div>
  459. </template>
  460. <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
  461. <el-form
  462. label-position="top"
  463. class="mb-auto"
  464. ref="cancelFormRef"
  465. :model="cancelForm"
  466. :rules="cancelFormRule"
  467. label-width="100px"
  468. >
  469. <el-form-item label="取消理由" prop="cancelReason">
  470. <span class="text-#878c93 text-12px">&nbsp; 取消后,该审批流程将自动结束</span>
  471. <el-input
  472. v-model="cancelForm.cancelReason"
  473. clearable
  474. placeholder="请输入取消理由"
  475. type="textarea"
  476. :rows="3"
  477. />
  478. </el-form-item>
  479. <el-form-item>
  480. <el-button :disabled="formLoading" type="primary" @click="handleCancel()">
  481. 确认
  482. </el-button>
  483. <el-button @click="closePopover('cancel', cancelFormRef)"> 取消 </el-button>
  484. </el-form-item>
  485. </el-form>
  486. </div>
  487. </el-popover>
  488. <!-- 【再次提交】 按钮-->
  489. <div
  490. @click="handleReCreate()"
  491. class="hover-bg-gray-100 rounded-xl p-6px"
  492. v-if="
  493. userId === processInstance?.startUser?.id &&
  494. isEndProcessStatus(processInstance?.status) &&
  495. processDefinition?.formType === 10
  496. "
  497. >
  498. <Icon :size="14" icon="ep:refresh" />&nbsp; 再次提交
  499. </div>
  500. </div>
  501. <!-- 签名弹窗 -->
  502. <SignDialog ref="signRef" @success="handleSignFinish" />
  503. </template>
  504. <script lang="ts" setup>
  505. import { useUserStoreWithOut } from '@/store/modules/user'
  506. import { setConfAndFields2 } from '@/utils/formCreate'
  507. import * as TaskApi from '@/api/bpm/task'
  508. import * as ProcessInstanceApi from '@/api/bpm/processInstance'
  509. import * as UserApi from '@/api/system/user'
  510. import {
  511. NodeType,
  512. OPERATION_BUTTON_NAME,
  513. OperationButtonType,
  514. CandidateStrategy
  515. } from '@/components/SimpleProcessDesignerV2/src/consts'
  516. import { BpmModelFormType, BpmProcessInstanceStatus } from '@/utils/constants'
  517. import type { FormInstance, FormRules } from 'element-plus'
  518. import SignDialog from './SignDialog.vue'
  519. import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
  520. import { isEmpty } from '@/utils/is'
  521. defineOptions({ name: 'ProcessInstanceBtnContainer' })
  522. const router = useRouter() // 路由
  523. const message = useMessage() // 消息弹窗
  524. const userId = useUserStoreWithOut().getUser.id // 当前登录的编号
  525. const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
  526. const props = defineProps<{
  527. processInstance: any // 流程实例信息
  528. processDefinition: any // 流程定义信息
  529. userOptions: UserApi.UserVO[]
  530. normalForm: any // 流程表单 formCreate
  531. normalFormApi: any // 流程表单 formCreate Api
  532. writableFields: string[] // 流程表单可以编辑的字段
  533. }>()
  534. const formLoading = ref(false) // 表单加载中
  535. const popOverVisible = ref({
  536. approve: false,
  537. reject: false,
  538. transfer: false,
  539. delegate: false,
  540. addSign: false,
  541. return: false,
  542. copy: false,
  543. cancel: false,
  544. deleteSign: false
  545. }) // 气泡卡是否展示
  546. const returnList = ref([] as any) // 退回节点
  547. // ========== 审批信息 ==========
  548. const runningTask = ref<any>() // 运行中的任务
  549. const approveForm = ref<any>({}) // 审批通过时,额外的补充信息
  550. const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
  551. const nodeTypeName = ref('审批') // 节点类型名称
  552. // 审批通过意见表单
  553. const reasonRequire = ref()
  554. const approveFormRef = ref<FormInstance>()
  555. const signRef = ref()
  556. const approveSignFormRef = ref()
  557. const nextAssigneesActivityNode = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 下一个审批节点信息
  558. const approveReasonForm = reactive({
  559. reason: '',
  560. signPicUrl: '',
  561. nextAssignees: {}
  562. })
  563. const approveReasonRule = computed(() => {
  564. return {
  565. reason: [
  566. { required: reasonRequire.value, message: nodeTypeName + '意见不能为空', trigger: 'blur' }
  567. ],
  568. signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }],
  569. nextAssignees: [{ required: true, message: '审批人不能为空', trigger: 'blur' }]
  570. }
  571. })
  572. // 拒绝表单
  573. const rejectFormRef = ref<FormInstance>()
  574. const rejectReasonForm = reactive({
  575. reason: ''
  576. })
  577. const rejectReasonRule = computed(() => {
  578. return {
  579. reason: [{ required: reasonRequire.value, message: '审批意见不能为空', trigger: 'blur' }]
  580. }
  581. })
  582. // 抄送表单
  583. const copyFormRef = ref<FormInstance>()
  584. const copyForm = reactive({
  585. copyUserIds: [],
  586. copyReason: ''
  587. })
  588. const copyFormRule = reactive<FormRules<typeof copyForm>>({
  589. copyUserIds: [{ required: true, message: '抄送人不能为空', trigger: 'change' }]
  590. })
  591. // 转办表单
  592. const transferFormRef = ref<FormInstance>()
  593. const transferForm = reactive({
  594. assigneeUserId: undefined,
  595. reason: ''
  596. })
  597. const transferFormRule = reactive<FormRules<typeof transferForm>>({
  598. assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
  599. reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
  600. })
  601. // 委派表单
  602. const delegateFormRef = ref<FormInstance>()
  603. const delegateForm = reactive({
  604. delegateUserId: undefined,
  605. reason: ''
  606. })
  607. const delegateFormRule = reactive<FormRules<typeof delegateForm>>({
  608. delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
  609. reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
  610. })
  611. // 加签表单
  612. const addSignFormRef = ref<FormInstance>()
  613. const addSignForm = reactive({
  614. addSignUserIds: undefined,
  615. reason: ''
  616. })
  617. const addSignFormRule = reactive<FormRules<typeof addSignForm>>({
  618. addSignUserIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
  619. reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
  620. })
  621. // 减签表单
  622. const deleteSignFormRef = ref<FormInstance>()
  623. const deleteSignForm = reactive({
  624. deleteSignTaskId: undefined,
  625. reason: ''
  626. })
  627. const deleteSignFormRule = reactive<FormRules<typeof deleteSignForm>>({
  628. deleteSignTaskId: [{ required: true, message: '减签人员不能为空', trigger: 'change' }],
  629. reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
  630. })
  631. // 退回表单
  632. const returnFormRef = ref<FormInstance>()
  633. const returnForm = reactive({
  634. targetTaskDefinitionKey: undefined,
  635. returnReason: ''
  636. })
  637. const returnFormRule = reactive<FormRules<typeof returnForm>>({
  638. targetTaskDefinitionKey: [{ required: true, message: '退回节点不能为空', trigger: 'change' }],
  639. returnReason: [{ required: true, message: '退回理由不能为空', trigger: 'blur' }]
  640. })
  641. // 取消表单
  642. const cancelFormRef = ref<FormInstance>()
  643. const cancelForm = reactive({
  644. cancelReason: ''
  645. })
  646. const cancelFormRule = reactive<FormRules<typeof cancelForm>>({
  647. cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }]
  648. })
  649. /** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
  650. watch(
  651. () => approveFormFApi.value,
  652. (val) => {
  653. val?.btn?.show(false)
  654. val?.resetBtn?.show(false)
  655. },
  656. {
  657. deep: true
  658. }
  659. )
  660. /** 弹出气泡卡 */
  661. const openPopover = async (type: string) => {
  662. if (type === 'approve') {
  663. // 校验流程表单
  664. const valid = await validateNormalForm()
  665. if (!valid) {
  666. message.warning('表单校验不通过,请先完善表单!!')
  667. return
  668. }
  669. initNextAssigneesFormField()
  670. }
  671. if (type === 'return') {
  672. // 获取退回节点
  673. returnList.value = await TaskApi.getTaskListByReturn(runningTask.value.id)
  674. if (returnList.value.length === 0) {
  675. message.warning('当前没有可退回的节点')
  676. return
  677. }
  678. }
  679. Object.keys(popOverVisible.value).forEach((item) => {
  680. popOverVisible.value[item] = item === type
  681. })
  682. // await nextTick()
  683. // formRef.value.resetFields()
  684. }
  685. /** 关闭气泡卡 */
  686. const closePopover = (type: string, formRef: FormInstance | undefined) => {
  687. if (formRef) {
  688. formRef.resetFields()
  689. }
  690. popOverVisible.value[type] = false
  691. nextAssigneesActivityNode.value = []
  692. }
  693. /** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */
  694. const initNextAssigneesFormField = async () => {
  695. // 获取修改的流程变量, 暂时只支持流程表单
  696. const variables = getUpdatedProcessInstanceVariables()
  697. const data = await ProcessInstanceApi.getNextApprovalNodes({
  698. processInstanceId: props.processInstance.id,
  699. taskId: runningTask.value.id,
  700. processVariablesStr: JSON.stringify(variables)
  701. })
  702. if (data && data.length > 0) {
  703. data.forEach((node: any) => {
  704. // 如果是发起人自选,并且没有审批人 或者 是审批人自选
  705. if (
  706. (isEmpty(node.tasks) &&
  707. isEmpty(node.candidateUsers) &&
  708. CandidateStrategy.START_USER_SELECT === node.candidateStrategy) ||
  709. CandidateStrategy.APPROVE_USER_SELECT === node.candidateStrategy
  710. ) {
  711. nextAssigneesActivityNode.value.push(node)
  712. }
  713. })
  714. }
  715. }
  716. /** 选择下一个节点的审批人 */
  717. const selectNextAssigneesConfirm = (id: string, userList: any[]) => {
  718. approveReasonForm.nextAssignees[id] = userList?.map((item: any) => item.id)
  719. }
  720. /** 审批通过时,校验每个自选审批人的节点是否都已配置了审批人 */
  721. const validateNextAssignees = () => {
  722. // 如果需要自选审批人,则校验自选审批人
  723. if (Object.keys(nextAssigneesActivityNode.value).length > 0) {
  724. // 校验每个节点是否都已配置审批人
  725. for (const item of nextAssigneesActivityNode.value) {
  726. if (isEmpty(approveReasonForm.nextAssignees[item.id])) {
  727. message.warning('下一个节点的审批人不能为空!')
  728. return false
  729. }
  730. }
  731. }
  732. return true
  733. }
  734. /** 处理审批通过和不通过的操作 */
  735. const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) => {
  736. formLoading.value = true
  737. try {
  738. // 校验表单
  739. if (!formRef) return
  740. await formRef.validate()
  741. // 校验流程表单必填字段
  742. const valid = await validateNormalForm()
  743. if (!valid) {
  744. message.warning('表单校验不通过,请先完善表单!!')
  745. return
  746. }
  747. if (pass) {
  748. const nextAssigneesValid = validateNextAssignees()
  749. if (!nextAssigneesValid) return
  750. const variables = getUpdatedProcessInstanceVariables()
  751. // 审批通过数据
  752. const data = {
  753. id: runningTask.value.id,
  754. reason: approveReasonForm.reason,
  755. variables, // 审批通过, 把修改的字段值赋于流程实例变量
  756. nextAssignees: approveReasonForm.nextAssignees // 下个自选节点选择的审批人信息
  757. } as any
  758. // 签名
  759. if (runningTask.value.signEnable) {
  760. data.signPicUrl = approveReasonForm.signPicUrl
  761. }
  762. // 多表单处理,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
  763. // TODO 芋艿 任务有多表单这里要如何处理,会和可编辑的字段冲突
  764. const formCreateApi = approveFormFApi.value
  765. if (Object.keys(formCreateApi)?.length > 0) {
  766. await formCreateApi.validate()
  767. // @ts-ignore
  768. data.variables = approveForm.value.value
  769. }
  770. await TaskApi.approveTask(data)
  771. popOverVisible.value.approve = false
  772. nextAssigneesActivityNode.value = []
  773. message.success('审批通过成功')
  774. } else {
  775. // 审批不通过数据
  776. const data = {
  777. id: runningTask.value.id,
  778. reason: rejectReasonForm.reason
  779. }
  780. await TaskApi.rejectTask(data)
  781. popOverVisible.value.reject = false
  782. message.success('审批不通过成功')
  783. }
  784. // 重置表单
  785. formRef.resetFields()
  786. // 加载最新数据
  787. reload()
  788. } finally {
  789. formLoading.value = false
  790. }
  791. }
  792. /** 处理抄送 */
  793. const handleCopy = async () => {
  794. formLoading.value = true
  795. try {
  796. // 1. 校验表单
  797. if (!copyFormRef.value) return
  798. await copyFormRef.value.validate()
  799. // 2. 提交抄送
  800. const data = {
  801. id: runningTask.value.id,
  802. reason: copyForm.copyReason,
  803. copyUserIds: copyForm.copyUserIds
  804. }
  805. await TaskApi.copyTask(data)
  806. copyFormRef.value.resetFields()
  807. popOverVisible.value.copy = false
  808. message.success('操作成功')
  809. } finally {
  810. formLoading.value = false
  811. }
  812. }
  813. /** 处理转交 */
  814. const handleTransfer = async () => {
  815. formLoading.value = true
  816. try {
  817. // 1.1 校验表单
  818. if (!transferFormRef.value) return
  819. await transferFormRef.value.validate()
  820. // 1.2 提交转交
  821. const data = {
  822. id: runningTask.value.id,
  823. reason: transferForm.reason,
  824. assigneeUserId: transferForm.assigneeUserId
  825. }
  826. await TaskApi.transferTask(data)
  827. transferFormRef.value.resetFields()
  828. popOverVisible.value.transfer = false
  829. message.success('操作成功')
  830. // 2. 加载最新数据
  831. reload()
  832. } finally {
  833. formLoading.value = false
  834. }
  835. }
  836. /** 处理委派 */
  837. const handleDelegate = async () => {
  838. formLoading.value = true
  839. try {
  840. // 1.1 校验表单
  841. if (!delegateFormRef.value) return
  842. await delegateFormRef.value.validate()
  843. // 1.2 处理委派
  844. const data = {
  845. id: runningTask.value.id,
  846. reason: delegateForm.reason,
  847. delegateUserId: delegateForm.delegateUserId
  848. }
  849. await TaskApi.delegateTask(data)
  850. popOverVisible.value.delegate = false
  851. delegateFormRef.value.resetFields()
  852. message.success('操作成功')
  853. // 2. 加载最新数据
  854. reload()
  855. } finally {
  856. formLoading.value = false
  857. }
  858. }
  859. /** 处理加签 */
  860. const handlerAddSign = async (type: string) => {
  861. formLoading.value = true
  862. try {
  863. // 1.1 校验表单
  864. if (!addSignFormRef.value) return
  865. await addSignFormRef.value.validate()
  866. // 1.2 提交加签
  867. const data = {
  868. id: runningTask.value.id,
  869. type,
  870. reason: addSignForm.reason,
  871. userIds: addSignForm.addSignUserIds
  872. }
  873. await TaskApi.signCreateTask(data)
  874. message.success('操作成功')
  875. addSignFormRef.value.resetFields()
  876. popOverVisible.value.addSign = false
  877. // 2 加载最新数据
  878. reload()
  879. } finally {
  880. formLoading.value = false
  881. }
  882. }
  883. /** 处理退回 */
  884. const handleReturn = async () => {
  885. formLoading.value = true
  886. try {
  887. // 1.1 校验表单
  888. if (!returnFormRef.value) return
  889. await returnFormRef.value.validate()
  890. // 1.2 提交退回
  891. const data = {
  892. id: runningTask.value.id,
  893. reason: returnForm.returnReason,
  894. targetTaskDefinitionKey: returnForm.targetTaskDefinitionKey
  895. }
  896. await TaskApi.returnTask(data)
  897. popOverVisible.value.return = false
  898. returnFormRef.value.resetFields()
  899. message.success('操作成功')
  900. // 2 重新加载数据
  901. reload()
  902. } finally {
  903. formLoading.value = false
  904. }
  905. }
  906. /** 处理取消 */
  907. const handleCancel = async () => {
  908. formLoading.value = true
  909. try {
  910. // 1.1 校验表单
  911. if (!cancelFormRef.value) return
  912. await cancelFormRef.value.validate()
  913. // 1.2 提交取消
  914. await ProcessInstanceApi.cancelProcessInstanceByStartUser(
  915. props.processInstance.id,
  916. cancelForm.cancelReason
  917. )
  918. popOverVisible.value.return = false
  919. message.success('操作成功')
  920. cancelFormRef.value.resetFields()
  921. // 2 重新加载数据
  922. reload()
  923. } finally {
  924. formLoading.value = false
  925. }
  926. }
  927. /** 处理再次提交 */
  928. const handleReCreate = async () => {
  929. // 跳转发起流程界面
  930. await router.push({
  931. name: 'BpmProcessInstanceCreate',
  932. query: { processInstanceId: props.processInstance?.id }
  933. })
  934. }
  935. /** 获取减签人员标签 */
  936. const getDeleteSignUserLabel = (task: any): string => {
  937. const deptName = task?.assigneeUser?.deptName || task?.ownerUser?.deptName
  938. const nickname = task?.assigneeUser?.nickname || task?.ownerUser?.nickname
  939. return `${nickname} ( 所属部门:${deptName} )`
  940. }
  941. /** 处理减签 */
  942. const handlerDeleteSign = async () => {
  943. formLoading.value = true
  944. try {
  945. // 1.1 校验表单
  946. if (!deleteSignFormRef.value) return
  947. await deleteSignFormRef.value.validate()
  948. // 1.2 提交减签
  949. const data = {
  950. id: deleteSignForm.deleteSignTaskId,
  951. reason: deleteSignForm.reason
  952. }
  953. await TaskApi.signDeleteTask(data)
  954. message.success('减签成功')
  955. deleteSignFormRef.value.resetFields()
  956. popOverVisible.value.deleteSign = false
  957. // 2 加载最新数据
  958. reload()
  959. } finally {
  960. formLoading.value = false
  961. }
  962. }
  963. /** 重新加载数据 */
  964. const reload = () => {
  965. emit('success')
  966. }
  967. /** 任务是否为处理中状态 */
  968. const isHandleTaskStatus = () => {
  969. let canHandle = false
  970. if (TaskApi.TaskStatusEnum.RUNNING === runningTask.value?.status) {
  971. canHandle = true
  972. }
  973. return canHandle
  974. }
  975. /** 流程状态是否为结束状态 */
  976. const isEndProcessStatus = (status: number) => {
  977. let isEndStatus = false
  978. if (
  979. BpmProcessInstanceStatus.APPROVE === status ||
  980. BpmProcessInstanceStatus.REJECT === status ||
  981. BpmProcessInstanceStatus.CANCEL === status
  982. ) {
  983. isEndStatus = true
  984. }
  985. return isEndStatus
  986. }
  987. /** 是否显示按钮 */
  988. const isShowButton = (btnType: OperationButtonType): boolean => {
  989. let isShow = true
  990. if (runningTask.value?.buttonsSetting && runningTask.value?.buttonsSetting[btnType]) {
  991. isShow = runningTask.value.buttonsSetting[btnType].enable
  992. }
  993. return isShow
  994. }
  995. /** 获取按钮的显示名称 */
  996. const getButtonDisplayName = (btnType: OperationButtonType) => {
  997. let displayName = OPERATION_BUTTON_NAME.get(btnType)
  998. if (runningTask.value?.buttonsSetting && runningTask.value?.buttonsSetting[btnType]) {
  999. displayName = runningTask.value.buttonsSetting[btnType].displayName
  1000. }
  1001. return displayName
  1002. }
  1003. const loadTodoTask = (task: any) => {
  1004. approveForm.value = {}
  1005. runningTask.value = task
  1006. approveFormFApi.value = {}
  1007. reasonRequire.value = task?.reasonRequire ?? false
  1008. nodeTypeName.value = task?.nodeType === NodeType.TRANSACTOR_NODE ? '办理' : '审批'
  1009. // 处理 approve 表单.
  1010. if (task && task.formId && task.formConf) {
  1011. const tempApproveForm = {}
  1012. setConfAndFields2(tempApproveForm, task.formConf, task.formFields, task.formVariables)
  1013. approveForm.value = tempApproveForm
  1014. } else {
  1015. approveForm.value = {} // 占位,避免为空
  1016. }
  1017. }
  1018. /** 校验流程表单 */
  1019. const validateNormalForm = async () => {
  1020. if (props.processDefinition?.formType === BpmModelFormType.NORMAL) {
  1021. let valid = true
  1022. try {
  1023. await props.normalFormApi?.validate()
  1024. } catch {
  1025. valid = false
  1026. }
  1027. return valid
  1028. } else {
  1029. return true
  1030. }
  1031. }
  1032. /**
  1033. * TODO @小北 TO @芋道
  1034. * 问题:这里存在一种场景会出现问题,流程发起后,A节点审批完成,B节点没有可编辑的流程字段且B节点为自选审批人节点,会导致流程审批人为空,
  1035. * 原因:因为没有可编辑的流程字段时props.writableFields为空,参数variables传递时也为空
  1036. */
  1037. /** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
  1038. const getUpdatedProcessInstanceVariables = () => {
  1039. const variables = {}
  1040. props.writableFields.forEach((field) => {
  1041. variables[field] = props.normalFormApi.getValue(field)
  1042. })
  1043. return variables
  1044. }
  1045. /** 处理签名完成 */
  1046. const handleSignFinish = (url: string) => {
  1047. approveReasonForm.signPicUrl = url
  1048. approveSignFormRef.value.validate('change')
  1049. }
  1050. defineExpose({ loadTodoTask })
  1051. </script>
  1052. <style lang="scss" scoped>
  1053. :deep(.el-affix--fixed) {
  1054. background-color: var(--el-bg-color);
  1055. }
  1056. .btn-container {
  1057. > div {
  1058. display: flex;
  1059. margin: 0 8px;
  1060. cursor: pointer;
  1061. align-items: center;
  1062. &:hover {
  1063. color: #6db5ff;
  1064. }
  1065. }
  1066. }
  1067. </style>