ProcessInstanceOperationButton.vue 37 KB

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