使用jQueryGantt自定義甘特圖


樓主最近在重構一個項目管理模塊,為項目下的任務生成甘特圖。嘗試了github上的幾款開源JavaScript工具,踩過一些坑,最終還是選擇TWProject的jQueryGantt來做。

jQueryGantt的Demo地址在:https://github.com/robicch/jQueryGantt

 

因為我們的項目和任務數據都是從后台讀取,並且頁面也有地方做CRUD,所以甘特圖只用來將從后台查到數據展示出來,並不需要原demo中的保存、加載和動態編輯等功能。樓主調試源碼大概花了一天時間,頁面上調整了按鈕、表格列數據,去除了多余的事件之后,就長這樣。

 

大概說一下主要的定制地方:

(1)html中被注釋掉的模板標簽內容,請不要刪除,它們在JS中有用到。

 

 

(2)整個gantt圖的繪制,需要給它一個div,為其取個id,這在作者的demo代碼中也能看到。

//創建GanttMaster對象
var ge= new GanttMaster();
//初始化它,init方法中的參數用的就是這個div的id
ge.init($("#workSpace"));

  在構造函數里,為ge對象默認添加了很多屬性,我們可以去構造函數里覆蓋這些屬性來做一些定制,比如

// 設置行高
ge.rowHeight = 30;
// 設置顯示多少行
ge.minRowsInEditor = 15;

  其他屬性大家可以自己去嘗試。

 

(3)真正開始繪制和渲染gantt圖的步驟,都在這個ge.init()方法里面,我們定制要修改的地方,主要也是從個方法里入手。

  我大概貼一下自己的注釋,幫助看的人節省點調試時間。

GanttMaster.prototype.init = function (workSpace) {
  // 動態創建一個div,為其添加id和設置樣式,然后append到workspace下一級作為子元素
  var place = $("<div>").prop("id","TWGanttArea").css({padding:0, "overflow-y":"auto", "overflow-x":"hidden","border":"1px solid #e5e5e5",position:"relative"});
  workSpace.append(place).addClass("TWGanttWorkSpace");

  this.workSpace = workSpace;
  this.element = place;
  this.numOfVisibleRows=Math.ceil(this.element.height()/this.rowHeight);

  // by default task are coloured by status
  // 顏色代表的狀態
  this.element.addClass('colorByStatus')

  var self = this;
  // 加載前面html中提到過的模板
  $("#gantEditorTemplates").loadTemplates().remove();

  // 創建GridEditor,左側表格
  this.editor = new GridEditor(this);
  //editor.gridified就是左側的table了
  place.append(this.editor.gridified);

  // 創建gantt圖,起止時間為當前時間前2天到后5天
  this.gantt = new Ganttalendar(new Date().getTime() - 3600000 * 24 * 2, new Date().getTime() + 3600000 * 24 * 5, this, place.width() * .6);

  // splitter是甘特圖和table的分隔符,table是first,甘特圖是second
  self.splitter = $.splittify.init(place, this.editor.gridified, this.gantt.element, 40);
  self.splitter.firstBoxMinWidth = 5;
  self.splitter.secondBoxMinWidth = 20;

  // 添加toolbar buttons
  var ganttButtons = $.JST.createFromTemplate({}, "GANTBUTTONS");
  // buttons放在place之前,並且check一下permission,這個permission是可以在new GanttMaster()后自己設置的
  place.before(ganttButtons);
  this.checkButtonPermissions();

  //開始為workspace綁定事件,如果不需要的事件,可以在這里刪除綁定(最好同時去把對應的事件定義也刪掉,減少代碼size)
  workSpace.bind("collapseAll.gantt", function () {
    self.collapseAll();
  }).bind("expandAll.gantt", function () {
    self.expandAll();
  }).bind("zoomPlus.gantt", function () {
    self.gantt.zoomGantt(true);
  }).bind("zoomMinus.gantt", function () {
    self.gantt.zoomGantt(false);
  }).bind("resize.gantt", function () {
    self.resize();
  });

  // 綁定大分隔條的拖動事件
  self.splitter.firstBox.scroll(function () {
    //notify scroll to editor and gantt
    self.gantt.element.stopTime("test").oneTime(10, "test", function () {
      var oldFirstRow = self.firstScreenLine;
      var newFirstRow = Math.floor(self.splitter.firstBox.scrollTop() / self.rowHeight);
      if (Math.abs(oldFirstRow - newFirstRow) >= self.rowBufferSize) {
        self.firstScreenLine = newFirstRow;
        self.scrolled(oldFirstRow);
      }
    });
  });

  // 窗口調整的resize事件
  $(window).resize(function () {
    place.css({width: "100%", height: $(window).height() - place.position().top});
    place.trigger("resize.gantt");
  }).oneTime(2, "resize", function () {$(window).trigger("resize")});
};

