使用dthmlGantt封装甘特图展示组件(vue2)

1,509 阅读2分钟

实现效果

需求甘特图

2231663826172_.pic.jpg

任务甘特图

image.png

组件部分代码

<template>
    <div class="gantt-box" style="height: 100%">
        <div class="ganttComponent" :id="refName" :ref="refName" v-if="this.tableData.data.length>0"></div>
        <div class="full-screen-box el-icon-full-screen" v-if="this.tableData.data.length>0 && allowFullScreen" @click="fullScreen"></div>
        <div class="no-data" v-else>
            <p style="font-size: 100px;margin-bottom: 12px"><nav-icon icon="public-empty-small"></nav-icon></p>
            <p style="font-size: 14px">{{ $t('public.noData') }}</p>
        </div>
    </div>
</template>

<script>
import { gantt } from 'dhtmlx-gantt';
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
import ganttFormat from '@/components/rainbowGantt/ganttFormat';

export default {
    name: 'ganttComponent',
    data() {
        return {
            // tableData: {
            //     data: [
            //         {
            //             id: 1,
            //             name: '需求1的名字很长长到离谱哈哈啊哈哈',
            //             text: '研发中',
            //             priority: 'P1',
            //             status: 0,
            //             start_date: '01-04-2013',
            //             end_date: '01-05-2013',
            //             schedule_test_time: '04-04-2013', // 计划提测时间
            //             schedule_check_time: '10-04-2013', // 计划验收时间
            //             color: '#73A5BD',
            //             assignedTo: '🐒'
            //         },
            //     ],
            // },
        };
    },
    props: {
        tableData: {
            type: Object,
            default: () => ({
                data: [],
            })
        },
        allowFullScreen: {
            type: Boolean,
            default: false
        },
        refName: {
            type: String,
            default: 'ganttComponent'
        },
        isTaskGantt: {
            type: Boolean,
            default: false
        }
    },
    methods: {
        fullScreen() {
            if (!gantt.getState().fullscreen) {
                // expanding the gantt to full screen
                const ganttComponent = document.getElementById(this.refName);
                ganttComponent.style.background = '#FFF';
                gantt.expand();
            } else {
                // collapsing the gantt to the normal mode
                gantt.collapse();
            }
        },
        initGantt() {
            const { taskFormat, demandFormat } = ganttFormat;
            gantt.config.readonly = true; // 只读
            gantt.config.columns = this.isTaskGantt ? taskFormat.columns : demandFormat.columns;
            // 设置表头的高度
            gantt.config.scale_height = 60;
            // 设置行高
            gantt.config.row_height = 48;
            gantt.attachEvent('onCollapse', () => {
                // any custom logic here
                const ganttComponent = document.getElementById(this.refName);
                if (!ganttComponent) return;
                ganttComponent.style.background = 'transparent';
            });
            // 重置条状图样式
            gantt.config.type_renderers[gantt.config.types.task] = (task) => {
                const mainEl = document.createElement('div');
                mainEl.setAttribute(gantt.config.task_attribute, task.id);
                const size = gantt.getTaskPosition(task);
                mainEl.innerText = task.text;
                mainEl.className = 'custom-project';
                mainEl.style.left = `${size.left }px`;
                mainEl.style.top = `${size.top + 7}px`;
                mainEl.style.width = `${size.width }px`;
                mainEl.style.background = task.color;
                return mainEl;
            };
            // 配置插件
            gantt.plugins({ tooltip: true, fullscreen: true, marker: true, });
            // 配置marker
            const today = new Date();
            gantt.addMarker({
                start_date: today,
                css: 'today',
                text: 'Today',
            });
            // 自定义tooltip内
            const toolTipFunction = this.isTaskGantt ? taskFormat.tooltip : demandFormat.tooltip;
            gantt.templates.tooltip_text = (start, end, task) => toolTipFunction(start, end, task);
            gantt.config.layout = {
                css: 'gantt_container',
                cols: [
                    {
                        width: this.isTaskGantt ? taskFormat.tableWidth : demandFormat.tableWidth,
                        rows: [
                            {
                                view: 'grid', scrollX: 'gridScroll', scrollable: true, scrollY: 'scrollVer'
                            },
                            { view: 'scrollbar', id: 'gridScroll' }
                        ]
                    },
                    { resizer: true, width: 1 },
                    {
                        rows: [
                            { view: 'timeline', scrollX: 'scrollHor', scrollY: 'scrollVer' },
                            { view: 'scrollbar', id: 'scrollHor' }
                        ]
                    },
                    { view: 'scrollbar', id: 'scrollVer' }
                ]
            };
            const zoomConfig = {
                // 设置右侧时间轴格式及宽高
                levels: [
                    {
                        name: 'month',
                        scale_height: 60,
                        min_column_width: 60,
                        scales: [
                            { unit: 'month', format: '%Y/%m' },
                            { unit: 'day', step: 1, format: '%d' }
                        ]
                    },
                ]
            };
            // 给年月展示框加类名
            gantt.templates.scale_cell_class = () => 'month-box';
            gantt.ext.zoom.init(zoomConfig);
            const ganttComponent = document.getElementById(this.refName);
            if (!ganttComponent) return;
            gantt.init(ganttComponent);
            gantt.parse(this.tableData);
        },
        nameClick(e) {
            if (!e) return;
            const target = e.target;
            const classNameList = target.classList;
            if (![].includes.call(classNameList, 'gantt-name-click')) return;
            const storyName = target.innerText;
            this.$emit('getTasksBelongDemand', storyName);
        },
    },
    watch: {
        'tableData.data': {
            immediate: true,
            deep: true,
            handler(val) {
                if (val.length === 0 && gantt.getState().fullscreen) {
                    gantt.collapse();
                }
                this.$nextTick(() => {
                    // 清空后再添加
                    gantt.clearAll();
                    this.initGantt();
                });
            }
        }
    },
    mounted() {
        this.initGantt();
        window.addEventListener('click', this.nameClick);
    },
    beforeDestroy() {
        window.removeEventListener('click', this.nameClick);
    },
};
</script>

