2023-08-18 17:42:47 +08:00

822 lines
19 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" v-if="projects.length > 0">
<div class="table" @scroll="handleScroll">
<div class="thead">
<div class="row">
<div class="td fixed_1">分部分项工程名称</div>
<div class="td fixed_2">开始日期</div>
<div class="td fixed_3" style="border-right: 1px solid #cbd1df">完成日期</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 fixed_1" @click="handleOpen(i)" style="color: #333333; padding-left: 25px">
<el-icon
size="16"
v-if="openedIndex === i && p.children.length > 0"
:style="{ visibility: openedIndex === i && p.children.length > 0 ? 'visible' : 'hidden' }"
><caret-bottom
/></el-icon>
<el-icon
size="16"
v-else
:style="{ visibility: openedIndex != i && p.children.length > 0 ? 'visible' : 'hidden' }"
><caret-right
/></el-icon>
<el-tooltip effect="dark" :content="p.name" placement="top-start">
<span>{{ p.name }}</span>
</el-tooltip>
</div>
<div class="td fixed_2">{{ p.planStartTime }}</div>
<div class="td fixed_3">{{ 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="e => handleHover(e, p)"
@mouseleave="handleLeave"
></div>
</div>
<div class="children" v-show="openedIndex === i">
<div class="row" v-for="child in p.children" :key="'child-' + child.name">
<div class="td fixed_1">{{ child.name }}</div>
<div class="td fixed_2">{{ child.planStartTime }}</div>
<div class="td fixed_3">{{ 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="e => handleHover(e, child)"
@mouseleave="handleLeave"
></div>
</div>
</div>
</div>
</div>
<div class="tooltips" :style="tooltipsStyle">
<div class="status">已逾期{{ tooltipContent.day }}</div>
<div class="charger">负责人{{ tooltipContent.name }}</div>
</div>
</div>
</div>
<div class="no-data" v-else>
<img src="@/assets/images/notData.png" alt="notData" />
<div>暂无数据</div>
</div>
<engineeringEngDrawer v-model="engVisable" :active="activeValue" ref="engDrawer" :engList="engList" @select="tabsSelect">
<template #default="{ data }">
<span style="margin-left: 10px" @click="onUpdate(data)">{{
activeValue == "eng" ? data.engineeringName : data.projectName
}}</span>
</template>
</engineeringEngDrawer>
<allEngineering @click="engVisable = true" />
</div>
</template>
<script setup lang="tsx" name="ProjectSupervisionRecord">
import { computed, reactive, ref, onMounted, onBeforeMount } from "vue";
import { bigItemAll, getEngineeringName } from "@/api/modules/project";
import engineeringEngDrawer from "@/components/engineeringEngDrawer/index.vue";
import allEngineering from "@/components/allEngineering/index.vue";
import { ElMessage } from "element-plus";
import { getRelevanceList } from "@/api/modules/common";
const activeValue = ref("eng");
const engList = ref([]);
const engVisable = ref(false);
const searchSn = ref("");
const headerList = reactive([
{ label: "未开始", color: "#35e5fd" },
{ label: "进行中", color: "#f1d520" },
{ label: "已完成", color: "#4fd389" },
{ label: "逾期未开始", color: "#F80840" },
{ label: "逾期进行中", color: "#fc6f8e" },
{ label: "逾期已完成", color: "#C13F5B" }
]);
const colors = ref(["#35e5fd", "#f1d520", "#4fd389", "#F80840", "#fc6f8e", "#C13F5B"]);
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 = [];
// 抽屉tab选择时
const tabsSelect = val => {
activeValue.value = val;
if (val == "eng") {
getengineering();
} else if (val == "project") {
getProject();
}
};
// 获取项目信息
const getProject = async () => {
const res = await getEngineeringName();
engList.value = [res.result];
if (res.result) {
searchSn.value = res.result.projectSn;
}
getDataList();
console.log(res);
};
// 获取工程信息
const getengineering = async () => {
// let newParams = JSON.parse(JSON.stringify(params));
const res = await getRelevanceList();
engList.value = res.result;
if (res.result && res.result.length > 0) {
searchSn.value = res.result[0].engineeringSn;
}
getDataList();
console.log(res);
};
// 点击抽屉的工程名称更新页面
const onUpdate = async row => {
if (activeValue.value == "eng") {
searchSn.value = row.engineeringSn;
} else if (activeValue.value == "project") {
searchSn.value = row.projectSn;
}
getDataList();
ElMessage.success("页面已更新");
};
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);
const tooltipsStyle = ref({
display: "none",
left: 0,
top: 0
});
const tooltipContent = ref({
day: 0,
name: ""
});
const getDataList = async () => {
let requestData = {};
if (activeValue.value == "eng") {
requestData.engineeringSn = searchSn.value;
} else if (activeValue.value == "project") {
requestData.projectSn = searchSn.value;
}
if (requestData.engineeringSn || requestData.projectSn) {
const res = await bigItemAll(requestData);
console.log(res);
if (res) {
const arr = dealArr(res.result);
projects.value = arr;
}
setTimeout(function () {
setGantts();
}, 300);
} else {
projects.value = [];
}
};
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(() => {
getengineering();
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;
}
setTimeout(function () {
setGantts();
}, 300);
};
const handleScroll = (e: any) => {
if (scrollTimer.value) {
clearTimeout(scrollTimer.value);
}
scrollTimer.value = setTimeout(() => {
scrollLeft.value = e.target.scrollLeft;
scrollTimer.value = null;
}, 100);
const fixedElement1 = document.querySelectorAll(".fixed_1");
const fixedElement2 = document.querySelectorAll(".fixed_2");
const fixedElement3 = document.querySelectorAll(".fixed_3");
console.log(fixedElement1);
console.log(fixedElement1[0]);
console.log(fixedElement1.length);
for (let i = 0; i < fixedElement1.length; i++) {
fixedElement1[i].style.left = e.target.scrollLeft + "px";
}
for (let i = 0; i < fixedElement2.length; i++) {
fixedElement2[i].style.left = e.target.scrollLeft + "px";
}
for (let i = 0; i < fixedElement3.length; i++) {
fixedElement3[i].style.left = e.target.scrollLeft + "px";
}
};
const handleHover = (e: any, item: any) => {
console.log(666);
console.log(e);
console.log(item);
if (item.state == 4 || item.state == 5 || item.state == 6) {
const { clientX, clientY } = e;
const decreaseLeft = clientX - 380;
const decreaseTop = clientY - 200;
tooltipsStyle.value = {
left: decreaseLeft + scrollLeft.value + "px",
top: decreaseTop + "px",
display: "block"
};
tooltipContent.value = {
day: item.slippage,
name: item.commander
};
}
};
const handleLeave = () => {
tooltipsStyle.value.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;
min-width: 1120px;
height: 100%;
padding: 18px;
background: #ffffff;
border-radius: 8px;
.header {
display: flex;
justify-content: space-between;
width: 100%;
height: 60px;
.left-content {
display: flex;
width: 65%;
height: 100%;
.item {
display: flex;
align-items: center;
// width: calc(100% / 4);
margin-right: 20px;
.color-block {
margin-right: 10px;
width: 15px;
height: 15px;
border-radius: 2px;
}
.label {
color: #29304d;
font-size: 18px;
}
}
}
.right-content {
font-size: 16px;
line-height: 60px;
color: #cccccc;
}
}
.gantt-chart {
height: calc(100% - 145px);
overflow: hidden;
.table {
position: relative;
height: 100%;
overflow-x: auto;
&::-webkit-scrollbar {
width: 10px;
height: 8px;
cursor: pointer;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
background-color: #e6f2ff;
}
.thead {
width: max-content;
// border-left: 1px solid #0a769a;
.row {
display: flex;
position: relative;
.fixed_1,
.fixed_2,
.fixed_3 {
width: 200px;
position: relative;
background: #e2ebff;
z-index: 10;
}
.fixed_1 {
border-left: 1px solid #cbd1df;
left: 0;
}
.fixed_2 {
left: 0px;
}
.fixed_3 {
left: 0px;
}
.td {
flex-shrink: 0;
box-sizing: border-box;
height: 42px;
line-height: 42px;
background-color: #e2ebff;
color: #666666;
font-size: 16px;
border-top: 1px solid #cbd1df;
.date {
line-height: 21px;
border-left: 1px solid #cbd1df;
.month {
border-bottom: 1px solid #cbd1df;
}
.days {
display: flex;
.day {
flex: 1;
}
}
}
&:nth-child(1) {
padding-left: 30px;
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 {
width: max-content;
// 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 #cbd1df;
.fixed_1,
.fixed_2,
.fixed_3 {
width: 200px;
position: relative;
background: #092945;
z-index: 10;
}
.fixed_1 {
left: 0;
border-left: 1px solid #cbd1df;
}
.fixed_2 {
left: 0px;
}
.fixed_3 {
left: 0px;
}
.td {
flex-shrink: 0;
box-sizing: border-box;
height: 42px;
line-height: 42px;
background-color: #fff;
border-right: 1px solid #cbd1df;
border-bottom: 1px solid #cbd1df;
border-top: 1px solid #cbd1df;
color: #333333;
font-size: 16px;
.grids {
height: 100%;
display: flex;
.grid {
position: relative;
flex: 1;
height: 100%;
&:not(:last-child) {
border-right: 1px solid #cbd1df;
}
}
}
&: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: 160px;
height: 65px;
border-radius: 8px;
// background-color: #50a6b3;
box-shadow: 1px 1px 10px #ccc;
background-color: white;
.status {
// display: inline-block;
margin-bottom: 6px;
height: 20px;
width: 100px;
line-height: 20px;
font-size: 14px;
border-radius: 10px;
// background-color: #ff6c7f;
background-color: #e83030;
color: white;
text-align: center;
margin: 0 auto;
}
.charger {
color: #333;
text-align: center;
}
}
}
}
.no-data {
height: calc(100% - 145px);
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
color: #999;
}
}
</style>