//msg的內容會影響右側甘特圖表頭的顯示
GanttMaster.messages = {
  "CANNOT_WRITE":                          "CANNOT_WRITE",
  "CHANGE_OUT_OF_SCOPE":                   "NO_RIGHTS_FOR_UPDATE_PARENTS_OUT_OF_EDITOR_SCOPE",
  "START_IS_MILESTONE":                    "START_IS_MILESTONE",
  "END_IS_MILESTONE":                      "END_IS_MILESTONE",
  "TASK_HAS_CONSTRAINTS":                  "TASK_HAS_CONSTRAINTS",
  "GANTT_ERROR_DEPENDS_ON_OPEN_TASK":      "GANTT_ERROR_DEPENDS_ON_OPEN_TASK",
  "GANTT_ERROR_DESCENDANT_OF_CLOSED_TASK": "GANTT_ERROR_DESCENDANT_OF_CLOSED_TASK",
  "TASK_HAS_EXTERNAL_DEPS":                "TASK_HAS_EXTERNAL_DEPS",
  "GANTT_ERROR_LOADING_DATA_TASK_REMOVED": "GANTT_ERROR_LOADING_DATA_TASK_REMOVED",
  "CIRCULAR_REFERENCE":                    "CIRCULAR_REFERENCE",
  "CANNOT_MOVE_TASK":                      "CANNOT_MOVE_TASK",
  "CANNOT_DEPENDS_ON_ANCESTORS":           "CANNOT_DEPENDS_ON_ANCESTORS",
  "CANNOT_DEPENDS_ON_DESCENDANTS":         "CANNOT_DEPENDS_ON_DESCENDANTS",
  "INVALID_DATE_FORMAT":                   "INVALID_DATE_FORMAT",
  "GANTT_SEMESTER_SHORT":                  "GANTT_SEMESTER_SHORT",
  "GANTT_SEMESTER":                        "GANTT_SEMESTER",
  "GANTT_QUARTER_SHORT":                   "季度",
  "GANTT_QUARTER":                         "季度",
  "GANTT_WEEK":                            "星期",
  "GANTT_WEEK_SHORT":                      "周",
  "CANNOT_CLOSE_TASK_IF_OPEN_ISSUE":       "CANNOT_CLOSE_TASK_IF_OPEN_ISSUE",
  "PLEASE_SAVE_PROJECT":                   "PLEASE_SAVE_PROJECT",
  "CANNOT_CREATE_SAME_LINK":               "CANNOT_CREATE_SAME_LINK"
};

// 下面是GanttMaster的一眾方法
// 創建任務,參數為每個任務的列屬性
GanttMaster.prototype.createTask = function (id, name, code, level, start, duration) {
  var factory = new TaskFactory();
  return factory.build(id, name, code, level, start, duration);
};

//...............

  

(4)如何自定義左側表格的列?

  你可能要在上面的代碼里打個斷點debug看一下加載模板生成表格的過程,然后去下面這兩個模板里將表頭和內容列都對應清除掉。如果刪除時數據錯位了,內容就會顯示不出來,並且不會在console報錯,因此可能需要反復嘗試。

  

 

  最后我上面清完的效果對應的代碼是:

