ProcessInstanceTimeline.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. <!-- 审批详情的右侧:审批流 -->
  2. <template>
  3. <el-timeline class="pt-20px">
  4. <!-- 遍历每个审批节点 -->
  5. <el-timeline-item
  6. v-for="(activity, index) in approveNodes"
  7. :key="index"
  8. size="large"
  9. :icon="getApprovalNodeIcon(activity.status, activity.nodeType)"
  10. :color="getApprovalNodeColor(activity.status)"
  11. >
  12. <div class="flex flex-col items-start">
  13. <div class="font-bold"> {{ activity.name }}</div>
  14. <div class="flex items-center flex-wrap mt-1 ">
  15. <!-- 情况一:遍历每个审批节点下的【进行中】task 任务 -->
  16. <div v-for="(task, idx) in activity.tasks" :key="idx" class="flex items-center">
  17. <div class="flex flex-col pr-2">
  18. <div class="position-relative" v-if="task.assigneeUser || task.ownerUser">
  19. <!-- 信息:头像 -->
  20. <el-tooltip :content="task.reason" placement="bottom" v-if="task.assigneeUser && task.assigneeUser.avatar" effect="light">
  21. <el-avatar
  22. :size="36"
  23. :src="task.assigneeUser.avatar"
  24. />
  25. </el-tooltip>
  26. <el-tooltip :content="task.reason" placement="bottom" v-else-if="task.assigneeUser && task.assigneeUser.nickname" effect="light">
  27. <el-avatar >
  28. {{ task.assigneeUser.nickname.substring(0, 1) }}
  29. </el-avatar>
  30. </el-tooltip>
  31. <el-tooltip :content="task.reason" placement="bottom" v-else-if="task.ownerUser && task.ownerUser.avatar" effect="light">
  32. <el-avatar :src="task.ownerUser.avatar"/>
  33. </el-tooltip>
  34. <el-tooltip :content="task.reason" placement="bottom" v-else-if="task.ownerUser && task.ownerUser.nickname" effect="light">
  35. <el-avatar >
  36. {{ task.ownerUser.nickname.substring(0, 1) }}
  37. </el-avatar>
  38. </el-tooltip>
  39. <!-- 信息:任务 ICON -->
  40. <div
  41. class="position-absolute top-26px left-26px bg-#fff rounded-full flex items-center p-2px"
  42. >
  43. <Icon
  44. :size="12"
  45. :icon="statusIconMap2[task.status]?.icon"
  46. :color="statusIconMap2[task.status]?.color"
  47. />
  48. </div>
  49. </div>
  50. <div class="flex flex-col mt-1">
  51. <!-- 信息:昵称 -->
  52. <div
  53. v-if="task.assigneeUser && task.assigneeUser.nickname"
  54. class="text-12px text-align-center"
  55. >
  56. {{ task.assigneeUser.nickname }}
  57. </div>
  58. <div
  59. v-else-if="task.ownerUser && task.ownerUser.nickname"
  60. class="text-10px text-align-center"
  61. >
  62. {{ task.ownerUser.nickname }}
  63. </div>
  64. <!--
  65. <div v-if="task.reason" class="text-#a5a5a5 my-4px text-12px flex items-center w-100%">
  66. <div
  67. :title="task.reason"
  68. class="text-truncate w-200px border-1px border-#a5a5a5 border-dashed rounded py-5px px-15px text-#2d2d2d"
  69. >
  70. {{ task.reason }}
  71. </div>
  72. </div>
  73. -->
  74. </div>
  75. </div>
  76. </div>
  77. <!-- 情况二:遍历每个审批节点下的【候选的】task 任务。例如说,1)依次审批,2)未来的审批任务等 -->
  78. <div
  79. v-for="(user, idx1) in activity.candidateUsers"
  80. :key="idx1"
  81. class="flex items-center"
  82. >
  83. <div class="flex items-center flex-col pr-2">
  84. <div class="position-relative">
  85. <!-- 信息:头像 -->
  86. <el-avatar :size="36" v-if="user.avatar" :src="user.avatar" />
  87. <el-avatar v-else-if="user.nickname && user.nickname">
  88. {{ user.nickname.substring(0, 1) }}
  89. </el-avatar>
  90. <!-- 信息:任务 ICON -->
  91. <div
  92. class="position-absolute top-26px left-26px bg-#fff rounded-full flex items-center p-2px"
  93. >
  94. <Icon
  95. :size="12"
  96. :icon="statusIconMap2['-1']?.icon"
  97. :color="statusIconMap2['-1']?.color"
  98. />
  99. </div>
  100. </div>
  101. <div class="flex flex-col mt-1">
  102. <!-- 信息:昵称 -->
  103. <div v-if="user.nickname" class="text-10px text-align-center">
  104. {{ user.nickname }}
  105. </div>
  106. </div>
  107. </div>
  108. </div>
  109. </div>
  110. <!-- 信息:时间 -->
  111. <div
  112. v-if="activity.status !== TaskStatusEnum.NOT_START"
  113. class="text-#a5a5a5 text-13px mt-1"
  114. >
  115. {{ getApprovalNodeTime(activity) }}
  116. </div>
  117. <!-- TODO @jason:审批意见,要展示哈。 -->
  118. <!-- <div class="color-#a1a6ae text-12px mb-10px"> {{ activity.assigneeUser.nickname }}</div>
  119. <div v-if="activity.opinion" class="text-#a5a5a5 text-12px w-100%">
  120. <div class="mb-5px">审批意见:</div>
  121. <div
  122. class="w-100% border-1px border-#a5a5a5 border-dashed rounded py-5px px-15px text-#2d2d2d"
  123. >
  124. {{ activity.opinion }}
  125. </div>
  126. </div>
  127. <div v-if="activity.createTime" class="text-#a5a5a5 text-13px">
  128. {{ formatDate(activity.createTime) }}
  129. </div> -->
  130. </div>
  131. </el-timeline-item>
  132. </el-timeline>
  133. </template>
  134. <script lang="ts" setup>
  135. import { formatDate } from '@/utils/formatTime'
  136. import * as ProcessInstanceApi from '@/api/bpm/processInstance'
  137. import { TaskStatusEnum } from '@/api/bpm/task'
  138. import { NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
  139. import { Check, Close, Loading, Clock, Minus, Delete } from '@element-plus/icons-vue'
  140. defineOptions({ name: 'BpmProcessInstanceTimeline' })
  141. defineProps<{
  142. approveNodes: ProcessInstanceApi.ApprovalNodeInfo[] // 审批节点信息
  143. }>()
  144. // 审批节点
  145. const statusIconMap2 = {
  146. // 未开始
  147. '-1': { color: '#e5e7ec', icon: 'ep-clock' },
  148. // 待审批
  149. '0': { color: '#e5e7ec', icon: 'ep:loading' },
  150. // 审批中
  151. '1': { color: '#448ef7', icon: 'ep:loading' },
  152. // 审批通过
  153. '2': { color: '#00b32a', icon: 'ep:circle-check-filled' },
  154. // 审批不通过
  155. '3': { color: '#f46b6c', icon: 'fa-solid:times-circle' },
  156. // 取消
  157. '4': { color: '#cccccc', icon: 'ep:delete-filled' },
  158. // 回退
  159. '5': { color: '#f46b6c', icon: 'ep:remove-filled' },
  160. // 委派中
  161. '6': { color: '#448ef7', icon: 'ep:loading' },
  162. // 审批通过中
  163. '7': { color: '#00b32a', icon: 'ep:circle-check-filled' }
  164. }
  165. const statusIconMap = {
  166. // 审批未开始
  167. '-1': { color: '#e5e7ec', icon: Clock },
  168. '0': { color: '#e5e7ec', icon: Clock },
  169. // 审批中
  170. '1': { color: '#448ef7', icon: Loading },
  171. // 审批通过
  172. '2': { color: '#00b32a', icon: Check },
  173. // 审批不通过
  174. '3': { color: '#f46b6c', icon: Close },
  175. // 已取消
  176. '4': { color: '#cccccc', icon: Delete },
  177. // 回退
  178. '5': { color: '#f46b6c', icon: Minus },
  179. // 委派中
  180. '6': { color: '#448ef7', icon: Loading },
  181. // 审批通过中
  182. '7': { color: '#00b32a', icon: Check }
  183. }
  184. const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => {
  185. if (taskStatus == TaskStatusEnum.NOT_START) {
  186. return statusIconMap[taskStatus]?.icon
  187. }
  188. if (nodeType === NodeType.START_USER_NODE || nodeType === NodeType.USER_TASK_NODE) {
  189. return statusIconMap[taskStatus]?.icon
  190. }
  191. }
  192. const getApprovalNodeColor = (taskStatus: number) => {
  193. return statusIconMap[taskStatus]?.color
  194. }
  195. const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
  196. if (node.endTime) {
  197. return `结束时间:${formatDate(node.endTime)}`
  198. }
  199. if (node.startTime) {
  200. return `创建时间:${formatDate(node.startTime)}`
  201. }
  202. }
  203. </script>