前端采用jqueryGantt,github地址为:https://github.com/robicch/jQueryGantt
原以为后端只需要简单地保存甘特图任务列表和返回任务列表就行了。
但功能需求是:创建一套任务模板(拖动图片,更改任务依赖关系),然后根据设置的项目开始时间和选择的任务模板动态生成项目的任务列表。
这个生成任务的时间有几点问题。
1.甘特图的任务时间是不包括周六日的时间(周末双休),生成的项目任务的开始时间计算要排除周六日时间。
2.这个任务间依赖规则,这个依赖规则不是按照任务顺序来(任务1要在任务3执行完后才开始执行即depends=3)
,如下图是按照从上到下下的顺序规则来执行任务的。
在经过研究这个jqueryGant甘特图的源码后,整理出了一套处理模板任务开始和结束时间的计算规则。
参考甘特图js过程
甘特图初始化任务过程:
gantt.html
ge = new GanttMaster();
ge.loadProject(Data)
GanttMaster.js
loadProject(project)
loadTasks(project.tasks)
加载任务就是把所有任务属性都加入master的tasks中,在此过程中会将所有任务开始和结束时间进行计算
updateLinks更新任务链接,需要判断所有links链接是否有循环引用。在这里会对存在依赖规则的任务开始和结束时间进行计算
执行这些过程需要用到了方法。
GanttUtilities.js ->computeStart(start)//计算开始日期,如果是周六日则会跳过。
GanttUtilities.js ->computeEnd(end) //计算结束日期也要把非工作日给去除。
i18nJs.js ->isHoliday(end) //判断是否是周六日。
GanttTask.js ->computeStartBySuperiors(proposedStart) //会计算依赖规则链接后算出这个任务开始时间的最大值
GanttTask.js ->computeEndByDuration(start, this.duration) //通过开始时间计算结束时间
后端要根据任务模板生成任务规则 :
前置条件:项目开始时间、任务模板列表、
处理过程:
1.以项目开始时间作为任务模板列表中最早开始任务的开始时间。
2.其他没有依赖规则的任务开始和结束时间优先计算,任务开始时间和这个固定不变的工作日时间差来计算任务开始时间。
3.最后再统一计算有依赖规则的任务的开始和结束时间。
过程1
根据甘特图的js限制规则,根节点,即level为0的节点开始时间一定比子节点的任务开始时间早。所以只要找出任务模板中任务是根节点,任务没有依赖规则且开始时间最早的任务作为最早任务(有依赖规则的任务的开始时间是不确定的。)。
代码
TaskTemplate earlyTask = null;
Date projectStartDate = project.getStartDate();
int taskPos = 0;
for (int i = 0; i < taskTemplateList.size(); i++) {
TaskTemplate taskItem = taskTemplateList.get(i);
if (taskItem.getLevel().equals(0) && StringUtils.isBlank(taskItem.getDepends())) {
if (earlyTask == null) {
earlyTask = taskItem;
taskPos = i;
} else {
if (earlyTask.getStart().getTime() > taskItem.getStart().getTime()) {
earlyTask = taskItem;
taskPos = i;
}
}
}
}
if (earlyTask == null) {
return null;//避免程序异常
}
过程2
遍历一次任务模板列表,将所有任务属性都复制到任务列表中(保证顺序不变,或者自己后面根据sort由小到大重排序)
在此过程中还要做件事,复制属性和计算无依赖规则的任务的开始和结束时间。
根据任务列表创建任务依赖规则链接 links
代码:
List<Task> taskList = new ArrayList<>();
//复制属性和计算无依赖规则任务时间,不处理任务文件
for (int i = 0; i < taskTemplateList.size(); i++) {
TaskTemplate item = taskTemplateList.get(i);
Task taskItem = new Task();
BeanUtils.copyProperties(item, taskItem);
if (item.getMilestone().equals(ProjectConsts.TASK_MILESTONE_YES)) {
taskItem.setCanDelete("false");
}
taskItem.setId(null);
taskItem.setProjectId(project.getId());
if (StringUtils.isNotBlank(taskItem.getDepends())) {
taskList.add(taskItem);
continue;
}
//无依赖规则任务计算
//初始化开始结束时间。当前任务和最早开始任务的时间差,只包含工作日
int subDay = DateUtils.getWorkdayTimeInDate(earlyTask.getStart(), item.getStart());
Date realStartDate = DateUtils.incrementDateByWorkingDays(projectStartDate, subDay);
taskItem.setStart(realStartDate);
Date realEndDate = DateUtils.incrementDateByWorkingDays(realStartDate, taskItem.getDuration());
taskItem.setEnd(realEndDate);
taskList.add(taskItem);
}
//创建任务依赖规则链接,并put到任务列表中
//创建没有依赖规则的任务开始和结束时间,获取所有任务的更新链接links
List<TaskLink> links = new ArrayList<>();
for (int i = 0; i < taskList.size(); i++) {
Task taskItem = taskList.get(i);
if (StringUtils.isBlank(taskItem.getDepends())) {
continue;//跳过
}
String[] depends = taskItem.getDepends().split(",");
for (int j = 0; j < depends.length; j++) {
String[] regular = depends[j].split(":");
TaskLink taskLink = null;
if (regular.length == 2) {
//这个规则的序号从1开始
taskLink = new TaskLink(taskList.get(Integer.valueOf(regular[0])-1), taskItem, Integer.valueOf(regular[1]));
} else {
taskLink = new TaskLink(taskList.get(Integer.valueOf(regular[0])-1), taskItem, 1);
}
links.add(taskLink);
}
}
过程3:最后再统一计算有依赖规则的任务的开始和结束时间
这个链接的结构
这个过程需要用到两次递归:
递归遍历获得该task节点的所有依赖规则。即获得List<TaskLink> taskLinkList
递归处理taskLinkList数据,知道这个列表数据为空为止
两次递归代码
/**
* 刷新taskList中存在依赖规则的开始时间和结束时间
*
* @param taskList
* @param taskLinkList
*/
public void refreshTaskLink(List<Task> taskList, List<TaskLink> taskLinkList) {
List<TaskLink> todoList = new ArrayList<>();
if (taskLinkList.isEmpty()) {
return;
}
TaskLink taskLink = taskLinkList.get(0);
todoList.add(taskLink);
if (StringUtils.isNotBlank(taskLink.getFrom().getDepends())) {
List<TaskLink> linkTmpList = getToLinkList(taskLink.getFrom(), taskLinkList);
if (linkTmpList != null) {
todoList.addAll(linkTmpList);
}
}
List<TaskLink> onceDealWithList = new ArrayList<>();
Task preTo = null;
//处理第一个节点开始到没有依赖规则为止的嵌套规则列表
for (int i = todoList.size() - 1; i >= 0; i--) {
//倒叙处理依赖规则
TaskLink link = todoList.get(i);
if (preTo == null) {
preTo = link.getTo();
}
onceDealWithList.add(link);
//是否还有下一个节点
if (i - 1 >= 0) {
if (preTo.getTaskSort().equals(todoList.get(i - 1).getTo().getTaskSort())) {
//还是相等,则跳过
continue;
} else {
//不相同,处理链接列表,设置任务开始结束时间
Task task = taskList.get(link.getTo().getTaskSort() - 1);
dealTaskByOneDealWithList(task, onceDealWithList);
//重置处理数据
onceDealWithList = new ArrayList<>();
preTo = null;
}
} else {
//不相同,处理链接列表,设置任务开始结束时间
Task task = taskList.get(link.getTo().getTaskSort() - 1);
dealTaskByOneDealWithList(task, onceDealWithList);
//重置处理数据
onceDealWithList = new ArrayList<>();
preTo = null;
}
}
taskLinkList.removeAll(todoList);
refreshTaskLink(taskList, taskLinkList);
}
public void dealTaskByOneDealWithList(Task task, List<TaskLink> dealLinkList) {
Date superEnd = null;
for (TaskLink taskLink1 : dealLinkList) {
if (superEnd == null) {
superEnd = DateUtils.incrementDateByWorkingDays(taskLink1.getFrom().getEnd(), taskLink1.getLag());
} else {
Date curEnd = DateUtils.incrementDateByWorkingDays(taskLink1.getFrom().getEnd(), taskLink1.getLag());
if (curEnd.getTime() > superEnd.getTime()) {
superEnd = curEnd;
}
}
}
//设置开始和结束时间,开始时间和结束时间计算不用再加1,因为当天表示如2019-11-11开始2019-11-11结束表示1天
task.setStart(superEnd);
task.setEnd(DateUtils.incrementDateByWorkingDays(task.getStart(), task.getDuration() - 1));
}
/**
* 递归遍历获得该task节点的所有依赖规则。
*
* @param to
* @param taskLinkList
* @return
*/
public List<TaskLink> getToLinkList(Task to, List<TaskLink> taskLinkList) {
List<TaskLink> list = null;
for (TaskLink taskLink : taskLinkList) {
if (taskLink.getTo().getTaskSort().equals(to.getTaskSort())) {
//序号相同
if (list == null) {
list = new ArrayList<>();
}
list.add(taskLink);
if (StringUtils.isNotBlank(taskLink.getFrom().getDepends())) {
List<TaskLink> childLinkList = getToLinkList(taskLink.getFrom(), taskLinkList);
if (childLinkList != null) {
list.addAll(childLinkList);
}
}
}
}
return list;
}
逻辑代码上传到github:https://github.com/innerjob/jQueryGantt/tree/master/java