需求:
1、不同状态的任务用不同颜色展示
2、当前日期添加竖线标志
3、周末设置背景颜色
4、可按小时、日、周切换(切换时间范围也需同步修改)
官方文档:Gantt API Gantt Docs
官方Demo Gantt : Samples
一、安装依赖
npm install dhtmlx-gantt
二、实现
<template>
<div class="app-container">
<!-- 操作按钮 -->
<div class="ope-wrap">
<div class="ope-left">
<el-form ref="form" :model="form" label-width="75px">
<el-form-item label="车间名称">
<el-select v-model="form.workShop" placeholder="请选择" clearable @change="handleWorkSopChange"
@clear="handleClear">
<el-option v-for="item in options" :key="item.workshopId" :label="item.workshopName"
:value="item.workshopId">
</el-option>
</el-select>
</el-form-item>
</el-form>
<div class="e-btn">
<el-button type="primary" @click="handleEditClick">编辑</el-button>
</div>
</div>
<div class="ope-right">
<el-button-group>
<el-button v-for="item in headUnitList" :key="item.value" @click="handleUnitClick(item)"
:type="activeUnit===item.value ? 'primary' : 'default'">{{ item.label }}</el-button>
</el-button-group>
</div>
</div>
<div ref='ganttContainer' class='ganttContainer'></div>
</div>
</template>
<script>
const proroderStatus = {
"CJ": "创建",
"SX": "生效",
"KG": "开工",
"ZT": "暂停"
};
const priorityList = {
"ZC": "正常",
"YX": "优先",
"JJ": "加急",
"TJ": "特急"
};
import gantt from 'dhtmlx-gantt';
import {
listWorkshop
} from "@/api/mes/md/workshop";
import {
selectWorkOrderByGantt
} from "@/api/mes/pro/proWorkOrder";
export default {
name: 'gantt',
data() {
return {
form: {
workShop: ""
},
options: [],
headUnitList: [{
value: "HOUR",
label: "小时"
}, {
value: "DAY",
label: "日"
}, {
value: "WEEK",
label: "周"
}],
activeUnit: "DAY", // 默认以日为表头单位 可选:HOUR、DAY、WEEK
// 示例数据
// [{
// id: 1,
// dome1: '项目一',
// unscheduled: true, // 配合显示计划外的任务
// render: 'split', // 这个是一行能够展示多个任务的关键点
// parent: 0, // 0 作为独立项目 这个是一行能够展示多个任务的关键点
// }, {
// id: 3,
// text: '这是一个开工工单',
// start_date: '2023-11-17',
// duration: 7,
// progress: 0.4,
// parent: 1
// }]
tasks: [], // 储存甘特图 - 内容数据集合
selectedTaskId: undefined,
}
},
methods: {
getWorkShopList() {
listWorkshop().then(res => {
this.options = res.rows;
}).catch(err => null);
},
getTasksList() {
selectWorkOrderByGantt({
workshopId: this.form.workShop
}).then(res => {
this.tasks = res.data || [];
// 根据不同状态设置颜色
this.setBarColor();
this.initGantt()
}).catch(err => null);
},
handleWorkSopChange(val) {
this.getTasksList();
},
handleClear() {
this.getTasksList();
},
initGantt() {
gantt.i18n.setLocale('cn'); // 汉化-默认是英文
gantt.plugins({
tooltip: true, // 启用tooltip悬浮框
marker: true, // 时间标记
// drag_timeline: true, // 拖动图
});
gantt.config.scale_height = 40; // 设置表头高度
// 显示计划外的任务 一般情况下刚开始时,数据是不存在任务开始时间的start_date的,不打开这个属性,则数据则无法显示。开启后需要数据配合设置:unscheduled: true
gantt.config.show_unscheduled = true;
// 隐藏进度条拖动按钮 任务中默认会有一个能够拖动的进度按钮
gantt.config.show_progress = true;
// 配置 甘特图 表头
// 小时 默认展示 当前日期前8点-明天8点 最大展示 2天前-5天后
// 日 默认展示 当前日期前4天-后10天 最大展示 7天前-30天后
// 周 默认展示 当前日期前2周-后3周 最大展示 4周前-未来12周
const startTime = this.getDay(-7);
const endTime = this.getDay(30);
gantt.config.start_date = new Date(startTime);
gantt.config.end_date = new Date(endTime);
// 设置表头维度,默认以日为单位
gantt.config.scales = [{
unit: 'day',
step: 1,
date: '%d',
format: this.dayTemplate
}];
gantt.config.scale_unit = 'day';
gantt.config.date_scale = '%D/%m.%d';
gantt.config.xml_date = '%Y-%m-%d %H:%i:%s'; // 格式化日期匹配格式
// 周末背景颜色设置
gantt.templates.scale_cell_class = function (date) {
if (date.getDay() == 0 || date.getDay() == 6) {
return "weekend";
}
};
// 日的时候表头左对齐
if (this.activeUnit === "DAY") {
gantt.templates.scale_row_class = function (date) {
return "day-label";
};
}
// 配置甘特图 左侧表头
gantt.config.columns = [{
name: 'dome1',
label: '制造单元',
width: 100
}];
// 点击空白事件
// gantt.attachEvent("onEmptyClick", function (e) {
// const task_id = e.target.parentNode.attributes.task_id.value; // id
// const targetLeft = e.target.style['left'].replace('px', ''); // 当前触发对象的位置
// const [datenode, weeksnode] = gantt.$task_scale['children']; // 获取日期所有节点数据
// let dateActive = null; // 存储点击位置对应的日期值
// for (let key in datenode.children) {
// if (datenode.children[key].style && datenode.children[key].style['left']) {
// let dateNodeLeft = datenode.children[key].style['left'].replace('px', '');
// if (dateNodeLeft == targetLeft) {
// dateActive = datenode.children[key].innerHTML
// }
// }
// };
// gantt.createTask({
// text: '',
// typeId: '',
// start_date: `2023-8-${dateActive}`,
// duration: 1,
// }, task_id)
// });
// 选择任务时触发
gantt.attachEvent("onTaskSelected", (id) => {
this.selectedTaskId = id;
});
// 任务被拖拽后处理逻辑
gantt.attachEvent("onAfterTaskDrag", (id, mode, e) => {
this.tasks.map(v => {
if (v.id == id) {
console.log(v, '----------------ccc')
}
});
});
// 显示到任务上的文本
gantt.templates.task_text = function (start, end, task) {
return "" + task.productionWorkorderCode + "<span style='margin-left:20px;'></span>" + task
.completedQuantity + "/" + task.planQuantity;
};
// 鼠标悬浮工具提示文本配置
gantt.templates.tooltip_text = function (start, end, task) {
if (task.parent === 0) {
return task.dome1;
} else {
return `
<div style='display:flex;flex-wrap:wrap;align-items: center;width:300px;'>
<div style='width: 60%;line-height: 18px;'>工单编号:${task.productionWorkorderCode}</div>
<div style='width: 40%;line-height: 18px;'>状态:${proroderStatus[task.status]}</div>
<div style='width: 60%;line-height: 18px;'>设备号:${task.deviceCode}</div>
<div style='width: 40%;line-height: 18px;'>物料编码:${task.itemCode}</div>
<div style='width: 60%;line-height: 18px;'>数量:${task.completedQuantity}/${task.planQuantity}</div>
<div style='width: 40%;line-height: 18px;'>生产班组:${task.teamName}</div>
<div style='width: 60%;line-height: 18px;'>工序名称:${task.processName}</div>
<div style='width: 40%;line-height: 18px;'>优先级:${priorityList[task.priority]}</div>
<div style='width: 60%;line-height: 18px;'>计划时间:${gantt.templates.tooltip_date_format(start)} ~ ${gantt.templates.tooltip_date_format(end)}</div>
</div>
`;
}
};
gantt.clearAll(); // 清除缓存
gantt.init(this.$refs.ganttContainer); // 初始化
// 配置数据
gantt.parse({
data: this.tasks
});
// 绘制今天的竖线
this.todayMaker();
},
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: '今天',
title: "今天: " + date_to_str(today)
});
},
weekScaleTemplate(date) {
var dateToStr = gantt.date.date_to_str("%M %d");
var endDate = gantt.date.add(gantt.date.add(date, 1, "week"), -1, "day");
return dateToStr(date) + "~" + dateToStr(endDate);
},
dayTemplate(date) {
var dateToStr = gantt.date.date_to_str("%M %d");
var weekDay = gantt.date.date_to_str("%D");
return weekDay(date) + "/" + dateToStr(date);
},
setBarColor() {
this.tasks.map(v => {
var newObj = {};
if (v.status === "CJ") {
newObj = Object.assign(v, {
'color': '#aaa8eb'
});
} else if (v.status === "SX") {
newObj = Object.assign(v, {
'color': '#7cbbff'
});
} else if (v.status === "KG") {
newObj = Object.assign(v, {
'color': '#aadab4'
});
} else {
// 暂停
newObj = Object.assign(v, {
'color': '#ffc97c'
});
}
return newObj;
});
},
handleUnitClick(obj) {
this.activeUnit = obj.value;
gantt.config.scales = [];
gantt.config.subscales = [];
switch (obj.value) {
case "HOUR":
gantt.config.start_date = new Date(this.getDay(-2));
gantt.config.end_date = new Date(this.getDay(5));
gantt.config.subscales = [{
unit: "hour",
step: 2,
format: "%H"
}];
gantt.templates.scale_cell_class = "";
gantt.templates.scale_row_class = function (date) {
return "hour-label";
};
gantt.config.scale_unit = 'day';
gantt.config.date_scale = '%m/%d';
gantt.config.scale_height = 50;
gantt.config.duration_unit = "hour";
gantt.config.duration_step = 2;
gantt.render();
break;
case 'DAY':
gantt.config.start_date = new Date(this.getDay(-7));
gantt.config.end_date = new Date(this.getDay(30));
gantt.config.scales = [{
unit: 'day',
step: 1,
format: this.dayTemplate
}];
gantt.templates.scale_cell_class = function (date) {
if (date.getDay() == 0 || date.getDay() == 6) {
return "weekend";
}
};
gantt.templates.scale_row_class = function (date) {
return "day-label";
};
gantt.config.scale_unit = 'day';
gantt.config.date_scale = '%D/%m.%d';
gantt.config.scale_height = 40;
gantt.config.duration_unit = "day";
gantt.config.duration_step = 1;
gantt.render();
break;
case 'WEEK':
gantt.config.start_date = new Date(this.getDay(-28));
gantt.config.end_date = new Date(this.getDay(84));
gantt.config.subscales = [{
unit: 'week',
step: 1,
template: this.weekScaleTemplate,
}];
gantt.templates.scale_cell_class = "";
gantt.templates.scale_row_class = function (date) {
return "";
};
gantt.config.scale_unit = 'week';
gantt.config.date_scale = '%F';
gantt.config.scale_height = 50;
gantt.config.duration_unit = "week";
gantt.config.duration_step = 1;
gantt.render();
break;
default:
return {};
}
},
getDay(day) {
var today = new Date();
var targetday_milliseconds = today.getTime() + 1000 * 60 * 60 * 24 * day;
today.setTime(targetday_milliseconds); //注意,这行是关键代码
var tYear = today.getFullYear();
var tMonth = today.getMonth();
var tDate = today.getDate();
tMonth = this.doHandleMonth(tMonth + 1);
tDate = this.doHandleMonth(tDate);
return tYear + "-" + tMonth + "-" + tDate;
},
doHandleMonth(month) {
var m = month;
if (month.toString().length == 1) {
m = "0" + month;
}
return m;
},
handleEditClick() {
this.tasks.map(v => {
if (v.id == this.selectedTaskId) {
this.$router.push({
path: "/proWorkOrder/editProWorkOrder",
query: {
operate: "edit",
productionWorkorderId: v.productionWorkorderId,
status: v.status,
editType: "schedule"
},
});
}
});
},
},
mounted() {
this.getWorkShopList();
this.getTasksList();
},
created() {}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.ganttContainer {
width: 100%;
min-height: calc(100vh - 200px);
}
.ope-wrap {
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
.ope-left {
display: flex;
align-items: center;
::v-deep .el-form-item {
margin-bottom: 0px !important;
}
.e-btn {
margin-left: 15px;
}
}
.ope-right {}
}
</style>
<style scoped>
@import "~dhtmlx-gantt/codebase/dhtmlxgantt.css";
/* 周末背景颜色 */
::v-deep .weekend {
background: #dedede !important;
}
/* 工作日背景颜色 */
::v-deep .weekday {
background: #c9e8f9 !important;
}
/* 日期的对齐方式 */
::v-deep .day-label .gantt_scale_cell {
text-align: left !important;
}
/* 时间 小时 样式 */
::v-deep .hour-label:nth-child(2) .gantt_scale_cell {
text-align: left !important;
}
</style>
三、效果展示
四、补充
甘特图事件
一开始事件我是这么写的,但是后面开发过程中发现事件会重复触发。于是查了些资料得知切换页面时vue是会销毁页面的,但是甘特图不会被销毁,重复添加事件会重复执行。所以要在vue页面销毁时删除甘特图事件,用 gantt.detachEvent 进行删除
1)在data中定义一个数组用来存放甘特图事件
2)甘特图事件处理函数写法如下,把事件处理函数push到events数组中
3)页面销毁时删除甘特图事件
这世界很喧嚣,做你自己就好