介绍

dhtmlx是有专门写了vue版本的,但是只支持vue3,不适用于当前需求的项目(该项目用的vue2),所以就用了普通版的
首先放上官方文档 链接

使用的版本是非商用版

该版本功能会比较少,但是做一个基础的甘特图足够啦(持续开发中,不定时更新

开始使用

安装dhtmlx

npm install dhtmlx-gantt -save

引入及使用

<template>
	<div ref="gantt" class="container" />
</template>
<script>
import { gantt } from 'dhtmlx-gantt'
</script>

在mounted函数中,初始化甘特图,其中的核心代码如下

// 初始化
gantt.init(this.$refs.gantt)
// 解析数据
gantt.parse(this.$props.tasks)

在初始化之前,可以任意的修改甘特图的一些属性,例如:

// 设置甘特图为只读模式,不能拖拽等操作
gantt.config.readonly = true

具体项目需要设置的性能相关的可以在官方文档 start=>Gantt API =>Properties中查找,另外一些功能也可以在plugins中设置,主要设置是一些插件是否启用

gantt.plugins({
      // 拖动图
      drag_timeline: true,
      // 时间标记
      marker: true,
      // 全屏
      fullscreen: true,
      // 鼠标经过时信息
      tooltip: true,
      // 允许撤销
      undo: true
    })

值得注意的是,这个甘特图中的数据是下面这种格式的

tasks: {
	data: [],
    links: []
    }

其中data中存的是主要任务信息,其中的id,start_data,end_data,name等一些字段都是必须的,关于时间长度的描述,可以在start_data,end_data,duration三个字段中选填两个字段即可,另外一些需要在甘特图中展示的字段也都可以放到data里面。data中的start_data的格式可以是多样的,例如:'2022-09-09’或者经过方法’new Date()'格式化过的数据,时间戳等可以自己探索看看,应该也可以。
links就是存依赖关系的字段,由id,source,target,type组成,id具有唯一性,type描述的是source_start\source_end\target_start\target_end之间的选取关系,取值有‘0’,‘1’,‘2’,‘3’,四种取值,其中0是source_end to target_start。
了解以上内容就可以使用、绘制出甘特图了

其他

更新数据,重新绘制

思路:绘制新数据前,先把旧的图形删掉,再重新绘制新的图像(如果没有清除的这一个动作,会接着当前数据继续绘制
dhtmlx中有一个清除全部数据的事件gantt.clearAll(),可以在获取数据,处理数据之前就先把旧数据清除掉。

// 获取数据的函数
 getGanttData1() {
 // 首先清除数据
      gantt.clearAll()
      // 通过接口获取数据
      getGanttData({ project_id: this.id }).then(res => {
        this.tasks.data = []
        this.tasks.links = res.data.link
        if (res.data.items.length > 0) {
          res.data.items.forEach(element => {
	        	var d ={
	        	// 获取到的数据也许不符合甘特图需求,可以在这里处理数据
	          		// 在此处理数据
	        	}
            }
            this.tasks.data.push(d)
          })
          // 绘制今天的竖线
          this.todayMaker()
          // 重新初始化数据
          gantt.parse(this.$props.tasks)
        }
      })
    },

this.todayMaker():该函数是用于绘制竖线的,此处绘制的是当前时刻的竖线,因为gantt.clearAll()函数在清除的时候会把竖线都清除掉,所以每次更新数据都要重新绘制竖线
在这里插入图片描述

todayMaker() {
      const date_to_str = gantt.date.date_to_str('%Y/%m/%d')
      const today = new Date()
      gantt.addMarker({
        start_date: today,
        css: 'today',
        text: 'Now',
        title: date_to_str(today)
      })
    },

甘特图内的文字(任务名)太长,显示不下

可以通过getTaskFitValue函数,可以计算文字长度是否超过矩形框

	getTaskFitValue(task) {
      const font_width_ratio = 7
      const taskStartPos = gantt.posFromDate(task.start_date)
      const taskEndPos = gantt.posFromDate(task.end_date)

      const width = taskEndPos - taskStartPos
      const textWidth = (task.text || '').length * font_width_ratio

      if (width < textWidth) {
        const ganttLastDate = gantt.getState().max_date
        const ganttEndPos = gantt.posFromDate(ganttLastDate)
        if (ganttEndPos - taskEndPos < textWidth) {
          return 'left'
        } else {
          return 'right'
        }
      } else {
        return 'center'
      }
    },

有了判断函数之后,就可以根据return值判断文字放哪边,通过dhtmlx的配置项

// 判断文字放哪边的函数
    gantt.templates.leftside_text = function leftSideTextTemplate(start, end, task) {
      if (getTaskFitValue(task) === 'left') {
        return task.text
      }
      return ''
    }
    gantt.templates.rightside_text = function rightSideTextTemplate(start, end, task) {
      if (getTaskFitValue(task) === 'right') {
        return task.text
      }
      return ''
    }
    gantt.templates.task_text = function taskTextTemplate(start, end, task) {
      if (getTaskFitValue(task) === 'center') {
        return task.text
      }
      return ''
    }

在矩形条上显示任务完成百分比

 gantt.templates.progress_text = function(start, end, task) {
      return "<div style='text-align:left;color:#fff;padding-left:2px'>" + Math.round(task.progress * 100) + '% </div>'
    }

设置鼠标悬浮时展示的内容

在这里插入图片描述

    // 设置鼠标悬浮展示内容
    gantt.templates.tooltip_text = function(start, end, task) {
      return '<b>' + task.text + '</b><br/><b>状态:' + task.status + '</b><br/><b>' + task.time_name[0] + ':</b> ' +
    gantt.templates.tooltip_date_format(start) +
    '<br/><b>' + task.time_name[1] + ':</b> ' + gantt.templates.tooltip_date_format(end)

拖动、拖拽修改单条数据的时间

添加事件:

    const onAfterTaskDrag = gantt.attachEvent('onAfterTaskDrag', this.onAfterTaskDrag)

这里为什么要赋值给这个onAfterTaskDrag ,在切换、销毁页面那边会解释

拖拽添加依赖关系、删除依赖关系

添加依赖关系:

gantt.attachEvent('onBeforeLinkAdd', this.beforAddLink)

this.beforAddLink中可以根据对应的操作发送相关的请求,跟后台联动起来
删除依赖关系:
双击依赖关系连线,会弹出是否删除的确认框,点击确定后,会执行到以下事件

gantt.attachEvent('onBeforeLinkDelete', this.delLink)

双击事件

双击事件我是用来双击之后展示任务详情的,任务详情我们有自己封装好的组件,只需要传入任务id即可

const onTaskDblClick = gantt.attachEvent('onTaskDblClick', this.onTaskDblClick)

甘特图缩放

看了文档,发现缩放功能其实很好实现。
首先设置好缩放的几种情况,根据步长、单位的不同(按天、按月等),设置好从小到大或从大到小排列的数组zoomConfig

 const zoomConfig = {
      levels: [
        {
          // 等级的名称,设置的时候可以指定名称或者指定下标
          name: 'hours',
          // 该等级下的刻度(这个跟设置不可变的刻度是一样的,就不细说了
          scales: [
            { unit: 'day', step: 1, format: '%M%j日' },
            { unit: 'hour', step: 8, format: '%H' }
          ],
          // 允许将任务的开始和结束日期四舍五入到最近的刻度线
          round_dnd_dates: true,
          // 最小列宽
          min_column_width: 30,
          // 行高,针对当前等级的
          scale_height: 60
        },
        {
          name: 'days',
          scales: [
            { unit: 'week', step: 1, format: '第%W周(%M)' },
            { unit: 'day', step: 1, format: '%j日' }
          ],
          round_dnd_dates: true,
          min_column_width: 60,
          scale_height: 60
        },
        {
          name: 'weeks',
          scales: [
            { unit: 'month', step: 1, format: '%M' },
            { unit: 'week', step: 1, format: '第%W周' }
          ],
          round_dnd_dates: false,
          min_column_width: 60,
          scale_height: 60
        },
        {
          name: 'months',
          scales: [
            { unit: 'year', step: 1, format: '%Y' },
            {
              unit: 'quarter', step: 1, format: function quarterLabel(date) {
                const month = date.getMonth()
                let q_num
                if (month >= 9) {
                  q_num = 4
                } else if (month >= 6) {
                  q_num = 3
                } else if (month >= 3) {
                  q_num = 2
                } else {
                  q_num = 1
                }
                return 'Q' + q_num
              }
            },
            { unit: 'month', step: 1, format: '%M' }
          ],
          round_dnd_dates: false,
          min_column_width: 50,
          scale_height: 60
        },
        {
          name: 'years',
          scales: [
            { unit: 'year', step: 1, format: '%Y' },
            {
              unit: 'year', step: 5, format: function(date) {
                const dateToStr = gantt.date.date_to_str('%Y')
                const endDate = gantt.date.add(gantt.date.add(date, 5, 'year'), -1, 'day')
                return dateToStr(date) + ' - ' + dateToStr(endDate)
              }
            }
          ],
          round_dnd_dates: false,
          min_column_width: 50,
          scale_height: 60
        }
      ]

    }

有了zoomConfig之后,初始化,我的这个zoomConfig一共是有4级,分别是day,week,month,year,对应下标是0,1,2,3,可以根据需要设置更多更细的等级明细,这个name是不能重复的

	//
	gantt.ext.zoom.init(zoomConfig)
	// 设置初始化甘特图时等级为1
    gantt.ext.zoom.setLevel(1)

要缩放,我们可以来两个按钮

	  <div class="">
        <i class="el-icon-zoom-in zoomBtn" @click="zoomIN" />
        <i class="el-icon-zoom-out zoomBtn" @click="zoomOut" />
      </div>
  //zoomIn和zoomOut
    zoomIN() {
    // 获取当前等级
      var level = gantt.ext.zoom.getCurrentLevel()
      // 做一下判断,大于0才能放大
      if (level > 0) gantt.ext.zoom.setLevel(level - 1)
    },
    zoomOut() {
      var level = gantt.ext.zoom.getCurrentLevel()
      if (level < 4) gantt.ext.zoom.setLevel(level + 1)
    }
  

这样就实现了缩放功能了

动态加载

dhtmlx的动态加载是需要收费的,但无奈数据九百多条,一下子全部加载出来需要好一会
所以就非常需要动态加载这个功能
所以,就用免费的事件 实现了动态加载的功能(不是)
以下是我实现的思路

首先,让后端多传一个值,代表的是改任务是否有子任务

让后端多传一个值,haschild,是一个布尔值,在处理数据的时候,把这个值传给$has_child,如下图,因为后端传值不符合,所以都是前端要进行处理
在这里插入图片描述

同时还要配置
    // 启用动态加载
    gantt.config.branch_loading = true

这样,在加载完第一级任务是,在任务的最前面就会有一个+号,是可以展开的
当然此时点击展开是没有反应的

然后我们再配置gantt.load

具体可以看 官方文档,这个方法只适用于多页面,不适用于vue,因为动态加载只有第一次点击+会调用gantt.load方法,当你切换其他页面再切回来时,点击按钮就无效了

为了解决上述问题,我找到了gantt的单击事件,发现点击+是会触发这个单击事件的
// 判断父任务是否需要动态请求数据
childExist(id, e) {
  var ele = e.target.className
  if (ele.indexOf('gantt_open') > -1) {
    var child = gantt.getChildren(id)
    if (child.length > 0) {
      return true
    } else {
      this.getSubTask(id)
    }
  }
  return true
},

我的做法是每次点击任务,判断点击元素是否是+如果是,就获取当前点击元素的子元素,如果子元素长度为0,择请求接口,获取子元素,然后把子元素插入到图中
预测可能会存在数据不能及时更新问题,唉~等提需求之后再改吧

切换页面、销毁页面

切换页面的时候vue是会销毁页面的,但是甘特图不会被销毁,而且重复添加事件会被重复执行,所以在钩子函数beforeDestroy里面要删除事件,就是gantt.attachEvent要在对应的gantt.detachEvent中进行删除
具体方法就是在添加事件的时候,赋值,并push到一个数组里面,反正就是每一次有执行到mounted钩子的时候是创建,执行到beforeDestroy再删除,这样就不会再重复发起请求了

    const onAfterTaskDrag = gantt.attachEvent('onAfterTaskDrag', this.onAfterTaskDrag)
    this.events.push(onAfterTaskDrag)

 beforeDestroy() {
    // console.log('woyao bei xiaohui la')
    // gantt.destructor()
    this.events.forEach(ele => {
      gantt.detachEvent(ele)
    })
  },

除此之外,我还给甘特图加上了图例,以颜色来区分不同状态下的任务

GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