1.上图

2.代码
<div class="content">
<div class="box start"></div>
<div class="task-box">
<template v-for="(item, index) in data" :key="index">
<div
:class="[
'col',
{ center: item.length == 1 || item.length < colMaxLen,
'col-green': item.find(i=>i.status == 1), 'col-red': item.every(i=>i.status == 2),
'col1': index == 0 },
]"
>
<template v-for="(item2, index2) in item" :key="index2">
<div
class="item"
:class="{
audit: index == 0,
approval: index == 1,
green: item2.status == 1,
red: item2.status == 2,
}"
>
<div class="item-c task-name">{{ item2.taskName }}</div>
<div class="item-c user">
<h4>张三</h4>
<div>{{ statusMap[item2.status] }}</div>
</div>
<span class="status">{{ statusMap[item2.status] }}</span>
<span class="time">2022-07-26 11:09:27</span>
<template v-if="!((item.length - 1) / 2 == index2)">
<span
:class="[
'left-line',
{
'is-half': isHalf(item, index2),
'right-line_top': index2 < (item.length - 1) / 2,
'right-line_bot': index2 > (item.length - 1) / 2,
},
]"
></span>
<span
:class="[
'right-line',
{
'is-half': isHalf(item, index2),
'must-green': mustGreen(item, index2),
'right-line_top': index2 < (item.length - 1) / 2,
'right-line_bot': index2 > (item.length - 1) / 2,
},
]"
></span>
</template>
</div>
</template>
</div>
<div
class="mid-line"
:class="{'mid-green': item.find(i=>i.status == 1), 'mid-red': item.every(i=>i.status == 2)}"
v-if="item.length > 1 && data[index + 1] && data[index + 1].length > 1"
></div>
</template>
</div>
<div class="box end" :class="[getEndStatus(data)]"></div>
</div>
</template>
<script setup lang="ts">
// @ts-nocheck
import { ref, computed } from "vue";
interface IProps {
statusMap?: {[key:string]: any};
data?: any[];
}
const props = withDefaults(defineProps<IProps>(),{
statusMap: ()=>({
0: "待处理",
1: "同意",
2: "不同意",
}),
data: ()=>[]
});
const firstGreenIndex = (arr: any[]) => {
return arr.findIndex((i) => i.status == 1);
};
const lastGreenIndex = (arr: any[]) => {
return arr.findLastIndex((i) => i.status == 1);
};
//是否是偶数 切是中间两个
const isHalf = (arr: any[], index: number) => {
if (arr.length % 2 !== 0) return false;
let index1 = arr.length / 2;
let index2 = index1 - 1;
return index == index1 || index == index2;
};
const mustGreen = (arr: any[], index: number) => {
const fI = firstGreenIndex(arr);
const lI = lastGreenIndex(arr);
let result1 = fI != -1 && index >= fI && index < (arr.length - 1) / 2;
let result2 = lI != -1 && index <= lI && index > (arr.length - 1) / 2;
if (result1 || result2) {
return "must-green";
} else {
return "";
}
};
const getEndStatus = (arr: any)=>{
if(!arr.length) return '';
let lastArr = arr[arr.length-1];
let hasGreen = lastArr.find((i:any)=>i.status == 1);
if(hasGreen) {
return 'green';
}
let allRed = lastArr.every((i:any)=>i.status == 2);
if(allRed) {
return 'red';
}
}
const colMaxLen = computed(()=>{
let max = 0;
props.data.forEach((i:any) => {
if(i.length > max) {
max = i.length;
}
});
return max;
})
</script>
<style lang="scss" scoped>
* {
margin: 0;
padding: 0;
}
$itemLen: 50px;
$rightLineLen: 100px;
$leftLineLen: 40px;
$startEndLineLen: 30px;
.content {
display: flex;
align-items: center;
min-height: 400px;
margin-left: 50px;
}
.task-box {
display: flex;
.mid-line {
width: 20px;
position: relative;
&::before {
content: "";
display: block;
width: 100%;
height: 1px;
position: absolute;
left: 0;
top: 50%;
background: #1177e5;
}
&.mid-green {
&::before {
background: #03d94f;
}
& + .col {
.left-line {
background: #03d94f;
}
.item::before {
background: #03d94f;
}
}
}
&.mid-red {
&::before {
background: red;
}
& + .col {
.col-left {
&::before {
background: red;
}
&::after {
background: red;
}
}
.item::before {
background: red;
}
}
}
}
.col {
padding: 0 $rightLineLen 0 $leftLineLen;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
&.center {
justify-content: center;
}
&.col1 {
.left-line,.item::before {
background: #03d94f;
}
}
}
.col-green + .col {
.left-line {
background: #03d94f;
}
.item::before {
background: #03d94f;
}
}
.col-red + .col {
.left-line {
background: red;
}
.item::before {
background: red;
}
}
}
.audit {
background: #03d94f;
}
.approval {
background: #1177e5;
}
.item {
width: calc($itemLen * 2);
background: #03d94f;
height: $itemLen;
position: relative;
display: flex;
border: 1px solid #1177e5;
border-radius: 4px;
.item-c {
flex: 1;
width: 0;
&.user {
background: #fff;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}
.status {
position: absolute;
display: block;
padding: 0 5px;
border: 1px solid #1177e5;
border-radius: 4px;
background: #fff;
left: 100%;
top: 50%;
white-space: nowrap;
transform: translate3d(20px, -50%, 0);
z-index: 9;
}
.time {
position: absolute;
left: 50%;
top: 100%;
transform: translateX(-50%);
white-space: nowrap;
}
.left-line {
width: 1px;
height: calc($itemLen + 40px + 2px);
position: absolute;
left: - calc($leftLineLen + 1px);
background-color: #1177e5;
&.is-half {
height: calc(($itemLen + 40px + 2px) / 2);
}
&.right-line_top {
top: calc($itemLen / 2);
}
&.right-line_bot {
bottom: calc($itemLen / 2);
}
}
.right-line {
width: 1px;
height: calc($itemLen + 40px + 2px);
position: absolute;
left: calc(100% + $rightLineLen);
background-color: #1177e5;
&.is-half {
height: calc(($itemLen + 40px + 2px) / 2);
}
&.right-line_top {
top: calc($itemLen / 2);
}
&.right-line_bot {
bottom: calc($itemLen / 2);
}
&.must-green {
background: #03d94f !important;
}
}
& + .item {
margin-top: 40px;
}
&::before {
content: "";
display: block;
position: absolute;
right: calc(100% + 1px);
top: 50%;
width: $leftLineLen;
height: 1px;
background: #1177e5;
}
&::after {
content: "";
display: block;
position: absolute;
left: calc(100% + 1px);
top: 50%;
width: $rightLineLen;
height: 1px;
background: #1177e5;
}
// 处理状态 颜色
&.green {
&::after {
background: #03d94f;
}
.status {
color: #03d94f;
border-color: #03d94f;
}
.right-line {
background: #03d94f;
}
}
&.red {
&::after {
background: red;
}
.status {
color: red;
border-color: red;
}
.right-line {
background: red;
}
}
}
.start {
width: $itemLen;
height: $itemLen;
border-radius: 4px;
// background: url("~@assets/img/bpmn/start.png") no-repeat left top /100%;
background: red;
margin-right: $startEndLineLen;
position: relative;
&::after {
content: "";
display: block;
height: 1px;
width: $startEndLineLen;
position: absolute;
left: 100%;
top: 50%;
background: #03d94f;
}
}
.end {
width: $itemLen;
height: $itemLen;
background: red;
border-radius: 4px;
margin-left: $startEndLineLen;
position: relative;
&::before {
content: "";
display: block;
height: 1px;
width: $startEndLineLen;
position: absolute;
right: 100%;
top: 50%;
background: #1177e5;
}
&.green {
&::before {
background: #03d94f;
}
}
&.red {
&::before {
background: red;
}
}
// background: url("~@assets/img/bpmn/end.png") no-repeat left top /100%;
}
</style>