ipms-sjy-ui/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue

317 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 审批详情的右侧审批流 -->
<template>
<el-timeline class="pt-20px">
<!-- 遍历每个审批节点 -->
<el-timeline-item
v-for="(activity, index) in approveNodes"
:key="index"
size="large"
:icon="getApprovalNodeIcon(activity.status, activity.nodeType)"
:color="getApprovalNodeColor(activity.status)"
>
<template #dot>
<div
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"
>
<img class="w-full h-full" :src="getApprovalNodeImg(activity.nodeType)" alt="" />
<div
class="position-absolute top-17px left-17px bg-#fff rounded-full flex items-center p-2px"
>
<el-icon :size="12" :color="getApprovalNodeColor(activity.status)">
<component :is="getApprovalNodeIcon(activity.status, activity.nodeType)" />
</el-icon>
</div>
</div>
</template>
<div class="flex flex-col items-start">
<!-- 第一行节点名称时间 -->
<div class="flex w-full">
<div class="font-bold"> {{ activity.name }}</div>
<!-- 信息时间 -->
<div
v-if="activity.status !== TaskStatusEnum.NOT_START"
class="text-#a5a5a5 text-13px mt-1 ml-auto"
>
{{ getApprovalNodeTime(activity) }}
</div>
</div>
<div class="flex items-center flex-wrap mt-1">
<!-- 情况一遍历每个审批节点下的进行中task 任务 -->
<div v-for="(task, idx) in activity.tasks" :key="idx" class="flex items-center">
<div class="flex flex-col pr-2">
<div class="position-relative" v-if="task.assigneeUser || task.ownerUser">
<!-- 信息头像 -->
<el-avatar
v-if="task.assigneeUser && task.assigneeUser.avatar"
:size="36"
:src="task.assigneeUser.avatar"
/>
<el-avatar v-else-if="task.assigneeUser && task.assigneeUser.nickname">
{{ task.assigneeUser.nickname.substring(0, 1) }}
</el-avatar>
<el-avatar
v-else-if="task.ownerUser && task.ownerUser.avatar"
:src="task.ownerUser.avatar"
/>
<el-avatar v-else-if="task.ownerUser && task.ownerUser.nickname">
{{ task.ownerUser.nickname.substring(0, 1) }}
</el-avatar>
<!-- 信息任务 ICON -->
<div
class="position-absolute top-26px left-26px bg-#fff rounded-full flex items-center p-2px"
>
<Icon
:size="12"
:icon="statusIconMap2[task.status]?.icon"
:color="statusIconMap2[task.status]?.color"
/>
</div>
</div>
<div class="flex flex-col mt-1">
<!-- 信息昵称 -->
<div
v-if="task.assigneeUser && task.assigneeUser.nickname"
class="text-12px text-align-center"
>
{{ task.assigneeUser.nickname }}
</div>
<div
v-else-if="task.ownerUser && task.ownerUser.nickname"
class="text-10px text-align-center"
>
{{ task.ownerUser.nickname }}
</div>
<div
v-if="task.reason && activity.nodeType === NodeType.USER_TASK_NODE"
class="text-#a5a5a5 text-13px mt-1"
>
审批意见{{ task.reason }}
</div>
</div>
</div>
</div>
<!-- 情况二遍历每个审批节点下的候选的task 任务例如说1依次审批2未来的审批任务等 -->
<div
v-for="(user, idx1) in activity.candidateUsers"
:key="idx1"
class="flex items-center self-start"
>
<div class="flex items-center flex-col pr-2">
<div class="position-relative">
<!-- 信息头像 -->
<el-avatar :size="36" v-if="user.avatar" :src="user.avatar" />
<el-avatar v-else-if="user.nickname && user.nickname">
{{ user.nickname.substring(0, 1) }}
</el-avatar>
<!-- 信息任务 ICON -->
<div
class="position-absolute top-26px left-26px bg-#fff rounded-full flex items-center p-2px"
>
<Icon
:size="12"
:icon="statusIconMap2['-1']?.icon"
:color="statusIconMap2['-1']?.color"
/>
</div>
</div>
<div class="flex flex-col mt-1">
<!-- 信息昵称 -->
<div v-if="user.nickname" class="text-10px text-align-center">
{{ user.nickname }}
</div>
</div>
</div>
</div>
</div>
</div>
</el-timeline-item>
</el-timeline>
</template>
<script lang="ts" setup>
import { formatDate } from '@/utils/formatTime'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { TaskStatusEnum } from '@/api/bpm/task'
import { NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
import { Check, Close, Loading, Clock, Minus, Delete } from '@element-plus/icons-vue'
import starterSvg from '@/assets/svgs/bpm/starter.svg'
import auditorSvg from '@/assets/svgs/bpm/auditor.svg'
import copySvg from '@/assets/svgs/bpm/copy.svg'
import conditionSvg from '@/assets/svgs/bpm/condition.svg'
import parallelSvg from '@/assets/svgs/bpm/parallel.svg'
defineOptions({ name: 'BpmProcessInstanceTimeline' })
// defineProps<{
// approveNodes: ProcessInstanceApi.ApprovalNodeInfo[] // 审批节点信息
// }>()
const approveNodes = [
{
id: 1,
name: '发起审批',
nodeType: NodeType.START_USER_NODE,
status: TaskStatusEnum.NOT_START,
startTime: new Date('2024-10-01 10:00:00'),
endTime: null,
candidateUsers: [],
tasks: []
},
{
id: 2,
name: '经理审批',
nodeType: NodeType.USER_TASK_NODE,
status: TaskStatusEnum.RUNNING, // 审批中
startTime: new Date('2024-10-02 11:00:00'),
endTime: null,
candidateUsers: [
{
nickname: '张经理',
avatar: 'https://picsum.photos/200?r=1'
},
{
nickname: '张经理',
avatar: 'https://picsum.photos/200?r=1'
},
{
nickname: '张经理',
avatar: 'https://picsum.photos/200?r=1'
},
{
nickname: '张经理',
avatar: 'https://picsum.photos/200?r=1'
}
],
tasks: [
{
assigneeUser: {
nickname: '李经理',
avatar: 'https://picsum.photos/200?r=1'
},
ownerUser: null,
status: TaskStatusEnum.RUNNING, // 审批中
reason: '同意'
}
]
},
{
id: 3,
name: '财务审批',
nodeType: NodeType.USER_TASK_NODE,
status: TaskStatusEnum.APPROVE, // 审批通过
startTime: new Date('2024-10-03 14:00:00'),
endTime: new Date('2024-10-03 15:00:00'),
candidateUsers: [],
tasks: [
{
assigneeUser: {
nickname: '王财务',
avatar: 'https://picsum.photos/200?r=1'
},
ownerUser: null,
status: TaskStatusEnum.APPROVE, // 审批通过
reason: '审批通过'
}
]
},
{
id: 4,
name: '总经理审批',
nodeType: NodeType.USER_TASK_NODE,
status: TaskStatusEnum.NOT_START, // 未开始
startTime: null,
endTime: null,
candidateUsers: [
{
nickname: '总经理',
avatar: 'https://picsum.photos/200?r=1'
}
],
tasks: []
}
]
// 审批节点
const statusIconMap2 = {
// 未开始
'-1': { color: '#909398', icon: 'ep-clock' },
// 待审批
'0': { color: '#e5e7ec', icon: 'ep:loading' },
// 审批中
'1': { color: '#448ef7', icon: 'ep:loading' },
// 审批通过
'2': { color: '#00b32a', icon: 'ep:circle-check-filled' },
// 审批不通过
'3': { color: '#f46b6c', icon: 'fa-solid:times-circle' },
// 取消
'4': { color: '#cccccc', icon: 'ep:delete-filled' },
// 退回
'5': { color: '#f46b6c', icon: 'ep:remove-filled' },
// 委派中
'6': { color: '#448ef7', icon: 'ep:loading' },
// 审批通过中
'7': { color: '#00b32a', icon: 'ep:circle-check-filled' }
}
const statusIconMap = {
// 审批未开始
'-1': { color: '#909398', icon: Clock },
'0': { color: '#e5e7ec', icon: Clock },
// 审批中
'1': { color: '#448ef7', icon: Loading },
// 审批通过
'2': { color: '#00b32a', icon: Check },
// 审批不通过
'3': { color: '#f46b6c', icon: Close },
// 已取消
'4': { color: '#cccccc', icon: Delete },
// 退回
'5': { color: '#f46b6c', icon: Minus },
// 委派中
'6': { color: '#448ef7', icon: Loading },
// 审批通过中
'7': { color: '#00b32a', icon: Check }
}
const nodeTypeSvgMap = {
// 发起人节点
[NodeType.START_USER_NODE]: { color: '', svg: starterSvg },
// 审批人节点
[NodeType.USER_TASK_NODE]: { color: '', svg: auditorSvg },
// 抄送人节点
[NodeType.COPY_TASK_NODE]: { color: '', svg: copySvg },
// 条件分支节点
[NodeType.CONDITION_NODE]: { color: '', svg: conditionSvg },
// 并行分支节点
[NodeType.PARALLEL_BRANCH_NODE]: { color: '', svg: parallelSvg }
}
const getApprovalNodeImg = (nodeType: NodeType) => {
return nodeTypeSvgMap[nodeType]?.svg
}
const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => {
if (taskStatus == TaskStatusEnum.NOT_START) {
return statusIconMap[taskStatus]?.icon
}
if (nodeType === NodeType.START_USER_NODE || nodeType === NodeType.USER_TASK_NODE) {
return statusIconMap[taskStatus]?.icon
}
}
const getApprovalNodeColor = (taskStatus: number) => {
return statusIconMap[taskStatus]?.color
}
const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
if (node.nodeType === NodeType.START_USER_NODE && node.startTime) {
return `${formatDate(node.startTime)}`
}
if (node.endTime) {
return `${formatDate(node.endTime)}`
}
if (node.startTime) {
return `${formatDate(node.startTime)}`
}
}
</script>