vue页面
<template>
<div class="content-gantt" ref="ganttRef">
<data-table-extend :key="tableData" :columns="ganttColumnList" :data="tableData" ref="$tableRef"
:scrollbar-props="{ trigger:'none' }"
:max-height="maxTableHeight"
:scroll-x="scrollWidth"
:expanded-row-keys="expandedKey"
:summary="(viewInfo?.isStatisticItem && viewInfo.isStatisticItem==='0')?createSummary:undefined"
@scroll="scrollTable"
></data-table-extend>
</div>
</template>
<script setup>
import { ref, watch, h, resolveComponent, provide, toRaw, computed, nextTick, onMounted, onUnmounted } from 'vue';
import { tableHeaderByTime } from '@/utils/table'
import { getGanttDateRange,getGanttDateRangeForPage } from '@/api/web'
import { error } from '@/extend/message';
import dayjs from 'dayjs';
const props = defineProps({
tableData:{
type:Array,
default:()=>[]
},
ganttInfo:{
type:Object,
default:null
},
maxTableHeight:{
type:Number,
default:100
},
expandedKey:{
type:Array,
default:()=>[]
},
viewInfo:{
type:Object,
default:null
},
parentColumnList:{
type:Array,
default:()=>[]
}
})
const ganttColumnList = ref([])
const scrollWidth = ref(0)
const $tableRef = ref(null)
const ganttRef = ref(null)
// 获取当前时间所在位置
const getCurrentTime = (info)=>{
let left = 0
let isSame = false
for(let i in ganttColumnList.value){
let item = ganttColumnList.value[i]
let same = dayjs().isSame(item.key,'month')
if(same){
isSame = true
if(info.timeDimension == 'day'){
let d = dayjs().format('D')-1
let w = d*(item.width/item.day)
left+=w
}
break;
} else {
left+=item.width
}
}
if(!isSame){
return
}
nextTick(()=>{
scrollLeft.value = left
$tableRef.value.scrollTo({
top:0,
left:left
})
})
}
// 获取表头
const getColumnList = async (info,dateRang)=>{
ganttColumnList.value = []
scrollWidth.value = 0
let isTitle = false
for (let key in props.parentColumnList) {
let row = props.parentColumnList[key]
if(row?.children && row.children.length>0){
isTitle = true
break;
}
}
if(isTitle && info.timeDimension == 'month') {
info.class = 'th-height'
}
if(info && info?.ganttOpen){
nextTick(()=>{
let timeData = tableHeaderByTime(info,dateRang,ganttRef.value.clientWidth)
ganttColumnList.value = [...timeData.header]
getCurrentTime(info)
scrollWidth.value = timeData.totleWidth
})
}
}
// 获取开始和结束时间
const ganttDateRange = async (info)=>{
try {
if(props.viewInfo?.evn && props.viewInfo.evn == 'page'){
let params = {
fixFilter:props.viewInfo.filter,
filter:null,
mode:{ganttData:{
...info
}}
}
let { data } = await getGanttDateRangeForPage(props.viewInfo.id,params)
if(data.min && data.max){
getColumnList(info,data)
}
} else {
let { data } = await getGanttDateRange(props.viewInfo.id)
if(data.min && data.max){
getColumnList(info,data)
}
}
} catch (e) {
error(e.msg)
}
}
// 有统计栏
const createSummary = ()=>{
let summaryRow = {};
ganttColumnList.value.map((element,i)=>{
if(element.children){
element.children.map(item=>{
summaryRow[item.key] = {
value:'\u3000',
colSpan:1
}
})
} else {
summaryRow[element.key] = {
value:'\u3000',
colSpan:1
}
}
})
return summaryRow
}
watch(()=>props.ganttInfo,(val,oldVal)=>{
ganttDateRange(val)
},{immediate:true})
const $emit = defineEmits(['scrollingTable'])
const scrollLeft = ref(0)
// 表格滚动
const scrollTable = (e)=>{
scrollLeft.value = e.target.scrollLeft
$emit('scrollingTable',e.target.scrollTop)
}
// 左边滚动右边同步滚动
const scrollingSet = (top)=>{
if($tableRef.value){
$tableRef.value.scrollTo({
top:top,
left:scrollLeft.value
})
}
}
defineExpose({ scrollingSet, ganttDateRange })
</script>
<style lang="scss" scoped>
.content-gantt{
width: 100%;
position: relative;
:deep(.n-data-table){
.n-scrollbar-rail.n-scrollbar-rail--vertical{
.n-scrollbar-rail__scrollbar{
background-color:transparent
}
}
.n-data-table-indent{
display: none;
}
.n-data-table-expand-trigger{
display: none;
}
// .n-data-table-td--summary{
// height: 42px;
// }
}
}
</style>
js代码
// 获取开始结束所有的月份
export function getMonthTime (startDate, endDate){
let yearMonthArr = []
let sYear = dayjs(startDate).year()
let eYear = dayjs(endDate).year()
let sMonth = dayjs(startDate).month()+1
let eMonth = dayjs(endDate).month()+1
// 需要判断当前结束日期是否是一年中的最后一个月
// 需要多增加一个月以便于显示文字
if(eMonth>=12){
eMonth = 1
eYear = eYear+1
} else {
eMonth = eMonth+1
}
for(let y=sYear; y<=eYear; y++){
let st = 1
let et = 12
if(y == sYear){
st = sMonth
}
if(y == eYear){
et = eMonth
}
for(let s=st; s<=et; s++){
let m = `${y}-${s}`
let dayNum = dayjs(m).daysInMonth()
yearMonthArr.push({
title:m,
key:m,
day:dayNum,
className:'ta-padding',
width:dayNum*46
})
}
}
return yearMonthArr
}
// 天渲染
export function renderDay(row,time,config){
let start = row[config.startTime]
let end = row[config.endTime]
if(!start || !end){
return h('div',{},[
h('span',{},'\u3000')
])
}
let timeS = convertToTimestamp(start)
let timeE = convertToTimestamp(end)
if(timeE<timeS){
return h('div',{},[
h('span',{},'\u3000')
])
}
let sameS = dayjs(time).isSame(start,'day')
let number = dayjs(end).diff(start,'day')+1
if(sameS){
let childrenDom = []
let style = {
backgroundColor:config.backgroundColor
}
if(config.showTime){
childrenDom.push(h('span',{},number))
}
if(row?.parentNode){
childrenDom.push(h('div',{ class:'mask-color',style:"opacity:0.6"}))
}
if(config?.title){
let title = row[config.title]
if(row[config.title+'Name'] !== undefined){
title = row[config.title+'Name']
}
if(title){
childrenDom.push(h(resolveComponent('n-ellipsis'),{class:'span-title'},()=>title))
}
}
return h('div',{class:'content-color',style:style}, [...childrenDom])
}
return null
}
// 天合并
export function rowSpanTime(row,time,config){
let start = row[config.startTime]
let end = row[config.endTime]
if(!start || !end){
return 1
}
let sameS = dayjs(time).isSame(start,'day')
if(sameS){
return dayjs(end).diff(start,'day')+1
}
}
// 根据日期获取中间有多少天
export function getDaysInCurrentMonth(time,config) {
let day = dayjs(time.key).daysInMonth()
let dayArr = []
let nowDay = dayjs()
for(let i=1; i<=day; i++){
dayArr.push({
title:()=>{
let same = nowDay.isSame(`${time.key}-${i}`,'day')
if(same){
return h('span',{ class:'now-day' },[
i,
h(resolveComponent('icon'),{ type:'star-s',fill:true})
])
} else {
return i
}
},
currentDay:i,
key:`${time.key}-${i}`,
day:time.day,
align:'center',
width:46,
className:'ta-padding',
colSpan:(row)=>rowSpanTime(row,`${time.key}-${i}`,config),
render:(row) => renderDay(row,`${time.key}-${i}`,config),
cellProps:(rowData,rowIndex)=>{
let same = nowDay.isSame(`${time.key}-${i}`,'day')
if(same && rowIndex != -1){
return {style:{backgroundColor:'#ffd6d6',borderRight:'1px solid #F53F3F'}}
}
}
})
}
return dayArr
}
//渲染月
export function renderMonth (row,time,config){
let start = row[config.startTime]
let end = row[config.endTime]
if(!start || !end){
return h('div',{},[
h('span',{ },'\u3000')
])
}
let timeS = convertToTimestamp(start)
let timeE = convertToTimestamp(end)
if(timeE<timeS){
return h('div',{},[
h('span',{ },'\u3000')
])
}
// 当前月数与开始月数是否相同
let sameS = dayjs(time.key).isSame(start,'month')
if(sameS){
// 获取跨度几个月
let monthS = dayjs(start).month()+1
let monthE = dayjs(end).month()+1
let yearS = dayjs(start).year()
let yearE = dayjs(end).year()
let endDay = null
let spanChild = []
let number = 0
for(let y=yearS; y<=yearE; y++){
let s = 1;
let max = 12;
if(y == yearS){
s = monthS
}
if(y == yearE){
max = monthE
}
for(let m=s; m<=max; m++){
let obj = {
title:`${y}-${m}`,
key:`${y}-${m}`,
day:dayjs(`${y}-${m}`).daysInMonth()
}
let days = getDaysInCurrentMonth(obj,config)
for(let j=0; j<days.length; j++){
let info = days[j]
let style = {}
let dayBetween = dayjs(info.key ).isBetween(start, dayjs(end))
let sameS = dayjs(info.key).isSame(start,'day')
let sameE = dayjs(info.key).isSame(end,'day')
if(dayBetween || sameE || sameS){
style = {
backgroundColor:config.backgroundColor
}
endDay = info
number++
}
spanChild.push(h('span',{ class:'span-dom',style:style}))
}
}
}
let childrenDom = []
if(config.showTime && endDay){
let enIndex = endDay.day-endDay.currentDay
let index = spanChild.length - enIndex -1
if(index>=0){
if(number>5){
spanChild[index].props.innerHTML = `<span class="span-number">${number}</span>`
} else {
spanChild[index].props.innerHTML = `<span class="span-number" style="right:0">${number}</span>`
}
}
}
if(row?.parentNode){
childrenDom.push(h('div',{ class:'mask-color',style:"opacity:0.6"}))
}
if(config?.title){
let title = row[config.title]
if(row[config.title+'Name'] !== undefined){
title = row[config.title+'Name']
}
if(title && endDay){
let enIndex = endDay.day-endDay.currentDay
let index = spanChild.length - enIndex
if(enIndex==0){
let right = 80
childrenDom.push(h(resolveComponent('n-ellipsis'),{class:'span-title',style:{right:-right+'px'}},()=>title))
} else{
spanChild[index] = h('span',{ ...spanChild[index].props },[
h(resolveComponent('n-ellipsis'),{class:'span-title'},()=>title)
])
}
}
}
return h('div',{ class:'gantt-flex'},[
h('div',{ class:'gantt-td'},[...spanChild]),
...childrenDom
])
}
}
// 月合并
export function colSpanMonth (row,time,config){
let start = row[config.startTime]
let end = row[config.endTime]
if(!start || !end){
return 1
}
let sameS = dayjs(time.key).isSame(start,'month')
if(sameS){
let monthS = dayjs(start).month()+1
let monthE = dayjs(end).month()+1
let yearS = dayjs(start).year()
let yearE = dayjs(end).year()
if(yearS == yearE){
return monthE - monthS + 1
} else {
let span = 0
for(let y=yearS; y<=yearE; y++ ){
if(y==yearS){
let sm = 13-monthS
span = span + sm
} else if(y == yearE){
let em = monthE
span = span + em
} else {
span = span + 12
}
}
return span
}
}
}
// 渲染周
export function renderWeek(row,time,week,config){
let start = row[config.startTime]
let end = row[config.endTime]
if(!start || !end){
return h('div',{},[
h('span',{ },'\u3000')
])
}
let timeS = convertToTimestamp(start)
let timeE = convertToTimestamp(end)
if(timeE<timeS){
return h('div',{},[
h('span',{ },'\u3000')
])
}
let monthS = dayjs(start).month()+1
let monthE = dayjs(end).month()+1
let yearS = dayjs(start).year()
let yearE = dayjs(end).year()
let dayE = dayjs(end).format('D')
// 获取当前周开始天和结束天日期
let eW = week*7
let sW = 0
if(eW>time.day){
eW = time.day
sW = ((week-1)*7)+1
} else {
sW =eW-7+1
}
let weekDayS = `${time.key}-${sW}`
let weekDayE = `${time.key}-${eW}`
// 判断开始时间是否在这个日期范围内
let dayBetween = dayjs(start).isBetween(weekDayS, dayjs(weekDayE))
let sameS = dayjs(start).isSame(weekDayS,'day')
let sameE = dayjs(start).isSame(weekDayE,'day')
if(dayBetween || sameS || sameE){
let spanChild = []
let endW = 0
let number = 0
let endDay = null
for(let y=yearS; y<=yearE; y++ ){
let sm = 1
let em = 12
if(y == yearS){
sm = monthS
}
if(y == yearE){
em = monthE
}
for(let m=sm; m<=em; m++){
let day = `${y}-${m}`
let dayNum = dayjs(day).daysInMonth()
let sameMS = dayjs(day).isSame(start, 'month')
let sameME = dayjs(day).isSame(end, 'month')
let sd = 1;
let ed = dayNum
if(sameMS){
sd = sW
}
if(sameME){
let dE = dayjs(end).format('D')
let w = Math.ceil(dE*1 / 7);
let t = w*7
endW = w
if(t<=dayNum){
ed = t
}
}
for(let d=sd;d<=ed;d++){
let toDay = `${y}-${m}-${d}`
let style = {}
let toDayBetween = dayjs(toDay).isBetween(start, dayjs(end))
let toDaySameS = dayjs(toDay).isSame(start,'day')
let toDaySameE = dayjs(toDay).isSame(end,'day')
if(toDayBetween || toDaySameS || toDaySameE){
style = {
backgroundColor:config.backgroundColor
}
endDay = {
totle:ed,
day:d
}
number++
}
spanChild.push(h('span',{ class:'span-dom', style:style}))
}
}
}
let childrenDom = []
if(config.showTime && endDay){
let enIndex = endDay.totle-endDay.day
let index = spanChild.length - enIndex - 1
if(index>=0){
if(number>3){
spanChild[index].props.innerHTML = `<span class="span-number">${number}</span>`
} else {
spanChild[index].props.innerHTML = `<span class="span-number" style="right:0">${number}</span>`
}
}
}
if(row?.parentNode){
childrenDom.push(h('div',{ class:'mask-color',style:"opacity:0.6"}))
}
if(config?.title){
let title = row[config.title]
if(row[config.title+'Name'] !== undefined){
title = row[config.title+'Name']
}
if(title && endDay){
let enIndex = endDay.totle-endDay.day
let index = spanChild.length - enIndex
if(enIndex==0){
let right = 90
childrenDom.push(h(resolveComponent('n-ellipsis'),{class:'span-title',style:{right:-right+'px'}},()=>title))
} else{
spanChild[index] = h('span',{ ...spanChild[index].props },[
h(resolveComponent('n-ellipsis'),{class:'span-title'},()=>title)
])
}
}
}
return h('div',{ class:'gantt-flex'},[
h('div',{ class:'gantt-td'},[...spanChild]),
...childrenDom
])
}
}
// 合并周
export function colSpanWeek (row,time,week,config){
let start = row[config.startTime]
let end = row[config.endTime]
if(!start || !end){
return 1
}
let monthS = dayjs(start).month()+1
let monthE = dayjs(end).month()+1
let yearS = dayjs(start).year()
let yearE = dayjs(end).year()
let dayE = dayjs(end).format('D')
// 获取当前周开始天和结束天日期
let eW = week*7
let sW = 0
if(eW>time.day){
eW = time.day
sW = ((week-1)*7)+1
} else {
sW =eW-7+1
}
let weekDayS = `${time.key}-${sW}`
let weekDayE = `${time.key}-${eW}`
// 判断开始时间是否在这个日期范围内
let dayBetween = dayjs(start).isBetween(weekDayS, dayjs(weekDayE))
let sameS = dayjs(start).isSame(weekDayS,'day')
let sameE = dayjs(start).isSame(weekDayE,'day')
if(dayBetween || sameS || sameE){
if(monthE == monthS){
return Math.ceil(dayE / 7)-week+1
} else {
let span = 1
for(let y=yearS; y<=yearE; y++ ){
let sm = 1
let em = 12
if(y == yearS){
sm = monthS
}
if(y == yearE){
em = monthE
}
for(let m=sm; m<=em; m++){
let day = `${y}-${m}`
let sameMS = dayjs(day).isSame(start, 'month')
let sameME = dayjs(day).isSame(end, 'month')
let dayNum = dayjs(day).daysInMonth()
let weekNum = Math.ceil(dayNum / 7);
// 开始日期与当前月相同
if(sameMS){
let cha = weekNum-week
span = span + cha
} else if(sameME){
let dE = dayjs(end).format('D')
let w = Math.ceil(dE*1 / 7);
span = span + w
} else {
span = span+weekNum
}
}
}
return span
}
}
}
// 获取周数据
export function getWeekInCurrentMonth(time,config,widthM){
let day = time.day
let week = Math.ceil(day / 7);
let weekChildren = []
let widthW = Math.floor(widthM/week)
if(widthW<60){
widthW = 60
}
for(let i=1; i<=week; i++){
weekChildren.push({
title:()=>{
let d = dayjs().format('D')
let w = Math.ceil(d / 7)
let same = dayjs().isSame(time.key,'month')
if(w==i && same){
let dayNum = dayjs().daysInMonth()
let max = w*7
let sp = 7
if(max>dayNum){
max = dayNum
sp = dayNum - (max-7)
}
let c = sp - (max - d)
let t = ((widthW/sp)*(c-1))+((widthW/sp)/2)
let lineStyle = {
left:t+'px'
}
return h('span',{ class:'now-day' },[
`第${i}周`,
h(resolveComponent('icon'),{ type:'star-s',fill:true}),
h('span',{class:'line-table',style:lineStyle})
])
} else {
return `第${i}周`
}
},
width:widthW,
key:`${time.key}-${i}`,
className:'ta-padding week',
colSpan: (rowData)=>colSpanWeek(rowData,time,i,config),
render:(rowData)=>renderWeek(rowData,time,i,config),
cellProps:(rowData,rowIndex)=>{
let d = dayjs().format('D')
let w = Math.ceil(d / 7)
let same = dayjs().isSame(time.key,'month')
if(w==i && same && rowIndex != -1){
return {style:{backgroundColor:'#ffd6d6',borderRight:'1px solid #F53F3F'}}
}
}
})
}
return weekChildren
}
// 根据开始日期和结束日期获取表头
export function tableHeaderByTime (row,dateRang,clientWidth){
let oneHeader = getMonthTime(dateRang.min,dateRang.max)
let totleWidth = 0
for(let i=0; i<oneHeader.length; i++){
if(row.timeDimension == 'day'){
// 获取天数据
oneHeader[i].children = getDaysInCurrentMonth(oneHeader[i],row)
totleWidth+=oneHeader[i].width
} else if(row.timeDimension == 'week') {
let widthM = Math.floor(clientWidth/oneHeader.length)
// 获取周数据
let weekChildren = getWeekInCurrentMonth(oneHeader[i],row,widthM)
oneHeader[i].children = [...weekChildren]
oneHeader[i].width = weekChildren.length*60
totleWidth+= oneHeader[i].width
} else {
let width = Math.floor(clientWidth/oneHeader.length)
if(width<80){
width = 80
}
// 获取月数据
oneHeader[i].render = (rowData)=>renderMonth(rowData,oneHeader[i],row)
oneHeader[i].colSpan = (rowData)=>colSpanMonth(rowData,oneHeader[i],row)
oneHeader[i].className = 'ta-padding week'
oneHeader[i].title = ()=>{
let same = dayjs().isSame(oneHeader[i].key,'month')
if(same){
let d = dayjs().format('D')
let t = (width/oneHeader[i].day)*d
let lineStyle = {
left:t+'px'
}
return h('span',{ class:'now-day' },[
oneHeader[i].key,
h(resolveComponent('icon'),{ type:'star-s',fill:true}),
h('span',{class:'line-table',style:lineStyle})
])
} else {
return oneHeader[i].key
}
}
oneHeader[i].cellProps = (rowData,rowIndex)=>{
let same = dayjs().isSame(oneHeader[i].key,'month')
if(same && rowIndex != -1){
return {style:{backgroundColor:'#ffd6d6',borderRight:'1px solid #F53F3F'}}
}
}
oneHeader[i].width = width
if(row?.class){
oneHeader[i].className = `${oneHeader[i].className} ${row.class}`
}
}
}
return {
header:oneHeader,
totleWidth:totleWidth
}
}