<style scoped lang="less">
@baseBackground:transparent;
@deepBackground:#EEF1F4;
@selectedBackground:#E2E5EA;
@fontBoldColor:#323438;
@baseFontColor:#62666D;
@hoverBackground:#E2E5EA;
@borderColor:#D4D7E5;
.gantt-box{
    position: relative;
    .full-screen-box{
        cursor: pointer;
        position: absolute;
        right: 20px;
        bottom: 20px;
        font-size: 40px;
    }
}
.ganttComponent{
    position: relative;
    min-height: 560px;
    height:calc(100% - 10px);
    padding-top: 10px;
    /deep/.gantt_layout_cell{
        background: @baseBackground;
    }
    /deep/.gantt_layout_cell_border_bottom{
        border-bottom: none;
    }
    /deep/.gantt_layout_cell_border_top{
        border-top: none;
    }
    /deep/.gantt_layout_cell_border_right{
        border-right: none;
    }
    /deep/.gantt_layout_cell_border_left{
        border-left: none;
    }
    /deep/.gantt_row{
        border-bottom: none;
        background: @baseBackground;
        &:hover{
            background-color: transparent;
        }
    }
    /deep/.gantt_grid_data .gantt_cell{
        color: @baseFontColor;
        font-size: 14px;
    }
    /deep/.custom-project{
        position: absolute;
        height: 32px;
        line-height: 32px;
        color: #fff;
        text-align: center;
        border: none !important;
        border-radius: 4px;
        font-weight: 500;
        font-size: 14px;
        cursor: pointer;
    }
    /deep/.gantt_grid_scale .gantt_grid_head_cell{
        color: @fontBoldColor;
        font-size: 14px;
        font-weight: 600;
        &:first-of-type{
            text-align: left;
            padding-left: 25px;
        }
    }
    /deep/.gantt_grid_scale{
        background: @baseBackground;
        border: none;
    }
    /deep/.gantt_task_scale{
        background: @baseBackground;
        border: none;
    }
    /deep/.gantt_scale_line{
        border: none;
    }
    /deep/.gantt_scale_cell{
        //border: none;
        border-bottom: 1px solid @borderColor;
        border-right: 1px solid @borderColor;
        border-top: 1px solid @borderColor;
        color: #62666D;
        font-size: 14px;
        &:first-of-type{
            border-left:  1px solid @borderColor;
        }
        &.month-box{
            text-align: left;
            border:none
        }
    }
    /deep/.gantt_task_row{
        border: none;
        background: @baseBackground;
    }
    /deep/.gantt_task_cell{
        border: none;
    }
    /deep/.gantt_task_row.gantt_selected{
        background: @selectedBackground !important;
    }
    /deep/.gantt_row.gantt_selected{
        background: @selectedBackground !important;
    }
    /deep/.gantt_task_content{
        font-size: 14px;
        font-weight: bold;
    }
    /deep/.gantt_task_inline_color.gantt_selected{
        box-shadow: none !important;
    }
}

