ProcessInstanceOperationButton.vue 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116
  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. if (
  705. // 情况一:当前节点没有审批人,并且是发起人自选
  706. (isEmpty(node.tasks) &&
  707. isEmpty(node.candidateUsers) &&
  708. CandidateStrategy.START_USER_SELECT === node.candidateStrategy) ||
  709. // 情况二:当前节点是审批人自选
  710. CandidateStrategy.APPROVE_USER_SELECT === node.candidateStrategy
  711. ) {
  712. nextAssigneesActivityNode.value.push(node)
  713. }
  714. })
  715. }
  716. }
  717. /** 选择下一个节点的审批人 */
  718. const selectNextAssigneesConfirm = (id: string, userList: any[]) => {
  719. approveReasonForm.nextAssignees[id] = userList?.map((item: any) => item.id)
  720. }
  721. /** 审批通过时,校验每个自选审批人的节点是否都已配置了审批人 */
  722. const validateNextAssignees = () => {
  723. if (Object.keys(nextAssigneesActivityNode.value).length === 0) {
  724. return true
  725. }
  726. // 如果需要自选审批人,则校验每个节点是否都已配置审批人
  727. for (const item of nextAssigneesActivityNode.value) {
  728. if (isEmpty(approveReasonForm.nextAssignees[item.id])) {
  729. message.warning('下一个节点的审批人不能为空!')
  730. return false
  731. }
  732. }
  733. return true
  734. }
  735. /** 处理审批通过和不通过的操作 */
  736. const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) => {
  737. formLoading.value = true
  738. try {
  739. // 校验表单
  740. if (!formRef) return
  741. await formRef.validate()
  742. // 校验流程表单必填字段
  743. const valid = await validateNormalForm()
  744. if (!valid) {
  745. message.warning('表单校验不通过,请先完善表单!!')
  746. return
  747. }
  748. if (pass) {
  749. const nextAssigneesValid = validateNextAssignees()
  750. if (!nextAssigneesValid) return
  751. const variables = getUpdatedProcessInstanceVariables()
  752. // 审批通过数据
  753. const data = {
  754. id: runningTask.value.id,
  755. reason: approveReasonForm.reason,
  756. variables, // 审批通过, 把修改的字段值赋于流程实例变量
  757. nextAssignees: approveReasonForm.nextAssignees // 下个自选节点选择的审批人信息
  758. } as any
  759. // 签名
  760. if (runningTask.value.signEnable) {
  761. data.signPicUrl = approveReasonForm.signPicUrl
  762. }
  763. // 多表单处理,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
  764. // TODO 芋艿 任务有多表单这里要如何处理,会和可编辑的字段冲突
  765. const formCreateApi = approveFormFApi.value
  766. if (Object.keys(formCreateApi)?.length > 0) {
  767. await formCreateApi.validate()
  768. // @ts-ignore
  769. data.variables = approveForm.value.value
  770. }
  771. await TaskApi.approveTask(data)
  772. popOverVisible.value.approve = false
  773. nextAssigneesActivityNode.value = []
  774. message.success('审批通过成功')
  775. } else {
  776. // 审批不通过数据
  777. const data = {
  778. id: runningTask.value.id,
  779. reason: rejectReasonForm.reason
  780. }
  781. await TaskApi.rejectTask(data)
  782. popOverVisible.value.reject = false
  783. message.success('审批不通过成功')
  784. }
  785. // 重置表单
  786. formRef.resetFields()
  787. // 加载最新数据
  788. reload()
  789. } finally {
  790. formLoading.value = false
  791. }
  792. }
  793. /** 处理抄送 */
  794. const handleCopy = async () => {
  795. formLoading.value = true
  796. try {
  797. // 1. 校验表单
  798. if (!copyFormRef.value) return
  799. await copyFormRef.value.validate()
  800. // 2. 提交抄送
  801. const data = {
  802. id: runningTask.value.id,
  803. reason: copyForm.copyReason,
  804. copyUserIds: copyForm.copyUserIds
  805. }
  806. await TaskApi.copyTask(data)
  807. copyFormRef.value.resetFields()
  808. popOverVisible.value.copy = false
  809. message.success('操作成功')
  810. } finally {
  811. formLoading.value = false
  812. }
  813. }
  814. /** 处理转交 */
  815. const handleTransfer = async () => {
  816. formLoading.value = true
  817. try {
  818. // 1.1 校验表单
  819. if (!transferFormRef.value) return
  820. await transferFormRef.value.validate()
  821. // 1.2 提交转交
  822. const data = {
  823. id: runningTask.value.id,
  824. reason: transferForm.reason,
  825. assigneeUserId: transferForm.assigneeUserId
  826. }
  827. await TaskApi.transferTask(data)
  828. transferFormRef.value.resetFields()
  829. popOverVisible.value.transfer = false
  830. message.success('操作成功')
  831. // 2. 加载最新数据
  832. reload()
  833. } finally {
  834. formLoading.value = false
  835. }
  836. }
  837. /** 处理委派 */
  838. const handleDelegate = async () => {
  839. formLoading.value = true
  840. try {
  841. // 1.1 校验表单
  842. if (!delegateFormRef.value) return
  843. await delegateFormRef.value.validate()
  844. // 1.2 处理委派
  845. const data = {
  846. id: runningTask.value.id,
  847. reason: delegateForm.reason,
  848. delegateUserId: delegateForm.delegateUserId
  849. }
  850. await TaskApi.delegateTask(data)
  851. popOverVisible.value.delegate = false
  852. delegateFormRef.value.resetFields()
  853. message.success('操作成功')
  854. // 2. 加载最新数据
  855. reload()
  856. } finally {
  857. formLoading.value = false
  858. }
  859. }
  860. /** 处理加签 */
  861. const handlerAddSign = async (type: string) => {
  862. formLoading.value = true
  863. try {
  864. // 1.1 校验表单
  865. if (!addSignFormRef.value) return
  866. await addSignFormRef.value.validate()
  867. // 1.2 提交加签
  868. const data = {
  869. id: runningTask.value.id,
  870. type,
  871. reason: addSignForm.reason,
  872. userIds: addSignForm.addSignUserIds
  873. }
  874. await TaskApi.signCreateTask(data)
  875. message.success('操作成功')
  876. addSignFormRef.value.resetFields()
  877. popOverVisible.value.addSign = false
  878. // 2 加载最新数据
  879. reload()
  880. } finally {
  881. formLoading.value = false
  882. }
  883. }
  884. /** 处理退回 */
  885. const handleReturn = async () => {
  886. formLoading.value = true
  887. try {
  888. // 1.1 校验表单
  889. if (!returnFormRef.value) return
  890. await returnFormRef.value.validate()
  891. // 1.2 提交退回
  892. const data = {
  893. id: runningTask.value.id,
  894. reason: returnForm.returnReason,
  895. targetTaskDefinitionKey: returnForm.targetTaskDefinitionKey
  896. }
  897. await TaskApi.returnTask(data)
  898. popOverVisible.value.return = false
  899. returnFormRef.value.resetFields()
  900. message.success('操作成功')
  901. // 2 重新加载数据
  902. reload()
  903. } finally {
  904. formLoading.value = false
  905. }
  906. }
  907. /** 处理取消 */
  908. const handleCancel = async () => {
  909. formLoading.value = true
  910. try {
  911. // 1.1 校验表单
  912. if (!cancelFormRef.value) return
  913. await cancelFormRef.value.validate()
  914. // 1.2 提交取消
  915. await ProcessInstanceApi.cancelProcessInstanceByStartUser(
  916. props.processInstance.id,
  917. cancelForm.cancelReason
  918. )
  919. popOverVisible.value.return = false
  920. message.success('操作成功')
  921. cancelFormRef.value.resetFields()
  922. // 2 重新加载数据
  923. reload()
  924. } finally {
  925. formLoading.value = false
  926. }
  927. }
  928. /** 处理再次提交 */
  929. const handleReCreate = async () => {
  930. // 跳转发起流程界面
  931. await router.push({
  932. name: 'BpmProcessInstanceCreate',
  933. query: { processInstanceId: props.processInstance?.id }
  934. })
  935. }
  936. /** 获取减签人员标签 */
  937. const getDeleteSignUserLabel = (task: any): string => {
  938. const deptName = task?.assigneeUser?.deptName || task?.ownerUser?.deptName
  939. const nickname = task?.assigneeUser?.nickname || task?.ownerUser?.nickname
  940. return `${nickname} ( 所属部门:${deptName} )`
  941. }
  942. /** 处理减签 */
  943. const handlerDeleteSign = async () => {
  944. formLoading.value = true
  945. try {
  946. // 1.1 校验表单
  947. if (!deleteSignFormRef.value) return
  948. await deleteSignFormRef.value.validate()
  949. // 1.2 提交减签
  950. const data = {
  951. id: deleteSignForm.deleteSignTaskId,
  952. reason: deleteSignForm.reason
  953. }
  954. await TaskApi.signDeleteTask(data)
  955. message.success('减签成功')
  956. deleteSignFormRef.value.resetFields()
  957. popOverVisible.value.deleteSign = false
  958. // 2 加载最新数据
  959. reload()
  960. } finally {
  961. formLoading.value = false
  962. }
  963. }
  964. /** 重新加载数据 */
  965. const reload = () => {
  966. emit('success')
  967. }
  968. /** 任务是否为处理中状态 */
  969. const isHandleTaskStatus = () => {
  970. let canHandle = false
  971. if (TaskApi.TaskStatusEnum.RUNNING === runningTask.value?.status) {
  972. canHandle = true
  973. }
  974. return canHandle
  975. }
  976. /** 流程状态是否为结束状态 */
  977. const isEndProcessStatus = (status: number) => {
  978. let isEndStatus = false
  979. if (
  980. BpmProcessInstanceStatus.APPROVE === status ||
  981. BpmProcessInstanceStatus.REJECT === status ||
  982. BpmProcessInstanceStatus.CANCEL === status
  983. ) {
  984. isEndStatus = true
  985. }
  986. return isEndStatus
  987. }
  988. /** 是否显示按钮 */
  989. const isShowButton = (btnType: OperationButtonType): boolean => {
  990. let isShow = true
  991. if (runningTask.value?.buttonsSetting && runningTask.value?.buttonsSetting[btnType]) {
  992. isShow = runningTask.value.buttonsSetting[btnType].enable
  993. }
  994. return isShow
  995. }
  996. /** 获取按钮的显示名称 */
  997. const getButtonDisplayName = (btnType: OperationButtonType) => {
  998. let displayName = OPERATION_BUTTON_NAME.get(btnType)
  999. if (runningTask.value?.buttonsSetting && runningTask.value?.buttonsSetting[btnType]) {
  1000. displayName = runningTask.value.buttonsSetting[btnType].displayName
  1001. }
  1002. return displayName
  1003. }
  1004. const loadTodoTask = (task: any) => {
  1005. approveForm.value = {}
  1006. runningTask.value = task
  1007. approveFormFApi.value = {}
  1008. reasonRequire.value = task?.reasonRequire ?? false
  1009. nodeTypeName.value = task?.nodeType === NodeType.TRANSACTOR_NODE ? '办理' : '审批'
  1010. // 处理 approve 表单.
  1011. if (task && task.formId && task.formConf) {
  1012. const tempApproveForm = {}
  1013. setConfAndFields2(tempApproveForm, task.formConf, task.formFields, task.formVariables)
  1014. approveForm.value = tempApproveForm
  1015. } else {
  1016. approveForm.value = {} // 占位,避免为空
  1017. }
  1018. }
  1019. /** 校验流程表单 */
  1020. const validateNormalForm = async () => {
  1021. if (props.processDefinition?.formType === BpmModelFormType.NORMAL) {
  1022. let valid = true
  1023. try {
  1024. await props.normalFormApi?.validate()
  1025. } catch {
  1026. valid = false
  1027. }
  1028. return valid
  1029. } else {
  1030. return true
  1031. }
  1032. }
  1033. /** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
  1034. const getUpdatedProcessInstanceVariables = () => {
  1035. const variables = {}
  1036. props.writableFields.forEach((field) => {
  1037. variables[field] = props.normalFormApi.getValue(field)
  1038. })
  1039. return variables
  1040. }
  1041. /** 处理签名完成 */
  1042. const handleSignFinish = (url: string) => {
  1043. approveReasonForm.signPicUrl = url
  1044. approveSignFormRef.value.validate('change')
  1045. }
  1046. defineExpose({ loadTodoTask })
  1047. </script>
  1048. <style lang="scss" scoped>
  1049. :deep(.el-affix--fixed) {
  1050. background-color: var(--el-bg-color);
  1051. }
  1052. .btn-container {
  1053. > div {
  1054. display: flex;
  1055. margin: 0 8px;
  1056. cursor: pointer;
  1057. align-items: center;
  1058. &:hover {
  1059. color: #6db5ff;
  1060. }
  1061. }
  1062. }
  1063. </style>