简介
本文基于客户需求及参考老东家plusgantt实现不同角度的gantt图,额外引入了moment.js对日期快速处理
1.为了达到客户这种效果首先要确认数据源,项目作为一条任务,作业作为横道。达到多计划管理的需求
{
"id": "2103211629501365",
"name": "测试项目",
"projCode": "458",
"taskNum": "2",
"latest_date": "2021/3/21",
"tasks": [
{
"id": "2103211630124214",
"start_date": "2021/3/21",
"end_date": "2021/3/23",
"name": "TF-L233-002-001",
"state": "add",
"resources": [{
"id": "a2fa0534-ba64-4b16-8fb8-0638868a09d1",
"name": "沈松涛"
}]
},
{
"id": "2103211636069805",
"start_date": "2021/3/21",
"end_date": "2021/3/23",
"name": "TF-458-002-002",
}
]
},
2.为了实现此效果gantt图,要先设计gantt的基础配置。总结了以下几个基础属性
this.config = {
width: "100%",
height: "100%",
columnDefaultWidth: "100px", //表格列默认宽度
scaleDefautWidth: 28, //gantt列默认宽度
scaleDefaultHeight: 48, //gantt 默认单行行高
ganttTopDateMode: "YYYY年MM月", //日历格式
type: "project", //项目维度, project:项目 resources:人员
initScrollTop: 0, //记录初始化滚动条top
initScrollLeft: 0, //记录初始化滚动条left
scrollTicking: false //滚动定时器
}
代码入口及使用方法
<div id="ganttChart" class="row row-grid ganttview" style="margin: 5px;height: 100%;"></div>
var powerGantt = new powerGantt(document.getElementById("ganttChart"));
powerGantt.setColumn([]);
powerGantt.setData([])
var powerGantt = function (element, config) {
this.config = {
width: "100%",
height: "100%",
columnDefaultWidth: "100px", //表格列默认宽度
scaleDefautWidth: 28, //gantt列默认宽度
scaleDefaultHeight: 48, //gantt 默认单行行高
ganttTopDateMode: "YYYY年MM月", //日历格式
type: "project", //项目维度, project:项目 user:人员
initScrollTop: 0, //记录初始化滚动条top
initScrollLeft: 0, //记录初始化滚动条left
scrollTicking: false //滚动定时器
}
this.data = [];
this.columns = [];
this.el = element;
this.config = {
...this.config,
...config
};
this.init();
}
powerGantt.prototype.init = function () {
let self = this;
//初始化gantt结构和配置
this.el.classList.add("power-gantt");
// this.el.style.width = this.config.width;
// this.el.style.height = this.config.height;
this.el.style.position = "relative";
//创建grid区域
this.ganttGrid = document.createElement("div");
this.ganttGrid.innerHTML = "";
this.ganttGrid.className = "power-ganttgrid"
//创建gantt区域
this.ganttView = document.createElement("div");
this.ganttView.innerHTML = "";
this.ganttView.className = "power-ganttview "
this.el.appendChild(this.ganttGrid);
this.el.appendChild(this.ganttView);
//开始绘制
this.render();
}
预览生成效果、是不是啥也看不到,看不到就对了,因为还没开始
3.开始绘制gantt,含预处理验证数据、计算gantt视图区域的开始结束、根据任务的startDate、endDate对数据进行level处理
powerGantt.prototype.render = function () {
//数据level处理
this.startDate = moment().add(-7, 'day').format('L');
this.endDate = moment().add(7, 'day').format('L');
if (this.data.length) {
//找出gantt的开始结束日期
for (var i = 0; i < this.data.length; i++) {
let projectTask = this.data[i].tasks;
var project = this.data[i];
//对每个项目的任务进行level分组
this.toLevelData(project);
//循环过程中更新startDate、endDate
this.startDate=....
this.endDate=....
}
}
//绘制grid,表格绘制,不在额外描述
this.renderGrid();
//绘制ganttview区域
this.renderGantt();
}
4.绘制ganttview(绘制grid不在额外介绍)
powerGantt.prototype.renderGantt = function () {
let self = this;
self.ganttView.innerHTML = '';
self.ganttViewHeader = document.createElement("div");
self.ganttViewHeader.className = "power-ganttview-header"
self.ganttViewBody = document.createElement("div");
self.ganttViewBody.className = "power-ganttview-body";
self.ganttView.appendChild(self.ganttViewHeader);
self.ganttView.appendChild(self.ganttViewBody);
self.ganttViewBody.onscroll = function (event) {
//上下滚动,同步表格和gantt的滚动高度
}
self.renderGanttHeader();
self.renderGanttBody();
}
//ganttview时间刻度
powerGantt.prototype.renderGanttHeader = function () {
//month-day
this.scalcColumnLength = 0;
let scaleWidth = this.config.scaleDefautWidth;
let topMode = this.config.ganttTopDateMode;
let topTimeScale = `<div class="power-ganttview-toptimescale">`;
let bottomTimeScale = `<div class="power-ganttview-bottomtimescale">`;
let viewData = this.startDate;
let left = 0;
//头部区域编辑无事件,可用字符串拼接
...
topTimeScale += `</div>`
bottomTimeScale += `</div>`
let gutter = `<div class='gutter' style='left:${left}px;width:17px'></div>`
this.ganttView.children[0].innerHTML = topTimeScale + bottomTimeScale + gutter;
}
//ganttview内容区域
powerGantt.prototype.renderGanttBody = function () {
let ganttGrid = this.renderGanttGrid();
this.ganttView.children[1].appendChild(ganttGrid)
let ganttTask = this.renderGanttTask();
this.ganttView.children[1].appendChild(ganttTask)
}
//gantt表格绘制
powerGantt.prototype.renderGanttGrid = function () {
let ganttGrid = document.createElement("div");
ganttGrid.className = "power-ganttview-grid";
//计算行、列数量
let scaleWidth = this.config.scaleDefautWidth;
let scaleHeight = this.config.scaleDefaultHeight;
let rowLength = this.data.length;
let columnLength = this.scalcColumnLength;
let rowHeight = 0;
for (let i = 0; i < rowLength; i++) {
let level = this.data[i].level;
let gridRow = document.createElement("div");
gridRow.className = "power-ganttview-row";
gridRow.style.top = rowHeight + "px";
gridRow.style.width = columnLength * scaleWidth + "px";
gridRow.style.height = scaleHeight * level + "px";
rowHeight += scaleHeight * level;
ganttGrid.appendChild(gridRow)
}
for (let l = 0; l < columnLength; l++) {
let weekdd = moment(this.startDate).add(l, 'day').days();;
let gridColumn = document.createElement("div");
gridColumn.className = "power-ganttview-column"
if (weekdd == "6" || weekdd == "0") {
gridColumn.classList.add("weeked");
}
gridColumn.style.left = l * scaleWidth + "px";
gridColumn.style.width = scaleWidth + "px";
gridColumn.style.height = rowHeight + "px";
ganttGrid.appendChild(gridColumn)
}
return ganttGrid;
}
//gantt任务绘制
powerGantt.prototype.renderGanttTask = function () {
let self = this;
let scaleHeight = this.config.scaleDefaultHeight;
let scaleWidth = this.config.scaleDefautWidth;
let taskDomContainer = document.createElement("div");
taskDomContainer.className = "power-ganttview-taskview"
let taskTop = 0;
let levelTop = 0;
for (var i = 0; i < this.data.length; i++) {
var row = this.data[i];
let rowLevelList = row.levelList;
let projectRow = document.createElement("div");
projectRow.className = "power-ganttview-project-row"
for (let key in rowLevelList) {
for (let j = 0; j < rowLevelList[key].length; j++) {
let task = rowLevelList[key][j]
let days = moment(task.end_date).diff(moment(task.start_date), 'days') + 1;
let width = scaleWidth * days;
let left = moment(task.start_date).diff(moment(this.startDate), 'days') * scaleWidth;
let taskDom = document.createElement("div");
taskDom.className = "power-ganttview-task";
taskDom.setAttribute("title", task.name);
taskDom.style.left = left + "px";
taskDom.style.top = levelTop + "px";
taskDom.style.width = width + "px";
taskDom.style.position = "absolute";
taskDom.ondblclick = this.onTaskDblClick.bind(this, taskDom, task);
projectRow.appendChild(taskDom);
let userContent = document.createElement("div");
userContent.className = "ganttview-block-user";
userContent.id = task.id;
userContent.setAttribute("project-id", row.id);
taskDom.appendChild(userContent);
let reszieLeft = document.createElement("div");
reszieLeft.className = "ui-resizable-handle ui-resizable-w";
reszieLeft.style.zIndex = 90;
let reszieRight = document.createElement("div");
reszieRight.className = "ui-resizable-handle ui-resizable-e";
reszieRight.style.zIndex = 90;
taskDom.appendChild(reszieLeft);
taskDom.appendChild(reszieRight);
let taksName = document.createElement("div");
taksName.className = "ganttview-block-text";
taksName.innerText = task.name;
taskDom.appendChild(taksName);
let startX = 0;
//任务拖动,以下省略业务代码
taskDom.onmousedown = function (event) {
event.stopPropagation();
document.onmousemove = function (event) {
event.stopPropagation();
console.log("onmousemove")
}
document.onmouseup = function (event) {
console.log("onmouseup")
event.stopPropagation();
document.onmousemove = null;
document.onmouseup = null;
}
}
//拖动修改任务结束日期
reszieRight.onmousedown = function (event) {
event.stopPropagation();
document.onmousemove = function (event) {
event.stopPropagation();
}
document.onmouseup = function (event) {
event.stopPropagation();
document.onmousemove = null;
document.onmouseup = null;
}
}
//拖动修改开始日期
reszieLeft.onmousedown = function (event) {
event.stopPropagation();
document.onmousemove = function (event) {
event.stopPropagation();
}
document.onmouseup = function (event) {
event.stopPropagation();
document.onmousemove = null;
document.onmouseup = null;
}
}
}
levelTop += scaleHeight;
}
//添加最晚时间线
if (row.latest_date) {
let left = (moment(row.latest_date).diff(moment(this.startDate), 'days') + 1) * scaleWidth;
let lastLine = document.createElement("div");
lastLine.className = "power-ganttview-laseLine";
lastLine.style.background = "red";
lastLine.style.left = left + "px";
lastLine.style.top = taskTop + "px";
lastLine.style.width = "2px";
lastLine.style.marginLeft = "-1.5px";
lastLine.style.position = "absolute";
lastLine.style.height = (row.level * scaleHeight) + "px";
projectRow.appendChild(lastLine);
}
taskTop += row.level * scaleHeight;
levelTop = taskTop;
taskDomContainer.appendChild(projectRow)
}
return taskDomContainer;
}
5.绘制完成。方法使用、绑定事件及事件使用方法
使用方式
<div id="ganttChart" class="row row-grid ganttview" style="margin: 5px;height: 100%;"></div>
var powerGantt = new powerGantt(document.getElementById("ganttChart"));
powerGantt.setData([]);
powerGantt.on("onTaskDblClick", function (event, task) {
console.log("onTaskDblClick",task)
})
powerGantt.prototype.setColumn = function (columns) {
this.columns = columns;
this.renderGrid();
}
powerGantt.prototype.on = function (event, callback) {
let eventName = "on" + event.replace(event[0], event[0].toUpperCase());
this[eventName] = callback.bind(this);
}
powerGantt.prototype.onTaskDblClick = function (event, task) {
console.log(task)
return false
}