Dhtmlx Gantt教程:创建交互式甘特图的完整指南

在现代的项目管理中,时间是一种宝贵的资源,而甘特图则是一把解锁项目进度的魔法钥匙,想象一下,您可以在一个直观而动态的时间轴上,清晰地看到项目的每一个任务如何交织在一起,如何随着时间的推移展开,如何影响着项目的整体进度。这就是Dhtmlx Gantt带给我们的强大能力。

目录

[初识Dhtmlx Gantt](#初识Dhtmlx Gantt)

[Dhtmlx Gantt配置项](#Dhtmlx Gantt配置项)

[Dhtmlx Gantt基础操作](#Dhtmlx Gantt基础操作)

多任务实现

初识Dhtmlx Gantt

Dhtmlx Gantt介绍:主要是用来创建和管理甘特图的工具库,甘特图是一种项目管理图表,通过条形图展示任务的开始时间、持续时间和完成进度,同时显示任务之间的依赖关系。Dhtmlx Gantt提供了丰富的功能和可定制选项,开发者可以轻松构建出功能强大、直观清晰的甘特图,用于项目计划和进度管理,从而使得用户可以灵活地配置和显示项目的时间轴、任务列表、资源分配情况等信息。这里我们可以阅读一下Dhtmlx Gantt的官网了解详细信息:地址

因为公司项目主要使用react技术栈,这里我们就拿该框架进行举例,参考文档:地址

插件安装:这里我们选择安装稳定版本(博主以稳定版本讲解)的,终端执行如下命令即可:

javascript 复制代码
npm i dhtmlx-gantt

后面使用该插件的时候,直接导入包和对应的样式即可:

javascript 复制代码
import { Gantt} from "dhtmlx-gantt";
import "dhtmlx-gantt/codebase/dhtmlxgantt.css";

当然也可以安装试用版本,和稳定版本有点区别,这里大家可自行去官网翻阅,这里不再赘述:

javascript 复制代码
npm install @dhx/trial-gantt

如果您选择安装试用版,导入路径应如下所示:

javascript 复制代码
import { Gantt} from "@dhx/trial-gantt";
import "@dhx/trial-gantt/codebase/dhtmlxgantt.css";

这里我们仿造官网给出的案例,如下给出先给出完整的代码进行基础演示:

javascript 复制代码
import { useEffect, useRef } from 'react';
import { gantt } from 'dhtmlx-gantt';
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
import './index.less'

const data = {
    // 任务数据
    data: [
        {
            id: 1,
            text: 'projectName',
            start_date: '01-04-2023',
            end_date: '05-12-2023',
            progress: 0.3,
            value: 20,
        },
        {
            id: 2,
            text: '任务1',
            start_date: '02-04-2023',
            end_date: '11-07-2023',
            progress: 0.6,
            value: 20,

        },
        {
            id: 3,
            text: '任务211',
            start_date: '02-04-2023',
            end_date: '23-07-2023',
            progress: 0,
            value: 20,

        }
    ],
    // 任务连线数据
    links: [
        { id: 1, source: 1, target: 2, type: '1' },
        { id: 2, source: 2, target: 3, type: '0' }
    ]
};

// 左侧标题数据
const columns = [
    { name: 'text', label: '项目名称',  width: 100, align: "center" },
    { name: 'start_date', label: '开始时间', width: 100, align: "center" },
    { name: 'start_date', label: '开始时间', width: 100, align: "center" },
    { name: 'value', label: '计划工期', width: 100, align: "center" },
];

const GanttView = () => {
    // 获取gantrt容器实例
    const ganttRef = useRef<any>();
    // 初始化gantt
    const initGantt = () => {
        // 基础配置
        gantt.clearAll() // 清空之前的配置
        gantt.i18n.setLocale('cn'); // 设置中文
        gantt.config.readonly = true; // 设置为只读,否则是可以移动甘特图和连线的
        gantt.init(ganttRef.current); // 初始化甘特图
        gantt.parse(data); // 渲染数据
        gantt.config.order_branch = "marker";

        // 表头样式设置
        gantt.config.scale_height = 60; // 设置表头高度
        gantt.config.sort = true; // 点击表头可排序
        gantt.config.columns = columns; // 设置左侧表头数据
        gantt.config.scales = [ // 设置表头右侧刻度
            // 设置时间刻度相关属性
            // 显示月日用这个
            // { unit: 'month', step: 1, format: '%Y-%m' },
            // { unit: 'day', step: 1, format: '%Y-%m-%d' }
            // 显示年月用这个
            { unit: 'year', step: 1, format: '%Y' },
            { unit: 'month', step: 1, format: '%M' }
        ];

        // 表内容样式设置
        gantt.config.row_height = 40; // 设置内容行高
        gantt.config.bar_height = 40; // 设置进度条高度
        gantt.templates.task_text = function (start, end, task) { // 自定义内容进度上的文本
            return '内容'
        };
        // 表内容背景颜色
        gantt.templates.task_row_class = function (start, end, task) {
            return "gantt_task111";
        };

        // tooltips样式设置
        gantt.plugins({
            tooltip: true,
        });
        gantt.config.tooltip_offset_x = 10; 
        gantt.config.tooltip_offset_y = 30;
        gantt.templates.tooltip_text = function (start, end, task) {
            return (
                task.text +
                '<br/><span>开始:</span> ' +
                gantt.templates.tooltip_date_format(start) +
                '<br/><span>结束:</span> ' +
                gantt.templates.tooltip_date_format(end) +
                '<br/><span>进度:</span> ' +
                // Math.round(task.progress * 100) +
                '%'
            );
        };

        // 设置连线事件
        gantt.config.show_links = true;
    }

    useEffect(() => {
        initGantt();
    }, []);

    return (
        <div className="ganttView">
            <div className='ganttContainer' ref={ganttRef}></div>
        </div>
    )
};
export default GanttView;

最终得到的效果如下所示:

Dhtmlx Gantt配置项

接下来开始对配置项进行一个简单的介绍,如下所示:

数据配置项:甘特图中表头标题和表内容中的数据

任务数据data:在任务数据中里面对象的配置项有各种属性值,接下来就常用的属性值介绍:

javascript 复制代码
/*
gantt.parse 方法中的 data 参数是一个包含任务信息的数组。每个任务都包含多个字段,下面是一些常用的字段及其作用:
id: 任务的唯一标识符。
text: 任务的名称。
start_date: 任务的开始日期,可以是一个字符串或者一个 Date 对象。
end_date: 任务的结束日期,可以是一个字符串或者一个 Date 对象。
duration: 任务的持续时间,以天为单位。如果 end_date 和 duration 都提供了,duration 会被忽略。
progress: 任务的进度,以百分比表示。
parent: 父任务的 id,如果当前任务是顶级任务,则该字段为 0。
open: 是否展开当前任务的子任务。如果不提供,默认为 true。
state: 用于设置任务的状态。状态可以是任何自定义值,例如 "in progress"、"completed"、"cancelled" 等等。在 Gantt 图中,任务的状态可以通过任务条颜色、边框、文本样式等 visulization 属性进行自定义渲染,以便用户更直观地了解任务状态的变化。可以在 Gantt 文档中的 Visualization 属性部分了解有关自定义任务状态可视化的更多信息。
除了上面列出的常用字段之外,还有很多其他可选字段,例如 color、link、type 等,可以根据实际需求来添加。
*/

const data = [
  { id: 1, text: "Task 1", start_date: "2023-03-15", duration: 3 },
  { id: 2, text: "Task 2", start_date: "2023-03-18", duration: 2, parent: 1 },
];

links数组:链接数据数组,包含每个链接的信息,例如 id、source、target 等等。

javascript 复制代码
// 任务连线数据
links: [
    { id: 1, source: 1, target: 2, type: '1' },
    { id: 2, source: 2, target: 3, type: '0' }
]

当然还有一些其他常见的配置项这里就不再一一赘述了,如下:

javascript 复制代码
key: 任务数据对象中唯一标识每个任务的属性名称,默认为 "id"。
parent: 任务数据对象中用于标识父任务的属性名称,默认为 "parent".
open_tree_initially: 布尔值,如果设置为 true,则所有任务默认展开。
preserve_links: 布尔值,如果设置为 true,则链接信息中的任务不存在时也会保留链接。
preserve_tasks: 布尔值,如果设置为 true,则链接信息中的任务不存在时也会保留任务。

基础配置项:初始化甘特图时要进行的基础配置

javascript 复制代码
gantt.clearAll() // 清空之前的配置
gantt.i18n.setLocale('cn'); // 设置中文
gantt.config.readonly = true; // 设置为只读,否则是可以移动甘特图和连线的
gantt.init(ganttRef.current); // 初始化甘特图
gantt.parse(data); // 渲染数据

表头样式配置项:对表头标题进行样式配置

javascript 复制代码
// 甘特图样式设置
gantt.config.scale_height = 60; // 设置表头高度
// 设置头部左侧表头标题背景颜色
gantt.templates.grid_header_class = function (date, scale) {
    return "gantt_grid_head111";
};
// 设置左侧标题表内容背景颜色
gantt.templates.grid_row_class = function (date, scale) {
    return "gantt_scale_cell111";
};
// 设置头部右侧上标题内容背景颜色
gantt.templates.scale_cell_class = function (scale) {
    return "gantt_grid_head111";
};
// 设置头部右侧下标题内容背景颜色
gantt.templates.scale_row_class = function (scale) {
    return "gantt_grid_head111";
};
// 设置表主内容背景颜色
gantt.templates.task_row_class = function (start, end, task) {
    return "gantt_task111";
};
gantt.config.sort = true; // 设置点击左侧表头可排序
gantt.config.columns = columns; // 设置左侧表头数据
gantt.config.scales = [ // 设置表头右侧刻度
    // 设置时间刻度相关属性
    // 显示月日用这个
    // { unit: 'month', step: 1, format: '%Y-%m' },
    // { unit: 'day', step: 1, format: '%Y-%m-%d' }
    // 显示年月用这个
    { unit: 'year', step: 1, format: '%Y' },
    { unit: 'month', step: 1, format: '%M' }
];

表内容样式配置项:对表内容进行样式配置

javascript 复制代码
// 表内容样式设置
gantt.config.row_height = 40; // 设置内容行高
gantt.config.bar_height = 40; // 设置进度条高度
gantt.templates.task_text = function (start, end, task) { // 自定义内容进度上的文本
    return '内容'
};

悬浮框样式配置项:对表悬浮框进行样式配置

javascript 复制代码
// tooltips样式设置
gantt.plugins({
    tooltip: true,
    // quick_info: true, // 快速信息框
    // multiselect: true,// 激活多任务选择
});
gantt.config.tooltip_offset_x = 10; // 设置tooltips水平偏移量
gantt.config.tooltip_offset_y = 30; // 设置tooltips垂直偏移量
gantt.templates.tooltip_text = function (start, end, task) {
    return (
        task.text +
        '<br/><span>开始:</span> ' +
        gantt.templates.tooltip_date_format(start) +
        '<br/><span>结束:</span> ' +
        gantt.templates.tooltip_date_format(end) +
        '<br/><span>进度:</span> ' +
        // Math.round(task.progress * 100) +
        '%'
    );
};

Dhtmlx Gantt基础操作

接下来复现一下一个甘特图的效果,这里我们给出书写甘特图的流程书写:

初始化甘特图:首先我们创建甘特图的容器,然后进行一些初始配置:

javascript 复制代码
import { useEffect, useRef } from 'react';
import { gantt } from 'dhtmlx-gantt';
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';

const GanttView = () => {
    // 获取gantrt容器实例
    const ganttRef = useRef<any>();
    // 初始化gantt
    const initGantt = () => {
        gantt.clearAll() // 清空之前的配置
        gantt.i18n.setLocale('cn'); // 设置中文
        gantt.config.readonly = true; // 设置为只读,否则是可以移动甘特图和连线的
        gantt.init(ganttRef.current); // 初始化甘特图
        gantt.parse(data); // 渲染数据
    }

    useEffect(() => {
        initGantt();
    }, []);

    return (
        <div className="ganttView">
            <div className='ganttContainer' ref={ganttRef}></div>
        </div>
    )
};
export default GanttView;

给出表头数据:接下来开始创建表头数据:

javascript 复制代码
const data = {
    data: [ // 任务数据
        { id: 1, text: '任务1', start_date: '01-04-2023', end_date: '05-12-2023', progress: 0.3 },
        { id: 2, text: '任务1', start_date: '02-04-2023', end_date: '11-07-2023', progress: 0.6 },
        { id: 3, text: '任务3', start_date: '12-07-2023', end_date: '09-09-2023', progress: 0 }
    ],
    links: [ // 任务连线数据
        { id: 1, source: 1, target: 2, type: '1' },
        { id: 2, source: 2, target: 3, type: '0' }
    ]
};

const columns = [ // 左侧标题数据
    { name: 'text', label: '项目名称',  width: 100, align: "center" },
    { name: 'start_date', label: '开始时间', width: 100, align: "center" },
    { name: 'end_date', label: '结束时间', width: 100, align: "center" },
];

样式配置:接下来给甘特图设置相关样式:

javascript 复制代码
const initGantt = () => {
    // 基础配置
    gantt.clearAll() // 清空之前的配置
    gantt.i18n.setLocale('cn'); // 设置中文
    gantt.config.readonly = false; // 设置为只读,否则是可以移动甘特图和连线的
    gantt.init(ganttRef.current); // 初始化甘特图
    gantt.parse(data); // 渲染数据

    // 甘特图样式设置
    gantt.config.scale_height = 60; // 设置表头高度
    // 设置头部左侧表头标题背景颜色
    gantt.templates.grid_header_class = function (date, scale) {
        return "gantt_grid_head111";
    };
    // 设置左侧标题表内容背景颜色
    gantt.templates.grid_row_class = function (date, scale) {
        return "gantt_scale_cell111";
    };
    // 设置头部右侧上标题内容背景颜色
    gantt.templates.scale_cell_class = function (scale) {
        return "gantt_grid_head111";
    };
    // 设置头部右侧下标题内容背景颜色
    gantt.templates.scale_row_class = function (scale) {
        return "gantt_grid_head111";
    };
    // 设置表主内容背景颜色
    gantt.templates.task_row_class = function (start, end, task) {
        return "gantt_task111";
    };
    gantt.config.sort = true; // 设置点击左侧表头可排序
    gantt.config.columns = columns; // 设置左侧表头数据
    gantt.config.scales = [ // 设置表头右侧刻度
        // 设置时间刻度相关属性
        // 显示月日用这个
        // { unit: 'month', step: 1, format: '%Y-%m' },
        // { unit: 'day', step: 1, format: '%Y-%m-%d' }
        // 显示年月用这个
        { unit: 'year', step: 1, format: '%Y' },
        { unit: 'month', step: 1, format: '%M' }
    ];

    // 表内容样式设置
    gantt.config.row_height = 40; // 设置内容行高
    gantt.config.bar_height = 40; // 设置进度条高度
    gantt.templates.task_text = function (start, end, task) { // 自定义内容进度上的文本
        return '内容'
    };

    // tooltips样式设置
    gantt.plugins({ tooltip: true });
    gantt.config.tooltip_offset_x = 10; // 设置tooltips水平偏移量
    gantt.config.tooltip_offset_y = 30; // 设置tooltips垂直偏移量
    gantt.templates.tooltip_text = function (start, end, task: any) {
        return (
            task.text +
            '<br/><span>开始:</span> ' +
            gantt.templates.tooltip_date_format(start) +
            '<br/><span>结束:</span> ' +
            gantt.templates.tooltip_date_format(end) +
            '<br/><span>进度:</span> ' +
            Math.round(task.progress * 100) +
            '%'
        );
    };

    // 设置连线事件
    gantt.config.show_links = true;
    gantt.config.drag_project = true;
}

最终呈现的效果如下所示:

多任务实现

如果想在同一个x轴显示多条任务,需要配置分组,每组的子任务通过parent属性进行联系主任务,这里通过如下代码进行演示:

javascript 复制代码
const data = {
    // 任务数据
    data: [
        //第一组 整条数据需要带上render属性  里面多段的数据parent执行整条的id
        { id: '1', name: '张三', render: 'split',  text: '' },
        { id: '1-1', parent: 1, text: '派工', color: '#008c8c', start_date: '15-06-2024 08:30', end_date: '15-06-2024: 10:30' },
        { id: '1-2', parent: 1, text: '休息', color: 'blue', start_date: '16-06-2024: 13:00', end_date: '16-06-2024:23:00' },
        
        // 第二组
        { id: '2', name: '李四', render: 'split', text: '' },
        { id: '2-1', parent: 2, text: '派工', color: '#008c8c', start_date: '15-06-2024 18:30', end_date: '15-06-2024: 22:30' },
        { id: '2-2', parent: 2, text: '休息', color: 'blue', start_date: '16-06-2024: 13:00', end_date: '16-06-2024:23:00' },

        // 第三组
        { id: '3', name: '王五', render: 'split', text: '' },
        { id: '3-1', parent: 3, text: '派工', color: '#008c8c', start_date: '15-06-2024 8:30', end_date: '15-06-2024: 22:30' },
        { id: '3-2', parent: 3, text: '休息', color: 'blue', start_date: '16-06-2024: 13:00', end_date: '16-06-2024:23:00' },
        { id: '3-3', parent: 3, text: '休息', color: 'blue', start_date: '16-06-2024: 13:00', end_date: '17-06-2024:3:00' },
        { id: '3-4', parent: 3, text: '派工', color: '#008c8c', start_date: '17-06-2024:2:00', end_date: '17-06-2024: 8:00' }
    ],
};

如果想在同一时段实现多任务,当前组件库好像实现不了,至少博主查阅了一下午的文档没找到,然后这里我通过css样式来进行控制其高度,然后通过判断任务之间的时段是否重叠进行动态添加类名,具体代码如下:

javascript 复制代码
import { useEffect, useRef } from 'react';
import { gantt } from 'dhtmlx-gantt';
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
import './index.less'

const data = {
    // 任务数据
    data: [
        //第一组 整条数据需要带上render属性  里面多段的数据parent执行整条的id
        { id: '1', name: '张三', render: 'split',  text: '' },
        { id: '1-1', parent: 1, text: '派工', color: '#008c8c', start_date: '15-06-2024 08:30', end_date: '15-06-2024: 10:30' },
        { id: '1-2', parent: 1, text: '休息', color: 'blue', start_date: '16-06-2024: 13:00', end_date: '16-06-2024:23:00' },
        
        // 第二组
        { id: '2', name: '李四', render: 'split', text: '' },
        { id: '2-1', parent: 2, text: '派工', color: '#008c8c', start_date: '15-06-2024 18:30', end_date: '15-06-2024: 22:30' },
        { id: '2-2', parent: 2, text: '休息', color: 'blue', start_date: '16-06-2024: 13:00', end_date: '16-06-2024:23:00' },

        // 第三组
        { id: '3', name: '王五', render: 'split', text: '' },
        { id: '3-1', parent: 3, text: '派工', color: '#008c8c', start_date: '15-06-2024 8:30', end_date: '15-06-2024: 22:30' },
        { id: '3-2', parent: 3, text: '休息', color: 'blue', start_date: '16-06-2024: 13:00', end_date: '16-06-2024:23:00' },
        { id: '3-3', parent: 3, text: '休息', color: 'blue', start_date: '16-06-2024: 13:00', end_date: '17-06-2024:3:00' },
        { id: '3-4', parent: 3, text: '派工', color: '#008c8c', start_date: '17-06-2024:2:00', end_date: '17-06-2024: 8:00' },
        // { id: '3-4', parent: 3, text: '派工', color: '#008c8c', start_date: '16-06-2024:12:00', end_date: '17-06-2024: 8:00' },
    ],
};

// 左侧标题数据
const columns = [
    { name: 'id', label: '序号', resize: true,  max_width: 60, align: "center" },
    { name: 'name', label: '姓名', resize: true, max_width: 60, align: "center" },
];

const GanttView = () => {
    // 获取gantrt容器实例
    const ganttRef = useRef<any>();
    // 初始化gantt
    const initGantt = () => {
        gantt.clearAll() // 清空之前的配置
        gantt.i18n.setLocale('cn'); // 设置中文
        gantt.config.readonly = true; // 设置为只读,否则是可以移动甘特图和连线的
        gantt.config.start_date = new Date(2024, 5, 15); // 设置甘特图开始日期  
        gantt.config.end_date = new Date(2024, 5, 18); // 设置甘特图结束日期  
        gantt.init(ganttRef.current); // 初始化甘特图
        gantt.parse(data); // 渲染数据

        gantt.config.columns = columns; // 设置左侧表头数据
        gantt.config.scale_height = 60; // 设置表头高度
        gantt.config.min_column_width = 10; // 设置列最小宽度
        // 设置头部右侧上标题内容背景颜色
        gantt.templates.scale_cell_class = function (scale) {
            return "gantt_grid_head_top";
        };

        gantt.config.scales = [ // 设置甘特图时间轴
            { unit: 'day', step: 1, format: '%Y/%m/%d' },
            { unit: 'hour', step: 1, format: function(date) {  
                return date.getHours(); // 显示从0到23的小时范围  
            }}  
        ];
        
        // 表内容样式设置
        gantt.templates.task_row_class = function (start, end, task) { // 设置表主内容背景颜色
            return "gantt_task_main_content";
        };
        gantt.config.row_height = 40; // 设置内容行高
        gantt.config.bar_height = 40; // 设置进度条高度
        gantt.templates.task_text = function (start, end, task) {
            return `<div style="color: #fff; font-size: 12px;">${task?.text}</div>`;
        };
        let count = 0
        gantt.templates.task_class = function(start, end, task) {  
            if (count < 1) {
                // 创建一个空对象来存储结果
                let tasksByParent: { [key: string]: any[] } = {}; // 创建空对象来存储任务按父任务分组的结果
                // 遍历任务数组
                data.data.forEach((task) => {
                    if (task.parent) {
                        // 检查是否已经存在该parent对应的数组,如果不存在则创建一个空数组
                        if (!tasksByParent[task.parent]) {
                            tasksByParent[task.parent] = [];
                        }
                        // 将当前任务对象添加到对应parent值的数组中
                        tasksByParent[task.parent].push(task);
                    }
            
                });

                // 检查时间重叠并添加类名
                Object.keys(tasksByParent).forEach(parentId => {
                    let tasks = tasksByParent[parentId];
                    for (let i = 0; i < tasks.length; i++) {
                        for (let j = i + 1; j < tasks.length; j++) {
                            if (tasksOverlap(tasks[i], tasks[j])) {
                                tasks[i].className = (i % 2 === 0) ? 'custom-height-top2' : 'custom-height-bottom2';
                                tasks[j].className = (j % 2 === 0) ? 'custom-height-top2' : 'custom-height-bottom2';
                            }
                        }
                    }

                    for (let i = 0; i < tasks.length; i++) {
                        for (let j = i + 1; j < tasks.length; j++) {
                            for (let k = j + 1; k < tasks.length; k++) {
                                if (tasksOverlap(tasks[i], tasks[j]) && tasksOverlap(tasks[i], tasks[k]) && tasksOverlap(tasks[j], tasks[k])) {
                                    // 为每个重叠任务分配不同的类名
                                    tasks[i].className = 'custom-height-top3';
                                    tasks[j].className = 'custom-height-bottom3';
                                    tasks[k].className = 'custom-height-center3';
                                }
                            }
                        }
                    }
                    
                });
                console.log(tasksByParent);
                count++;
            }
            return task.className || ""; // 返回任务的类名,如果没有类名则返回空字符串
        };  

        // 检查两个任务时间段是否有重叠
        function tasksOverlap(task1: any, task2: any) {
            return (task1.start_date < task2.end_date && task2.start_date < task1.end_date);
        }
        // tooltips样式设置
        gantt.plugins({ tooltip: true });
        gantt.config.tooltip_offset_x = 10; // 设置tooltips水平偏移量
        gantt.config.tooltip_offset_y = 30; // 设置tooltips垂直偏移量
        gantt.templates.tooltip_text = function (start: Date, end: Date, task: any): string {
            if (task.text) {
                return (
                    `<div class="gantt-tooltip">
                        <div class="gantt-tooltip-time">
                            <div class="time-word">当前时间:</div>
                            <div class="time-value">
                                <div class="time-value-content"><span>开始时间:</span>${start.toLocaleString()}</div>
                                <div class="time-value-content"><span>结束时间:</span>${end.toLocaleString()}</div>
                            </div>
                        </div>
                        <div class="gantt-tooltip-task">
                            <div class="task-word">当前任务:</div>
                            <div class="task-value">${task.text}</div>
                        </div>
                    </div>`
                );
            }
            return "";
        };
    }

    useEffect(() => {
        initGantt();
    }, []);

    return (
        <div className="ganttView">
            <div className='ganttContainer' ref={ganttRef} style={{ width: '100%' }}></div>
        </div>
    )
};
export default GanttView;

上述代码我通过for循环来判断,识别当前是双任务还是三任务的时间进行了重叠,这里给出具体的css样式:

css 复制代码
.ganttView {
    width: 100%;
    height: 250px;
    .ganttContainer {
        width: 100vw;
        overflow: hidden;
        height: 100%;
        // 设置左侧头部数据
        .gantt_grid_scale {
            background-color: #dce8ee;
            .gantt_grid_head_cell {
                font-size: 14px;
                font-weight: bold;
                color: #000;
            }
        }
        // 设置右侧头部数据
        .gantt_task_scale {
            background-color: #dce8ee;
            .gantt_grid_head_top {
                font-size: 14px !important;
                font-weight: bold;
                color: #000;
            }
            .gantt_scale_cell {
                font-size: 10px;
                font-weight: bold;
                color: #000;
            }
 
        }
        .gantt_task_main_content {
            background-color: #57ce7d;
        }
        
         // 去除表格单元格的竖线  
        .gantt_task_cell,  
        .gantt_task_cell.gantt_cell {  
            border-right: none !important;
        }  
        // 保留左侧标题单元格的竖线  
        .gantt_grid_head_cell,  
        .gantt_grid_data .gantt_cell {  
            border-right: 1px solid #dcdcdc !important; 
        }  
        /* 去除左侧标题和右侧表格内容的激活状态样式 */  
        .gantt_grid_data, .gantt_row.gantt_selected {
            background-color: transparent !important;
        }
     
        // 去除左侧标题内容的悬停效果
        .gantt_row:hover {
            background: none !important; // 去除背景色  
        }
        .gantt_row:active {
            background: none !important; // 去除背景色  
        }

        .custom-height-top2 {
            height: 20px !important; /* 设置特定任务条的高度 */  
            line-height: 20px !important; /* 调整行高以垂直居中文本 */  
        }
        .custom-height-bottom2 {
            height: 20px !important; /* 设置特定任务条的高度 */  
            line-height: 20px !important; /* 调整行高以垂直居中文本 */  
            margin-top: 20px;
        }
        .custom-height-top3 {
            height: 13.3px !important; /* 设置特定任务条的高度 */  
            line-height: 13.3px !important; /* 调整行高以垂直居中文本 */  
        }
        .custom-height-center3 {
            height: 13.3px !important; /* 设置特定任务条的高度 */  
            line-height: 13.3px !important; /* 调整行高以垂直居中文本 */  
            margin-top: 13.3px;
        }
        .custom-height-bottom3 {
            margin-top: 26.6px;
            height: 13.3px !important; /* 设置特定任务条的高度 */  
            line-height: 13.3px !important; /* 调整行高以垂直居中文本 */  
        }
    }
}

// tooltips样式
.gantt_tooltip {
    background-color: #ccc;
    .gantt-tooltip-time {
        display: flex;
        flex-direction: column;
        gap: 4px;
        margin-bottom: 4px;
        .time-word {
            color: #000;
            font-size: 12px;
            font-weight: bold;
        }
        .time-value {
            display: flex;
            flex-direction: column;
            gap: 2px;
            span {
                font-weight: bold;
            }
        }
    }
    .gantt-tooltip-task {
        display: flex;
        align-items: center;
        .task-word {
            color: #000;
            font-size: 12px;
            font-weight: bold;
        }
        .task-value {
            color: red;
        }
    }
}

当然还有单个任务的展示:

javascript 复制代码
import { useEffect, useRef } from 'react';
import { gantt } from 'dhtmlx-gantt';
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
import './index.less'

const data = {
    // 任务数据
    data: [
        //第一组 整条数据需要带上render属性  里面多段的数据parent执行整条的id
        { id: '1', name: '张三', render: 'split',  text: '' },
        { id: '1-1', parent: 1, text: '工', color: 'blue', start_date: '14-06-2024', duration: 1 },
        { id: '1-2', parent: 1, text: '工', color: 'blue', start_date: '15-06-2024', duration: 1 },
        { id: '1-3', parent: 1, text: '工', color: 'blue', start_date: '16-06-2024', duration: 1 },
        { id: '1-4', parent: 1, text: '工', color: 'blue', start_date: '17-06-2024', duration: 1 },
        { id: '1-5', parent: 1, text: '工', color: 'blue', start_date: '18-06-2024', duration: 1 },
        { id: '1-6', parent: 1, text: '工', color: 'blue', start_date: '19-06-2024', duration: 1 },
        { id: '1-7', parent: 1, text: '年', color: 'red', start_date: '20-06-2024', duration: 1 },
        { id: '1-8', parent: 1, text: '闲', color: 'green', start_date: '21-06-2024', duration: 1 },
        { id: '1-9', parent: 1, text: '工', color: 'blue', start_date: '22-06-2024', duration: 1 },
        { id: '1-10', parent: 1, text: '工', color: 'blue', start_date: '23-06-2024', duration: 1 },
        { id: '1-11', parent: 1, text: '工', color: 'blue', start_date: '24-06-2024', duration: 1 },
        { id: '1-12', parent: 1, text: '工', color: 'blue', start_date: '25-06-2024', duration: 1 },
        { id: '1-13', parent: 1, text: '工', color: 'blue', start_date: '26-06-2024', duration: 1 },
        { id: '1-14', parent: 1, text: '工', color: 'blue', start_date: '27-06-2024', duration: 1 },
        { id: '1-15', parent: 1, text: '工', color: 'blue', start_date: '28-06-2024', duration: 1 },
        { id: '1-16', parent: 1, text: '工', color: 'blue', start_date: '29-06-2024', duration: 1 },
        { id: '1-17', parent: 1, text: '闲', color: 'green', start_date: '30-06-2024', duration: 1 },
        { id: '1-18', parent: 1, text: '闲', color: 'green', start_date: '1-07-2024', duration: 1 },
        { id: '1-19', parent: 1, text: '工', color: 'blue', start_date: '2-07-2024', duration: 1 },
        { id: '1-20', parent: 1, text: '工', color: 'blue', start_date: '3-07-2024', duration: 1 },
        { id: '1-21', parent: 1, text: '工', color: 'blue', start_date: '4-07-2024', duration: 1 },
        { id: '1-22', parent: 1, text: '工', color: 'blue', start_date: '5-07-2024', duration: 1 },
        { id: '1-23', parent: 1, text: '工', color: 'blue', start_date: '6-07-2024', duration: 1 },
        { id: '1-24', parent: 1, text: '工', color: 'blue', start_date: '7-07-2024', duration: 1 },
        { id: '1-25', parent: 1, text: '工', color: 'blue', start_date: '8-07-2024', duration: 1 },
        { id: '1-26', parent: 1, text: '工', color: 'blue', start_date: '9-07-2024', duration: 1 },
        { id: '1-27', parent: 1, text: '工', color: 'blue', start_date: '10-07-2024', duration: 1 },
        { id: '1-28', parent: 1, text: '工', color: 'blue', start_date: '11-07-2024', duration: 1 },
        { id: '1-29', parent: 1, text: '工', color: 'blue', start_date: '12-07-2024', duration: 1 },
        { id: '1-30', parent: 1, text: '工', color: 'red', start_date: '13-07-2024', duration: 1 },
        
        // 第二组
        { id: '2', name: '李四', render: 'split', text: '' },
        { id: '2-1', parent: 2, text: '工', color: 'blue', start_date: '14-06-2024', duration: 1 },
        { id: '2-2', parent: 2, text: '工', color: 'blue', start_date: '15-06-2024', duration: 1 },
        { id: '2-3', parent: 2, text: '工', color: 'blue', start_date: '16-06-2024', duration: 1 },
        { id: '2-4', parent: 2, text: '工', color: 'blue', start_date: '17-06-2024', duration: 1 },
        { id: '2-5', parent: 2, text: '工', color: 'blue', start_date: '18-06-2024', duration: 1 },
        { id: '2-6', parent: 2, text: '工', color: 'blue', start_date: '19-06-2024', duration: 1 },
        { id: '2-7', parent: 2, text: '工', color: 'blue', start_date: '20-06-2024', duration: 1 },
        { id: '2-8', parent: 2, text: '工', color: 'blue', start_date: '21-06-2024', duration: 1 },
        { id: '2-9', parent: 2, text: '工', color: 'blue', start_date: '22-06-2024', duration: 1 },
        { id: '2-10', parent: 2, text: '工', color: 'blue', start_date: '23-06-2024', duration: 1 },
        { id: '2-11', parent: 2, text: '工', color: 'blue', start_date: '24-06-2024', duration: 1 },
        { id: '2-12', parent: 2, text: '工', color: 'blue', start_date: '25-06-2024', duration: 1 },
        { id: '2-13', parent: 2, text: '工', color: 'blue', start_date: '26-06-2024', duration: 1 },
        { id: '2-14', parent: 2, text: '工', color: 'blue', start_date: '27-06-2024', duration: 1 },
        { id: '2-15', parent: 2, text: '工', color: 'blue', start_date: '28-06-2024', duration: 1 },
        { id: '2-16', parent: 2, text: '工', color: 'blue', start_date: '29-06-2024', duration: 1 },
        { id: '2-17', parent: 2, text: '工', color: 'blue', start_date: '30-06-2024', duration: 1 },
        { id: '2-18', parent: 2, text: '工', color: 'blue', start_date: '01-07-2024', duration: 1 },
        { id: '2-19', parent: 2, text: '工', color: 'blue', start_date: '02-07-2024', duration: 1 },
        { id: '2-20', parent: 2, text: '工', color: 'blue', start_date: '03-07-2024', duration: 1 },
        { id: '2-21', parent: 2, text: '工', color: 'blue', start_date: '04-07-2024', duration: 1 },
        { id: '2-22', parent: 2, text: '工', color: 'blue', start_date: '05-07-2024', duration: 1 },
        { id: '2-23', parent: 2, text: '工', color: 'blue', start_date: '06-07-2024', duration: 1 },
        { id: '2-24', parent: 2, text: '工', color: 'blue', start_date: '07-07-2024', duration: 1 },
        { id: '2-25', parent: 2, text: '工', color: 'blue', start_date: '08-07-2024', duration: 1 },
        { id: '2-26', parent: 2, text: '工', color: 'blue', start_date: '09-07-2024', duration: 1 },
        { id: '2-27', parent: 2, text: '闲', color: 'green', start_date: '10-07-2024', duration: 1 },
        { id: '2-28', parent: 2, text: '工', color: 'blue', start_date: '11-07-2024', duration: 1 },
        { id: '2-29', parent: 2, text: '工', color: 'blue', start_date: '12-07-2024', duration: 1 },
        { id: '2-30', parent: 2, text: '工', color: 'blue', start_date: '13-07-2024', duration: 1 }, 

        // 第三组
        { id: '3', name: '王五1', render: 'split', text: '' },
        { id: '3-1', parent: 3, text: '工', color: 'blue', start_date: '14-06-2024', duration: 1 },
        { id: '3-2', parent: 3, text: '工', color: 'blue', start_date: '15-06-2024', duration: 1 },
        { id: '3-3', parent: 3, text: '工', color: 'blue', start_date: '16-06-2024', duration: 1 },
        { id: '3-4', parent: 3, text: '工', color: 'blue', start_date: '17-06-2024', duration: 1 },
        { id: '3-5', parent: 3, text: '工', color: 'blue', start_date: '18-06-2024', duration: 1 },
        { id: '3-6', parent: 3, text: '工', color: 'blue', start_date: '19-06-2024', duration: 1 },
        { id: '3-7', parent: 3, text: '工', color: 'blue', start_date: '20-06-2024', duration: 1 },
        { id: '3-8', parent: 3, text: '工', color: 'blue', start_date: '21-06-2024', duration: 1 },
        { id: '3-9', parent: 3, text: '工', color: 'blue', start_date: '22-06-2024', duration: 1 },
        { id: '3-10', parent: 3, text: '工', color: 'blue', start_date: '23-06-2024', duration: 1 },
        { id: '3-11', parent: 3, text: '工', color: 'blue', start_date: '24-06-2024', duration: 1 },
        { id: '3-12', parent: 3, text: '工', color: 'blue', start_date: '25-06-2024', duration: 1 },
        { id: '3-13', parent: 3, text: '工', color: 'blue', start_date: '26-06-2024', duration: 1 },
        { id: '3-14', parent: 3, text: '工', color: 'blue', start_date: '27-06-2024', duration: 1 },
        { id: '3-15', parent: 3, text: '工', color: 'blue', start_date: '28-06-2024', duration: 1 },
        { id: '3-16', parent: 3, text: '工', color: 'blue', start_date: '29-06-2024', duration: 1 },
        { id: '3-17', parent: 3, text: '工', color: 'blue', start_date: '30-06-2024', duration: 1 },
        { id: '3-18', parent: 3, text: '工', color: 'blue', start_date: '01-07-2024', duration: 1 },
        { id: '3-19', parent: 3, text: '工', color: 'blue', start_date: '02-07-2024', duration: 1 },
        { id: '3-20', parent: 3, text: '工', color: 'blue', start_date: '03-07-2024', duration: 1 }, 
        { id: '3-21', parent: 3, text: '工', color: 'blue', start_date: '04-07-2024', duration: 1 }, 
        { id: '3-22', parent: 3, text: '工', color: 'blue', start_date: '05-07-2024', duration: 1 },
        { id: '3-23', parent: 3, text: '工', color: 'blue', start_date: '06-07-2024', duration: 1 },
        { id: '3-24', parent: 3, text: '工', color: 'blue', start_date: '07-07-2024', duration: 1 }, 
        { id: '3-25', parent: 3, text: '工', color: 'blue', start_date: '08-07-2024', duration: 1 },
        { id: '3-26', parent: 3, text: '工', color: 'blue', start_date: '09-07-2024', duration: 1 },
        { id: '3-27', parent: 3, text: '工', color: 'blue', start_date: '10-07-2024', duration: 1 },
        { id: '3-28', parent: 3, text: '工', color: 'blue', start_date: '11-07-2024', duration: 1 },
        { id: '3-29', parent: 3, text: '工', color: 'blue', start_date: '12-07-2024', duration: 1 },
        { id: '3-30', parent: 3, text: '工', color: 'blue', start_date: '13-07-2024', duration: 1 },
    ],
};

// 左侧标题数据
const columns = [
    { name: 'id', label: '序号', resize: true,  max_width: 60, align: "center" },
    { name: 'name', label: '姓名', resize: true, max_width: 60, align: "center" },
];

const GanttView1 = () => {
    // 获取gantrt容器实例
    const ganttRef = useRef<any>();
    // 初始化gantt
    const initGantt = () => {
        gantt.clearAll() // 清空之前的配置
        gantt.i18n.setLocale('cn'); // 设置中文
        gantt.config.readonly = true; // 设置为只读,否则是可以移动甘特图和连线的
        gantt.config.start_date = new Date(2024, 5, 14); // 设置甘特图开始日期  
        gantt.config.end_date = new Date(2024, 6, 14); // 设置甘特图结束日期  
        gantt.init(ganttRef.current); // 初始化甘特图
        gantt.parse(data); // 渲染数据

        gantt.config.columns = columns; // 设置左侧表头数据
        gantt.config.scale_height = 60; // 设置表头高度
        gantt.config.min_column_width = 10; // 设置列最小宽度
        // 设置头部右侧上标题内容背景颜色
        gantt.templates.scale_cell_class = function (scale) {
            return "gantt_grid_head_top";
        };

        gantt.config.scales = [
            { unit: 'month', step: 1, format: function(date) {
                var formattedMonth = gantt.date.date_to_str('%m')(date); // 获取月份的两位数字表示
                formattedMonth = formattedMonth.replace(/^0+/, ''); // 去除月份前面的零
                return formattedMonth + '月'; // 返回格式化后的月份字符串
            }},
            
            { unit: 'day', step: 1, format: function(date) {
                var formattedDay = gantt.date.date_to_str('%d')(date); // 获取天的两位数字表示  
                formattedDay = formattedDay.replace(/^0+/, ''); // 去除天数前面的零  
                return formattedDay; // 返回格式化后的天数字符串  
            }}
        ];

        // 表内容样式设置
        gantt.templates.task_row_class = function (start, end, task) { // 设置表主内容背景颜色
            return "gantt_task_main_content";
        };
        gantt.config.row_height = 40; // 设置内容行高
        gantt.config.bar_height = 40; // 设置进度条高度
        gantt.templates.task_text = function (start, end, task) {
            return `<div style="color: #fff; font-size: 14px;">${task?.text}</div>`;
        };

        // tooltips样式设置
        gantt.plugins({ tooltip: true });
        gantt.config.tooltip_offset_x = 10; // 设置tooltips水平偏移量
        gantt.config.tooltip_offset_y = 30; // 设置tooltips垂直偏移量
        gantt.templates.tooltip_text = function (start: Date, end: Date, task: any): string {
            if (task.text) {
                return (
                    `<div class="gantt-tooltip">
                        <div class="gantt-tooltip-time-space">
                            <div class="time-word">当前时间:</div>
                            <div class="time-value">${start.getMonth() + 1}月${start.getDate()}日</div>
                        </div>
                        <div class="gantt-tooltip-task">
                            <div class="task-word">当前状态:</div>
                            <div class="task-value">${task.text}</div>
                        </div>
                    </div>`
                );
            }
            return "";
        };
    }

    useEffect(() => {
        initGantt();
    }, []);

    return (
        <div className="ganttView2">
            <div className='ganttContainer' ref={ganttRef} style={{ width: '100%' }}></div>
        </div>
    )
};
export default GanttView1;

CSS样式如下:

css 复制代码
.ganttView2 {
    width: 100%;
    height: 250px;
    .ganttContainer {
        width: 100vw;
        overflow: hidden;
        height: 100%;
        // 设置左侧头部数据
        .gantt_grid_scale {
            background-color: #dce8ee;
            .gantt_grid_head_cell {
                font-size: 14px;
                font-weight: bold;
                color: #000;
            }
        }
        // 设置右侧头部数据
        .gantt_task_scale {
            background-color: #dce8ee;
            .gantt_grid_head_top {
                font-size: 14px !important;
                font-weight: bold;
                color: #000;
            }
            .gantt_scale_cell {
                font-size: 10px;
                font-weight: bold;
                color: #000;
            }
 
        }
        // 保留左侧标题单元格的竖线  
        .gantt_grid_head_cell,  
        .gantt_grid_data .gantt_cell {  
            border-right: 1px solid #dcdcdc !important; 
        }  
        /* 去除左侧标题和右侧表格内容的激活状态样式 */  
        .gantt_grid_data, .gantt_row.gantt_selected {
            background-color: transparent !important;
        }
     
        // 去除左侧标题内容的悬停效果
        .gantt_row:hover {
            background: none !important; // 去除背景色  
        }
        .gantt_row:active {
            background: none !important; // 去除背景色  
        }
    }
}

// tooltips样式
.gantt_tooltip {
    background-color: #ccc;
    .gantt-tooltip-time-space {
        display: flex;
        align-items: center;
        margin-bottom: 4px;
        align-items: baseline;
        .time-word {
            color: #000;
            font-size: 12px;
            font-weight: bold;
        }
        .time-value {
            color: #000;
        }
    }
    .gantt-tooltip-task {
        display: flex;
        align-items: center;
        .task-word {
            color: #000;
            font-size: 12px;
            font-weight: bold;
        }
        .task-value {
            color: red;
        }
    }
}

实现的界面大家可自行运行,这里不再赘述。

相关推荐
桂月二二43 分钟前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
CodeClimb2 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
hunter2062062 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb2 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角2 小时前
CSS 颜色
前端·css
联蔚盘云2 小时前
2024.1.22 安全周报
经验分享
浪浪山小白兔3 小时前
HTML5 新表单属性详解
前端·html·html5
lee5764 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579654 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
光头程序员4 小时前
grid 布局react组件可以循数据自定义渲染某个数据 ,或插入某些数据在某个索引下
javascript·react.js·ecmascript