123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- <!-- 审批详情的右侧:审批流 -->
- <template>
- <el-timeline class="pt-20px">
- <!-- 遍历每个审批节点 -->
- <el-timeline-item
- v-for="(activity, index) in activityNodes"
- :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
- v-if="showStatusIcon"
- class="position-absolute top-17px left-17px rounded-full flex items-center p-2px"
- :style="{ backgroundColor: getApprovalNodeColor(activity.status) }"
- >
- <el-icon :size="12" color="#fff">
- <component :is="getApprovalNodeIcon(activity.status, activity.nodeType)" />
- </el-icon>
- </div>
- </div>
- </template>
- <div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}`">
- <!-- 第一行:节点名称、时间 -->
- <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 flex-wrap gap2 items-center"
- v-if="
- isEmpty(activity.tasks) &&
- isEmpty(activity.candidateUsers) &&
- CandidateStrategy.START_USER_SELECT === activity.candidateStrategy
- "
- >
- <!-- && activity.nodeType === NodeType.USER_TASK_NODE -->
- <el-tooltip content="添加用户" placement="left">
- <el-button
- class="!px-6px"
- @click="handleSelectUser(activity.id, customApproveUsers[activity.id])"
- >
- <img class="w-18px text-#ccc" src="@/assets/svgs/bpm/add-user.svg" alt="" />
- </el-button>
- </el-tooltip>
- <div
- v-for="(user, idx1) in customApproveUsers[activity.id]"
- :key="idx1"
- class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
- >
- <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
- <el-avatar class="!m-5px" :size="28" v-else>
- {{ user.nickname.substring(0, 1) }}
- </el-avatar>
- {{ user.nickname }}
- </div>
- </div>
- <div v-else class="flex items-center flex-wrap mt-1 gap2">
- <!-- 情况一:遍历每个审批节点下的【进行中】task 任务 -->
- <div v-for="(task, idx) in activity.tasks" :key="idx" class="flex flex-col pr-2 gap2">
- <div
- class="position-relative flex flex-wrap gap2"
- v-if="task.assigneeUser || task.ownerUser"
- >
- <!-- 信息:头像昵称 -->
- <div
- class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
- >
- <template v-if="task.assigneeUser?.avatar || task.assigneeUser?.nickname">
- <el-avatar
- class="!m-5px"
- :size="28"
- v-if="task.assigneeUser?.avatar"
- :src="task.assigneeUser?.avatar"
- />
- <el-avatar class="!m-5px" :size="28" v-else>
- {{ task.assigneeUser?.nickname.substring(0, 1) }}
- </el-avatar>
- {{ task.assigneeUser?.nickname }}
- </template>
- <template v-else-if="task.ownerUser?.avatar || task.ownerUser?.nickname">
- <el-avatar
- class="!m-5px"
- :size="28"
- v-if="task.ownerUser?.avatar"
- :src="task.ownerUser?.avatar"
- />
- <el-avatar class="!m-5px" :size="28" v-else>
- {{ task.ownerUser?.nickname.substring(0, 1) }}
- </el-avatar>
- {{ task.ownerUser?.nickname }}
- </template>
- <!-- 信息:任务 ICON -->
- <div
- v-if="showStatusIcon && onlyStatusIconShow.includes(task.status)"
- class="position-absolute top-19px left-23px rounded-full flex items-center p-2px"
- :style="{ backgroundColor: statusIconMap2[task.status]?.color }"
- >
- <Icon :size="12" :icon="statusIconMap2[task.status]?.icon" color="#FFFFFF" />
- </div>
- </div>
- </div>
- <teleport defer :to="`#activity-task-${activity.id}`">
- <div
- v-if="
- task.reason &&
- [NodeType.USER_TASK_NODE, NodeType.END_EVENT_NODE].includes(activity.nodeType)
- "
- class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
- >
- 审批意见:{{ task.reason }}
- </div>
- </teleport>
- </div>
- <!-- 情况二:遍历每个审批节点下的【候选的】task 任务。例如说,1)依次审批,2)未来的审批任务等 -->
- <div
- v-for="(user, idx1) in activity.candidateUsers"
- :key="idx1"
- class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
- >
- <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
- <el-avatar class="!m-5px" :size="28" v-else>
- {{ user.nickname.substring(0, 1) }}
- </el-avatar>
- {{ user.nickname }}
- <!-- 信息:任务 ICON -->
- <div
- v-if="showStatusIcon"
- class="position-absolute top-19px left-23px rounded-full flex items-center p-2px"
- :style="{ backgroundColor: statusIconMap2['-1']?.color }"
- >
- <Icon :size="12" :icon="statusIconMap2['-1']?.icon" color="#FFFFFF" />
- </div>
- </div>
- </div>
- </div>
- </el-timeline-item>
- </el-timeline>
- <!-- 用户选择弹窗 -->
- <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
- </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, CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
- import { isEmpty } from '@/utils/is'
- 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'
- import finishSvg from '@/assets/svgs/bpm/finish.svg'
- defineOptions({ name: 'BpmProcessInstanceTimeline' })
- withDefaults(
- defineProps<{
- activityNodes: ProcessInstanceApi.ApprovalNodeInfo[] // 审批节点信息
- showStatusIcon?: boolean // 是否显示头像右下角状态图标
- }>(),
- {
- showStatusIcon: true // 默认值为 true
- }
- )
- // 审批节点
- const statusIconMap2 = {
- // 未开始
- '-1': { color: '#909398', icon: 'ep-clock' },
- // 待审批
- '0': { color: '#00b32a', 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: '#00b32a', 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.END_EVENT_NODE]: { color: '#909398', svg: finishSvg },
- // 发起人节点
- [NodeType.START_USER_NODE]: { color: '#909398', svg: starterSvg },
- // 审批人节点
- [NodeType.USER_TASK_NODE]: { color: '#ff943e', svg: auditorSvg },
- // 抄送人节点
- [NodeType.COPY_TASK_NODE]: { color: '#3296fb', svg: copySvg },
- // 条件分支节点
- [NodeType.CONDITION_NODE]: { color: '#14bb83', svg: conditionSvg },
- // 并行分支节点
- [NodeType.PARALLEL_BRANCH_NODE]: { color: '#14bb83', svg: parallelSvg }
- }
- // 只有只有状态是 -1、0、1 才展示头像右小角状态小icon
- const onlyStatusIconShow = [-1, 0, 1]
- // timeline时间线上icon图标
- 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 ||
- nodeType === NodeType.END_EVENT_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)}`
- }
- }
- // 选择自定义审批人
- const userSelectFormRef = ref()
- const handleSelectUser = (activityId, selectedList) => {
- userSelectFormRef.value.open(activityId, selectedList)
- }
- const emit = defineEmits<{
- selectUserConfirm: [id: any, userList: any[]]
- }>()
- const customApproveUsers: any = ref({}) // key:activityId,value:用户列表
- // 选择完成
- const handleUserSelectConfirm = (activityId: string, userList: any[]) => {
- customApproveUsers.value[activityId] = userList || []
- emit('selectUserConfirm', activityId, userList)
- }
- </script>
|