gantt.html.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. <?php
  2. /**
  3. * The control file of execution module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2012 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license business(商业软件)
  7. * @author Yangyang Shi <shiyangyang@cnezsoft.com>
  8. * @package execution
  9. * @version $Id$
  10. * @link http://www.zentao.net
  11. */
  12. ?>
  13. <?php include $app->getModuleRoot() . 'common/view/header.html.php';?>
  14. <?php include $app->getModuleRoot() . 'common/view/gantt.html.php';?>
  15. <style>
  16. #ganttView {height: 600px;}
  17. #ganttPris > span {display: inline-block; line-height: 20px; min-width: 20px; border-radius: 2px;}
  18. .gantt_container, .gantt_tooltip {font-family: "Helvetica Neue", Helvetica, Tahoma, Arial, "PingFang SC", "Source Han Sans CN", "Source Han Sans", "Hiragino Sans GB", "WenQuanYi Micro Hei", sans-serif}
  19. .gantt_grid_scale .gantt_grid_head_cell,.gantt_task .gantt_task_scale .gantt_scale_cell {color: #838a9d;}
  20. .gantt_task_line.gantt_selected {box-shadow: 0 1px 1px rgba(0,0,0,.05), 0 2px 6px 0 rgba(0,0,0,.045)}
  21. .gantt_link_arrow_right {border-left-color: #2196F3;}
  22. .gantt_link_arrow_left {border-right-color: #2196F3;}
  23. .gantt_task_link .gantt_line_wrapper div{background-color: #2196F3;}
  24. .gantt_critical_link .gantt_line_wrapper>div {background-color: #e63030 !important;}
  25. .gantt_critical_link.start_to_start .gantt_link_arrow_right {border-left-color: #e63030 !important;}
  26. .gantt_critical_link.finish_to_start .gantt_link_arrow_right {border-left-color: #e63030 !important;}
  27. .gantt_critical_link.start_to_finish .gantt_link_arrow_left {border-right-color: #e63030 !important;}
  28. .gantt_critical_link.finish_to_finish .gantt_link_arrow_left {border-right-color: #e63030 !important;}
  29. .gantt_task_link.start_to_start .gantt_line_wrapper div { background-color: #DD55EA; }
  30. .gantt_task_link.start_to_start:hover .gantt_line_wrapper div { box-shadow: 0 0 5px 0px #DD55EA; }
  31. .gantt_task_link.start_to_start .gantt_link_arrow_right { border-left-color: #DD55EA; }
  32. .gantt_task_link.finish_to_start .gantt_line_wrapper div { background-color: #7576ba; }
  33. .gantt_task_link.finish_to_start:hover .gantt_line_wrapper div { box-shadow: 0 0 5px 0px #7576ba; }
  34. .gantt_task_link.finish_to_start .gantt_link_arrow_right { border-left-color: #7576ba; }
  35. .gantt_task_link.finish_to_finish .gantt_line_wrapper div { background-color: #55d822; }
  36. .gantt_task_link.finish_to_finish:hover .gantt_line_wrapper div { box-shadow: 0 0 5px 0px #55d822; }
  37. .gantt_task_link.finish_to_finish .gantt_link_arrow_left { border-right-color: #55d822; }
  38. .gantt_task_link.start_to_finish .gantt_line_wrapper div { background-color: #975000; }
  39. .gantt_task_link.start_to_finish:hover .gantt_line_wrapper div { box-shadow: 0 0 5px 0px #975000; }
  40. .gantt_task_link.start_to_finish .gantt_link_arrow_left { border-right-color: #975000; }
  41. .gantt_critical_task{background:#e63030 !important; border-color:#9d3a3a !important;}
  42. .gantt_grid_head_cell.gantt_grid_head_text{overflow:visible;}
  43. .gantt_grid_head_cell.gantt_grid_head_text .btn-group .dropdown-menu{text-align:left;}
  44. #ganttDownload, #ganttHeader {display: none;}
  45. #ganttContainer {margin-top: 10px;}
  46. #mainContent:before {background: #fff;}
  47. #mainContent.loading {overflow: hidden}
  48. #mainContent.loading #ganttView {overflow: hidden}
  49. #mainContent.loading #ganttHeader {display: block; padding-bottom: 20px; margin: 0; height: 46px;}
  50. #mainContent.loading #ganttDownload {display: inline}
  51. #mainContent.loading #ganttContainer {padding: 40px;}
  52. #mainContent.loading .scrollVer_cell,
  53. #mainContent.loading .scrollVer_cell {display: none;}
  54. .gantt-fullscreen #header,
  55. .gantt-fullscreen #mainMenu,
  56. .gantt-fullscreen #footer {display: none!important;}
  57. .gantt-fullscreen #mainContent {position: fixed; top: 0; right: 0; bottom: 0; left: 0}
  58. .gantt_cell .delayed {background: #FFEBEB; color: #FB2B2B;}
  59. .gantt_task_content{color:#000000;}
  60. .gantt_grid_head_cell .sort {
  61. position: relative;
  62. display: inline-block;
  63. padding-right: 16px;
  64. color: #3c4353;
  65. }
  66. .gantt_grid_head_cell .sort.sort-up,.gantt_grid_head_cell .sort.sort-down{
  67. color: #000;
  68. text-decoration: none;
  69. }
  70. .gantt_grid_head_cell .sort:before,.gantt_grid_head_cell .sort:after{
  71. position: absolute;
  72. top: calc(50% - 8px);
  73. right: 0;
  74. font-family: ZentaoIcon;
  75. font-size: 14px;
  76. font-style: normal;
  77. font-weight: 400;
  78. font-variant: normal;
  79. line-height: 1;
  80. color: #3c495c;
  81. text-transform: none;
  82. content: "\f0de";
  83. opacity: .5;
  84. speak: none;
  85. -webkit-font-smoothing: antialiased;
  86. -moz-osx-font-smoothing: grayscale;
  87. }
  88. .gantt_grid_head_cell .week.sort:before,.gantt_grid_head_cell .week.sort:after { top: 26px;}
  89. .gantt_grid_head_cell .month.sort:before,.gantt_grid_head_cell .month.sort:after{ top: 26px;}
  90. .gantt_grid_head_cell .sort:after{content: "\f0dd";}
  91. .gantt_grid_head_cell .sort.sort-down:after, .gantt_grid_head_cell .sort.sort-up:before { color: #000; opacity: 1;}
  92. .gantt_grid_head_cell, .gantt_scale_cell{color:#000000!important;}
  93. .gantt_tree_content{color:#838A9D;}
  94. .gantt_row > div:first-child .gantt_tree_content{color:#3C4353;}
  95. .gantt_task_line.gantt_task_inline_color{border:0px;}
  96. .gantt_grid_scale, .gantt_task_scale, .gantt_task_vscroll{background-color: #F2F7FF;}
  97. #myCover {display:none;left:24px !important;height:unset!important;}
  98. .toggle-icon{cursor: pointer;}
  99. .toggle-icon .icon-expand-alt:before{content: "\e6f2";}
  100. .is-collapsed .toggle-icon .icon-expand-alt:before{content: "\e6f1";}
  101. </style>
  102. <?php js::set('canGanttEdit', common::hasPriv('execution', 'ganttEdit'));?>
  103. <?php include "featurebar.html.php";?>
  104. <?php js::set('zooming', isset($zooming) ? $zooming : 'day');?>
  105. <?php js::set('showFields', $this->config->execution->ganttCustom->ganttFields);?>
  106. <div id='mainContent' class='main-content load-indicator' data-loading='<?php echo $lang->execution->gantt->exporting;?>'>
  107. <div id='ganttContainer'>
  108. <h1 id='ganttHeader'>
  109. <?php echo $lang->execution->gantt->common . ' - ' . $executionName;?>
  110. &nbsp; <small id='ganttExportDate'></small>
  111. </h1>
  112. <div class='gantt is-collapsed' id='ganttView'></div>
  113. </div>
  114. <?php $fileName = ($execution->type == 'stage' ? "$project->name-" : '') . $executionName . '-' . $lang->execution->ganttchart;?>
  115. <a id='ganttDownload' target='hiddenwin' download='<?php echo "$fileName.png";?>'></a>
  116. <?php
  117. $typeHtml = '<span class="toggle-icon" onclick="toggleAllTasks()"><i class="icon-expand-alt"></i></span><div class="btn-group"><span class="toggle-icon">';
  118. $typeHtml .= '<button class="btn btn-link" data-toggle="dropdown"><span class="text">' . $lang->execution->gantt->browseType[$ganttType] . '</span> <span class="caret"></span></button>';
  119. $typeHtml .= '<ul class="dropdown-menu">';
  120. foreach($lang->execution->gantt->browseType as $browseType => $typeName)
  121. {
  122. $typeHtml .= '<li ' . ($ganttType == $browseType ? "class='active'" : '') . '>' . html::a($this->createLink('execution', 'gantt', "executionID=$executionID&type=$browseType"), $typeName) . '</li>';
  123. }
  124. $typeHtml .= '</ul></div>';
  125. ?>
  126. </div>
  127. <div id="myCover">
  128. <div class="gantt_control">
  129. <div class='btn btn-link' id='ganttPris'>
  130. <strong><?php echo $lang->task->pri . " : "?></strong>
  131. <?php foreach($lang->execution->gantt->progressColor as $pri => $color):?>
  132. <?php if($pri > 0 && $pri <= 4):?>
  133. <span style="background:<?php echo $color?>"><?php echo $pri;?></span> &nbsp;
  134. <?php endif;?>
  135. <?php endforeach;?>
  136. </div>
  137. </div>
  138. <div id="gantt_here"></div>
  139. </div>
  140. <script>
  141. var scriptLoadedMap = {};
  142. var loadingPrefixText = '<?php echo $lang->execution->gantt->exporting;?>';
  143. //After that you have to redefine the getFullscreenElement() method to return a custom root node that will be expanded to full screen:
  144. gantt.ext.fullscreen.getFullscreenElement = function() {
  145. return document.getElementById("myCover");
  146. }
  147. gantt.init("gantt_here");
  148. var showError = function(message)
  149. {
  150. new $.zui.Messager(message, {
  151. type: 'danger',
  152. icon: 'exclamation-sign'
  153. }).show();
  154. };
  155. // before gantt is expanded to full screen
  156. gantt.attachEvent("onBeforeExpand",function(){
  157. $('#myCover').css('display', 'unset');
  158. $('#myCover').css('padding', '8px');
  159. $('#mainContent').css('padding', '40px');
  160. return true;
  161. });
  162. // when gantt exited the full screen mode
  163. gantt.attachEvent("onCollapse", function (){
  164. $('#myCover').css('display', 'none');
  165. });
  166. /* 默认折叠所有任务。*/
  167. gantt.attachEvent("onTaskLoading", function(task){
  168. task.$open = false;
  169. return true;
  170. });
  171. /**
  172. * Get remote script for export.
  173. *
  174. * @param string $url
  175. * @param function $successCallback
  176. * @param function $errorCallback
  177. * @access public
  178. * @return void
  179. */
  180. function getRemoteScript(url, successCallback, errorCallback)
  181. {
  182. if(scriptLoadedMap[url]) return successCallback && successCallback();
  183. $.getScript(url, function()
  184. {
  185. scriptLoadedMap[url] = true;
  186. if(successCallback) successCallback();
  187. }).fail(function()
  188. {
  189. if(errorCallback) errorCallback('Cannot load "' + url + '".');
  190. });
  191. }
  192. /**
  193. * Update export progress.
  194. *
  195. * @param int $progress
  196. * @access public
  197. * @return void
  198. */
  199. function updateProgress(progress)
  200. {
  201. var progressText = loadingPrefixText;
  202. if(progress < 1) progressText += Math.floor(progress * 100) + '%';
  203. $('#mainContent').attr('data-loading', progressText);
  204. }
  205. /**
  206. * Draw gantt to canvas.
  207. *
  208. * @param string $exportType
  209. * @param function $successCallback
  210. * @param function $errorCallback
  211. * @access public
  212. * @return void
  213. */
  214. function drawGanttToCanvas(exportType, successCallback, errorCallback)
  215. {
  216. updateProgress(0);
  217. exportType = exportType || 'image';
  218. var $ganttView = $('#ganttView');
  219. var oldHeight = $ganttView.css('height');
  220. var $ganttContainer = $('#ganttContainer');
  221. var $ganttDataArea = $ganttView.find('.gantt_data_area');
  222. var $ganttDridData = $ganttView.find('.gantt_grid_data');
  223. var ganttHeight = $ganttView.find('.gantt_task_bg').outerHeight() + $ganttView.find('.gantt_grid_scale').outerHeight() + 1;
  224. var ganttWidth = $ganttDataArea.outerWidth() + $ganttDridData.outerWidth();
  225. $ganttContainer.css(
  226. {
  227. height: ganttHeight + $('#ganttHeader').outerHeight() + 80,
  228. width: ganttWidth + 93
  229. });
  230. $ganttView.css('height', ganttHeight);
  231. gantt.render();
  232. updateProgress(0.1);
  233. getRemoteScript('<?php echo $jsRoot . 'html2canvas/min.js';?>', function()
  234. {
  235. updateProgress(0.2);
  236. var afterFinish = function(canvas)
  237. {
  238. $ganttContainer.css(
  239. {
  240. width: '',
  241. height: ''
  242. });
  243. $ganttView.css('height', oldHeight);
  244. if(canvas)
  245. {
  246. try
  247. {
  248. canvas.removeNode(true)
  249. }
  250. catch(err)
  251. {
  252. canvas.remove()
  253. };
  254. }
  255. };
  256. var delayTime = Math.max(1000, Math.floor(10 * (ganttHeight * ganttWidth) / 100000));
  257. var progressTimer;
  258. if(delayTime > 1500)
  259. {
  260. var startProgress = 0.2;
  261. var deltaProgress = 0.5 / Math.floor(delayTime/1000);
  262. progressTimer = setInterval(function()
  263. {
  264. startProgress += deltaProgress;
  265. updateProgress(Math.min(0.7, startProgress));
  266. }, 1000);
  267. }
  268. setTimeout(function()
  269. {
  270. if(progressTimer) clearInterval(progressTimer);
  271. updateProgress(0.7);
  272. html2canvas($ganttContainer[0], {logging: false}).then(function(canvas)
  273. {
  274. var isExportPDF = exportType === 'pdf';
  275. updateProgress(isExportPDF ? 0.8 : 0.9);
  276. canvas.onerror = function()
  277. {
  278. afterFinish(canvas);
  279. if(errorCallback) errorCallback('Cannot convert image to blob.');
  280. };
  281. if(isExportPDF)
  282. {
  283. var width = canvas.width;
  284. var height = canvas.height;
  285. getRemoteScript('<?php echo $jsRoot . 'pdfjs/min.js';?>', function()
  286. {
  287. updateProgress(0.8);
  288. var pdf = new jsPDF(
  289. {
  290. format: [width, height],
  291. unit: 'px',
  292. orientation: width > height ? 'l' : 'p'
  293. });
  294. pdf.addImage(canvas, 0, 0, pdf.internal.pageSize.getWidth(), pdf.internal.pageSize.getHeight());
  295. pdf.save('<?php echo "$fileName.pdf"?>');
  296. if(successCallback) successCallback(pdf);
  297. afterFinish();
  298. }, function(error)
  299. {
  300. afterFinish(canvas);
  301. if(errorCallback) errorCallback(error);
  302. });
  303. }
  304. else
  305. {
  306. canvas.toBlob(function(blob)
  307. {
  308. updateProgress(0.95);
  309. var imageUrl = URL.createObjectURL(blob);
  310. if(navigator.msSaveBlob)
  311. {
  312. navigator.msSaveOrOpenBlob(blob, '<?php echo $fileName; ?>.png');
  313. }
  314. else
  315. {
  316. $('#ganttDownload').attr({href: imageUrl})[0].click();
  317. }
  318. if(successCallback) successCallback(imageUrl);
  319. afterFinish(canvas);
  320. });
  321. }
  322. }).catch(function(error)
  323. {
  324. afterFinish();
  325. if(errorCallback) errorCallback('Cannot draw graphic to canvas.');
  326. });
  327. }, delayTime);
  328. }, errorCallback);
  329. }
  330. /**
  331. * Export gantt.
  332. *
  333. * @param string $exportType
  334. * @access public
  335. * @return void
  336. */
  337. function exportGantt(exportType)
  338. {
  339. var $mainContent = $('#mainContent');
  340. $mainContent.addClass('loading').css('height', Math.max(200, Math.floor($(window).height() - $('#footer').outerHeight() - $('#header').outerHeight() - $('#mainMenu').outerHeight() - 38)));
  341. $('#ganttExportDate').text(new Date().format('yyyy-MM-dd hh:mm:ss'));
  342. var afterFinish = function(url)
  343. {
  344. setTimeout(function()
  345. {
  346. $mainContent.css('height', '').removeClass('loading');
  347. }, 300);
  348. updateProgress(1);
  349. };
  350. drawGanttToCanvas(exportType, afterFinish, function(errorText)
  351. {
  352. afterFinish();
  353. $.zui.messager.danger('<?php echo $lang->execution->gantt->exportFail;?>' + (errorText || ''));
  354. });
  355. }
  356. /**
  357. * Get by id for gantt.
  358. *
  359. * @param array $list
  360. * @param string $id
  361. * @access public
  362. * @return string
  363. */
  364. function getByIdForGantt(list, id)
  365. {
  366. for (var i = 0; i < list.length; i++)
  367. {
  368. if (list[i].key == id) return list[i].label || "";
  369. }
  370. return id;
  371. }
  372. /**
  373. * Zoom tasks.
  374. *
  375. * @param node $node
  376. * @access public
  377. * @return void
  378. */
  379. function zoomTasks(value)
  380. {
  381. switch(value)
  382. {
  383. case "day":
  384. gantt.config.min_column_width = 70;
  385. gantt.config.scales = [{unit: "year", step: 1, format: "%Y"}, {unit: 'day', step: 1, format: '%m-%d'}];
  386. gantt.config.scale_height = 22 * gantt.config.scales.length;
  387. break;
  388. case "week":
  389. gantt.config.min_column_width = 70;
  390. gantt.config.scales = [{unit: "year", step: 1, format: "%Y"}, {unit: 'week', step: 1, format: "<?php echo $lang->execution->gantt->zooming['week'];?> #%W"}, {unit:"day", step:1, date:"%D"}]
  391. gantt.config.scale_height = 22 * gantt.config.scales.length;
  392. break;
  393. case "month":
  394. gantt.config.min_column_width = 70;
  395. gantt.config.scales = [{unit: "year", step: 1, format: "%Y"}, {unit: 'month', step: 1, format: '%M'}, {unit:"week", step:1, date:"<?php echo $lang->execution->gantt->zooming['week'];?> #%W"}];
  396. gantt.config.scale_height = 22 * gantt.config.scales.length;
  397. break;
  398. }
  399. gantt.render();
  400. $('.gantt_grid_head_cell .sort').addClass(value);
  401. }
  402. /**
  403. * Update criticalPath
  404. *
  405. * @access public
  406. * @return void
  407. */
  408. function updateCriticalPath()
  409. {
  410. gantt.config.highlight_critical_path = !gantt.config.highlight_critical_path;
  411. if(gantt.config.highlight_critical_path)
  412. {
  413. $('#criticalPath').html(<?php echo json_encode($lang->execution->gantt->hideCriticalPath);?>);
  414. gantt.config.highlight_critical_path = true;
  415. }
  416. else
  417. {
  418. $('#criticalPath').html(<?php echo json_encode($lang->execution->gantt->showCriticalPath);?>);
  419. gantt.config.highlight_critical_path = false;
  420. }
  421. gantt.render();
  422. }
  423. /**
  424. * 父级的开始结束跟着子级变动。
  425. * Change parent task.
  426. *
  427. * @param child $child
  428. * @param id $id
  429. * @access public
  430. * @return bool
  431. */
  432. function changeParentTask(child, id)
  433. {
  434. let task = gantt.getTask(id);
  435. if(child.start_date < task.start_date) task.start_date = child.start_date;
  436. if(child.end_date > task.end_date) task.end_date = child.end_date;
  437. gantt.updateTask(task.id, task);
  438. if(task.parent) return changeParentTask(task, task.parent);
  439. return true;
  440. }
  441. /**
  442. * Validate resources.
  443. *
  444. * @param int id
  445. * @access public
  446. * @return bool
  447. */
  448. function validateResources(id)
  449. {
  450. var task = gantt.getTask(id);
  451. var from = new Date(task.start_date);
  452. var to = new Date(task.end_date);
  453. var status = task.status;
  454. var statusLang = "<?php echo $this->lang->task->statusList['wait'];?>";
  455. flag = true;
  456. if(task.id < 0) return false;
  457. /* Check status. */
  458. if(status !== statusLang)
  459. {
  460. showError("<?php echo $lang->programplan->error->taskDrag;?>".replace('%s', status))
  461. gantt.refreshData();
  462. return false;
  463. }
  464. /* Check data. */
  465. var postData = {
  466. 'id' : task.id,
  467. 'startDate': from.toLocaleDateString('en-CA'),
  468. 'endDate' : to.toLocaleDateString('en-CA'),
  469. 'type' : 'task'
  470. };
  471. var link = createLink('programplan', 'ajaxResponseGanttDragEvent');
  472. /* Sync Close. */
  473. $.ajax({
  474. url: link,
  475. dataType: "json",
  476. async: false,
  477. data: postData,
  478. type: "post",
  479. success: function(response)
  480. {
  481. if(response.result == 'fail' && response.message)
  482. {
  483. showError(response.message)
  484. flag = false;
  485. }
  486. else
  487. {
  488. if(task.parent) changeParentTask(task, task.parent);
  489. }
  490. }
  491. });
  492. return flag;
  493. }
  494. $(function()
  495. {
  496. // Set gantt view height
  497. var resizeGanttView = function()
  498. {
  499. if(gantt.getState().fullscreen) return false;
  500. $('#ganttView').css('height', Math.max(200, Math.floor($(window).height() - $('#footer').outerHeight() - $('#header').outerHeight() - $('#mainMenu').outerHeight() - 80)));
  501. };
  502. var ganttData = $.parseJSON(<?php echo json_encode($executionData);?>);
  503. if(!ganttData.data) ganttData.data = [];
  504. gantt.serverList("userList", <?php echo json_encode($userList);?>);
  505. gantt.config.readonly = canGanttEdit ? false : true;
  506. gantt.config.details_on_dblclick = false;
  507. gantt.config.order_branch = false;
  508. gantt.config.drag_progress = false;
  509. gantt.config.drag_links = true;
  510. gantt.config.smart_rendering = true;
  511. gantt.config.smart_scales = true;
  512. gantt.config.static_background = true;
  513. gantt.config.show_task_cells = false;
  514. gantt.config.row_height = 32;
  515. gantt.config.min_column_width = 40;
  516. gantt.config.details_on_create = false;
  517. gantt.config.scales = [{unit: "year", step: 1, format: "%Y"}, {unit: 'day', step: 1, format: '%m-%d'}];
  518. gantt.config.scale_height = 18 * gantt.config.scales.length;
  519. gantt.config.duration_unit = "day";
  520. gantt.config.columns = [];
  521. gantt.config.columns.push({name: 'text', width: '*', tree: true, resize: true, min_width:120, width:250});
  522. if(showFields.indexOf('assignedTo') != -1) gantt.config.columns.push({name: 'owner_id', align: 'center', resize: true, width:90, template: function(task){return getByIdForGantt(gantt.serverList('userList'), task.owner_id)}});
  523. if(showFields.indexOf('progress') != -1) gantt.config.columns.push({name: 'progress', align: 'center', resize: true, width:70, template: function(task) { return Math.floor(task.progress * 100) + '%'}});
  524. if(showFields.indexOf('begin') != -1) gantt.config.columns.push({name: 'start_date', align: 'center', resize: true, width: 90});
  525. if(showFields.indexOf('realBegan') != -1) gantt.config.columns.push({name: 'realBegan', align: 'center', resize: true, width: 90});
  526. if(showFields.indexOf('deadline') != -1) gantt.config.columns.push({name: 'deadline', align: 'center', resize: true, width: 90});
  527. if(showFields.indexOf('realEnd') != -1) gantt.config.columns.push({name: 'realEnd', align: 'center', resize: true, width: 90});
  528. if(showFields.indexOf('duration') != -1) gantt.config.columns.push({name: 'duration', align: 'center', resize: true, width: 70});
  529. if(showFields.indexOf('estimate') != -1) gantt.config.columns.push({name: 'estimate', align: 'center', resize: true, width: 90});
  530. if(showFields.indexOf('consumed') != -1) gantt.config.columns.push({name: 'consumed', align: 'center', resize: true, width: 90});
  531. if(showFields.indexOf('left') != -1) gantt.config.columns.push({name: 'left', align: 'center', resize: true, width: 90});
  532. if(showFields.indexOf('delayDays') != -1) gantt.config.columns.push({name: 'delayDays', align: 'center', resize: true, width: 70});
  533. if(showFields.indexOf('openedBy') != -1) gantt.config.columns.push({name: 'openedBy', align: 'center', resize: true, width: 70, template: function(task){return getByIdForGantt(gantt.serverList('userList'), task.openedBy)}});
  534. if(showFields.indexOf('finishedBy') != -1) gantt.config.columns.push({name: 'finishedBy', align: 'center', resize: true, width: 70, template: function(task){return getByIdForGantt(gantt.serverList('userList'), task.finishedBy)}});
  535. if(showFields.indexOf('delay') != -1)
  536. {
  537. gantt.config.columns.push({name: 'delay', align: 'center', resize: true, width: 60, template:function(task)
  538. {
  539. if(task.delayDays > 0) return "<div class='delayed'>" + task.delay + "</div>";
  540. return task.delay;
  541. }});
  542. }
  543. gantt.config.layout =
  544. {
  545. css: "gantt_container",
  546. cols:[{
  547. width: 600,
  548. rows:[
  549. {view: "grid", scrollX: "gridScroll", scrollable: true, scrollY: "scrollVer"},
  550. {view: "scrollbar", id: "gridScroll", group:"horizontal"}
  551. ]},
  552. {resizer: true, width: 1},
  553. {
  554. rows:[
  555. {view: "timeline", scrollX: "scrollHor", scrollY: "scrollVer"},
  556. {view: "scrollbar", id: "scrollHor", group:"horizontal"}
  557. ]},
  558. {view: "scrollbar", id: "scrollVer"}
  559. ]};
  560. gantt.templates.task_end_date = function(data)
  561. {
  562. return gantt.templates.task_date(new Date(date.valueOf() - 1));
  563. }
  564. var gridDateToStr = gantt.date.date_to_str("%Y-%m-%d");
  565. gantt.templates.date_grid = function(date, task, column)
  566. {
  567. if(column === "end_date")
  568. {
  569. var endDate = gridDateToStr(new Date(date.valueOf() - 1));
  570. if(task.isDelay) endDate = "<div class='delayed'>" + endDate + "</div>";
  571. return endDate;
  572. }
  573. else
  574. {
  575. return gridDateToStr(date);
  576. }
  577. }
  578. <?php list($orderField, $orderDirect) = $this->execution->parseOrderBy($orderBy);?>
  579. gantt.locale.labels.column_text = <?php echo json_encode($typeHtml);?>;
  580. <?php
  581. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('owner_id', $orderField, $orderDirect);
  582. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  583. ?>
  584. gantt.locale.labels.column_owner_id = <?php echo json_encode(html::a($link, $lang->task->assignedTo, '', "class='$fieldClass'"));?>;
  585. <?php
  586. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('progress', $orderField, $orderDirect);
  587. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  588. ?>
  589. gantt.locale.labels.column_progress = <?php echo json_encode(html::a($link, $lang->task->progress, '', "class='$fieldClass'"));?>;
  590. <?php
  591. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('start_date', $orderField, $orderDirect);
  592. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  593. ?>
  594. gantt.locale.labels.column_start_date = <?php echo json_encode(html::a($link, $lang->execution->ganttCustom['begin'], '', "class='$fieldClass'"));?>;
  595. <?php
  596. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('deadline', $orderField, $orderDirect);
  597. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  598. ?>
  599. gantt.locale.labels.column_end_date = <?php echo json_encode(html::a($link, $lang->execution->gantt->endDate, '', "class='$fieldClass'"));?>;
  600. <?php
  601. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('duration', $orderField, $orderDirect);
  602. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  603. ?>
  604. gantt.locale.labels.column_duration = <?php echo json_encode(html::a($link, $lang->execution->gantt->duration, '', "class='$fieldClass'"));?>;
  605. <?php
  606. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('begin', $orderField, $orderDirect);
  607. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  608. ?>
  609. gantt.locale.labels.column_begin = <?php echo json_encode(html::a($link, $lang->execution->ganttCustom['begin'], '', "class='$fieldClass'"));?>;
  610. <?php
  611. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('deadline', $orderField, $orderDirect);
  612. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  613. ?>
  614. gantt.locale.labels.column_deadline = <?php echo json_encode(html::a($link, $lang->execution->ganttCustom['deadline'], '', "class='$fieldClass'"));?>;
  615. <?php
  616. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('realEnd', $orderField, $orderDirect);
  617. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  618. ?>
  619. gantt.locale.labels.column_realEnd = <?php echo json_encode(html::a($link, $lang->execution->ganttCustom['realEnd'], '', "class='$fieldClass'"));?>;
  620. <?php
  621. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('realBegan', $orderField, $orderDirect);
  622. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  623. ?>
  624. gantt.locale.labels.column_realBegan = <?php echo json_encode(html::a($link, $lang->execution->ganttCustom['realBegan'], '', "class='$fieldClass'"));?>;
  625. <?php
  626. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('estimate', $orderField, $orderDirect);
  627. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  628. ?>
  629. gantt.locale.labels.column_estimate = <?php echo json_encode(html::a($link, $lang->execution->ganttCustom['estimate'], '', "class='$fieldClass'"));?>;
  630. <?php
  631. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('consumed', $orderField, $orderDirect);
  632. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  633. ?>
  634. gantt.locale.labels.column_consumed = <?php echo json_encode(html::a($link, $lang->execution->ganttCustom['consumed'], '', "class='$fieldClass'"));?>;
  635. <?php
  636. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('left', $orderField, $orderDirect);
  637. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  638. ?>
  639. gantt.locale.labels.column_left = <?php echo json_encode(html::a($link, $lang->execution->ganttCustom['left'], '', "class='$fieldClass'"));?>;
  640. <?php
  641. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('openedBy', $orderField, $orderDirect);
  642. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  643. ?>
  644. gantt.locale.labels.column_openedBy = <?php echo json_encode(html::a($link, $lang->execution->ganttCustom['openedBy'], '', "class='$fieldClass'"));?>;
  645. <?php
  646. list($fieldOrderBy, $fieldClass) = $this->execution->buildKanbanOrderBy('finishedBy', $orderField, $orderDirect);
  647. $link = $this->createLink('execution', 'gantt', "executionID=$executionID&type=$ganttType&orderBy=$fieldOrderBy");
  648. ?>
  649. gantt.locale.labels.column_finishedBy = <?php echo json_encode(html::a($link, $lang->execution->ganttCustom['finishedBy'], '', "class='$fieldClass'"));?>;
  650. gantt.locale.labels.column_delay = <?php echo json_encode($lang->execution->ganttCustom['delay']);?>;
  651. gantt.locale.labels.column_delayDays = <?php echo json_encode($lang->execution->ganttCustom['delayDays']);?>;
  652. var date2Str = gantt.date.date_to_str(gantt.config.task_date);
  653. var today = new Date();
  654. gantt.addMarker({
  655. start_date: today,
  656. css: "today",
  657. text: "<?php echo $lang->action->dynamic->today;?>",
  658. title: "<?php echo $lang->action->dynamic->today;?>: " + date2Str(today)
  659. });
  660. gantt.templates.task_class = function(start, end, task){return 'pri-' + (task.pri || 0);};
  661. gantt.templates.rightside_text = function(start, end, task)
  662. {
  663. return getByIdForGantt(gantt.serverList('userList'), task.owner_id);
  664. };
  665. gantt.templates.scale_cell_class = function(date)
  666. {
  667. if(date.getDay() == 0 || date.getDay() == 6) return 'weekend';
  668. };
  669. gantt.templates.timeline_cell_class = function(item, date)
  670. {
  671. if(date.getDay() == 0 || date.getDay() == 6) return 'weekend';
  672. };
  673. gantt.templates.link_class = function(link)
  674. {
  675. var types = gantt.config.links;
  676. if(link.type == types.finish_to_start) return 'finish_to_start';
  677. if(link.type == types.start_to_start) return 'start_to_start';
  678. if(link.type == types.finish_to_finish) return 'finish_to_finish';
  679. if(link.type == types.start_to_finish) return 'start_to_finish';
  680. };
  681. gantt.templates.tooltip_text = function (start, end, task){return task.text;};
  682. gantt.templates.grid_file = function(item){return "";};
  683. gantt.attachEvent('onTemplatesReady', function()
  684. {
  685. $('#fullScreenBtn').click(function()
  686. {
  687. gantt.expand();
  688. });
  689. });
  690. var isGanttExpand = false;
  691. var delayTimer = null;
  692. var handleFullscreen = function()
  693. {
  694. if(isGanttExpand)
  695. {
  696. $('body').addClass('gantt-fullscreen');
  697. $('#ganttView').css('height', $(window).height() - 60);
  698. isGanttExpand = false;
  699. }
  700. else
  701. {
  702. $('body').removeClass('gantt-fullscreen');
  703. resizeGanttView();
  704. }
  705. delayTimer = null;
  706. };
  707. var delayHandleFullscreen = function()
  708. {
  709. if(delayTimer) clearTimeout(delayTimer);
  710. delayTimer = setTimeout(handleFullscreen, 50);
  711. };
  712. gantt.attachEvent('onBeforeExpand', function()
  713. {
  714. $('body').addClass('gantt-fullscreen');
  715. isGanttExpand = true;
  716. return true;
  717. });
  718. if(document.addEventListener)
  719. {
  720. document.addEventListener('webkitfullscreenchange', delayHandleFullscreen, false);
  721. document.addEventListener('mozfullscreenchange', delayHandleFullscreen, false);
  722. document.addEventListener('fullscreenchange', delayHandleFullscreen, false);
  723. document.addEventListener('MSFullscreenChange', delayHandleFullscreen, false);
  724. }
  725. resizeGanttView();
  726. $(window).resize(resizeGanttView);
  727. gantt.templates.grid_folder = function(item) {
  728. return "";
  729. };
  730. gantt.templates.grid_file = function(item) {
  731. return "";
  732. };
  733. gantt.init('ganttView');
  734. gantt.parse(ganttData);
  735. // Show task in modal on click task
  736. var taskModalTrigger = new $.zui.ModalTrigger({type: 'iframe', width: '95%'});
  737. gantt.attachEvent('onTaskClick', function(id, e)
  738. {
  739. if($(e.srcElement).hasClass('gantt_close') || $(e.srcElement).hasClass('gantt_open')) return false;
  740. if(typeof id === 'string') id = parseInt(id);
  741. if(!isNaN(id) && id > 0)
  742. {
  743. taskModalTrigger.show({url: createLink('task', 'view', 'taskID=' + id, 'html', true)});
  744. }
  745. });
  746. // Make folder can open or close by click
  747. $('#ganttView').on('click', '.gantt_close,.gantt_open', function()
  748. {
  749. var $task = $(this).closest('.gantt_row_task');
  750. var task = gantt.getTask($task.attr('task_id'));
  751. if(task) gantt[task.$open ? 'close' : 'open'](task.id);
  752. });
  753. $('#ganttContainer').mouseleave(function()
  754. {
  755. setTimeout(function(){$('.gantt_tooltip').remove()}, 100);
  756. });
  757. gantt.attachEvent("onBeforeTaskChanged", function(id, mode, task) {return validateResources(id);});
  758. gantt.attachEvent('onBeforeLinkAdd', function(id, link)
  759. {
  760. var source = +link.source;
  761. var target = +link.target;
  762. if(source <= 0 || target <= 0)
  763. {
  764. let message = "<?php echo $lang->execution->error->wrongGanttRelation;?>";
  765. if(source <= 0) message += "<?php echo $lang->execution->error->wrongGanttRelationSource;?>";
  766. if(target <= 0) message += "<?php echo $lang->execution->error->wrongGanttRelationTarget;?>";
  767. showError(message);
  768. return false;
  769. }
  770. const sourceTask = gantt.getTask(link.source);
  771. const targetTask = gantt.getTask(link.target);
  772. if(sourceTask.allowLinks === false || targetTask.allowLinks === false)
  773. {
  774. showError("<?php echo $lang->execution->error->wrongKanbanTasks;?>");
  775. return false;
  776. };
  777. return true;
  778. });
  779. gantt.attachEvent('onAfterLinkAdd', function(linkID, item)
  780. {
  781. var sourceData = +item.source;
  782. var target = +item.target;
  783. var type = +item.type;
  784. var conditionData = (type == 1 || type == 3) ? 'begin' : 'end';
  785. var actionData = (type == 1 || type == 0) ? 'begin' : 'end';
  786. var id = {1:1}
  787. var pretask = {1:sourceData}
  788. var condition = {1:conditionData}
  789. var task = {1:target}
  790. var action = {1:actionData}
  791. var relation = {pretask, condition, task, action};
  792. var link = createLink('execution', 'ajaxMaintainRelation', 'projectID=<?php echo $execution->project;?>&executionID=<?php echo $execution->id;?>');
  793. $.post(link, relation, function(response)
  794. {
  795. response = $.parseJSON(response)
  796. if(response.result == 'fail' && response.message)
  797. {
  798. showError(response.message);
  799. gantt.deleteLink(linkID);
  800. }
  801. else
  802. {
  803. gantt.changeLinkId(linkID, response.linkID);
  804. }
  805. });
  806. });
  807. gantt.attachEvent("onLinkDblClick", function(id,e)
  808. {
  809. bootbox.confirm('<?php echo $lang->execution->gantt->confirmDelete;?>', function (result)
  810. {
  811. if(result)
  812. {
  813. $.post(createLink('programplan', 'ajaxResponseGanttDeleteRelationEvent'), {id: id});
  814. gantt.deleteLink(id);
  815. }
  816. });
  817. return false;
  818. });
  819. /* 监听任务展开事件。*/
  820. gantt.attachEvent("onTaskOpened", function(id) {
  821. $('#ganttView').removeClass('is-collapsed');
  822. return true;
  823. });
  824. /* 监听任务折叠事件。*/
  825. gantt.attachEvent("onTaskClosed", function(id) {
  826. let allCollapsed = true;
  827. gantt.eachTask(function(task) {
  828. if(task.$open && task.id == -1) { // 只判断顶级是否折叠
  829. allCollapsed = false;
  830. return false;
  831. }
  832. });
  833. if(allCollapsed) $('#ganttView').addClass('is-collapsed');
  834. return true;
  835. });
  836. zoomTasks(zooming);
  837. window.toggleAllTasks = function()
  838. {
  839. const isCollapsed = $('#ganttView').hasClass('is-collapsed');
  840. gantt.eachTask(function(task) {
  841. task.$open = isCollapsed;
  842. });
  843. gantt.render();
  844. $('#ganttView').toggleClass('is-collapsed', !isCollapsed);
  845. }
  846. });
  847. </script>
  848. <?php include $app->getModuleRoot() . 'common/view/footer.html.php';?>