dengzedong
5 天以前 3357b5f0f0919f7a52cd32a6d6de0acb14e0daaf
提交 | 用户 | 时间
3e359e 1 <!-- 审批详情的右侧:审批流 -->
H 2 <template>
3   <el-timeline class="pt-20px">
4     <!-- 遍历每个审批节点 -->
5     <el-timeline-item
6       v-for="(activity, index) in activityNodes"
7       :key="index"
8       size="large"
9       :icon="getApprovalNodeIcon(activity.status, activity.nodeType)"
10       :color="getApprovalNodeColor(activity.status)"
11     >
12       <template #dot>
13         <div
14           class="position-absolute left--10px top--6px rounded-full border border-solid border-#dedede w-30px h-30px flex justify-center items-center bg-#3f73f7 p-5px"
15         >
16           <img class="w-full h-full" :src="getApprovalNodeImg(activity.nodeType)" alt="" />
17           <div
18             v-if="showStatusIcon"
19             class="position-absolute top-17px left-17px rounded-full flex items-center p-1px border-2 border-white border-solid"
20             :style="{ backgroundColor: getApprovalNodeColor(activity.status) }"
21           >
22             <el-icon :size="11" color="#fff">
23               <component :is="getApprovalNodeIcon(activity.status, activity.nodeType)" />
24             </el-icon>
25           </div>
26         </div>
27       </template>
28       <div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}`">
29         <!-- 第一行:节点名称、时间 -->
30         <div class="flex w-full">
31           <div class="font-bold"> {{ activity.name }}</div>
32           <!-- 信息:时间 -->
33           <div
34             v-if="activity.status !== TaskStatusEnum.NOT_START"
35             class="text-#a5a5a5 text-13px mt-1 ml-auto"
36           >
37             {{ getApprovalNodeTime(activity) }}
38           </div>
39         </div>
40         <!-- 需要自定义选择审批人 -->
41         <div
42           class="flex flex-wrap gap2 items-center"
43           v-if="
44             isEmpty(activity.tasks) &&
45             isEmpty(activity.candidateUsers) &&
46             CandidateStrategy.START_USER_SELECT === activity.candidateStrategy
47           "
48         >
49           <!--  && activity.nodeType === NodeType.USER_TASK_NODE -->
50
51           <el-tooltip content="添加用户" placement="left">
52             <el-button
53               class="!px-6px"
54               @click="handleSelectUser(activity.id, customApproveUsers[activity.id])"
55             >
56               <img class="w-18px text-#ccc" src="@/assets/svgs/bpm/add-user.svg" alt="" />
57             </el-button>
58           </el-tooltip>
59           <div
60             v-for="(user, idx1) in customApproveUsers[activity.id]"
61             :key="idx1"
62             class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
63           >
64             <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
65             <el-avatar class="!m-5px" :size="28" v-else>
66               {{ user.nickname.substring(0, 1) }}
67             </el-avatar>
68             {{ user.nickname }}
69           </div>
70         </div>
71         <div v-else class="flex items-center flex-wrap mt-1 gap2">
72           <!-- 情况一:遍历每个审批节点下的【进行中】task 任务 -->
73           <div v-for="(task, idx) in activity.tasks" :key="idx" class="flex flex-col pr-2 gap2">
74             <div
75               class="position-relative flex flex-wrap gap2"
76               v-if="task.assigneeUser || task.ownerUser"
77             >
78               <!-- 信息:头像昵称 -->
79               <div
80                 class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
81               >
82                 <template v-if="task.assigneeUser?.avatar || task.assigneeUser?.nickname">
83                   <el-avatar
84                     class="!m-5px"
85                     :size="28"
86                     v-if="task.assigneeUser?.avatar"
87                     :src="task.assigneeUser?.avatar"
88                   />
89                   <el-avatar class="!m-5px" :size="28" v-else>
90                     {{ task.assigneeUser?.nickname.substring(0, 1) }}
91                   </el-avatar>
92                   {{ task.assigneeUser?.nickname }}
93                 </template>
94                 <template v-else-if="task.ownerUser?.avatar || task.ownerUser?.nickname">
95                   <el-avatar
96                     class="!m-5px"
97                     :size="28"
98                     v-if="task.ownerUser?.avatar"
99                     :src="task.ownerUser?.avatar"
100                   />
101                   <el-avatar class="!m-5px" :size="28" v-else>
102                     {{ task.ownerUser?.nickname.substring(0, 1) }}
103                   </el-avatar>
104                   {{ task.ownerUser?.nickname }}
105                 </template>
106                 <!-- 信息:任务 ICON -->
107                 <div
108                   v-if="showStatusIcon && onlyStatusIconShow.includes(task.status)"
109                   class="position-absolute top-19px left-23px rounded-full flex items-center p-1px border-2 border-white border-solid"
110                   :style="{ backgroundColor: statusIconMap2[task.status]?.color }"
111                 >
112                   <Icon :size="11" :icon="statusIconMap2[task.status]?.icon" color="#FFFFFF" />
113                 </div>
114               </div>
115             </div>
116             <teleport defer :to="`#activity-task-${activity.id}`">
117               <div
118                 v-if="
119                   task.reason &&
120                   [NodeType.USER_TASK_NODE, NodeType.END_EVENT_NODE].includes(activity.nodeType)
121                 "
122                 class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
123               >
124                 审批意见:{{ task.reason }}
125               </div>
126             </teleport>
127           </div>
128           <!-- 情况二:遍历每个审批节点下的【候选的】task 任务。例如说,1)依次审批,2)未来的审批任务等 -->
129           <div
130             v-for="(user, idx1) in activity.candidateUsers"
131             :key="idx1"
132             class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
133           >
134             <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
135             <el-avatar class="!m-5px" :size="28" v-else>
136               {{ user.nickname.substring(0, 1) }}
137             </el-avatar>
138             {{ user.nickname }}
139
140             <!-- 信息:任务 ICON -->
141             <div
142               v-if="showStatusIcon"
143               class="position-absolute top-20px left-24px rounded-full flex items-center p-1px border-2 border-white border-solid"
144               :style="{ backgroundColor: statusIconMap2['-1']?.color }"
145             >
146               <Icon :size="11" :icon="statusIconMap2['-1']?.icon" color="#FFFFFF" />
147             </div>
148           </div>
149         </div>
150       </div>
151     </el-timeline-item>
152   </el-timeline>
153
154   <!-- 用户选择弹窗 -->
155   <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
156 </template>
157
158 <script lang="ts" setup>
159 import { formatDate } from '@/utils/formatTime'
160 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
161 import { TaskStatusEnum } from '@/api/bpm/task'
162 import { NodeType, CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
163 import { isEmpty } from '@/utils/is'
164 import { Check, Close, Loading, Clock, Minus, Delete } from '@element-plus/icons-vue'
165 import starterSvg from '@/assets/svgs/bpm/starter.svg'
166 import auditorSvg from '@/assets/svgs/bpm/auditor.svg'
167 import copySvg from '@/assets/svgs/bpm/copy.svg'
168 import conditionSvg from '@/assets/svgs/bpm/condition.svg'
169 import parallelSvg from '@/assets/svgs/bpm/parallel.svg'
170 import finishSvg from '@/assets/svgs/bpm/finish.svg'
171
172 defineOptions({ name: 'BpmProcessInstanceTimeline' })
173 withDefaults(
174   defineProps<{
175     activityNodes: ProcessInstanceApi.ApprovalNodeInfo[] // 审批节点信息
176     showStatusIcon?: boolean // 是否显示头像右下角状态图标
177   }>(),
178   {
179     showStatusIcon: true // 默认值为 true
180   }
181 )
182
183 // 审批节点
184 const statusIconMap2 = {
185   // 未开始
186   '-1': { color: '#909398', icon: 'ep-clock' },
187   // 待审批
188   '0': { color: '#00b32a', icon: 'ep:loading' },
189   // 审批中
190   '1': { color: '#448ef7', icon: 'ep:loading' },
191   // 审批通过
192   '2': { color: '#00b32a', icon: 'ep:circle-check-filled' },
193   // 审批不通过
194   '3': { color: '#f46b6c', icon: 'fa-solid:times-circle' },
195   // 取消
196   '4': { color: '#cccccc', icon: 'ep:delete-filled' },
197   // 退回
198   '5': { color: '#f46b6c', icon: 'ep:remove-filled' },
199   // 委派中
200   '6': { color: '#448ef7', icon: 'ep:loading' },
201   // 审批通过中
202   '7': { color: '#00b32a', icon: 'ep:circle-check-filled' }
203 }
204
205 const statusIconMap = {
206   // 审批未开始
207   '-1': { color: '#909398', icon: Clock },
208   '0': { color: '#00b32a', icon: Clock },
209   // 审批中
210   '1': { color: '#448ef7', icon: Loading },
211   // 审批通过
212   '2': { color: '#00b32a', icon: Check },
213   // 审批不通过
214   '3': { color: '#f46b6c', icon: Close },
215   // 已取消
216   '4': { color: '#cccccc', icon: Delete },
217   // 退回
218   '5': { color: '#f46b6c', icon: Minus },
219   // 委派中
220   '6': { color: '#448ef7', icon: Loading },
221   // 审批通过中
222   '7': { color: '#00b32a', icon: Check }
223 }
224
225 const nodeTypeSvgMap = {
226   // 结束节点
227   [NodeType.END_EVENT_NODE]: { color: '#909398', svg: finishSvg },
228   // 发起人节点
229   [NodeType.START_USER_NODE]: { color: '#909398', svg: starterSvg },
230   // 审批人节点
231   [NodeType.USER_TASK_NODE]: { color: '#ff943e', svg: auditorSvg },
232   // 抄送人节点
233   [NodeType.COPY_TASK_NODE]: { color: '#3296fb', svg: copySvg },
234   // 条件分支节点
235   [NodeType.CONDITION_NODE]: { color: '#14bb83', svg: conditionSvg },
236   // 并行分支节点
237   [NodeType.PARALLEL_BRANCH_NODE]: { color: '#14bb83', svg: parallelSvg }
238 }
239
240 // 只有只有状态是 -1、0、1 才展示头像右小角状态小icon
241 const onlyStatusIconShow = [-1, 0, 1]
242
243 // timeline时间线上icon图标
244 const getApprovalNodeImg = (nodeType: NodeType) => {
245   return nodeTypeSvgMap[nodeType]?.svg
246 }
247
248 const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => {
249   if (taskStatus == TaskStatusEnum.NOT_START) {
250     return statusIconMap[taskStatus]?.icon
251   }
252
253   if (
254     nodeType === NodeType.START_USER_NODE ||
255     nodeType === NodeType.USER_TASK_NODE ||
256     nodeType === NodeType.END_EVENT_NODE
257   ) {
258     return statusIconMap[taskStatus]?.icon
259   }
260 }
261
262 const getApprovalNodeColor = (taskStatus: number) => {
263   return statusIconMap[taskStatus]?.color
264 }
265
266 const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
267   if (node.nodeType === NodeType.START_USER_NODE && node.startTime) {
268     return `${formatDate(node.startTime)}`
269   }
270   if (node.endTime) {
271     return `${formatDate(node.endTime)}`
272   }
273   if (node.startTime) {
274     return `${formatDate(node.startTime)}`
275   }
276 }
277
278 // 选择自定义审批人
279 const userSelectFormRef = ref()
280 const handleSelectUser = (activityId, selectedList) => {
281   userSelectFormRef.value.open(activityId, selectedList)
282 }
283 const emit = defineEmits<{
284   selectUserConfirm: [id: any, userList: any[]]
285 }>()
286 const customApproveUsers: any = ref({}) // key:activityId,value:用户列表
287 // 选择完成
288 const handleUserSelectConfirm = (activityId: string, userList: any[]) => {
289   customApproveUsers.value[activityId] = userList || []
290   emit('selectUserConfirm', activityId, userList)
291 }
292 </script>