vue甘特图

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
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值