Vue Dhtmlxgantt甘特图/横道图 baselines 含(计划、实际时间对比)树形实例实现及部分扩展

1,248 阅读4分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

Vue Dhtmlxgantt甘特图/横道图 baselines 含(计划、实际时间对比)树形实例实现及部分扩展

背景: 需满足计划、实际时间对比需求,本人查看了很多文档和资料(对比了dhtmlxgantt、jquery gantt、wl-gantt、Any Gantt、ganttView)【耗时近一周】,基本都是不太理想的,其中有一个wl-gantt(基于vue element-ui框架)可以使用,但是本系统基于Ant Design Vue框架,需要按需加载element-ui,故而摒弃;最后选择了dhtmlxgantt pro很多文档上面没有记载dhtmlxgantt普通版本是否包含计划字段及扩展字段,会导致占用大家很多徒劳的时间,如下图:

上图链接:

Extra Elements in Timeline Area

一、项目引入

1.根目录结构

2.Vue 引入

js:

import gantt from '../gantt/dhtmlxgantt.js?v=7.0.13'

css lang="less":

@import url('../gantt/skins/dhtmlxgantt_terrace.css'); 

二、实例初始化

<div id="gantt_here"
           style="position: relative;min-height: 560px;width: 100%; height:100%;"></div>
gantt.init('gantt_here_master')
gantt.parse(this.tableData) // tableData则当前甘特图的属性极其数据

支持tableData格式为(List,树形关联关系:id->patrent):

[
    {id:1, text:"Project #2", start_date:"01-04-2013", duration:18},
    {id:2, text:"Task #1",    start_date:"02-04-2013", duration:8,
     progress:0.6, parent:1},
    {id:3, text:"Task #2",    start_date:"11-04-2013", duration:8,
     progress:0.6, parent:1}
]

三、具体实现

1.源数据获取(后端接口)->组装成dhtmlx gantt支持的数据结构:

  this.$get(this.api).then((response) => {
        this.tableData.data = response.data.map((item) => {
          /**  说明:
           *   此处api中的此处api中的planned_start,planned_end与计划时间不同,系统需要的实际时间即API中的计				划时间 故现用实际时间替代[该地方不同业务,对应不同的方式]
           */
          // 根据计划情况配置表
          if (item.startTime !== null && item.endTime !== null) {
            item.start_date = item.startTime
            item.end_date = item.endTime

            // 用于格式化
            item.startTime = this.eightBitTimestamp(new Date(item.startTime))
            item.endTime = this.eightBitTimestamp(new Date(item.endTime))
          }
          // 根据实际情况配置表
          if (item.actualStartTime !== null && item.actualFinishTime !== null) {
              item.planned_start = item.actualStartTime
              item.planned_end = item.actualFinishTime
          } else if (
              item.actualStartTime !== null &&
              item.actualFinishTime == null
          ) {      
            item.planned_start = item.actualStartTime
            item.planned_end = this.returnBitTimestamp(new Date())
          }
          return {
            ...item,
            parent: item.parentId
          }
        })
        // 引入甘特图配置及初始化
        this.ganttConfig()
      })
    }

2.gantt图初始化配置

gantt表格 grid列配置:

columns: [
    { name: 'sortNum', label: '序号', align: 'center' },
    {
        name: 'warningLevel',
        label: '预警',
        min_width: 30,
        align: 'center',
        template: function (obj) {
            let textHtml = ''
            if (obj.warningLevel === 4) {
                // 正常延期
                textHtml =
                    "<img src='/img/yujing_blue.png' style='width:20px;height:20px;'>"
            } else if (obj.warningLevel === 5) {
                // 一般延期
                textHtml =
                    "<img src='/img/yujing_yellow.png' style='width:20px;height:20px;'>"
            } else if (obj.warningLevel === 6) {
                // 严重延期
                textHtml =
                    "<img src='/img/yujing_red.png' style='width:20px;height:20px;'>"
            } else {
                textHtml = ''
            }
            return textHtml
        }
    },
    {
        name: 'planName',
        label: '任务名称',
        tree: true,
        min_width: 180,
        align: 'center',
        template: function (obj) {
            let textHtml =
                "<div class='moreText' title='" +
                obj.planName +
                "'>" +
                obj.planName +
                '</div>'
            return textHtml
        }
    },
    {
        name: 'startTime',
        label: '计划开始时间',
        min_width: 110,
        align: 'center'
    },
    {
        name: 'endTime',
        label: '计划完成时间',
        min_width: 110,
        align: 'center'
    },
    { name: 'duration1', label: '工期天数', align: 'center' },
    {
        name: 'keyNodeLevel',
        label: '节点等级',
        align: 'center'
    },
    {
        name: 'actualStartTime',
        label: '实际开始时间',
        min_width: 110,
        align: 'center'
    },
    {
        name: 'actualFinishTime',
        label: '实际完成时间',
        min_width: 110,
        align: 'center'
    }
],