</style>
<style lang="less">
.gantt_tooltip{
    padding: 20px;
    padding-bottom: 13px;
    box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
    border-radius: 4px;
}
.gantt_tooltip_task_name{
    font-size: 16px;
    font-weight: 500;
    margin-bottom: 7px;
    color: #373737;
}
.gantt_tooltip_task_content{
    font-size: 14px;
    height: 28px;
    line-height: 28px;
    color: #62666D;
}
.gantt-name-box{
    box-sizing: border-box;
    display: block;
    width: 100%;
    text-align: left;
    padding-left: 19px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.gantt-name-click{
    cursor: pointer;
    &:hover{
        color: @baseBlue;
    }
}
#priority-box{
    margin-top: 12px;
    display: block;
    font-size: 14px;
    height: 24px;
    line-height: 24px;
    padding: 0 16px;
    border-radius: 12px;
}
.priority-P1{
    color: #FF3900;
    background: #FFF1ED;
}
.priority-P2{
    color: #FF8400;
    background: #FFF2E5;
}
.priority-P3{
    color: #285DE2;
    background: #E6EDFF;
}
.priority-P4{
    color: #4FA401;
    background: #F3FFE8;
}
.gantt-error{
    display: none;
}
</style>

数据格式化代码

const priorityStyle = (story) => {
    const priority = story.priority;
    if (!priority) {
        return '';
    }
    const className = `priority-P${story.priority}`;
    return `<span class=${className} id="priority-box">P${story.priority}</span>`;
};
const demandFormat = {
    tooltip: (start, end, task) => `<p class="gantt_tooltip_task_name">${task.name}</p>
                <p class="gantt_tooltip_task_content">持续时间:${task.duration}天</p>
                <p class="gantt_tooltip_task_content">当前进度:${task.progress * 100}%</p>
                <p class="gantt_tooltip_task_content">计划提测时间:${task.planTestTime || ''}</p>
                <p class="gantt_tooltip_task_content">实际提测时间:${task.actualTestTime}</p>`,
    columns: [
        {
            name: 'name',
            label: '需求名称',
            align: 'left',
            width: 120,
            template: obj => `<span class="gantt-name-box gantt-name-click">${obj.name}</span>`
        },
        {
            name: 'priority',
            label: '优先级',
            align: 'center',
            width: 60,
            template: obj => priorityStyle(obj)
        },
        {
            name: 'start_date',
            label: '开始时间',
            align: 'center',
            width: 100,
            template: obj => `<span>${obj.beginTime}</span>`
        },
        {
            name: 'end_date',
            label: '结束时间',
            align: 'center',
            width: 100,
            template: obj => (obj.isOvertime ? `<span style="color: red">${obj.endTime}</span>` : `<span>${obj.endTime}</span>`)
        },
        {
            name: 'planTestTime', label: '计划提测时间', align: 'center', width: 100
        },
        {
            name: 'planAcceptanceTime', label: '计划验收时间', align: 'center', width: 100
        },
        {
            name: 'assignedTo', label: '处理人', align: 'center', width: 100
        },
    ],
    tableWidth: 680
};
const taskFormat = {
    tooltip: (start, end, task) => `<p class="gantt_tooltip_task_name">${task.name}</p>
                <p class="gantt_tooltip_task_content">实际开始时间:${task.beginTime}</p>
                <p class="gantt_tooltip_task_content">实际结束时间:${task.endTime}</p>`,
    columns: [
        {
            name: 'name',
            label: '任务名称',
            align: 'left',
            width: 120,
            template: obj => `<span class="gantt-name-box">${obj.name}</span>`
        },
        {
            name: 'priority',
            label: '优先级',
            align: 'center',
            width: 60,
            template: obj => priorityStyle(obj)
        },
        {
            name: 'start_date',
            label: '开始时间',
            align: 'center',
            width: 100,
            template: obj => `<span>${obj.beginTime}</span>`
        },
        {
            name: 'end_date',
            label: '结束时间',
            align: 'center',
            width: 100,
            template: obj => (obj.isOvertime ? `<span style="color: red">${obj.endTime}</span>` : `<span>${obj.endTime}</span>`)
        },
        {
            name: 'assignedTo', label: '处理人', align: 'center', width: 100
        },
    ],
    tableWidth: 480
};
export default { demandFormat, taskFormat };

遇到的问题

本来要做的效果是点击需求名称弹窗展示任务的甘特图,但是dhtmlGantt貌似不支持一个页面同时渲染,两个甘特图,实现出来的效果是需求的甘特图变为空,只渲染任务的甘特图。后来无奈改变成点击需求名称刷新页面的方式实现。如果有懂得大佬希望能指点一下,感激不尽!!!