batchcreate.ui.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /**
  2. * Get select of stories.
  3. *
  4. * @access public
  5. * @return void
  6. */
  7. function setStories(event)
  8. {
  9. const $module = $(event.target);
  10. const $currentRow = $module.closest('tr');
  11. const moduleID = $module.val();
  12. const getStoryLink = $.createLink('task', 'ajaxGetStories', 'executionID=' + executionID + '&moduleID=' + moduleID + '&zeroTaskStory=' + $('#zeroTaskStory').hasClass('checked'));
  13. let $row = $currentRow;
  14. if($row.length == 0) return;
  15. $.getJSON(getStoryLink, function(stories)
  16. {
  17. const $storyPicker = $row.find('[name^=story]').zui('picker');
  18. const storyID = $row.find('[name^=story]').val();
  19. $storyPicker.render({items: stories})
  20. $storyPicker.$.setValue(storyID);
  21. });
  22. }
  23. /**
  24. * Toggle zero task story.
  25. *
  26. * @access public
  27. * @return void
  28. */
  29. function toggleZeroTaskStory()
  30. {
  31. let $toggle = $('#zeroTaskStory').toggleClass('checked');
  32. let zeroTask = $toggle.hasClass('checked');
  33. $.cookie.set('zeroTask', zeroTask, {expires:config.cookieLife, path:config.webRoot});
  34. $('td[data-name="story"]').each(function()
  35. {
  36. const moduleID = $(this).closest('tr').find('input[name^=module]').val();
  37. const getStoryLink = $.createLink('task', 'ajaxGetStories', 'executionID=' + executionID + '&moduleID=' + moduleID + '&zeroTaskStory=' + zeroTask);
  38. const $storyTd = $(this);
  39. $.getJSON(getStoryLink, function(stories)
  40. {
  41. const $storyPicker = $storyTd.find('[name^=story]').zui('picker');
  42. $storyPicker.render({items: stories});
  43. $storyPicker.$.setValue(0);
  44. });
  45. });
  46. }
  47. /**
  48. * Set preview.
  49. *
  50. * @access public
  51. * @return void
  52. */
  53. function setStoryRelated(event)
  54. {
  55. let $story = $(event.target).closest('td').find('input[name^=story]');
  56. let $currentRow = $(event.target).closest('tr');
  57. let storyID = $story.val();
  58. let link = $.createLink('story', 'ajaxGetInfo', 'storyID=' + storyID + '&pageType=batch');
  59. let $storyEstimate = $currentRow.find('.form-batch-input[data-name="storyEstimate"]');
  60. let $storyPri = $currentRow.find('.form-batch-input[data-name="storyPri"]');
  61. let $storyDesc = $currentRow.find('.form-batch-input[data-name="storyDesc"]');
  62. let $module = $currentRow.find('input[name^="module"]');
  63. let $preview = $currentRow.find('.form-batch-input[data-name="preview"] + button');
  64. if(storyID > 0)
  65. {
  66. $.getJSON(link, function(data)
  67. {
  68. const storyInfo = data['storyInfo'];
  69. $module.zui('picker').$.setValue(parseInt(storyInfo.moduleID), true);
  70. $storyEstimate.val(storyInfo.estimate);
  71. $storyPri.val(storyInfo.pri);
  72. $storyDesc.val(storyInfo.spec);
  73. $preview.removeClass('disabled');
  74. $preview.css('pointer-events', 'auto');
  75. $preview.attr('data-url', $.createLink('story', 'view', "storyID=" + storyID));
  76. });
  77. }
  78. else
  79. {
  80. $storyEstimate.val('');
  81. $storyPri.val(3);
  82. $storyDesc.val('');
  83. $preview.addClass('disabled');
  84. $preview.css('pointer-events', 'none');
  85. $preview.attr('data-url', '#');
  86. }
  87. }
  88. /**
  89. * Copy story title as task title.
  90. *
  91. * @access public
  92. * @return void
  93. */
  94. function copyStoryTitle(event)
  95. {
  96. const $currentRow = $(event.target).closest('tr');
  97. const $story = $currentRow.find('td[data-name="story"]');
  98. const $storyEstimate = $currentRow.find('.form-batch-input[data-name="storyEstimate"]');
  99. const $storyPri = $currentRow.find('.form-batch-input[data-name="storyPri"]');
  100. const $storyDesc = $currentRow.find('.form-batch-input[data-name="storyDesc"]');
  101. let storyTitle = $story.find('.picker-single-selection').text();
  102. startPosition = storyTitle.indexOf(':') + 1;
  103. endPosition = storyTitle.lastIndexOf('[');
  104. if(endPosition < 0) endPosition = storyTitle.lastIndexOf('(');
  105. $currentRow.find('.form-batch-input[data-name="name"]').val(storyTitle.substr(startPosition, endPosition - startPosition));
  106. $currentRow.find('.form-batch-input[data-name="estimate"]').val($storyEstimate.val());
  107. $currentRow.find('input[name^="pri"]').zui('pripicker').$.setValue($storyPri.val() ? $storyPri.val() : 0);
  108. $currentRow.find('.form-batch-input[data-name="desc"]').val(($storyDesc.val()).replace(/<[^>]+>/g,'').replace(/(\n)+\n/g, "\n").replace(/^\n/g, '').replace(/\t/g, ''));
  109. }
  110. /**
  111. * Load lanes.
  112. *
  113. * @access public
  114. * @return void
  115. */
  116. function loadLanes(event)
  117. {
  118. const regionID = $(event.target).val();
  119. const $currentRow = $(event.target).closest('tr');
  120. const laneLink = $.createLink('kanban', 'ajaxGetLanes', 'regionID=' + regionID + '&type=task&field=lanes&pageType=batch');
  121. let $row = $currentRow;
  122. while($row.length)
  123. {
  124. const $lanePicker = $row.find('[name^=lane]').zui('picker');
  125. $.getJSON(laneLink, function(lanes)
  126. {
  127. $lanePicker.render({items: lanes})
  128. $lanePicker.$.setValue(lanes[0].value);
  129. });
  130. $row = $row.next('tr');
  131. if(!$row.find('td[data-name="region"][data-ditto="on"]').length) break;
  132. }
  133. }
  134. $(document).off('change', '#formSettingBtn input[value=story]').on('change', '#formSettingBtn input[value=story]', function()
  135. {
  136. const checked = $('#formSettingBtn input[value=story]').prop('checked');
  137. $('#formSettingBtn input[value=preview], #formSettingBtn input[value=copyStory]').prop('checked', checked);
  138. })
  139. function checkBatchEstStartedAndDeadline(event)
  140. {
  141. if(taskDateLimit != 'limit') return;
  142. const $currentRow = $(event.target).closest('tr');
  143. const field = $(event.target).closest('.form-batch-control').data('name');
  144. const estStarted = $currentRow.find('[name^=estStarted]').val();
  145. const deadline = $currentRow.find('[name^=deadline]').val();
  146. const level = $currentRow.attr('data-level');
  147. let $nextRow = $currentRow.next();
  148. while($nextRow.length)
  149. {
  150. let nextLevel = $nextRow.attr('data-level');
  151. if(nextLevel <= level) break;
  152. if(field == 'estStarted') $nextRow.find('td[data-name=estStarted]').find('[id^=estStarted]').zui('datepicker').render({disabled: estStarted == ''});
  153. if(field == 'deadline') $nextRow.find('td[data-name=deadline]').find('[id^=deadline]').zui('datepicker').render({disabled: deadline == ''});
  154. $nextRow = $nextRow.next();
  155. }
  156. if($currentRow.find('td[data-name=name]').find('input[name^=name]').val() == '') return;
  157. const $estStartedTd = $currentRow.find('td[data-name=estStarted]');
  158. $estStartedTd.find('.date-tip').remove();
  159. if(field == 'estStarted' && estStarted.length > 0 && parentEstStarted.length > 0 && estStarted < parentEstStarted)
  160. {
  161. let $datetip = $('<div class="date-tip"></div>');
  162. $datetip.append('<div class="form-tip text-danger">' + overParentEstStartedLang + '</div>');
  163. $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)});
  164. $estStartedTd.append($datetip);
  165. }
  166. const $deadlineTd = $currentRow.find('td[data-name=deadline]');
  167. $deadlineTd.find('.date-tip').remove();
  168. if(field == 'deadline' && deadline.length > 0 && parentDeadline.length > 0 && deadline > parentDeadline)
  169. {
  170. let $datetip = $('<div class="date-tip"></div>');
  171. $datetip.append('<div class="form-tip text-danger">' + overParentDeadlineLang + '</div>');
  172. $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)});
  173. $deadlineTd.append($datetip);
  174. }
  175. }
  176. window.handleClickBatchFormAction = function(action, $row, rowIndex)
  177. {
  178. if(action !== 'addSub' && action !== 'addSibling') return;
  179. if(!this.nestedLevelMap) this.nestedLevelMap = {};
  180. const level = this.nestedLevelMap[$row.attr('data-gid')] || 0;
  181. const nextGid = this._idSeed++;
  182. this.nestedLevelMap[nextGid] = action === 'addSub' ? level + 1 : level;
  183. $row.find('input[data-name="estimate"]').prop('readonly', true); // 如果有子任务,不允许修改预计工时
  184. $row.find('input[data-name="isParent"]').val('1'); // 如果有子任务,该任务为父任务
  185. const nextLevel = level + 1;
  186. while(true)
  187. {
  188. $nextRow = $row.next();
  189. if($nextRow.length == 0 || $nextRow.attr('data-level') < nextLevel)
  190. {
  191. rowIndex = $nextRow.length == 0 ? $row.index() : $nextRow.index() - 1;
  192. break;
  193. }
  194. $row = $nextRow;
  195. }
  196. this.addRow(rowIndex, nextGid);
  197. };
  198. window.handleRenderRow = function($row, index)
  199. {
  200. if(!this.nestedLevelMap) this.nestedLevelMap = {};
  201. /* 上一行: */
  202. const $prevRow = $row.prev();
  203. /* 添加序号。 */
  204. const $nameTd = $row.find('td[data-name="name"]');
  205. if($nameTd.find('.input-group').length == 0)
  206. {
  207. $nameTd.find('.input-control').wrap('<div class="input-group"></div>');
  208. $nameTd.find('.input-group').prepend('<div class="input-group-addon max-w-100px"></div>');
  209. }
  210. /* 从行中查找层级文本展示元素: */
  211. const nestedTextSelector = 'td[data-name="name"] .input-group-addon';
  212. /* 获取当前行的层级,下面可能会根据上一行层级修改当前行层级: */
  213. let level = this.nestedLevelMap[$row.attr('data-gid')] || 0;
  214. /* 当前行层级信息文本: */
  215. let text = '1';
  216. /* 处理有上一行的情况: */
  217. if($prevRow.length)
  218. {
  219. /* 根据上一行层级,重新计算当前行层级: */
  220. const prevLevel = +$prevRow.attr('data-level') || 0;
  221. if(prevLevel < level) level = prevLevel + 1;
  222. /* 根据上一行的层级文本,生成当前行的层级文本: */
  223. const prevText = $prevRow.find(nestedTextSelector).text();
  224. const parts = prevText.split('.');
  225. if(prevLevel === level) parts[level] = +parts[level] + 1;
  226. else if(prevLevel > level)
  227. {
  228. parts.length = level + 1;
  229. parts[level] = +parts[level] + 1;
  230. }
  231. else parts[level] = 1;
  232. text = parts.join('.');
  233. }
  234. else
  235. {
  236. /* 如果没有上一行,当前行层级为 0: */
  237. level = 0;
  238. }
  239. /* 存储当前行层级信息: */
  240. this.nestedLevelMap[$row.attr('data-gid')] = level;
  241. $row.attr('data-level', level);
  242. /* 创建隐藏表单域用于向服务器提交当前行层级信息。 */
  243. $row.find(nestedTextSelector).attr('title', text).text(text).append(`<input type="hidden" data-name="level" name="level[${index + 1}]" value="${level}">`);
  244. $row.find('.form-batch-col-actions').addClass('is-pinned');
  245. if($prevRow.length && $prevRow.attr('data-level') >= level)
  246. {
  247. $prevRow.find('input[data-name="estimate"]').prop('readonly', false); // 如果没有子任务,重置预计字段的可编辑状态。
  248. $prevRow.find('input[data-name="isParent"]').val('0'); // 如果没有子任务,该任务不是父任务
  249. }
  250. if(edition == 'open' && (level > 0 || parentID)) $row.find('button[data-type=addSub]').attr('disabled', 'disabled');
  251. /* 复制上一行的人员下拉。*/
  252. $row.find('[data-name=assignedTo]').find('.picker-box').on('inited', function(e, info)
  253. {
  254. const $assignedTo = info[0];
  255. const $preAssignedTo = $prevRow.find('input[name^=assignedTo]').zui('picker');
  256. if($preAssignedTo != undefined) $assignedTo.render({items: $preAssignedTo.options.items});
  257. })
  258. if(taskDateLimit == 'limit')
  259. {
  260. let disabledStarted = false;
  261. let disabledDeadline = false;
  262. if(parentID)
  263. {
  264. disabledStarted = parentEstStarted == '';
  265. disabledDeadline = parentDeadline == '';
  266. }
  267. else if(parentID == 0 && level > 0)
  268. {
  269. const $prevLevelRow = $row.prevAll('tr[data-level="' + (level - 1) + '"]').first();
  270. const $prevLevelStarted = $prevLevelRow.find('td[data-name=estStarted]').find('input[name^=estStarted]');
  271. const $prevLevelDeadline = $prevLevelRow.find('td[data-name=deadline]').find('input[name^=deadline]');
  272. disabledStarted = $prevLevelStarted.val() == '' || $prevLevelStarted.prop('disabled');
  273. disabledDeadline = $prevLevelDeadline.val() == '' || $prevLevelDeadline.prop('disabled');
  274. }
  275. $row.find('td[data-name=estStarted]').find('[id^=estStarted]').on('inited', function(e, info) { info[0].render({disabled: disabledStarted}); })
  276. $row.find('td[data-name=deadline]').find('[id^=deadline]').on('inited', function(e, info) { info[0].render({disabled: disabledDeadline}); })
  277. }
  278. };
  279. $(function()
  280. {
  281. if(taskHasConsumed) zui.Modal.alert({message: langAddChildTask, closeBtn: false});
  282. })