甘特图配置:

	// 甘特图配置
    ganttConfig() {
      gantt.config.date_format = '%Y-%m-%d %H:%i:%s'
      // gantt图布局设计 本例使用_scrollable_grid布局,方便多时间跨度查看
      gantt.config.layout = {
        css: 'gantt_container',
        cols: [
          {
            width: 500,
            min_width: 300,
            rows: [
              {
                view: 'grid',
                scrollX: 'gridScroll',
                scrollable: true,
                scrollY: 'scrollVer'
              },
              { view: 'scrollbar', id: 'gridScroll', group: 'horizontal' }
            ]
          },
          { resizer: true, width: 1 },
          {
            // width: 950,
            // min_width: 600,
            rows: [
              {
                view: 'timeline',
                scrollX: 'scrollHor',
                scrollable: true,
                scrollY: 'scrollVer'
              },
              { view: 'scrollbar', id: 'scrollHor', group: 'horizontal' }
            ]
          },
          { view: 'scrollbar', id: 'scrollVer' }
        ]
      }

      gantt.config.task_height = 16
      gantt.config.row_height = 40
      gantt.config.auto_scheduling = true
      // 网络图部分表头格式化
      gantt.config.scales = [
        { unit: 'year', step: 1, date: '%Y年' },
        { unit: 'month', step: 1, date: '%m月' }
      ]

      gantt.config.min_column_width = 50
      // 表头高度
      gantt.config.scale_height = 48
      // gantt task
      // adding baseline display
      // 该方法用于添加甘特图baseline,即【绘制计划时间】
      this.ganttTask()
      // 只读模式
      gantt.config.readonly = true

      // 默认是否展开树结构
      gantt.config.open_tree_initially = false
	  // grid 列表配置 上段代码即是
      gantt.config.columns = this.columns
      // 如果时间跨度很长,则使用此配置可用于显著加快图表显示速度。
      gantt.config.smart_scales = true
	  // 启用/禁用在图表区域中显示列边框
      gantt.config.show_task_cells = true
      // 甘特图以自动扩展时间范围,以适应所有显示的任务
      gantt.config.fit_tasks = true
      // 是否调用模版方法渲染source时间轴的空白元素
      gantt.config.resource_render_empty_cells = true

      gantt.init('gantt_here_master')
      gantt.parse(this.tableData)
      this.ganttTask()
      gantt.refreshData()
    },
    // 该方法用于添加甘特图baseline,即【绘制计划时间】
    // adding baseline display
    ganttTask() {
      gantt.addTaskLayer({
        renderer: {
          render: function draw_planned(task) {
            if (task.planned_start && task.planned_end) {
              var sizes = gantt.getTaskPosition(
                task,
                task.planned_start,
                task.planned_end
              )
              var el = document.createElement('div')
              if (task.pending) {
                el.className = 'baseline pending-gantt'
              } else {
                el.className = 'baseline'
              }
              el.style.left = sizes.left + 'px'
              el.style.width = sizes.width + 'px'
              el.style.top = sizes.top + gantt.config.task_height + 13 + 'px'
              return el
            }
            return false
          },
          // define getRectangle in order to hook layer with the smart rendering
          getRectangle: function (task, view) {
            if (task.planned_start && task.planned_end) {
              return gantt.getTaskPosition(
                task,
                task.planned_start,
                task.planned_end
              )
            }
            return null
          }
        }
      })
      gantt.templates.task_class = function (start, end, task) {
        if (task.planned_end) {
          var classes = ['has-baseline']
          if (end.getTime() > task.planned_end.getTime()) {
            classes.push('overdue')
          }
          return classes.join(' ')
        }
      }

      gantt.attachEvent('onTaskLoading', function (task) {
        task.planned_start = gantt.date.parseDate(
          task.planned_start,
          'xml_date'
        )
        task.planned_end = gantt.date.parseDate(task.planned_end, 'xml_date')
        return true
      })
      gantt.config.lightbox.sections = [
        // { name: 'time', map_to: 'auto', type: 'duration' },
        {
          name: 'baseline',
          map_to: { start_date: 'planned_start', end_date: 'planned_end' },
          button: true,
          type: 'duration_optional'
        }
      ]
      gantt.locale.labels.section_baseline = 'Planned'
    }

四、部分样式优化

甘特图样式,可以通过API内置方法添加类Class,然后在css中实现:

gantt.templates.task_class = function (start, end, task) {
        if (task.planned_end) {
          return classes.join('class-name')
        }
      }

图例css:

.gantt_task_line {
    background-color: #3b97fe;
    border: #3b97fe;
    height: 10px !important;
    border-top-right-radius: 100px;
    border-bottom-right-radius: 100px;
  }
  .gantt_task_progress {
    background: #ffd180;
    border-top-right-radius: 100px;
    border-bottom-right-radius: 100px;
  }
  .baseline {
    position: absolute;
    border-radius: 2px;
    opacity: 0.6;
    margin-top: -9px;
    height: 12px;
    background: #ffc93a;
    // border: 1px solid rgb(255, 153, 0);
  }

五、预览效果

本例为demo效果,未投入正式使用环境