2023-08-28 16:31:56 +08:00

944 lines
23 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="main-box">
<LeftMenu
v-model="active"
:tabs="['项目名称', '工程名称']"
:records="records"
@change-page="onCurChange"
@search="onSearchInput"
:pageable="pages"
class="leftMenu"
>
<template #default="{ data }">
<div class="leftProject" @click="onSearch(data)">
<span class="projectName">{{ data.projectName || data.engineeringName }}</span>
<div class="leftMenu_item">
<div class="leftMenu_item flx-justify-between">
<div style="margin-top: 5px" class="flx-justify-between">
<img style="margin-right: 5px" src="@/assets/images/AIwaring/dustMap.png" alt="" />
<el-tooltip effect="dark" :content="data.projectAddress || data.address" placement="top-start">
<span class="middleSize">{{ data.projectAddress || data.address }}</span>
</el-tooltip>
</div>
<!-- <div>
<img src="@/assets/images/AIwaring/报警.png" alt="" />
<span class="middleSize">今日报警次数:{{ data.todayAlarm }}</span>
</div> -->
</div>
</div>
</div>
</template>
</LeftMenu>
<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>
</div>
</div>
</template>
<script setup lang="tsx" name="ProjectSupervisionRecord">
import { computed, reactive, ref, onMounted, onBeforeMount, watch } from "vue";
import LeftMenu from "@/components/LeftMenu/LeftMenu.vue";
import { getDustprojectPage, getDustengineeringPage, bigItemGovermentAll } from "@/api/modules/goverment";
const refChart = 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 active = ref(0);
const records = ref([]);
const projects = ref([]);
const pages = ref({
pageNo: 1,
pageSize: 7,
total: 0
});
const pageable = ref({
pageNo: 1,
pageSize: 12,
total: 0
});
// 项目或者工程名字
const searchName = ref<string>("");
// {
// 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 = [];
// 搜索用的项目sn或者工程sn
const searchSn = ref("");
// 页面的项目名称和工程名称的div点击事件
const onSearch = async (params: any) => {
console.log(params);
// const { result } = await getAIQuestionPage(
// active.value === 0
// ? { projectSn: (params as ResAiProjectPage).projectSn, pageNo: pageable.value.pageNo, pageSize: pageable.value.pageSize }
// : {
// engineeringSn: (params as ResAiEngineerPage).engineeringSn,
// pageNo: pageable.value.pageNo,
// pageSize: pageable.value.pageSize
// }
// );
// pageable.value.total = Number(result.total);
active.value === 0 ? (searchSn.value = params ? params.projectSn : "") : (searchSn.value = params ? params.engineeringSn : "");
active.value === 0
? (searchName.value = params ? params.projectName : "")
: (searchName.value = params ? params.engineeringName : "");
getDataList();
};
// leftMenu页面的搜索按钮
const onSearchInput = async (params: string) => {
console.log(params);
if (active.value === 0) {
const { result } = await getDustprojectPage({ projectName: params, ...pages.value });
records.value = result.records;
} else {
const { result } = await getDustengineeringPage({ engineeringName: params, ...pages.value });
records.value = result.records;
}
};
// leftMenu页面的分页
const onCurChange = async (params: number) => {
console.log(active.value);
if (active.value === 0) {
const { result } = await getDustprojectPage({ ...pages.value, pageNo: params });
records.value = result.records;
} else {
const { result } = await getDustengineeringPage({ ...pages.value, pageNo: params });
records.value = result.records;
pages.value.total = +result.total;
}
pages.value.total = +res.result.total;
};
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 (active.value === 0) {
requestData.projectSn = searchSn.value;
} else {
requestData.engineeringSn = searchSn.value;
}
if (requestData.projectSn || requestData.engineeringSn) {
const res = await bigItemGovermentAll(requestData);
console.log(res);
if (res) {
const arr = dealArr(res.result);
projects.value = arr;
}
} else {
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;
};
// 获取项目名称分页
const getProPage = async () => {
const { result } = await getDustprojectPage(pages.value);
records.value = result.records;
records.value.map(item => {
let showGif = false;
item.showGif = showGif;
});
pages.value.total = Number(result.total);
};
// 获取工程名称分页
const getEngPage = async () => {
const { result } = await getDustengineeringPage(pages.value);
records.value = result.records;
records.value.map(item => {
let showGif = false;
item.showGif = showGif;
});
pages.value.total = +result.total;
};
onBeforeMount(() => {});
onMounted(async () => {
getDataList();
await getProPage();
onSearch(records.value[0]);
searchSn.value = (records.value as ResAiProjectPage[])[0].projectSn;
searchName.value = (records.value as ResAiProjectPage[])[0].projectName;
});
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) => {
console.log(e);
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 - 680;
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 = () => {
console.log(777);
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;
});
watch(
() => active.value,
async (value: number) => {
pages.value.pageNo = 1;
pages.value.total = 0;
// console.log(value);
if (value === 0) {
await getProPage();
onSearch(records.value[0]);
} else {
await getEngPage();
onSearch(records.value[0]);
}
},
{
deep: true
}
);
</script>
<style scoped lang="scss">
.leftMenu {
width: 300px;
border-radius: 8px;
// width: 290px;
// 页面的项目工程
:deep(.item) {
height: 78px !important;
}
:deep(.content) {
height: calc(100% - 150px) !important;
min-height: auto;
}
.leftProject {
// padding: 5px 8px;
color: #333333;
.projectName {
display: block;
width: 100%;
overflow: hidden;
// font-weight: 700;
font-family: "siyuan_Medium";
text-overflow: ellipsis;
white-space: nowrap;
}
.leftMenu_item {
display: flex;
justify-content: space-between;
overflow: hidden;
.middleSize {
display: inline-block;
font-size: 16px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
}
}
.gantt-box {
box-sizing: border-box;
width: calc(100% - 320px);
min-width: 1120px;
height: 100%;
padding: 18px;
background: #ffffff;
margin-left: 20px;
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>