效果图:
甘特图的概念
一种用于管理时间和任务活动的工具,以条状图或者说横道图表现出来,方便管理者查看活动计划、跟进任务进度、合理分配资源。
官网地址
https://dhtmlx.com/
可下载整个文档 ,解压如下:
开始使用
1. 导入js文件
// 本地
<script src="./dhtmlx-gantt/codebase/ganttapi.js"></script>
<script src="./dhtmlx-gantt/codebase/dhtmlxgantt.js"></script>
// 主题颜色css文件
<link rel="stylesheet" href="./dhtmlx-gantt/codebase/skins/dhtmlxgantt_terrace.css">
// 在线
<script src="http://export.dhtmlx.com/gantt/api.js"></script>
2. 页面加载
<div id="gantt" ref="gantt" style="height: 500px;"></div>
扩展功能
<div class="app-icon">
<div>
<el-button :icon="isShow ? 'el-icon-s-fold' : 'el-icon-s-unfold'" size="mini" @click="toggle_grid"></el-button>
<el-button size="mini" :icon="enlarge ? 'el-icon-full-screen' : 'el-icon-full-screen'" @click="toggle_fullScreen"
id="full-screen-btn"></el-button>
<el-button size="mini" :icon="isopened ? '' : ''" @click="toggle_open_folder">
<img v-show="isopened" style="width: 10px" src="../assets/icon/层级展开.svg" alt="" />
<img v-show="!isopened" style="width: 10px" src="../assets/icon/收起.svg" alt="" />
</el-button>
</div>
<div>
<el-dropdown style="margin-left: 10px ;transform:translateY(1px)" @command="downloadPng" size="mini"
type="primary">
<span class="el-dropdown-link">
导出<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="png">PNG</el-dropdown-item>
<el-dropdown-item command="pdf">PDF</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-select style="width:95px;margin-left: 10px" size="mini" @change="changeSkin" v-model="skins" placeholder="主题">
<el-option v-for="item in themeOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<el-select style="width:64px;margin-left: 10px" size="mini" @change="changeZoom" v-model="radio1">
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
</div>
3. vue 加载甘特图
3.1 初始化甘特图
initGantt() {
gantt.clearAll();
// 启用表格中的排序
gantt.config.sort = this.ganttSort ? true : false;
// 设置日期格式, 该日期格式用于解析数据集中的数据并将日期发送回服务器
gantt.config.date_format = "%Y-%m-%d %H:%i:%s";
// 时间轴的高度
gantt.config.bar_height = 16;
// 默认表格的行高
gantt.config.row_height = 40;
// 对应于 一个单位的持续时间的数据属性 建议两个同时使用
gantt.config.duration_step = 1;
gantt.config.duration_unit = "minute";
// 只读模式
gantt.config.readonly = true;
// 设置成中文
gantt.i18n.setLocale("cn");
gantt.plugins({ marker: true, tooltip: true });
if (this.columns.length) {
gantt.config.columns = this.columns;
}
// tootip 提示
gantt.templates.tooltip_text = function (start, end, task) {
let htmlStr = "";
this.columns?.forEach((item) => {
if (item.tooltip) {
htmlStr = htmlStr + `<b>${item.label}:</b>${task[item.name]}<br/>`;
}
});
return htmlStr;
};
//设置 年月日周显示模式
gantt.ext.zoom.init(this.zoomConfig);
gantt.ext.zoom.setLevel("日");
var dateToStr = gantt.date.date_to_str(gantt.config.task_date);
gantt.addMarker({
//添加今日时间线
start_date: new Date(), //a Date object that sets the marker's date
css: "today", //a CSS class applied to the marker
text: "当前", //the marker title
title: dateToStr(new Date()), // the marker's tooltip
});
gantt.init(this.$refs.gantt);
this.addPlanTime();
gantt.parse(this.tasks);
},
3.2 添加计划时间
addPlanTime(){
gantt.config.lightbox.sections = [
{
name: 'description', // section的名字
height: 70, // 数字部分的高度,不适用复选框和单选部分
map_to: 'text', //字符串,数据属性的名称映射到部分
type: 'textarea',//字符串,部分控制的类型(编辑)
focus: true //布尔,如果设置为true,将部分打开lightbox
},
{
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'
// 添加计划时间条
gantt.addTaskLayer({
renderer: {
render: function draw_planned(task){
if (task.planned_start && task.planned_end) {
const sizes = gantt.getTaskPosition(
task,
task.planned_start,
task.planned_end
)
const el = document.createElement('div')
el.className = 'baseline'
el.style.left = sizes.left + 'px'
el.style.width = sizes.width + 'px'
el.style.top = sizes.top + gantt.config.bar_height + 13 + 'px'
return el
}
return false
},
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){
let classes = ['has-baseline']
if( end.getTime() > task.planned_end.getTime()){
classes.push('overdue')
}
return classes.join(' ')
}
}
// 逾期文本设置
gantt.templates.rightside_text = function(start, end, task) {
if(task.planned_end) {
if(end.getTime() > task.planned_end.getTime()) {
let overdue = Math.ceil(
Math.abs(
(end.getTime() - task.planned_end.getTime()) / (24 * 60 * 60 * 100)
)
)
let text = '<b>延期: ' + overdue + ' days</b>'
return text
}
}
}
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
})
}
3.3 切换日期 根据日期显示不同视角
changeZoom(name) {
gantt.ext.zoom.setLevel(this.radio1)
}
3.4 切换表格是否显示
toggle_grid() {
this.isShow = !this.isShow
gantt.config.show_grid = !gantt.config.show_grid
gantt.render()
}
3.5 表格项目展示折叠
toggle_open_folder(){
this.isopened = !this.isopened
if(!this.isopened){
gantt.batchUpdate(() => {
gantt.eachTask((task) => {
gantt.close(task.id)
})
})
}else {
gantt.batchUpdate( () => {
gantt.eachTask((task) => {
gantt.open(task.id)
})
})
}
}
3.6 下载图片
downloadPng(type) {
let exportStyle = `
<style>
.gantt_task_line {
margin-top: -9px;
}
.gantt_line_wrapper {
margin-top: -9px;
}
.gantt_side_content {
margin-bottom: 7px;
}
.gantt_task_link .gantt_link_arrow {
margin-top: -12px;
}
.gantt_side_content.gantt_right {
bottom: 0;
}
.baseline {
position: absolute;
border-radius: 2px;
opacity: 0.6;
margin-top: -7px;
height: 14px;
background: #ffd180;
border: 1px solid rgb(255, 153, 0);
}
</style>
`
switch(type) {
case 'png':
gantt.exportToPNG({
raw: true,
header: exportStyle
})
break;
case: 'pdf':
gantt.exportToPDF({
raw: true,
header: exportStyle
})
break;
}
}
3.7 更换主题皮肤
changeSkin(){
var link = document.createElement('link')
link.onload = () => {
gantt.render
}
link.rel = 'stylesheet'
link.id = 'skin'
link.href = "./dhtmlx-gantt/codebase/skins/dhtmlxgantt_" + this.skins + '.css'
if( !linkdom ){
document.head.appendChild(link)
}
document.head.replaceChild(link, document.querySelectior('#skin'))
}
3.8 全屏
toggle_fullScreen(){
const html = document.querySelector('html')
const fullScreenBtn = document.getElementById('full-screen-btn')
fullScreenBtn.onclick = () => {
html.requestFullScreen().then(() => {
console.log('进入全屏')
})catch(() => {
console.log('全屏失败')
})
}
}
数据源
const tasks = {
// 渲染数据位置
"data": [
{
"id": "10", //当前节点id 必传项
"text": "Project #1", //文本内容
"description": "project description", //描述
"start_date": "01-04-2025", //起始位置
"duration": 3, //占位时间段长度 **此项若不传 就需要end_date
"order": 10, //
"progress": 0.4, //进度条
"open": true //是否默认展开
},
{
"id": "4", "text": "Task #4", "description": "Task #4 description", "start_date": "01-04-2025", "duration": 2, "order": 20, "progress": 0.6,
"parent": "20" //设置表格是否有父子关系
}
],
// 渲染连接线
"links": [
{
"id": 4, //当前节点id
"source": 2, //
"target": 5, //连接目标
"type": "0" //设置连接线得位置
}
]
}