前言
本文是根据项目实际开发中一个需求开发的demo,仅用了elementUI,可当作独立组件使用,C V即用。
当然没考虑其他的扩展性和一些数据的校验,主要是提供一个处理思路,有需要的小伙伴可以直接复制;
本demo的思路是根据开始时间和结束时间动态生成工程时间表,再根据工程的计划开始和结束日期、
实际开始和结束日期再对应单元格生成进度条,最后根据完成进度百分比计算红色进度条。
一、demo成品图
表格使用的是elementUI,表头是动态的,根据开始日期的年月和结束时间的年月计算获取;
单元格第一行绿色进度条是计划工程进度,第二行绿色是实际功能进度条,红色是实际进度的百分比
二、代码
<template>
<div class="app-container">
<el-table :data="tableData" style="width: 100%">
<el-table-column label="名称" prop="name" width="200"></el-table-column>
<el-table-column align="center" v-for="(months, year) in dateList" :key="year" :label="`${year}年`">
<el-table-column v-for="month in months" align="center" width="100" :key="month" :label="`${month}月`">
<template slot-scope="scope">
<div class="process-box" v-if="scope.row.plan_process[year] && scope.row.plan_process[year].includes(month) || scope.row.actual_process[year] && scope.row.actual_process[year].includes(month)">
<div class="plan-process" v-if="scope.row.plan_process[year] && scope.row.plan_process[year].includes(month)">
<div class="item" v-if="scope.row.planSameYearMonth" :style="scope.row.planProcessStyle"></div>
<div v-else>
<div class="item start" v-if="scope.row.plan_start.Y == year && scope.row.plan_start.M == month" :style="scope.row.plan_start.itemStyle"></div>
<div class="item end" v-if="scope.row.plan_end.Y == year && scope.row.plan_end.M == month" :style="scope.row.plan_end.itemStyle"></div>
<div class="item" v-if="!(scope.row.plan_start.Y == year && scope.row.plan_start.M == month || scope.row.plan_end.Y == year && scope.row.plan_end.M == month)"></div>
</div>
</div>
<div class="actual-process" v-if="scope.row.actual_process[year] && scope.row.actual_process[year].includes(month)">
<div class="item" v-if="scope.row.actualSameYearMonth" :style="scope.row.actualProcessStyle">
<div class="percent_item start" v-if="scope.row.percentSameYearMonth" :style="scope.row.percentProcessStyle"></div>
</div>
<div class="item-box" v-else>
<div class="item start" v-if="scope.row.actual_start.Y == year && scope.row.actual_start.M == month" :style="scope.row.actual_start.itemStyle">
<div class="percent_item start" v-if="scope.row.percent_process[year] && scope.row.percent_process[year].includes(month) && scope.row.percent_start.Y == year && scope.row.percent_start.M == month" :style="scope.row.percent_start.itemStyle"></div>
</div>
<div class="item end" v-if="scope.row.actual_end.Y == year && scope.row.actual_end.M == month" :style="scope.row.actual_end.itemStyle">
<div class="percent_item end" v-if="scope.row.percent_process[year] && scope.row.percent_process[year].includes(month) && scope.row.percent_end.Y == year && scope.row.percent_end.M == month" :style="scope.row.percent_end.itemStyle"></div>
</div>
<div class="item" v-if="!(scope.row.actual_start.Y == year && scope.row.actual_start.M == month || scope.row.actual_end.Y == year && scope.row.actual_end.M == month)">
<div class="percent_item" v-if="scope.row.percent_process[year] && scope.row.percent_process[year].includes(month) && scope.row.percent_end.Y == year && scope.row.percent_end.M == month" :style="scope.row.percent_end.itemStyle"></div>
<div class="percent_item" v-if="scope.row.percent_process[year] && scope.row.percent_process[year].includes(month) && !(scope.row.percent_end.Y == year && scope.row.percent_end.M == month)"></div>
</div>
</div>
</div>
</div>
</template>
</el-table-column>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [
{
name: "单位A施工期间",
plan_start_time: "2023-02-1",
plan_end_time: "2023-2-28",
actual_start_time: "2023-2-7",
actual_end_time: "2023-6-22",
percent: 85,
},
{
name: "单位B施工期间",
plan_start_time: "2023-07-12",
plan_end_time: "2024-01-12",
actual_start_time: "2023-11-10",
actual_end_time: "2024-01-10",
percent: 76,
}
],
dateList: {},
}
},
mounted(){
this.initTableData("2023-01-12", "2025-01-30")
},
methods: {
handleDate(date) {
let monthHasDay = 30;
let currentDate = new Date(date)
let day = currentDate.getDate()
let month = currentDate.getMonth() + 1;
let year = currentDate.getFullYear();
if ([1, 3, 5, 7, 8, 10, 12].includes(month)) {
monthHasDay = 31
} else {
if (month === 2) {
if ((year % 400 === 0) || (year % 4 === 0 && year % 100 !== 0)) {
monthHasDay = 29;
} else {
monthHasDay = 28;
}
} else {
monthHasDay = 30;
}
}
return {d: day, M: month, Y: year, monthHasDay: monthHasDay}
},
getDataBetweenDates(startTime, endTime){
let start = this.handleDate(startTime);
let end = this.handleDate(endTime);
let data = {}
data[start.Y] = [];
data[end.Y] = [];
let year = end.Y - start.Y
if (year === 0) {
for(let m = start.M; m <= end.M; m++) {
data[start.Y].push(m)
}
} else if (year === 1) {
for(let m = start.M; m <= 12; m++) {
data[start.Y].push(m)
}
for(let n = 1; n <= end.M; n++) {
data[end.Y].push(n)
}
} else {
for(let m = start.M; m <= 12; m++) {
data[start.Y].push(m)
}
for(let mid = 1; mid < year; mid++) {
data[start.Y + mid] = [1,2,3,4,5,6,7,8,9,10,11,12];
}
for(let n = 1; n <= end.M; n++) {
data[end.Y].push(n)
}
}
return data;
},
getDaysBetweenDates(startTime, endTime) {
let d1 = new Date(startTime);
let d2 = new Date(endTime);
let timeDiff = Math.abs(d2.getTime() - d1.getTime());
let days = Math.ceil(timeDiff / (1000 * 3600 * 24));
return days;
},
handleDateStyle(startDate, endDate){
let start = this.handleDate(startDate)
let end = this.handleDate(endDate);
let sameYearMonth = false;
let processStyle = null;
if (end.Y === start.Y && end.M === start.M) {
processStyle = {
"left": ((start.d - 1) * 100 / start.monthHasDay) + "%",
"right": ((start.monthHasDay - end.d) * 100 / start.monthHasDay) + "%",
"border-radius": '4px'
}
if (end.d > start.monthHasDay) processStyle.right = 0
sameYearMonth = true
} else {
start.itemStyle = {
"left": ((start.d + 1) * 100 / start.monthHasDay) + "%",
"right": 0
}
end.itemStyle = {
"left": 0,
"right": ((start.monthHasDay - (end.d + 1)) * 100 / start.monthHasDay) + "%"
}
}
return {
start: start,
end: end,
sameYearMonth: sameYearMonth,
processStyle: processStyle
}
},
handlePercentDateStyle(actualStartTime, actualEndTime, percent){
let start = this.handleDate(actualStartTime)
let end = this.handleDate(actualEndTime);
let days = this.getDaysBetweenDates(actualStartTime, actualEndTime)
let percentTime = new Date(actualStartTime).getTime() + days * percent * 24 * 36000
let percentProcess = this.getDataBetweenDates(actualStartTime, percentTime)
let startBorderRadius = '4px 0 0 4px'
let endBorderRadius = '0 4px 4px 0'
let percentDate = this.handleDate(percentTime)
let sameYearMonth = false;
let processStyle = null;
if (end.Y === start.Y) {
if (end.M === start.M) {
processStyle = {
"left": 0,
"right": ((end.d - (percentDate.d)) * 100 / end.d) + "%",
"border-radius": '4px'
}
sameYearMonth = true
} else {
if(percentDate.M === start.M) {
start.itemStyle = {
"left": 0,
"right": ((start.monthHasDay - percentDate.d) * 100 / (start.monthHasDay - start.d)) + "%",
"border-radius": '4px'
}
percentDate.itemStyle = {
"left": 0,
"right": ((start.monthHasDay - percentDate.d) * 100 / start.monthHasDay) + "%",
"border-radius": '4px'
}
} else if (percentDate.M > start.M && percentDate.M < end.M) {
start.itemStyle = {
"left": 0,
"right": 0,
"border-radius": startBorderRadius
}
percentDate.itemStyle = {
"left": 0,
"right": ((percentDate.monthHasDay - percentDate.d) * 100 / percentDate.monthHasDay) + "%",
"border-radius": endBorderRadius
}
} else if (percentDate.M === end.M) {
start.itemStyle = {
"left": 0,
"right": 0,
"border-radius": startBorderRadius
}
percentDate.itemStyle = {
"left": 0,
"right": ((end.d - percentDate.d) * 100 / end.d) + "%",
"border-radius": endBorderRadius
}
}
}
} else {
if (percentDate.M === start.M) {
start.itemStyle = {
"left": 0,
"right": ((start.monthHasDay - percentDate.d) * 100 / (start.monthHasDay - start.d)) + "%",
"border-radius": '4px'
}
} else if (percentDate.M === end.M && percentDate.Y === end.Y) {
start.itemStyle = {
"left": 0,
"right": 0,
"border-radius": startBorderRadius
}
percentDate.itemStyle = {
"left": 0,
"right": ((end.d - percentDate.d) * 100 / end.d) + "%",
"border-radius": endBorderRadius
}
} else {
start.itemStyle = {
"left": 0,
"right": 0,
"border-radius": startBorderRadius
}
percentDate.itemStyle = {
"left": 0,
"right": ((percentDate.monthHasDay - percentDate.d) * 100 / percentDate.monthHasDay) + "%",
"border-radius": endBorderRadius
}
}
}
return {
start: start,
end: percentDate,
sameYearMonth: sameYearMonth,
processStyle: processStyle,
percentProcess: percentProcess
}
},
initTableData(startTime, endTime){
this.dateList = this.getDataBetweenDates(startTime, endTime);
this.tableData.map(item => {
item.plan_process = this.getDataBetweenDates(item.plan_start_time, item.plan_end_time);
item.actual_process = this.getDataBetweenDates(item.actual_start_time, item.actual_end_time);
let dateStyle = this.handleDateStyle(item.plan_start_time,item.plan_end_time) ;
item.planSameYearMonth = dateStyle.sameYearMonth;
item.planProcessStyle = dateStyle.processStyle ? dateStyle.processStyle : '';
item.plan_start = dateStyle.start;
item.plan_end = dateStyle.end;
let actualDateStyle = this.handleDateStyle(item.actual_start_time,item.actual_end_time);
item.actualSameYearMonth = actualDateStyle.sameYearMonth;
item.actualProcessStyle = actualDateStyle.processStyle ? actualDateStyle.processStyle : '';
item.actual_start = actualDateStyle.start;
item.actual_end = actualDateStyle.end;
let percentDateStyle = this.handlePercentDateStyle(item.actual_start_time, item.actual_end_time, item.percent);
item.percent_start = percentDateStyle.start;
item.percent_end = percentDateStyle.end;
item.percentProcessStyle = percentDateStyle.processStyle ? percentDateStyle.processStyle : '';
item.percentSameYearMonth = percentDateStyle.sameYearMonth;
item.percent_process = percentDateStyle.percentProcess
console.log(item)
})
},
},
}
</script>
<style lang="scss" scoped>
::v-deep .el-table td.el-table__cell div{
padding: 0;
}
.process-box{
width: 100px;
height: 40px;
position: relative;
.plan-process{
position: absolute;
top: 0;
left: 0;
right: 0;
height: 15px;
}
.actual-process{
position: absolute;
top: 25px;
left: 0;
right: 0;
height: 15px;
}
.percent_item{
position: absolute;
height: 15px;
left: 0;
right: 0;
background-color: red;
}
}
.item {
position: absolute;
left: 0;
right: 0;
background: greenyellow;
height: 15px;
&.start{
border-radius: 4px 0 0 4px;
}
&.end{
border-radius: 0 4px 4px 0 ;
}
}
</style>