ProcessInstanceOperationButton.vue 39 KB

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