<div class="__template__" type="GANTBUTTONS">
    <div class="ganttButtonBar noprint">
        <div class="buttons">
            <button onclick="$('#workSpace').trigger('expandAll.gantt');return false;" class="button textual icon " title="EXPAND_ALL"><span class="teamworkIcon">6</span></button>
            <button onclick="$('#workSpace').trigger('collapseAll.gantt'); return false;" class="button textual icon " title="COLLAPSE_ALL"><span class="teamworkIcon">5</span></button>
            <span class="ganttButtonSeparator"></span>
            <button onclick="$('#workSpace').trigger('zoomMinus.gantt'); return false;" class="button textual icon " title="zoom out"><span class="teamworkIcon">)</span></button>
            <button onclick="$('#workSpace').trigger('zoomPlus.gantt');return false;" class="button textual icon " title="zoom in"><span class="teamworkIcon">(</span></button>
            <span class="ganttButtonSeparator"></span>
            <button onclick="ge.gantt.showCriticalPath=!ge.gantt.showCriticalPath; ge.redraw();return false;" class="button textual icon requireCanSeeCriticalPath" title="CRITICAL_PATH"><span class="teamworkIcon">£</span></button>
            <span class="ganttButtonSeparator requireCanSeeCriticalPath"></span>
            <button onclick="ge.splitter.resize(.1);return false;" class="button textual icon" ><span class="teamworkIcon">F</span></button>
            <button onclick="ge.splitter.resize(50);return false;" class="button textual icon" ><span class="teamworkIcon">O</span></button>
            <button onclick="ge.splitter.resize(100);return false;" class="button textual icon"><span class="teamworkIcon">R</span></button>
        </div>
    </div>
</div>

<!-- 表頭 -->
<div class="__template__" type="TASKSEDITHEAD">
    <table class="gdfTable" cellspacing="0" cellpadding="0">
        <thead>
        <tr style="height:40px">
            <th class="gdfColHeader" style="width:35px; border-right: none"></th>
            <th class="gdfColHeader" style="width:25px;"></th>
            <th class="gdfColHeader gdfResizable" style="width:300px;">任務名</th>
            <th class="gdfColHeader gdfResizable" style="width:80px;">開始日期</th>
            <th class="gdfColHeader gdfResizable" style="width:80px;">結束日期</th>
            <th class="gdfColHeader gdfResizable" style="width:60px;">跨度(天)</th>
            <th class="gdfColHeader gdfResizable" style="width:300px; text-align: left; padding-left: 10px;">責任人</th>
        </tr>
        </thead>
    </table>
</div>

<!-- 內容行 -->
<div class="__template__" type="TASKROW">
    <!--
    <tr id="tid_(#=obj.id#)" taskId="(#=obj.id#)" class="taskEditRow (#=obj.isParent()?'isParent':''#) (#=obj.collapsed?'collapsed':''#)" level="(#=level#)">
        <th class="gdfCell edit" align="right" style="cursor:pointer;"><span class="taskRowIndex">(#=obj.getRow()+1#)</span> <span class="teamworkIcon" style="font-size:12px;" >e</span></th>
        <td class="gdfCell noClip" align="center"><div class="taskStatus cvcColorSquare" status="(#=obj.status#)"></div></td>
        <td class="gdfCell indentCell" style="padding-left:(#=obj.level*10+18#)px;">
          <div class="exp-controller" align="center"></div>
          <input type="text" name="name" value="(#=obj.name#)" placeholder="name">
        </td>
        <td class="gdfCell"><input type="text" name="start"  value="" class="date"></td>
        <td class="gdfCell"><input type="text" name="end" value="" class="date"></td>
        <td class="gdfCell"><input type="text" name="duration" autocomplete="off" value="(#=obj.duration#)"></td>
        <td class="gdfCell taskAssigs">(#=obj.getAssigsString()#)</td>
    </tr>
    -->
</div>

  

(5)如何從服務器讀取數據? 

  之前看別人說直接用$.ajax()讀取后台,在success里賦值,會導致數據灌不進去,我試了一下,果然報了一個什么jquery錯誤。

  jquery-3.4.1.min.js:2 Uncaught TypeError: Cannot read property 'rowElement' of undefined

  本來考慮要不要用Promise的then來解決,但后來嘗試了一下ajax同步取數據,發現也可以,就懶得改了。

$.ajax({
    url: "你自己的請求url",
    type: "GET",
    async: false, //改為同步
    data: {id: $('#projectId').val()},
    success: function (res) {
        if (res.success) {
            //按照格式從服務器取出部分數據,組裝出渲染需要的字段即可
            res.data.forEach(item => {
                item.level = 0;
                item.status = 'STATUS_ACTIVE';
                item.start = new Date(item.beginDate).getTime();
                item.end = new Date(item.endDate).getTime();
                item.collapsed = false;
                item.hasChild = false;
                item.duration = (item.end - item.start)/(3600000*24);

            });
            project.tasks = res.data;
            //console.log(project.tasks);
        } else {
            toastr.error(res.message);
        }
    },
    error: function (data) {
        toastr.error("請求失敗");
    }
});

 gantt表格渲染需要的數據格式如下,如果自己的后台返回的JSON不是這種格式,至少要拼裝出你前面定制的table列需要的字段。注意start和end的值都是毫秒,如果寫字符串似乎出不來。

{
	"id": -1,
	"name": "做一頓年夜飯",
	"progress": 0,
	"progressByWorklog": false,
	"relevance": 0,
	"type": "",
	"typeId": "",
	"description": "",
	"code": "",
	"level": 0,
	"status": "STATUS_ACTIVE",
	"depends": "",
	"canWrite": true,
	"start": 1396994400000,
	"duration": 20,
	"end": 1399586399999,
	"startIsMilestone": false,
	"endIsMilestone": false,
	"collapsed": false,
	"assigs": [],
	"hasChild": true
}

  

(6)至於去除不需要的btn等元素,自己斟酌着刪除就好了。需要注意的是,渲染gantt圖的div你要手動給它寬高,或者它能從父元素繼承到寬高,大家可以自己體會一下。

  其他的樣式不滿意的,可以去gantt.css這個文件中找,然后在<style>標簽里覆蓋就行了。

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM