2023-07-10 11:00:11 +08:00

651 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="gantt-box">
<div class="header">
<div class="left-content">
<div class="item" v-for="(item, index) in headerList" :key="index">
<div class="color-block" :style="{ background: item.color }"></div>
<div class="label">{{ item.label }}</div>
</div>
</div>
<div class="right-content">备注:更新(进度填报)内容后该甘特图将自动更新</div>
</div>
<div class="gantt-chart">
<div class="table" @scroll="handleScroll">
<div class="thead">
<div class="row">
<div class="td">分部分项工程名称</div>
<div class="td">开始日期</div>
<div class="td" style="border-right: 1px solid #0a769a">完成日期</div>
<div class="td" v-for="date in dateList" :key="date">
<div class="date">
<div class="month">
{{ date }}
</div>
<div class="days">
<div class="day" v-for="day in getDays(date)" :key="day.num">
{{ day.num }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tbody">
<div class="row-groups" :class="{ open: openedIndex === i }" v-for="(p, i) in projects" :key="p.name">
<div class="row">
<div class="td" @click="handleOpen(i)" style="color: #35e5fd">{{ p.name }}</div>
<div class="td">{{ p.planStartTime }}</div>
<div class="td">{{ p.planEndTime }}</div>
<div class="td" v-for="date in dateList" :key="date + 'grid-date'">
<div class="grids">
<div
class="grid"
v-for="day in getDays(date)"
:key="'grid' + day.num"
:ref="(el: any) => setItemRef(el, p.name + '@|@' + day.date)"
></div>
</div>
</div>
<div
class="progress"
:style="gantt"
v-for="(gantt, index) in p.gantts"
:key="index"
@mouseenter="handleHover"
@mouseleave="handleLeave"
></div>
</div>
<div class="children">
<div class="row" v-for="child in p.children" :key="'child-' + child.name">
<div class="td">{{ child.name }}</div>
<div class="td">{{ child.planStartTime }}</div>
<div class="td">{{ child.planEndTime }}</div>
<div class="td" v-for="date in dateList" :key="date + 'grid-date'">
<div class="grids">
<div
class="grid"
v-for="day in getDays(date)"
:key="'grid' + day.num"
:ref="(el: any) => setItemRef(el, child.name + '@|@' + day.date)"
></div>
</div>
</div>
<div
class="progress"
:style="gantt"
v-for="(gantt, index) in child.gantts"
:key="index"
@mouseenter="handleHover"
@mouseleave="handleLeave"
></div>
</div>
</div>
</div>
</div>
<div class="tooltips" :style="tooltipsStyle">
<div class="status">已逾期5天</div>
<div class="charger">负责人史蒂夫</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="tsx" name="ProjectSupervisionRecord">
import { computed, reactive, ref, onMounted, onBeforeMount } from "vue";
import { bigItemAll } from "@/api/modules/huizhou";
const refChart = ref();
const headerList = reactive([
{ label: "未开始", color: "#35e5fd" },
{ label: "进行中", color: "#f1d520" },
{ label: "已完成", color: "#4fd389" },
{ label: "已逾期", color: "#fc6f8e" }
]);
const colors = ref(["#35e5fd", "#f1d520", "#4fd389", "#fc6f8e"]);
const projects = ref([]);
// const projects = ref([
// {
// pName: "抹灰工程1",
// startTime: "2023/05/29",
// endTime: "2023/07/29",
// status: 1,
// delay: 0,
// gantts: [],
// children: [
// {
// pName: "抹灰工程1-1",
// startTime: "2023/05/29",
// endTime: "2023/07/29",
// status: 1,
// delay: 0,
// gantts: []
// }
// ]
// },
// {
// pName: "抹灰工程2",
// startTime: "2023/06/29",
// endTime: "2023/08/29",
// status: 1,
// delay: 0,
// gantts: [],
// children: [
// {
// pName: "抹灰工程2-1",
// startTime: "2023/06/29",
// endTime: "2023/07/29",
// status: 1,
// delay: 0,
// gantts: []
// }
// ]
// },
// {
// pName: "抹灰工程3",
// startTime: "2023/07/29",
// endTime: "2023/09/29",
// status: 1,
// delay: 0,
// gantts: [],
// children: [
// {
// pName: "抹灰工程3-1",
// startTime: "2023/07/29",
// endTime: "2023/10/29",
// status: 1,
// delay: 0,
// gantts: []
// }
// ]
// }
// ]);
const itemRefs = [];
const setItemRef = (el: any, va: any) => {
if (el) {
const b = va.split("@|@");
const dataItem = {
refData: va,
item: el
};
if (itemRefs.length == 0) {
const itemList = [];
itemList.push(dataItem);
const data = {
name: b[0],
itemS: itemList
};
itemRefs.push(data);
} else {
let isCheck = true;
for (let index = 0; index < itemRefs.length; index++) {
const element = itemRefs[index];
if (element.name === b) {
isCheck = false;
element.itemS.push(dataItem);
break;
}
}
if (isCheck) {
const itemList = [];
itemList.push(dataItem);
const data = {
name: b[0],
itemS: itemList
};
itemRefs.push(data);
}
}
}
};
const scrollLeft = ref(0);
const scrollTimer = ref(0);
const openedIndex = ref(9999);
let tooltipsStyle = reactive({
display: "none",
left: 0,
top: 0
});
const getDataList = async () => {
const res = await bigItemAll({});
console.log(res);
if (res) {
const arr = dealArr(res.result);
projects.value = arr;
}
setTimeout(function () {
setGantts();
}, 300);
};
const dealArr = arr => {
arr.map(item => {
item.gantts = [];
item.delay = 0;
if (item.children && item.children.length > 0) {
dealArr(item.children);
}
});
return arr;
};
onBeforeMount(() => {});
onMounted(() => {
getDataList();
console.log(getDays("2022-09"), "月日期分布");
});
const getGanttStyle = (project: any) => {
console.log("进入 getGanttStyle --- ", project);
let { planStartTime, planEndTime, name } = project;
const startArr = planStartTime.split("-");
const endArr = planEndTime.split("-");
let startRef = null;
let endRef = null;
// 抹灰工程1-1-2022/09/03
const a = !(startArr[2] % 2);
console.log(a, ":!(startArr[2] % 2:");
if (a) {
const day = startArr[2] - 1;
startArr[2] = day < 10 ? "0" + day : day;
planStartTime = startArr.join("-");
}
const b = !(endArr[2] % 2);
console.log(a, ":!(endArr[2] % 2):");
if (!(endArr[2] % 2)) {
const day = endArr[2] - 1;
endArr[2] = day < 10 ? "0" + day : day;
planEndTime = endArr.join("-");
}
console.log(dateList, "planEndTime ------------", itemRefs.length);
for (let index = 0; index < itemRefs.length; index++) {
const data = itemRefs[index];
if (data.name === name) {
for (let index = 0; index < data.itemS.length; index++) {
const element = data.itemS[index];
if (element.refData === name + "@|@" + planStartTime) {
startRef = data.itemS[index].item;
}
if (element.refData === name + "@|@" + planEndTime) {
endRef = data.itemS[index].item;
}
}
}
}
const startLeft = startRef.offsetLeft;
const endWidth = endRef.offsetWidth;
const endLeft = endRef.offsetLeft;
const ganttWidth = endLeft - startLeft + endWidth;
return { left: startLeft + "px", width: ganttWidth + "px" };
};
const setGantts = () => {
const configGantts = (projects: any) => {
console.log("进入渲染 setGantts -- ", projects);
projects.map(project => {
let { planEndTime, name, gantts } = project;
const gantt = getGanttStyle(project);
gantt.background = colors.value[project.state - 1];
if (gantts) {
gantts.push(gantt);
} else {
project.gantts = [gantt];
}
if (project.delay) {
const delayStartTime = increaseDate(planEndTime, 1);
const delayEndTime = increaseDate(planEndTime, project.delay);
const gantt = getGanttStyle({
planStartTime: delayStartTime,
planEndTime: delayEndTime,
name
});
gantt.background = colors.value[3];
gantts.push(gantt);
}
project.children && configGantts(project.children);
});
};
configGantts(projects.value);
};
const increaseDate = (date: any, delay: any) => {
const timestamp = new Date(date).getTime() + (delay + 1) * 1000 * 60 * 60 * 24;
return new Date(timestamp).toISOString().replace(/-/g, "-").slice(0, 10);
};
const getDays = (date: any) => {
const year = date.split("-")[0];
const month = +date.split("-")[1];
const large = [1, 3, 5, 7, 8, 10, 12];
const normal = [4, 6, 9, 11];
const small = [2];
let count = 0;
switch (true) {
case large.includes(month):
count = 31;
break;
case normal.includes(month):
count = 30;
break;
case small.includes(month):
count = year % 4 ? 28 : 29;
break;
}
return (() => {
const days = new Array(count)
.fill(0)
.map((item, index) => {
let num = index + 1;
let fulldate = date + (num < 10 ? "-0" + num : "-" + num);
return { num, date: fulldate };
})
.filter(item => item.num % 2);
if (count === 28) {
days.push({ num: 28, date: date + "-28" });
} else if (count === 30) {
days.push({ num: 30, date: date + "-30" });
}
return days;
})();
};
const handleOpen = (index: any) => {
console.log(666);
console.log(index);
console.log(openedIndex.value);
if (index === openedIndex.value) {
openedIndex.value = 9999;
} else {
openedIndex.value = index;
}
};
const handleScroll = (e: any) => {
if (scrollTimer.value) {
clearTimeout(scrollTimer.value);
}
scrollTimer.value = setTimeout(() => {
scrollLeft.value = e.target.scrollLeft;
scrollTimer.value = null;
}, 100);
};
const handleHover = (e: any) => {
const { clientX, clientY } = e;
const decreaseLeft = clientX - 680;
const decreaseTop = clientY - 200;
tooltipsStyle = {
left: decreaseLeft + scrollLeft.value + "px",
top: decreaseTop + "px",
display: "block"
};
};
const handleLeave = () => {
tooltipsStyle.display = "none";
};
const dateList = computed(() => {
let dates = [];
const mapDates = data => {
data.map(p => {
dates.push(p.planStartTime);
dates.push(p.planEndTime);
p.children && mapDates(p.children);
});
};
mapDates(projects.value);
dates = dates.map(date => date.slice(0, 7)).sort();
dates = [...new Set(dates)];
console.log("dateList----", dates);
return dates;
});
</script>
<style scoped lang="scss">
.gantt-box {
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 18px;
background: #092945;
.header {
display: flex;
justify-content: space-between;
width: 100%;
height: 60px;
.left-content {
display: flex;
width: 40%;
height: 100%;
.item {
display: flex;
align-items: center;
width: calc(100% / 4);
.color-block {
margin-right: 10px;
width: 15px;
height: 15px;
border-radius: 2px;
}
.label {
color: #ccc;
font-size: 16px;
}
}
}
.right-content {
font-size: 16px;
line-height: 60px;
color: #ccc;
}
}
.gantt-chart {
height: calc(100% - 145px);
.table {
position: relative;
height: 100%;
overflow-x: auto;
&::-webkit-scrollbar {
width: 10px;
height: 8px;
cursor: pointer;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
background-color: skyblue;
}
.thead {
border-left: 1px solid #0a769a;
.row {
display: flex;
.td {
flex-shrink: 0;
box-sizing: border-box;
height: 42px;
line-height: 42px;
background-color: #092945;
color: #fff;
font-size: 16px;
border-top: 1px solid #0a769a;
.date {
line-height: 21px;
border-left: 1px solid #0a769a;
.month {
border-bottom: 1px solid #0a769a;
}
.days {
display: flex;
.day {
flex: 1;
}
}
}
&:nth-child(1) {
padding-left: 40px;
width: 200px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&:not(:nth-child(1)) {
width: 100px;
text-align: center;
}
&:nth-child(n + 4) {
width: 400px;
}
}
}
}
.tbody {
border-left: 1px solid #0a769a;
.row-groups {
position: relative;
height: 42px;
&::before {
content: "";
position: absolute;
left: 26px;
top: 16px;
width: 0;
height: 0;
border-top: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 4px solid #5be1f4;
z-index: 99;
}
&.open {
height: unset;
&::before {
border-left-color: transparent;
border-top-color: #5be1f4;
}
}
> .row .td:first-child {
user-select: none;
cursor: pointer;
}
.row {
position: relative;
display: flex;
border-top: 1px solid #0a769a;
.td {
flex-shrink: 0;
box-sizing: border-box;
height: 42px;
line-height: 42px;
background-color: #092945;
border-right: 1px solid #0a769a;
border-bottom: 1px solid #0a769a;
color: #fff;
font-size: 16px;
border-top: 1px solid #0a769a;
.grids {
height: 100%;
display: flex;
.grid {
position: relative;
flex: 1;
height: 100%;
&:not(:last-child) {
border-right: 1px solid #0a769a;
}
}
}
&:nth-child(1) {
padding-left: 40px;
width: 200px;
}
&:not(:nth-child(1)) {
width: 100px;
font-size: 14px;
text-align: center;
}
&:nth-child(n + 4) {
width: 400px;
}
}
.progress {
flex-shrink: 0;
position: absolute;
top: calc(50% - 7px);
width: 20px;
height: 14px;
background: #557dee;
}
}
.children {
.td {
height: 38px;
line-height: 38px;
font-size: 14px;
}
}
}
}
.tooltips {
position: absolute;
box-sizing: border-box;
padding: 10px;
width: 130px;
height: 65px;
font-size: 16px;
color: #fff;
border-radius: 8px;
background-color: #50a6b3;
.status {
display: inline-block;
margin-bottom: 6px;
padding: 0 10px;
height: 20px;
min-width: 60px;
line-height: 20px;
font-size: 12px;
border-radius: 10px;
background-color: #ff6c7f;
}
}
}
}
}
</style>