<script lang="ts" setup>
|
import { ref } from 'vue';
|
|
import { useGlobSetting } from '@jnpf/hooks';
|
import { useModal } from '@jnpf/ui/modal';
|
import { formatToDateTime } from '@jnpf/utils';
|
|
import { Timeline, TimelineItem } from 'ant-design-vue';
|
|
import { flowNodeList } from '#/components/FlowProcess/src/helper/componentMap';
|
import { useFlowState } from '#/hooks/flow/useFlowStatus';
|
|
import LogErrorModal from './../components/modal/LogErrorModal.vue';
|
import CirculateUserModal from './modal/CirculateUserModal.vue';
|
import RecordModal from './modal/RecordModal.vue';
|
import TaskLogModal from './modal/TaskLogModal.vue';
|
|
const props: any = defineProps({
|
list: { type: Array, default: () => [] },
|
endTime: { type: Number, default: 0 },
|
opType: { default: '' },
|
taskId: { type: [String, Number], default: '' },
|
});
|
const emit = defineEmits(['onRetry']);
|
const globSetting = useGlobSetting();
|
const apiUrl = ref(globSetting.apiURL);
|
const { getFlowStateContent, getFlowStateColor, getHexColor } = useFlowState();
|
const [registerRecord, { openModal: openRecordModal }] = useModal();
|
const [registerTaskLog, { openModal: openTaskLogModal }] = useModal();
|
const [registerCirculateUserModal, { openModal: openCirculateUserModal }] = useModal();
|
const [registerLogErrorModal, { openModal: openLogErrorModal }] = useModal();
|
function getNodeStatusColor(status) {
|
return status == 1 || status == 2 ? 'success' : status == 3 ? 'error' : 'blue';
|
}
|
function getTimeLineTagColor(status) {
|
return status == 1 || status == 2 ? '#08AF28' : status == 3 ? '#ed6f6f' : '#0177FF';
|
}
|
function getNodeStatusContent(status) {
|
const list = ['', '已提交', '已通过', '已拒绝', '审批中', '已退回', '已撤回', '等待中', '办理中'];
|
return list[status] || '';
|
}
|
function getCounterSignContent(counterSign, assigneeType) {
|
if (assigneeType == 10) return '逐级审批';
|
return counterSign == 0 ? '或签' : counterSign == 1 ? '会签' : '依次审批';
|
}
|
function getOutsideState(state: boolean) {
|
return state ? '成功' : '失败';
|
}
|
function getNodeIcon(nodeType) {
|
const list = flowNodeList.find((o) => o.option.wnType == nodeType);
|
return list?.icon || 'icon-ym icon-ym-flow-node-start';
|
}
|
function handleShowRecordModal(item) {
|
const title = `${item.nodeName}(${getCounterSignContent(item.counterSign, item.assigneeType)})`;
|
openRecordModal(true, { taskId: props.taskId, nodeId: item.nodeId, title });
|
}
|
function handleShowTaskLogModal(item) {
|
openTaskLogModal(true, { taskId: props.taskId, nodeId: item.nodeId });
|
}
|
function handleShowErrorModal(item) {
|
openLogErrorModal(true, { errorTip: item.errorTip, errorData: item.errorData });
|
}
|
function handleRetry(item) {
|
emit('onRetry', item.nodeId);
|
}
|
function handleShowCirculateUserModal(item) {
|
openCirculateUserModal(true, { taskId: props.taskId, nodeId: item.nodeId });
|
}
|
</script>
|
<template>
|
<Timeline class="record-time-list-container">
|
<TimelineItem v-for="item in props.list" :key="item">
|
<template #dot>
|
<span class="tag" :style="{ background: getTimeLineTagColor(item.nodeStatus) }"></span>
|
</template>
|
<span>{{ formatToDateTime(item.startTime, 'YYYY-MM-DD HH:mm') }}</span>
|
<div class="time-item-container">
|
<div class="time-node-name">
|
<i :class="getNodeIcon(item.nodeType)"></i>
|
<span class="node-name">{{ item.nodeName }}</span>
|
<a-tag :color="getNodeStatusColor(item.nodeStatus)" :bordered="false" class="node-status">{{ getNodeStatusContent(item.nodeStatus) }}</a-tag>
|
</div>
|
<div class="time-node-approver" v-if="item.approver?.length">
|
<div class="approver-container">
|
<div class="approver-item" v-for="child in item.approver.slice(0, 4)" :key="child">
|
<a-avatar :size="24" :src="apiUrl + child.headIcon" />
|
<a-tag class="node-handle-type" :color="getHexColor(getFlowStateColor(child.handleType))">{{ getFlowStateContent(child.handleType) }}</a-tag>
|
<span class="user-name">{{ child.userName }}</span>
|
</div>
|
</div>
|
<div class="approver-count" v-if="item.approverCount">{{ item.approverCount }}</div>
|
</div>
|
<div class="counter-sign" @click="handleShowRecordModal(item)" v-if="['approver', 'processing'].includes(item.nodeType)">
|
<span>{{ getCounterSignContent(item.counterSign, item.assigneeType) }}</span>
|
<i class="icon-ym icon-ym-right"></i>
|
</div>
|
<div class="counter-sign" @click="handleShowCirculateUserModal(item)" v-if="item.isCirculate">
|
<span>抄送人员</span>
|
<i class="icon-ym icon-ym-right"></i>
|
</div>
|
<div class="outside-sign" v-if="item.nodeType == 'outside'">
|
<div>数据传递{{ getOutsideState(item.outSideStatus) }}</div>
|
<div v-if="!item.outSideStatus">
|
<a-button type="link" size="small" @click="handleShowErrorModal(item)">查看异常</a-button>
|
<a-button type="link" size="small" @click="handleRetry(item)" danger v-if="item.isRetry">重试</a-button>
|
</div>
|
</div>
|
<div class="counter-sign" @click="handleShowTaskLogModal(item)" v-if="item.showTaskFlow">
|
<span>任务流程</span>
|
<i class="icon-ym icon-ym-right"></i>
|
</div>
|
</div>
|
</TimelineItem>
|
</Timeline>
|
<RecordModal @register="registerRecord" />
|
<TaskLogModal @register="registerTaskLog" />
|
<LogErrorModal @register="registerLogErrorModal" />
|
<CirculateUserModal @register="registerCirculateUserModal" />
|
</template>
|
<style lang="scss">
|
.record-time-list-container {
|
height: 100%;
|
padding: 24px 12px;
|
overflow: auto;
|
|
.tag {
|
display: block;
|
width: 10px;
|
height: 10px;
|
border-radius: 50%;
|
}
|
|
.time-item-container {
|
margin-top: 8px;
|
background-color: var(--app-content-background);
|
border-radius: 4px;
|
|
.time-node-name {
|
display: flex;
|
align-items: center;
|
height: 40px;
|
margin-left: 10px;
|
|
i {
|
margin-right: 4px;
|
font-size: 12px;
|
}
|
|
.node-name {
|
flex: 1;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.node-status {
|
padding-inline: 10px;
|
border-radius: 10px;
|
}
|
}
|
|
.time-node-approver {
|
display: flex;
|
|
.approver-container {
|
display: flex;
|
flex: 1;
|
justify-content: flex-start;
|
min-width: 0;
|
margin: 0 10px 10px;
|
|
.approver-item {
|
position: relative;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
width: 25%;
|
|
.node-handle-type {
|
z-index: 999;
|
margin: -8px auto 0;
|
}
|
|
.user-name {
|
width: 100%;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
text-align: center;
|
white-space: nowrap;
|
}
|
}
|
}
|
|
.approver-count {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
width: 24px;
|
height: 24px;
|
margin-right: 10px;
|
background-color: var(--component-background);
|
border-radius: 12px;
|
}
|
}
|
|
.counter-sign {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
height: 40px;
|
margin: 0 16px;
|
cursor: pointer;
|
border-top: 1px solid var(--border-color-base);
|
|
span {
|
height: 20px;
|
padding: 0 12px;
|
line-height: 20px;
|
text-align: center;
|
background: #e2e2e2;
|
border-radius: 4px;
|
}
|
}
|
|
.outside-sign {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
height: 40px;
|
margin: 0 16px;
|
cursor: pointer;
|
border-top: 1px solid var(--border-color-base);
|
|
span {
|
height: 20px;
|
line-height: 20px;
|
text-align: center;
|
border-radius: 4px;
|
}
|
}
|
}
|
}
|
</style>
|