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效果,未投入正式使用环境