852 lines
20 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>
<Card title="倒计时">
<div class="main-box">
<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 #3e5a8d">完成日期</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.taskName">
<div class="row">
<div class="td fixed_1" @click="handleOpen(i)" style="color: #fff; 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.taskName" placement="top-start">
<span>{{ p.taskName }}</span>
</el-tooltip>
</div>
<div class="td fixed_2">{{ p.startDate }}</div>
<div class="td fixed_3">{{ p.finishDate }}</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.id + '@|@' + day.date)"
></div>
</div>
</div>
<!-- <div
class="progress"
:style="gantt"
v-for="(gantt, index) in p.gantts"
:key="index"
:title="p.taskName"
@mouseenter="e => handleHover(e, p)"
@mouseleave="handleLeave"
></div> -->
<div
class="progress"
:style="gantt"
v-for="(gantt, index) in p.gantts"
:key="index"
:title="p.taskName + ' ' + headerList[p.status].label"
@click="openGanttDialog(p)"
></div>
</div>
<div class="children" v-show="openedIndex === i">
<div class="row" v-for="child in p.children" :key="'child-' + child.taskName">
<div class="td fixed_1">{{ child.taskName }}</div>
<div class="td fixed_2">{{ child.startDate }}</div>
<div class="td fixed_3">{{ child.finishDate }}</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.id + '@|@' + 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">
{{ tooltipContent.taskName }}
<span>
{{
tooltipContent.status == 0
? "未开始"
: tooltipContent.status == 1
? "进行中"
: tooltipContent.status == 2
? "已完成"
: "已逾期"
}}</span
>
</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/noData.png" alt="notData" />
<div>暂无数据</div>
</div>
</div>
</div>
<gantt-dialog ref="ganttDetailRef"></gantt-dialog>
</Card>
</template>
<script setup lang="tsx" name="ProjectSupervisionRecord">
import ganttDialog from "@/views/sevenLargeScreen/qualityControl/schedulePlan/gantt-dialog.vue";
import { computed, reactive, ref, onMounted, onBeforeMount, watch } from "vue";
import { getParentChildTaskListApi } from "@/api/modules/projectOverview";
import { ElMessage } from "element-plus";
import { GlobalStore } from "@/stores";
import Card from "@/components/card.vue";
const store = GlobalStore();
// 甘特图详情弹窗
const ganttDetailRef = ref();
const openGanttDialog = (type: any) => {
ganttDetailRef.value.openDialog(type);
// console.log(ganttDetailRef.value);
};
// const activeValue = ref(store.activeType);
const searchSn = ref("" as any);
const headerList = reactive([
{ label: "未开始", color: "#35e5fd" },
{ label: "进行中", color: "#f1d520" },
{ label: "已完成", color: "#4fd389" },
{ label: "已逾期", color: "#F80840" }
// { label: "逾期未开始", color: "#F80840" },
// { label: "逾期进行中", color: "#fc6f8e" },
// { label: "逾期已完成", color: "#C13F5B" }
]);
const colors = ref(["#35e5fd", "#f1d520", "#4fd389", "#F80840"] as any);
const projects = ref([] as any);
// 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 onUpdate = async () => {
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 getParentChildTaskListApi(requestData);
// console.log(res);
// if (res) {
// const arr = dealArr(res.result);
// projects.value = arr;
// }
// } else {
// projects.value = [];
// }
// setTimeout(function () {
// setGantts();
// }, 300);
// };
function getDataList() {
getParentChildTaskListApi({
projectSn: searchSn.value
}).then(res => {
projects.value = dealArr(res.result);
// console.log("获取甘特图数据", projects.value);
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;
};
// 监听右侧抽屉值的变化
watch(
() => store.sn,
n => {
searchSn.value = store.sn;
onUpdate();
}
);
// 监听右侧抽屉类型的变化
onBeforeMount(() => {
searchSn.value = store.sn;
});
onMounted(() => {
getDataList();
});
const getGanttStyle = (project: any) => {
// console.log("进入 getGanttStyle --- ", project);
let { startDate, finishDate, id } = project;
const startArr = startDate.split("-");
const endArr = finishDate.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;
startDate = 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;
finishDate = endArr.join("-");
}
// console.log(dateList, "finishDate ------------", itemRefs.length);
// console.log(id, "甘特图");
for (let index = 0; index < itemRefs.length; index++) {
const data = itemRefs[index];
// console.log(data, "循环里面甘特图", id);
if (data.name === id) {
for (let index = 0; index < data.itemS.length; index++) {
const element = data.itemS[index];
if (element.refData === id + "@|@" + startDate) {
startRef = data.itemS[index].item;
}
if (element.refData === id + "@|@" + finishDate) {
endRef = data.itemS[index].item;
}
}
}
}
// console.log(startRef, endRef, "甘特图");
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 { finishDate, name, gantts } = project;
const gantt = getGanttStyle(project);
// gantt.background = colors.value[project.state - 1];
gantt.background = colors.value[project.status];
if (gantts) {
gantts.push(gantt);
} else {
project.gantts = [gantt];
}
if (project.delay) {
const delayStartTime = increaseDate(finishDate, 1);
const delayEndTime = increaseDate(finishDate, project.delay);
const gantt = getGanttStyle({
startDate: delayStartTime,
finishDate: 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 = {
taskName: item.taskName,
status: item.status
};
}
};
const handleLeave = () => {
tooltipsStyle.value.display = "none";
};
const dateList = computed(() => {
let dates = [];
const mapDates = data => {
data.map(p => {
dates.push(p.startDate);
dates.push(p.finishDate);
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">
.main-box {
overflow-x: scroll;
}
.gantt-box {
box-sizing: border-box;
min-width: 1120px;
width: 100%;
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: 50px;
.color-block {
margin-right: 10px;
width: 12px;
height: 12px;
border-radius: 2px;
}
.label {
color: #fff;
font-size: 14px;
margin-left: 10px;
}
}
}
.right-content {
font-size: 16px;
line-height: 60px;
color: #cccccc;
}
}
.gantt-chart {
height: calc(100% - 45px);
overflow: hidden;
.table {
position: relative;
height: 100%;
overflow-x: auto;
&::-webkit-scrollbar {
width: 6px;
height: 6px;
cursor: pointer;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
background-color: #d3d3d3;
}
.thead {
width: max-content;
// background-color: rgba(64, 94, 151, 1);
// height: 12%;
// border-left: 1px solid #0a769a;
.row {
display: flex;
position: relative;
.fixed_1,
.fixed_2,
.fixed_3 {
width: 200px;
position: relative;
z-index: 10;
// background: #e2ebff;
}
.fixed_1 {
border-left: 1px solid #3e5a8d;
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: #1e3862;
color: #fff;
font-size: 16px;
border-top: 1px solid #3e5a8d;
.date {
line-height: 21px;
border-left: 1px solid #3e5a8d;
.month {
border-bottom: 1px solid #3e5a8d;
}
.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 #3e5a8d;
.fixed_1,
.fixed_2,
.fixed_3 {
width: 200px;
position: relative;
background: #11306a;
z-index: 10;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.fixed_1 {
left: 0;
border-left: 1px solid #3e5a8d;
}
.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 #3e5a8d;
border-bottom: 1px solid #3e5a8d;
border-top: 1px solid #3e5a8d;
color: #fff;
font-size: 16px;
.grids {
height: 100%;
display: flex;
.grid {
position: relative;
flex: 1;
height: 100%;
&:not(:last-child) {
border-right: 1px solid #3e5a8d;
}
}
}
&: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;
cursor: pointer;
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: #fff;
text-align: center;
}
}
}
}
.no-data {
height: calc(100% - 145px);
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
color: #fff;
}
}
</style>