vue页面,绘制项目的计划进度和实际进度;展示不同阶段示意图
如下如图,公司有很多项目,想看每个工程项目计划情况和实际情况,他们之间的进展对比,效果如下:
在这里插入图片描述
代码如下:
页面
<template>
<div class="image-schedule-dialog">
<div class="content-box">
<div class="colse-btn" @click="cliclBtn">x</div>
<div class="image-schedule">
<div class="date-progress">
<div class="label" style="align-self: flex-end">进度</div>
<div v-if="dateAndMonth.length==0"></div>
<div v-else class="date" v-for="(yitem, yindex) in dateAndMonth" :key="yindex">
<div class="year">
<div class="year-item" :style="yearItem(yitem)">{{ yitem.year }}年</div>
</div>
<div class="month">
<div class="month-item" :style="{width: widthNum+'px'}"
v-for="(mitem, mindex) in yitem.months"
:key="mindex">
{{ mitem }}月份
</div>
</div>
</div>
</div>
<div class="plan-progress">
<div class="label">计划进度</div>
<div v-if="plannedProgress.length==0"></div>
<div
v-else
class="bookmark"
v-for="(item, index) in plannedProgress"
:key="item.code"
:style="bookmarkStyleP(item)">
<span class="bookmark-text" :style="bookmarkTextP(item)">{{ item.plan_schedule }}</span>
</div>
</div>
<div class="actual-progress">
<div class="label">实际进度</div>
<div v-if="actualProgress.length==0"></div>
<div
v-else
class="bookmark"
:style="bookmarkStyleA()">
<span class="bookmark-text" style="color: #fff">{{actualProgress[aLength-1].plan_schedule}}</span>
</div>
</div>
</div>
</div>
</div>
</template>
逻辑数据处理
<script>
export default {
props: ['scheduleData'],
data() {
return {
widthNum: 70,
schedule: {
// actualProgress: [
// {
// code: "3",
// min_plan: 202402,
// max_plan: 202403,
// plan_schedule: "招标采购",
// },
// {
// code: "4",
// min_plan: 202404,
// max_plan: 202407,
// plan_schedule: "签订合同",
// },
// {
// code: "5",
// min_plan: 202408,
// max_plan: 202408,
// plan_schedule: "到货",
// },
// ],
// plannedProgress: [
// {
// code: "1",
// min_plan: 202310,
// max_plan: 202402,
// plan_schedule: "前期",
// },
// {
// code: "2",
// min_plan: 202403,
// max_plan: 202403,
// plan_schedule: "招标",
// },
// {
// code: "3",
// min_plan: 202404,
// max_plan: 202408,
// plan_schedule: "合同",
// },
// {
// code: "4",
// min_plan: 202409,
// max_plan: 202409,
// plan_schedule: "到货",
// },
// {
// code: "5",
// min_plan: 202410,
// max_plan: 202410,
// plan_schedule: "施工",
// },
// {
// code: "6",
// min_plan: 202411,
// max_plan: 202411,
// plan_schedule: "投运",
// },
// {
// code: "8",
// min_plan: 202412,
// max_plan: 202412,
// plan_schedule: "关闭",
// },
// ],
},
plannedProgress: [],
actualProgress: [],
colorObj1: {
1: { bgColor: "#CAF6D8", textColor: "#24A74B" },
2: { bgColor: "#C8FAF0", textColor: "#10CFA5" },
3: { bgColor: "#D1E5FF", textColor: "#006FFF" },
4: { bgColor: "#FDDACD", textColor: "#E82222" },
5: { bgColor: "#E8CAFD", textColor: "#8034B5" },
6: { bgColor: "#CACAFD", textColor: "#3A35C9" },
7: { bgColor: "#FAE9C5", textColor: "#E79900" },
8: { bgColor: "#D5E1ED", textColor: "#4E6581" },
},
colorObj2: {
1: { bgColor: "linear-gradient( 270deg, #5BE284 0%, #2FC25B 99%)"},
2: { bgColor: "linear-gradient( 270deg, #EEA073 0%, #E8864D 100%)"},
3: { bgColor: "linear-gradient( 270deg, #64E6CA 0%, #31CCBC 100%)"},
4: { bgColor: "linear-gradient( 270deg, #48BBFC 0%, #398FFF 100%)"},
5: { bgColor: "linear-gradient( 270deg, #FF7272 0%, #FF0808 100%)"},
6: { bgColor: "linear-gradient( 270deg, #B988DC 0%, #8939C1 100%)"},
7: { bgColor: "linear-gradient( 270deg, #AAA7F8 0%, #554FF3 100%)"},
8: { bgColor: "linear-gradient( 270deg, #FAC458 0%, #E99B00 100%)"},
9: { bgColor: "linear-gradient( 270deg, #8EB1DD 0%, #60799B 100%)"},
},
actualBar: "", //实际进度用了多少月
pLength:0,
aLength:0,
dateAndMonth: [],
advance: 0,//实际第一阶段比计划第一阶段提前
lagging:0,//实际最后阶段比计划最后阶段滞后
};
},
created() {
},
mounted() {
},
methods: {
handleData() {
this.plannedProgress = [];
this.actualProgress = [];
if (JSON.stringify(this.scheduleData) == '{}') {
return
} else {
this.schedule = this.scheduleData;
}
this.getWidthBar(this.schedule.actualProgress, 0);
this.getWidthBar(this.schedule.plannedProgress, 1);
this.getAdvanceMargin()
this.getActualBar();
this.getDateAndMonth();
},
getWidthBar(progress, flag) {
if (!progress) {
return;
}
if (progress.length == 0) {
return;
}
let result = progress; let length = progress.length;
let minDate, maxDate, minYear, maxYear, minMonth, maxMonth;
result.forEach((item, index) => {
//当前阶段 数据
minDate = item.min_plan;
maxDate = item.max_plan;
minYear = Math.floor(item.min_plan / 100);
maxYear = Math.floor(item.max_plan / 100);
minMonth = Math.floor(item.min_plan % 100);
maxMonth = Math.floor(item.max_plan % 100);
if (minYear == maxYear) {
item.widthBar = maxMonth - minMonth + 1;
} else {
item.widthBar =
12 - minMonth + maxMonth + 1 + (maxYear - minYear - 1) * 12;
}
if (index == 0) {
}
else {
//上个阶段
let fmaxDate = result[index - 1].max_plan;
let fmaxYear = Math.floor(result[index - 1].max_plan / 100); //上一个阶段年
let fmaxMonth = Math.floor(result[index - 1].max_plan % 100);//上个阶段月
//判断是否同年
if (minYear == fmaxYear) {//同年
if ((minMonth-fmaxMonth) == 1) {//月份连续
item.marginLeftBar = 0
} else {//月份不连续
item.marginLeftBar=(minMonth - fmaxMonth - 1)
}
} else {//隔年 例如 202311 202401 隔了 12月,一个月
item.marginLeftBar=(minMonth + 12 - fmaxMonth - 1)
}
}
});
console.log(result, "result");
if (flag == 0) {
this.actualProgress = result||[];
}
if (flag == 1) {
this.plannedProgress = result||[];
}
},
getAdvanceMargin() {
if (this.plannedProgress.length != 0 && this.actualProgress.length != 0) {
let pminDate, pminYear, pminMonth, aminDate, aminYear, aminMonth;
let marginLeftBar = 0
pminDate = this.plannedProgress[0].min_plan;
pminYear = Math.floor(this.plannedProgress[0].min_plan / 100);
pminMonth = Math.floor(this.plannedProgress[0].min_plan % 100);
aminDate = this.actualProgress[0].min_plan;
aminYear = Math.floor(this.actualProgress[0].min_plan / 100);
aminMonth = Math.floor(this.actualProgress[0].min_plan % 100);
//比计划提前 p 202403 a 202401 p 202401 a 202312
if (aminDate < pminDate) {
if (pminYear == aminYear) {
marginLeftBar = pminMonth - aminMonth
} else {
marginLeftBar = aminMonth + 12 - pminMonth
}
this.plannedProgress[0]['marginLeftBar'] =marginLeftBar
}
}
},
getActualBar() {
if(this.actualProgress.length==0){return}
let minDate, maxDate, minYear, maxYear, minMonth, maxMonth;
this.aLength = this.actualProgress.length;
if (this.plannedProgress.length == 0) {
minDate = this.actualProgress[0].min_plan;
} else {
minDate =this.actualProgress[0].min_plan>this.plannedProgress[0].min_plan?this.plannedProgress[0].min_plan:this.actualProgress[0].min_plan
}
maxDate = this.actualProgress[this.aLength - 1].max_plan;
minYear = Math.floor(minDate / 100);
maxYear = Math.floor(maxDate / 100);
minMonth = Math.floor(minDate % 100);
maxMonth = Math.floor(maxDate % 100);
this.actualBar =
12 - minMonth + maxMonth + 1 + (maxYear - minYear - 1) * 12;
console.log(this.actualBar, "this.actualBar");
},
getDateAndMonth() {
if (this.plannedProgress.length == 0 && this.actualProgress.length == 0) {
return
}
let pLength,aLength, minDate, maxDate, minYear, maxYear, minMonth, maxMonth;
//有计划进度
if (this.plannedProgress.length != 0) {
pLength = this.plannedProgress.length;
minDate = this.plannedProgress[0].min_plan;
maxDate = this.plannedProgress[pLength - 1].max_plan;
//有实际进度,则对比最小时间,最大时间
if (this.actualProgress.length != 0) {
aLength=this.actualProgress.length;
let aminDate = this.actualProgress[0].min_plan;
let amaxDate = this.actualProgress[aLength - 1].max_plan;
minDate = minDate < aminDate ? minDate : aminDate;
maxDate = maxDate < amaxDate ? amaxDate : maxDate;
}
}
//无计划进度,用实际进度
else {
aLength = this.actualProgress.length;
minDate = this.actualProgress[0].min_plan;
maxDate = this.actualProgress[aLength - 1].max_plan;
}
minYear = Math.floor(minDate / 100);
maxYear = Math.floor(maxDate / 100);
minMonth = Math.floor(minDate % 100);
maxMonth = Math.floor(maxDate % 100);
//年、月
this.dateAndMonth = [];
let temp = minYear;
while (temp <= maxYear) {
let minarr = [];
//一年之内
if (minYear==maxYear) {
for (let i = minMonth; i <= maxMonth; i++) {
minarr.push(i);
}
this.dateAndMonth.push({
year: temp,
months: minarr,
});
}
//两年
else if (maxYear - minYear == 1) {
//项目总阶段两年之内;计算项目在哪年,有哪些月
if (temp == minYear&&temp!=maxYear) {
for (let i = minMonth; i <= 12; i++) {
minarr.push(i);
}
this.dateAndMonth.push({
year: temp,
months: minarr,
});
}
else if (temp == maxYear && temp != minYear) {
for (let i = 1; i <= maxMonth; i++) {
minarr.push(i);
}
this.dateAndMonth.push({
year: temp,
months: minarr,
});
}
}
//大于两年
else if (maxYear - minYear > 1) {
if (minYear != temp && maxYear != temp) {
this.dateAndMonth.push({
year: temp,
months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
});
}
}
temp++;
}
console.log(this.dateAndMonth, "this.dateAndMonth");
},
yearItem(yitem) {
let obj = { width: yitem.months.length * this.widthNum + 'px' }
return obj
},
bookmarkStyleP(item, val) {
if (!item) {
return
}
let obj = {};
obj = {
'width': item.widthBar * this.widthNum + 'px',
'background': this.colorObj1[item.code].bgColor,
'margin-left':item.marginLeftBar * this.widthNum + 'px',
}
return obj;
},
bookmarkTextP(item) {
if (!item) { return }
let obj = {};
obj = {
'color':this.colorObj1[item.code].textColor
}
return obj
},
bookmarkStyleA() {
let obj={};
let aLength = this.actualProgress.length
let aitem=this.actualProgress[aLength-1]
obj = {
'width': this.actualBar * this.widthNum + 'px',
'background':this.colorObj2[aitem.code].bgColor,
}
return obj
},
cliclBtn(){
this.$emit('close')
}
},
watch: {
scheduleData: {
handler(n, o) {
this.handleData()
},
immediate: true,
}
}
};
</script>
样式书写
<style lang="less" scoped>
.image-schedule-dialog{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
z-index: 998;
}
.content-box{
background: #fff;
display: inline-block;
margin: 0 auto;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 999;
}
.image-schedule {
min-width: 300px;
max-width: 1500px;
overflow-x: auto;
}
.colse-btn{
color: #bbcbde;
cursor: pointer;
text-align: right;
font-weight: 600;
padding-right: 10px;
height: 20px;
line-height: 20px;
}
.date-progress,
.plan-progress,
.actual-progress {
height: 40px;
display: flex;
align-items: center;
justify-content: flex-start;
.label {
width: 80px;
text-align: center;
flex-shrink: 0; //不允许弹性布局挤压子元素宽度
}
.bookmark {
width: 60px;
height: 30px;
background: red;
flex-shrink: 0; //不允许弹性布局挤压子元素宽度
clip-path: polygon(
calc(100% - 0.5rem) 0%,
100% 50%,
calc(100% - 0.5rem) 100%,
0% 100%,
0.5rem 50%,
0% 0%
);
}
.bookmark-text {
font-size: 12px;
font-weight: 700;
float: right;
padding-right: 12px;
line-height: 30px;
}
}
.date-progress {
margin-bottom: 5px;
.date {
text-align: center;
.year,.month {
display: flex;
}
.year-item {
height: 20px;
border-left: 1px solid #ccc;
box-sizing: border-box;
}
}
.date:nth-child(2){
.year-item{
border: none;
}
}
.month-item {
height: 20px;
border-left: 1px solid #ccc;
box-sizing: border-box;
}
}
.plan-progress {
margin: 0 5px;
}
.actual-progress {
margin: 0 5px;
}
</style>