dhtmlx甘特图--vue2
dhtmlx甘特图
介绍
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)
})
},
除此之外,我还给甘特图加上了图例,以颜色来区分不同状态下的任务
更多推荐
所有评论(0)