2022-08-29 16:31:45 +08:00

494 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>
<Card :title="title" showRefresh @query="handleQuery">
<div class="container">
<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">完成日期</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.pName">
<div class="row">
<div class="td" @click="handleOpen(i)">{{ p.pName }}</div>
<div class="td">{{ p.startTime }}</div>
<div class="td">{{ p.endTime }}</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="p.pName + '-' + 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.pName">
<div class="td">{{ child.pName }}</div>
<div class="td">{{ child.startTime }}</div>
<div class="td">{{ child.endTime }}</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="child.pName + '-' + 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>
</Card>
</template>
<script>
import Card from '../components/Card.vue'
export default {
components: { Card },
props: {
title: {
type: String,
default: ''
}
},
created() {
console.log(this.getDays('2022/09'), '到')
},
mounted() {
this.setGantts()
},
data() {
return {
// 头部行
headerList: [
{ label: '阶段完成', color: '#4983fc' },
{ label: '任务完成', color: '#4fd389' },
{ label: '进行中', color: '#f1d520' },
{ label: '逾期', color: '#fc6f8e' },
],
projects: [
{
pName: '地基与基础工程',
startTime: '2020/04/06',
endTime: '2020/05/09',
delay: 10,
status: 0,
gantts: [],
// gantts: [{ left: '420px', width: '100px', status: '' }],
children: [
{ pName: '无支护土方工程', startTime: '2020/04/06', endTime: '2020/04/09', status: 1 },
{ pName: '有支护土方工程', startTime: '2020/04/10', endTime: '2020/05/08', status: 3 }
]
},
{ pName: '主体结构', startTime: '2020/03/12', endTime: '2020/06/04', status: 2 },
{ pName: '建筑装饰装修', startTime: '2020/04/21', endTime: '2020/07/30', status: 1 }
],
openedIndex: 9999,
colors: ['#4C87FF', '#54CF8E', '#F2D026', '#FF6C7F'],
tooltipsStyle: {
display: 'none',
left: 0,
top: 0
},
scrollLeft: 0,
scrollTimer: 0
}
},
methods: {
/** 查询 */
handleQuery() {
this.$nextTick(() => {
this.setGantts()
})
},
getGanttStyle(project) {
let { startTime, endTime, pName } = project
const startArr = startTime.split('/')
const endArr = endTime.split('/')
let startRef = null
let endRef = null
if (!(startArr[2] % 2)) {
const day = startArr[2] - 1
startArr[2] = day < 10 ? '0' + day : day
startTime = startArr.join('/')
console.log(startTime, 'xxx')
}
if (!(endArr[2] % 2)) {
const day = endArr[2] - 1
endArr[2] = day < 10 ? '0' + day : day
endTime = endArr.join('/')
console.log(endTime, 'xxx')
}
startRef = this.$refs[`${pName}-${startTime}`][0]
const startLeft = startRef.offsetLeft
endRef = this.$refs[`${pName}-${endTime}`][0]
const endWidth = endRef.offsetWidth
const endLeft = endRef.offsetLeft
const ganttWidth = endLeft - startLeft + endWidth
return { left: startLeft + 'px', width: ganttWidth + 'px' }
},
setGantts() {
const configGantts = projects => {
projects.map(project => {
let { endTime, pName, gantts } = project
const gantt = this.getGanttStyle(project)
gantt.background = this.colors[project.status]
if (gantts) {
gantts.push(gantt)
} else {
project.gantts = [gantt]
}
if (project.delay) {
const delayStartTime = this.increaseDate(endTime, 1)
const delayEndTime = this.increaseDate(endTime, project.delay)
const gantt = this.getGanttStyle({ startTime: delayStartTime, endTime: delayEndTime, pName })
gantt.background = this.colors[3]
gantts.push(gantt)
console.log(gantt, '是的发生的范范')
}
// debugger
project.children && configGantts(project.children)
})
}
configGantts(this.projects)
},
increaseDate(date, delay) {
const timestamp = new Date(date).getTime() + (delay + 1) * 1000 * 60 * 60 * 24
return new Date(timestamp)
.toISOString()
.replace(/-/g, '/')
.slice(0, 10)
},
getDays(date) {
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
})()
},
handleOpen(index) {
if (index === this.openedIndex) {
this.openedIndex = 9999
} else {
this.openedIndex = index
}
},
handleScroll(e) {
if (this.scrollTimer) {
clearTimeout(this.scrollTimer)
}
this.scrollTimer = setTimeout(() => {
this.scrollLeft = e.target.scrollLeft
this.scrollTimer = null
}, 100)
},
handleHover(e) {
const { clientX, clientY } = e
const decreaseLeft = clientX - 680
const decreaseTop = clientY - 200
this.tooltipsStyle = {
left: decreaseLeft + this.scrollLeft + 'px',
top: decreaseTop + 'px',
display: 'block'
}
},
handleLeave() {
this.tooltipsStyle = {
display: 'none'
}
}
},
computed: {
dateList() {
let dates = []
const mapDates = data => {
data.map(p => {
dates.push(p.startTime)
dates.push(p.endTime)
p.children && mapDates(p.children)
})
}
mapDates(this.projects)
dates = dates.map(date => date.slice(0, 7)).sort()
dates = [...new Set(dates)]
return dates
}
}
}
</script>
<style lang="less" scoped>
.container {
box-sizing: border-box;
padding: 0 13px;
}
.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: #fff;
font-size: 13px;
}
}
}
.right-content {
font-size: 13px;
line-height: 60px;
}
}
.gantt-chart {
height: calc(100% - 60px);
.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-bottom: 1px solid #234d5f;
.row {
display: flex;
.td {
flex-shrink: 0;
box-sizing: border-box;
height: 42px;
line-height: 42px;
background-color: #163549;
.date {
line-height: 21px;
border-left: 1px solid #234d5f;
.month {
border-bottom: 1px solid #234d5f;
}
.days {
display: flex;
.day {
flex: 1;
}
}
}
&:nth-child(1) {
padding-left: 40px;
width: 200px;
}
&:not(:nth-child(1)) {
width: 100px;
text-align: center;
}
&:nth-child(n + 4) {
width: 400px;
}
}
}
}
.tbody {
border-left: 1px solid #234d5f;
.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;
.td {
flex-shrink: 0;
box-sizing: border-box;
height: 42px;
line-height: 42px;
background-color: #0a1b2f;
border-right: 1px solid #234d5f;
border-bottom: 1px solid #234d5f;
.grids {
height: 100%;
display: flex;
.grid {
position: relative;
flex: 1;
height: 100%;
&:not(:last-child) {
border-right: 1px solid #234d5f;
}
}
}
&: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: 14px;
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>