需求:
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)页面销毁时删除甘特图事件
这世界很喧嚣,做你自己就好