ProcessInstanceTimeline.vue 8.2 KB

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