ProcessInstanceOperationButton.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  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. v-if="runningTask.id"
  5. >
  6. <el-popover
  7. :visible="passVisible"
  8. placement="top-end"
  9. :width="500"
  10. trigger="click"
  11. v-if="isShowButton(OperationButtonType.APPROVE)"
  12. >
  13. <template #reference>
  14. <el-button plain type="success" @click="openPopover('1')">
  15. <Icon icon="ep:select" />&nbsp; {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
  16. </el-button>
  17. </template>
  18. <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
  19. <el-form
  20. label-position="top"
  21. class="mb-auto"
  22. ref="formRef"
  23. :model="auditForm"
  24. :rules="auditRule"
  25. label-width="100px"
  26. >
  27. <el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人">
  28. {{ processInstance?.startUser.nickname }}
  29. <el-tag size="small" type="info" class="ml-8px">
  30. {{ processInstance?.startUser.deptName }}
  31. </el-tag>
  32. </el-form-item>
  33. <el-card v-if="runningTask.formId > 0" class="mb-15px !-mt-10px">
  34. <template #header>
  35. <span class="el-icon-picture-outline"> 填写表单【{{ runningTask?.formName }}】 </span>
  36. </template>
  37. <form-create
  38. v-model="approveForm.value"
  39. v-model:api="approveFormFApi"
  40. :option="approveForm.option"
  41. :rule="approveForm.rule"
  42. />
  43. </el-card>
  44. <el-form-item label="审批建议" prop="reason">
  45. <el-input v-model="auditForm.reason" placeholder="请输入审批建议" type="textarea" />
  46. </el-form-item>
  47. <el-form-item label="抄送人" prop="copyUserIds">
  48. <el-select v-model="auditForm.copyUserIds" multiple placeholder="请选择抄送人">
  49. <el-option
  50. v-for="itemx in userOptions"
  51. :key="itemx.id"
  52. :label="itemx.nickname"
  53. :value="itemx.id"
  54. />
  55. </el-select>
  56. </el-form-item>
  57. <el-form-item>
  58. <el-button :disabled="formLoading" type="success" @click="handleAudit(true)">
  59. {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
  60. </el-button>
  61. <el-button @click="passVisible = false"> 取消 </el-button>
  62. </el-form-item>
  63. </el-form>
  64. </div>
  65. </el-popover>
  66. <el-popover
  67. :visible="rejectVisible"
  68. placement="top-end"
  69. :width="500"
  70. trigger="click"
  71. v-if="isShowButton(OperationButtonType.REJECT)"
  72. >
  73. <template #reference>
  74. <el-button class="mr-20px" plain type="danger" @click="openPopover('2')">
  75. <Icon icon="ep:close" />&nbsp; {{ getButtonDisplayName(OperationButtonType.REJECT) }}
  76. </el-button>
  77. </template>
  78. <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
  79. <el-form
  80. label-position="top"
  81. class="mb-auto"
  82. ref="formRef"
  83. :model="auditForm"
  84. :rules="auditRule"
  85. label-width="100px"
  86. >
  87. <el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人">
  88. {{ processInstance?.startUser.nickname }}
  89. <el-tag size="small" type="info" class="ml-8px">
  90. {{ processInstance?.startUser.deptName }}
  91. </el-tag>
  92. </el-form-item>
  93. <el-card v-if="runningTask.formId > 0" class="mb-15px !-mt-10px">
  94. <template #header>
  95. <span class="el-icon-picture-outline"> 填写表单【{{ runningTask?.formName }}】 </span>
  96. </template>
  97. <form-create
  98. v-model="approveForm.value"
  99. v-model:api="approveFormFApi"
  100. :option="approveForm.option"
  101. :rule="approveForm.rule"
  102. />
  103. </el-card>
  104. <el-form-item label="审批建议" prop="reason">
  105. <el-input v-model="auditForm.reason" placeholder="请输入审批建议" type="textarea" />
  106. </el-form-item>
  107. <el-form-item label="抄送人" prop="copyUserIds">
  108. <el-select v-model="auditForm.copyUserIds" multiple placeholder="请选择抄送人">
  109. <el-option
  110. v-for="itemx in userOptions"
  111. :key="itemx.id"
  112. :label="itemx.nickname"
  113. :value="itemx.id"
  114. />
  115. </el-select>
  116. </el-form-item>
  117. <el-form-item>
  118. <el-button :disabled="formLoading" type="danger" @click="handleAudit(false)">
  119. {{ getButtonDisplayName(OperationButtonType.REJECT) }}
  120. </el-button>
  121. <el-button @click="rejectVisible = false"> 取消 </el-button>
  122. </el-form-item>
  123. </el-form>
  124. </div>
  125. </el-popover>
  126. <div @click="handleSend"> <Icon :size="14" icon="svg-icon:send" />&nbsp;抄送 </div>
  127. <div @click="openTaskUpdateAssigneeForm" v-if="isShowButton(OperationButtonType.TRANSFER)">
  128. <Icon :size="14" icon="fa:share-square-o" />&nbsp;{{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
  129. </div>
  130. <div @click="handleDelegate" v-if="isShowButton(OperationButtonType.DELEGATE)">
  131. <Icon :size="14" icon="ep:position" />&nbsp;{{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
  132. </div>
  133. <div @click="handleSign" v-if="isShowButton(OperationButtonType.ADD_SIGN)">
  134. <Icon :size="14" icon="ep:plus" />&nbsp;{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
  135. </div>
  136. <div @click="handleBack" v-if="isShowButton(OperationButtonType.RETURN)">
  137. <Icon :size="14" icon="fa:mail-reply" />&nbsp;{{ getButtonDisplayName(OperationButtonType.RETURN) }}
  138. </div>
  139. </div>
  140. <!-- </div> -->
  141. <!-- 弹窗:转派审批人 -->
  142. <TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
  143. <!-- 弹窗:回退节点 -->
  144. <TaskReturnForm ref="taskReturnFormRef" @success="getDetail" />
  145. <!-- 弹窗:委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
  146. <TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
  147. <!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
  148. <TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
  149. </template>
  150. <script lang="ts" setup>
  151. import { setConfAndFields2 } from '@/utils/formCreate'
  152. import { useUserStore } from '@/store/modules/user'
  153. import * as TaskApi from '@/api/bpm/task'
  154. import { propTypes } from '@/utils/propTypes'
  155. import TaskReturnForm from './dialog/TaskReturnForm.vue'
  156. import TaskDelegateForm from './dialog/TaskDelegateForm.vue'
  157. import TaskTransferForm from './dialog/TaskTransferForm.vue'
  158. import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
  159. import { isEmpty } from '@/utils/is'
  160. import {
  161. OperationButtonType,
  162. OPERATION_BUTTON_NAME
  163. } from '@/components/SimpleProcessDesignerV2/src/consts'
  164. defineOptions({ name: 'ProcessInstanceBtnConatiner' })
  165. const userId = useUserStore().getUser.id // 当前登录的编号
  166. const message = useMessage() // 消息弹窗
  167. const { proxy } = getCurrentInstance() as any
  168. const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
  169. defineProps({
  170. processInstance: propTypes.any, // 流程实例信息
  171. userOptions: propTypes.any
  172. })
  173. const formLoading = ref(false) // 表单加载中
  174. const passVisible = ref(false) // 是否显示
  175. const rejectVisible = ref(false) // 是否显示
  176. // ========== 审批信息 ==========
  177. const runningTask = ref<any>({}) // 运行中的任务
  178. const auditForm = ref<any>({}) // 审批任务的表单
  179. const approveForm = ref<any>({}) // 审批通过时,额外的补充信息
  180. const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
  181. const formRef = ref()
  182. const auditRule = reactive({
  183. reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
  184. })
  185. /** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
  186. watch(
  187. () => approveFormFApi.value,
  188. (val) => {
  189. val?.btn?.show(false)
  190. val?.resetBtn?.show(false)
  191. },
  192. {
  193. deep: true
  194. }
  195. )
  196. /**
  197. * 设置 runningTasks 中的任务
  198. */
  199. const loadRunningTask = (tasks) => {
  200. runningTask.value = {}
  201. auditForm.value = {}
  202. approveForm.value = {}
  203. approveFormFApi.value = {}
  204. tasks.forEach((task) => {
  205. if (!isEmpty(task.children)) {
  206. loadRunningTask(task.children)
  207. }
  208. // 2.1 只有待处理才需要
  209. if (task.status !== 1 && task.status !== 6) {
  210. return
  211. }
  212. // 2.2 自己不是处理人
  213. if (!task.assigneeUser || task.assigneeUser.id !== userId) {
  214. return
  215. }
  216. // 2.3 添加到处理任务
  217. runningTask.value = { ...task }
  218. auditForm.value = {
  219. reason: '',
  220. copyUserIds: []
  221. }
  222. // 2.4 处理 approve 表单
  223. if (task.formId && task.formConf) {
  224. const tempApproveForm = {}
  225. setConfAndFields2(tempApproveForm, task.formConf, task.formFields, task.formVariables)
  226. approveForm.value = tempApproveForm
  227. } else {
  228. approveForm.value = {} // 占位,避免为空
  229. }
  230. })
  231. }
  232. /** 处理审批通过和不通过的操作 */
  233. const handleAudit = async (pass) => {
  234. formLoading.value = true
  235. try {
  236. const auditFormRef = proxy.$refs['formRef']
  237. // 1.2 校验表单
  238. const elForm = unref(auditFormRef)
  239. if (!elForm) return
  240. const valid = await elForm.validate()
  241. if (!valid) return
  242. // 2.1 提交审批
  243. const data = {
  244. id: runningTask.value.id,
  245. reason: auditForm.value.reason,
  246. copyUserIds: auditForm.value.copyUserIds
  247. }
  248. if (pass) {
  249. // 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
  250. const formCreateApi = approveFormFApi.value
  251. if (Object.keys(formCreateApi)?.length > 0) {
  252. await formCreateApi.validate()
  253. // @ts-ignore
  254. data.variables = approveForm.value.value
  255. }
  256. await TaskApi.approveTask(data)
  257. message.success('审批通过成功')
  258. } else {
  259. await TaskApi.rejectTask(data)
  260. message.success('审批不通过成功')
  261. }
  262. // 2.2 加载最新数据
  263. getDetail()
  264. } finally {
  265. formLoading.value = false
  266. }
  267. }
  268. /* 抄送 TODO */
  269. const handleSend = () => {}
  270. const openPopover = (flag) => {
  271. passVisible.value = false
  272. rejectVisible.value = false
  273. formRef.value.resetFields()
  274. flag === '1' ? (passVisible.value = true) : (rejectVisible.value = true)
  275. }
  276. /** 转派审批人 */
  277. const taskTransferFormRef = ref()
  278. const openTaskUpdateAssigneeForm = () => {
  279. taskTransferFormRef.value.open(runningTask.value.id)
  280. }
  281. /** 处理审批退回的操作 */
  282. const taskDelegateForm = ref()
  283. const handleDelegate = async () => {
  284. taskDelegateForm.value.open(runningTask.value.id)
  285. }
  286. /** 处理审批退回的操作 */
  287. const taskReturnFormRef = ref()
  288. const handleBack = async () => {
  289. taskReturnFormRef.value.open(runningTask.value.id)
  290. }
  291. /** 处理审批加签的操作 */
  292. const taskSignCreateFormRef = ref()
  293. const handleSign = async () => {
  294. taskSignCreateFormRef.value.open(runningTask.value.id)
  295. }
  296. /** 获得详情 */
  297. const getDetail = () => {
  298. emit('success')
  299. }
  300. /** 是否显示按钮 */
  301. const isShowButton = (btnType: OperationButtonType): boolean => {
  302. let isShow = true
  303. if (runningTask.value.buttonsSetting && runningTask.value.buttonsSetting[btnType]) {
  304. isShow = runningTask.value.buttonsSetting[btnType].enable
  305. }
  306. return isShow
  307. }
  308. /** 获取按钮的显示名称 */
  309. const getButtonDisplayName = (btnType: OperationButtonType) => {
  310. let diaplayName = OPERATION_BUTTON_NAME.get(btnType)
  311. if (runningTask.value.buttonsSetting && runningTask.value.buttonsSetting[btnType]) {
  312. diaplayName = runningTask.value.buttonsSetting[btnType].displayName
  313. }
  314. return diaplayName
  315. }
  316. defineExpose({ loadRunningTask })
  317. </script>
  318. <style lang="scss" scoped>
  319. :deep(.el-affix--fixed) {
  320. background-color: var(--el-bg-color);
  321. }
  322. .btn-container {
  323. > div {
  324. display: flex;
  325. margin: 0 15px;
  326. cursor: pointer;
  327. align-items: center;
  328. &:hover {
  329. color: #6db5ff;
  330. }
  331. }
  332. }
  333. </style>