[Vue3]简易时间轴(能根据时间长短计算阶段长度)
<template>
<div ref="container" class="timeline-container">
<div class="plan-box">
<span class="plan-title">计划</span>
<div class="plan-timeline">
<div class="timeline-progress" ref="planProgressRef" :style="{ ...planProgressStyle }"></div>
<template v-for="(node, index) in planNodeList">
<div class="timeline-item" :style="{ left: node.pass * 100 + '%' }">
<div class="timeline-item-content">
<p>{{ node.name }}</p>
<p>{{ node.time }}</p>
</div>
</div>
</template>
</div>
</div>
<div class="actual-box">
<span class="actual-title">实际</span>
<div class="actual-timeline">
<div class="timeline-progress" ref="actualProgressRef" :style="{ ...actualProgressStyle }"></div>
<template v-for="(node, index) in actualNodeList">
<div class="timeline-item" :style="{ left: node.pass * 100 + '%' }">
<div class="timeline-item-content">
<p>{{ node.name }}</p>
<p>{{ node.time }}</p>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script setup>
import dayjs from 'dayjs';
import minMax from 'dayjs/plugin/minMax';
import { onMounted, toRefs, ref } from 'vue';
dayjs.extend(minMax);
const props = defineProps({
planNodeList: {
type: Array,
default: [
{
name: 'name1',
time: '2021-05-01'
},
{
name: 'name2',
time: '2021-08-01'
},
{
name: 'name3',
time: '2022-02-01'
}
]
},
actualNodeList: {
type: Array,
default: [
{
name: 'name1',
time: '2021-05-02'
},
{
name: 'name2',
time: '2021-08-01'
},
{
name: 'name3',
time: '2022-03-01'
}
]
}
});
const { planNodeList, actualNodeList } = toRefs(props);
const persistentTime = ref(0);
const planProgressRef = ref();
const minTime = ref('');
const maxTime = ref('');
const planProgressStyle = ref({
width: 0,
marginLeft: 0
});
const actualProgressStyle = ref({
width: 0,
marginLeft: 0
});
const getPersistentTimeByNodes = nodeList => {
const allNode = [...nodeList].map(node => {
return dayjs(node['time']);
});
const currentMaxTime = dayjs.max(allNode);
const currentMinTime = dayjs.min(allNode);
return [currentMinTime, currentMaxTime, dayjs(currentMaxTime).diff(currentMinTime, 'day')];
};
const getRateByDate = time => {
const num = dayjs(time).diff(minTime.value, 'day') / persistentTime.value;
return Math.floor(num * 1000) / 1000;
};
const calculateProgress = nodeList => {
let width = 0;
let marginLeft = 0;
const [startTime, endTime, currentPersistentTime] = getPersistentTimeByNodes(nodeList);
console.log('calculateProgress');
console.log(startTime, endTime, currentPersistentTime);
const rate1 = getRateByDate(startTime);
const rate2 = getRateByDate(endTime);
width = (rate2 - rate1) * 100 + '%';
marginLeft = rate1 * 100 + '%';
return {
width,
marginLeft
};
};
const init = () => {
const allNodeList = [...planNodeList.value, ...actualNodeList.value];
[minTime.value, maxTime.value, persistentTime.value] = getPersistentTimeByNodes(allNodeList);
console.log('总持续时间');
console.log(persistentTime.value);
const allNode = [...planNodeList.value, ...actualNodeList.value];
allNode.forEach(node => {
node.pass = getRateByDate(node['time']);
});
console.log('整理完数据后得到的结果');
console.log(allNode);
planProgressStyle.value = calculateProgress(planNodeList.value);
console.log('计划时间计算结果');
console.log(planProgressStyle.value);
actualProgressStyle.value = calculateProgress(actualNodeList.value);
console.log('实际时间计算结果');
console.log(actualProgressStyle.value);
};
onMounted(() => {
init();
});
</script>
<style lang="less" scoped>
//@cbackground: #f1f8ffb3;
//@borderColor: #f2f6fc54;
@mainColor: #3cca64;
@progressHeight: 10px;
@planItemBgColor: #c6ebf2;
@actualItemBgColor: #c6ebf2;
@arrowHeight: 10px;
// -------------------------------公共样式--------------------------------------
.timeline-container {
// border: 1px solid black;
padding: 70px 10px;
overflow: auto;
margin-top: 20px;
font-size: 10px;
}
// 时间轴上的圆点样式
.item-node {
width: 15px;
height: 15px;
position: absolute;
left: 0;
border-radius: 50%;
border: 4px solid @mainColor;
background: white;
}
// 时间轴里的进度条
.timeline-progress {
width: 92%;
border: 1px solid black;
border-radius: 4px;
height: @progressHeight;
background: @mainColor;
margin-right: 50px;
}
.end-item {
padding: 0 !important;
border: 0 !important;
margin: 0 !important;
width: 0 !important;
}
// -----------------时间轴上一个item的样式-------------------------------
.timeline-item {
display: inline-block;
border: 1px solid black;
vertical-align: top;
position: absolute;
width: 70px;
&:before {
content: '';
border-left: 5px solid transparent;
border-right: 5px solid transparent;
//border-top: 10px solid black;
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.timeline-item-content {
border: 1px solid black;
background: #c6ebf2;
color: black;
z-index: 999;
&:hover {
z-index: 9999;
//transform: translateX(-50%);
}
}
}
// -------------------------------计划部分样式-----------------------------------
.plan-box {
display: flex;
justify-content: flex-start;
align-items: center;
.plan-title {
padding: 5px;
background: @mainColor;
color: white;
font-weight: bold;
}
.plan-timeline {
flex: 1;
margin: 0 30px 0 10px;
border: 1px solid black;
position: relative;
.timeline-progress {
// border: 1px solid black;
position: relative;
background: @planItemBgColor;
}
// 计划时间轴上的时间节点
.timeline-item {
padding: 0 0 10px;
top: 0;
left: 0;
transform: translate(-50%, -100%);
&:before {
bottom: 0;
border-top: 10px solid black;
}
}
}
}
// ----------------------------------实际部分样式-----------------------------
.actual-box {
margin-top: 10px;
display: flex;
justify-content: flex-start;
align-items: center;
.actual-title {
padding: 5px;
border: 1px solid black;
background: @mainColor;
color: white;
font-weight: bold;
}
.actual-timeline {
flex: 1;
margin: 0 30px 0 10px;
// border: 1px solid black;
position: relative;
.timeline-progress {
// border: 1px solid black;
background: @actualItemBgColor;
}
// 计划时间轴上的时间节点
.timeline-item {
padding: 10px 0 0;
bottom: 0;
left: 0;
transform: translate(-50%, 100%);
&:before {
top: 0;
border-bottom: 10px solid black;
}
}
}
}
</style>