提交 | 用户 | 时间
|
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> |