create.ui.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. window.handleClickBatchFormAction = function(action, $row, rowIndex)
  2. {
  3. if(action == 'delete')
  4. {
  5. const $nextRow = this.$tbody.find('tr[data-index="' + rowIndex + '"]');
  6. if($nextRow.length == 0) return;
  7. if($nextRow.find('td[data-name=type]').hasClass('hidden')) return;
  8. const $prevRow = $nextRow.prev();
  9. if($prevRow.length == 1 && $prevRow.attr('data-level') != $nextRow.attr('data-level') - 1) return;
  10. let $typePicker = $nextRow.find('.picker-box[data-name=type]').zui('picker');
  11. let typeItems = [];
  12. for(i in typeList) typeItems.push({'text': typeList[i], 'value': i});
  13. $typePicker.render({items: typeItems});
  14. }
  15. if(action !== 'addSub' && action !== 'addSibling') return;
  16. $this = this;
  17. if(action == 'addSub' && $row.find('td[data-name="id"] input[name^=id]').val() > 0)
  18. {
  19. const $syncData = $row.find('td[data-name="syncData"] input[name^=syncData]');
  20. if($syncData.val() == '0')
  21. {
  22. const executionID = $row.find('td[data-name="id"] input[name^=id]').val();
  23. $.get($.createLink('project', 'ajaxCheckHasStageData', `executionID=${executionID}`), function(hasData)
  24. {
  25. if(!hasData) return window.clickToAddRow($this, action, $row, rowIndex);
  26. zui.Modal.confirm(confirmCreateTip).then((res) =>
  27. {
  28. if(!res) return;
  29. $syncData.val('1');
  30. window.clickToAddRow($this, action, $row, rowIndex);
  31. });
  32. });
  33. }
  34. else
  35. {
  36. window.clickToAddRow($this, action, $row, rowIndex);
  37. }
  38. }
  39. else
  40. {
  41. window.clickToAddRow($this, action, $row, rowIndex);
  42. }
  43. };
  44. window.clickToAddRow = function($this, action, $row, rowIndex)
  45. {
  46. if(!$this.nestedLevelMap) $this.nestedLevelMap = {};
  47. const level = $this.nestedLevelMap[$row.attr('data-gid')] || 0;
  48. const nextGid = $this._idSeed++;
  49. $this.nestedLevelMap[nextGid] = action === 'addSub' ? level + 1 : level;
  50. $row.find('input[data-name="estimate"]').prop('readonly', true); // 如果有子任务,不允许修改预计工时
  51. const nextLevel = level + 1;
  52. while(true)
  53. {
  54. $nextRow = $row.next();
  55. if($nextRow.length == 0 || $nextRow.attr('data-level') < nextLevel)
  56. {
  57. rowIndex = $nextRow.length == 0 ? $row.index() : $nextRow.index() - 1;
  58. break;
  59. }
  60. $row = $nextRow;
  61. }
  62. $this.addRow(rowIndex, nextGid);
  63. };
  64. window.handleRenderRow = function($row, index, data)
  65. {
  66. if(!this.nestedLevelMap) this.nestedLevelMap = {};
  67. /* 上一行: */
  68. const $prevRow = $row.prev();
  69. /* 添加序号。 */
  70. const $nameTd = $row.find('td[data-name="name"]');
  71. if($nameTd.find('.input-group').length == 0)
  72. {
  73. $nameTd.find('input.form-control').wrap('<div class="input-group"></div>');
  74. $nameTd.find('.input-group').prepend('<div class="input-group-addon max-w-100px"></div>');
  75. }
  76. /* 从行中查找层级文本展示元素: */
  77. const nestedTextSelector = 'td[data-name="name"] .input-group-addon';
  78. /* 获取当前行的层级,下面可能会根据上一行层级修改当前行层级: */
  79. let level = this.nestedLevelMap[$row.attr('data-gid')] || 0;
  80. if(typeof data != 'undefined' && typeof data.grade != 'undefined') level = data.grade - planGrade;
  81. /* 当前行层级信息文本: */
  82. let text = '1';
  83. /* 处理有上一行的情况: */
  84. if($prevRow.length)
  85. {
  86. /* 根据上一行层级,重新计算当前行层级: */
  87. const prevLevel = +$prevRow.attr('data-level') || 0;
  88. if(prevLevel < level) level = prevLevel + 1;
  89. /* 根据上一行的层级文本,生成当前行的层级文本: */
  90. const prevText = $prevRow.find(nestedTextSelector).text();
  91. const parts = prevText.split('.');
  92. if(prevLevel === level) parts[level] = +parts[level] + 1;
  93. else if(prevLevel > level)
  94. {
  95. parts.length = level + 1;
  96. parts[level] = +parts[level] + 1;
  97. }
  98. else parts[level] = 1;
  99. text = parts.join('.');
  100. }
  101. else
  102. {
  103. /* 如果没有上一行,当前行层级为 0: */
  104. level = 0;
  105. }
  106. /* 存储当前行层级信息: */
  107. this.nestedLevelMap[$row.attr('data-gid')] = level;
  108. $row.attr('data-level', level);
  109. $row.find(nestedTextSelector).attr('title', text).text(text).append(`<input type="hidden" data-name="level" name="level[${index + 1}]" value="${level}">`); // 创建隐藏表单域用于向服务器提交当前行层级信息。
  110. /* 追加 parent 属性,以记录父级index。 */
  111. let $prevLevelRow = $prevRow;
  112. $row.attr('data-parent', '-1');
  113. if($prevRow.length == 1)
  114. {
  115. if($prevLevelRow.attr('data-level') != level - 1) $prevLevelRow = $row.prevAll('tr[data-level="' + (level - 1) + '"]').first();
  116. if($prevLevelRow.length == 1) $row.attr('data-parent', $prevLevelRow.attr('data-gid'));
  117. }
  118. if($row.find('input[data-name="milestone"]:checked').length == 0) $row.find('input[data-name="milestone"]').eq(1).prop('checked', true); //里程碑默认选择"否"。
  119. if($prevLevelRow.length && $prevLevelRow.find('input[data-name="syncData"]').val() == '1') $row.find('input[data-name="syncData"]').val(1);
  120. /* 处理已有数据字段状态。隐藏的删除按钮,禁用管理方法字段。 */
  121. if(typeof data != 'undefined' && typeof data.id != 'undefined')
  122. {
  123. $row.find('[data-name="ACTIONS"]').find('[data-type="delete"]').addClass('hidden');
  124. $row.find('[data-name="type"]').find('.picker-box').on('inited', function(e, info){ info[0].render({disabled: true}); });
  125. }
  126. /* 如果管理方法不是"阶段",禁用拆分子级按钮,禁用工作量占比字段。 */
  127. const $currentType = $row.find('[data-name="type"] input[name^=type]');
  128. if((data != undefined && data.type != undefined && data.type != 'stage') || ($currentType.length && $currentType.val() != 'stage'))
  129. {
  130. $row.find('input[data-name="percent"]').prop('disabled', true);
  131. $row.find('[data-name="ACTIONS"]').find('[data-type="addSub"]').prop('disabled', true).attr('title', addSubTip);
  132. }
  133. $row.find('[data-name="type"]').find('.picker-box').on('inited', function(e, info)
  134. {
  135. let $typePicker = info[0];
  136. let dataExist = (typeof data != 'undefined' && typeof data.type != 'undefined');
  137. let options = {};
  138. options.disabled = (planID == 0 && level == 0) ? true : false;
  139. if(dataExist) options.disabled = true;
  140. let prevType = '';
  141. if(level > 0)
  142. {
  143. let $firstChild = $prevLevelRow.next();
  144. if($firstChild.length > 0)
  145. {
  146. let $firstType = $firstChild.find('[data-name="type"]').find('[name^=type]');
  147. if($firstType.length > 0) prevType = $firstType.val();
  148. }
  149. if(prevType && $firstChild.attr('data-index') != $row.attr('data-index'))
  150. {
  151. options.items = [];
  152. for(i in $typePicker.options.items)
  153. {
  154. let item = $typePicker.options.items[i];
  155. if(item.value == '') continue;
  156. if(prevType == 'stage' && item.value == 'stage') options.items.push(item);
  157. if(prevType != 'stage' && item.value != 'stage') options.items.push(item);
  158. }
  159. }
  160. }
  161. else if(level == 0 && planID && $row.attr('data-index') > 0)
  162. {
  163. options.items = [];
  164. for(i in $typePicker.options.items)
  165. {
  166. let item = $typePicker.options.items[i];
  167. if(item.value == '') continue;
  168. if(initType == 'stage' && item.value == 'stage') options.items.push(item);
  169. if(initType != 'stage' && item.value != 'stage') options.items.push(item);
  170. }
  171. }
  172. $typePicker.render(options);
  173. if(!dataExist)
  174. {
  175. if(level > 0 && prevType != '' && prevType != 'stage') $typePicker.$.setValue('sprint');
  176. if(level == 0 && planID) $typePicker.$.setValue(initType);
  177. }
  178. });
  179. /* Render type picker when move. */
  180. let $typePicker = $row.find('[data-name="type"]').find('.picker-box').zui('picker');
  181. if($typePicker != undefined && level > 0)
  182. {
  183. let $prevRow = $row.prev();
  184. let typeItems = [];
  185. if($prevRow.length == 0 || $prevRow.attr('data-level') < level)
  186. {
  187. for(i in typeList) typeItems.push({'text': typeList[i], 'value': i});
  188. }
  189. else
  190. {
  191. let type = $row.find('[data-name="type"]').find('[name^=type]').val();
  192. for(i in typeList)
  193. {
  194. if(type == 'stage' && i == 'stage') typeItems.push({'text': typeList[i], 'value': i});
  195. if(type != 'stage' && i != 'stage') typeItems.push({'text': typeList[i], 'value': i});
  196. }
  197. }
  198. $typePicker.render({items: typeItems});
  199. }
  200. /* 已关闭且有交付物的阶段无法变更阶段类型。 */
  201. $row.find('[data-name="attribute"]').find('.picker-box').on('inited', function(e, info)
  202. {
  203. if(typeof data != 'undefined' && typeof data.hasDeliverable != 'undefined') info[0].render({disabled: true});
  204. });
  205. if(project.model == 'ipd')
  206. {
  207. if(planID == 0 && level == 0)
  208. {
  209. $row.find('[data-name="ACTIONS"]').find('[data-type="sort"]').addClass('disabled').attr('title', sortableTip);
  210. $row.find('[data-name="ACTIONS"]').find('[data-type="addSibling"]').prop('disabled', true).attr('title', addSiblingTip);
  211. $row.find('[data-name="ACTIONS"]').find('[data-type="delete"]').addClass('hidden');
  212. }
  213. $row.find('[data-name="attribute"]').find('.picker-box').on('inited', function(e, info){ info[0].render({disabled: true}); });
  214. const $point = $row.find('[data-name="point"],[data-name="defaultPoint"]');
  215. $point.find('.picker-box').on('inited', function(e, info)
  216. {
  217. let items = [{text: '', value: ''}];
  218. let disabled = level > 0 ? true : false;
  219. if(typeof data != 'undefined' && typeof data.attribute != 'undefined' && !disabled)
  220. {
  221. const stageID = data.stageID;
  222. for(let point in ipdStagePoint[stageID])
  223. {
  224. let disabled = false
  225. let text = ipdStagePoint[stageID][point];
  226. let hint = text;
  227. /* 如果已经评审过,则置灰不能选。*/
  228. if(reviewedPoints[point] !== undefined && reviewedPoints[point].disabled)
  229. {
  230. disabled = true;
  231. hint = reviewedPointTip;
  232. }
  233. items.push({text: text, value: point, disabled: disabled, hint: hint});
  234. }
  235. }
  236. info[0].render({items: items, disabled: disabled});
  237. });
  238. const $enabled = $row.find('td[data-name=enabled]');
  239. if($enabled.length > 0 && !$enabled.hasClass('hidden'))
  240. {
  241. const $checkbox = $enabled.find('input[type=checkbox]');
  242. if(data != undefined && data.status != undefined && data.status != 'wait') $checkbox.attr('disabled', 'disabled').attr('title', cropStageTip);
  243. if(level > 0) $checkbox.attr('disabled', 'disabled').attr('title', childEnabledTip);
  244. if($enabled.find('input.hidden').length == 0) $enabled.append("<input type='hidden' name='" + $checkbox.attr('name') + "' value='on' class='hidden'/>")
  245. if($enabled.find('input.hidden').length > 0) $enabled.find('input.hidden').attr('name', $checkbox.attr('name'));
  246. let $rootRow = $row;
  247. if($rootRow.attr('data-level') != 0) $rootRow = $row.prevAll('tr[data-level="0"]').first();
  248. if($rootRow.length == 1 && !$rootRow.find('td[data-name=enabled] input[type=checkbox]').prop('checked'))
  249. {
  250. $row.addClass('disabled');
  251. $checkbox.prop('checked', false);
  252. $enabled.find('input.hidden').val('off');
  253. }
  254. }
  255. window.waitDom("div.picker-multi-selection", function()
  256. {
  257. let selection = $row.find('div.picker-multi-selection');
  258. selection.each(function(index, element) {
  259. /* 获取评审点ID。*/
  260. let pointID = $(element).find('div.picker-deselect-btn').data('value');
  261. /* 如果该评审点以提交评审,则不能取消选择。*/
  262. if(reviewedPoints[pointID] !== undefined && reviewedPoints[pointID].disabled)
  263. {
  264. $(element).attr('title', reviewedPointTip); // 修改title
  265. $(element).find(".picker-deselect-btn").remove(); // 禁用点击事件
  266. }
  267. });
  268. });
  269. }
  270. if(level > 0 || planGrade > 1)
  271. {
  272. let preAttribute = $prevLevelRow.length == 0 ? '' : $prevLevelRow.find('[name^=attribute]').val();
  273. $row.find('[data-name="attribute"]').find('.picker-box').on('inited', function(e, info)
  274. {
  275. let $attributePicker = info[0];
  276. let disabled = false;
  277. if(preAttribute && preAttribute != 'mix') disabled = true;
  278. if(data != undefined && data.attribute != undefined && data.attribute != 'mix') disabled = true;
  279. if(disabled)
  280. {
  281. $attributePicker.render({disabled: disabled});
  282. if(preAttribute) $attributePicker.$.setValue(preAttribute);
  283. }
  284. });
  285. }
  286. };
  287. window.onChangeExecutionType = function(event)
  288. {
  289. loadPage($.createLink('programplan', 'create', `projectID=${projectID}&productID=${productID}&planID=${planID}&type=` + $(event.target).val() + `&from=&syncData=${syncData}`));
  290. };
  291. window.onChangeParallel = function(event)
  292. {
  293. const parallel = $(event.target).val();
  294. $('input[name^="parallel"]').val(parallel);
  295. }
  296. window.changeEnabled = function(obj)
  297. {
  298. const $target = $(obj);
  299. const $row = $target.closest('tr');
  300. $row.toggleClass('disabled', !$target.prop('checked'))
  301. let $checkbox = $row.find('[data-name=enabled]').find('input[type=checkbox]');
  302. if($row.find('[data-name=enabled]').find('input.hidden') == 0) $row.find('[data-name=enabled]').append("<input type='hidden' name='" + $checkbox.attr('name') + "' value='on' class='hidden'/>");
  303. $row.find('[data-name=enabled]').find('input.hidden').val($target.prop('checked') ? 'on' : 'off');
  304. let $nextRow = $row.next();
  305. while(true)
  306. {
  307. if($nextRow.length == 0) break;
  308. if($nextRow.attr('data-level') == 0) break;
  309. $nextRow.toggleClass('disabled', !$target.prop('checked'))
  310. let $nextCheckbox = $nextRow.find('[data-name=enabled]').find('input[type=checkbox]');
  311. if($nextRow.find('[data-name=enabled]').find('input.hidden') == 0) $nextRow.find('[data-name=enabled]').append("<input type='hidden' name='" + $nextCheckbox.attr('name') + "' value='on' class='hidden'/>");
  312. $nextCheckbox.prop('checked', $target.prop('checked'));
  313. $nextRow.find('[data-name=enabled]').find('input.hidden').val($target.prop('checked') ? 'on' : 'off');
  314. $nextRow = $nextRow.next();
  315. }
  316. };
  317. window.changeAttribute = function(obj)
  318. {
  319. const attribute = obj.value;
  320. const $target = $(obj);
  321. let $row = $target.closest('tr');
  322. let level = $row.attr('data-level');
  323. let $nextRow = $row.next();
  324. while(true)
  325. {
  326. if($nextRow.length == 0) break;
  327. if($nextRow.attr('data-level') <= level) break;
  328. let $picker = $nextRow.find('.picker-box[data-name="attribute"]').zui('picker');
  329. $picker.render({disabled: attribute == 'mix' ? false : true});
  330. $picker.$.setValue(attribute);
  331. $nextRow = $nextRow.next();
  332. }
  333. };
  334. window.changeType = function(obj)
  335. {
  336. const type = obj.value;
  337. const $target = $(obj);
  338. let $row = $target.closest('tr');
  339. $row.find('input[data-name="percent"]').prop('disabled', type != 'stage');
  340. $row.find('[data-name="ACTIONS"]').find('[data-type="addSub"]').prop('disabled', type != 'stage').attr('title', type != 'stage' ? addSubTip : '');
  341. let $nextRow = $row.next();
  342. let level = $row.attr('data-level');
  343. let $typePicker = $row.find('.picker-box[data-name=type]').zui('picker');
  344. /* 检查子阶段是否又被拆分,如果有拆分,则不允许修改为非阶段的管理方法。 */
  345. if(type != 'stage')
  346. {
  347. while(true)
  348. {
  349. if($nextRow.length == 0) break;
  350. if($nextRow.attr('data-level') < level) break;
  351. if($nextRow.attr('data-level') > level)
  352. {
  353. zui.Modal.alert(errorLang.notStage);
  354. $typePicker.$.setValue('stage');
  355. return;
  356. }
  357. $nextRow = $nextRow.next();
  358. }
  359. }
  360. $nextRow = $row.next();
  361. if($nextRow.length == 0) return;
  362. if($nextRow.attr('data-level') != level) return; //只修改同级的管理方法。
  363. $nextRow.find('input[data-name="percent"]').prop('disabled', type != 'stage');
  364. $nextRow.find('[data-name="ACTIONS"]').find('[data-type="addSub"]').prop('disabled', type != 'stage').attr('title', type != 'stage' ? addSubTip : '');
  365. let $nextTypePicker = $nextRow.find('.picker-box[data-name=type]').zui('picker');
  366. if($nextTypePicker == undefined) return;
  367. let nextTypeItems = [];
  368. for(i in typeList)
  369. {
  370. if(i == '') continue;
  371. if(type == 'stage' && i == 'stage') nextTypeItems.push({'text': typeList[i], 'value': i});
  372. if(type != 'stage' && i != 'stage') nextTypeItems.push({'text': typeList[i], 'value': i});
  373. }
  374. $nextTypePicker.render({items: nextTypeItems});
  375. $nextTypePicker.$.setValue(type);
  376. };
  377. window.onMove = function(event, originEvent)
  378. {
  379. const fromLevel = $(event.dragged).attr('data-level');
  380. const toLevel = $(event.related).attr('data-level');
  381. const fromParent = $(event.dragged).attr('data-parent');
  382. const toParent = $(event.related).attr('data-parent');
  383. if(fromLevel != toLevel) return false;
  384. if(fromParent != toParent) return false;
  385. return true;
  386. }
  387. window.onSort = function(e)
  388. {
  389. const gid = $(e.item).attr('data-gid');
  390. const $duplicates = $(`tr[data-gid='${gid}']`);
  391. if($duplicates.length > 1) $duplicates.slice(1).remove();
  392. const id = $(e.item).attr('data-parent');
  393. window.moveChildren(id);
  394. const $batchForm = $('[data-zui-batchform]').zui('batchForm');
  395. $batchForm._rows = $batchForm._sortable.toArray().map(Number);
  396. $batchForm.render();
  397. }
  398. window.moveChildren = function(id, processedIds = new Set())
  399. {
  400. if(processedIds.has(id)) return;
  401. processedIds.add(id);
  402. const $parent = $(`tr[data-gid='${id}']`);
  403. const $children = $(`tr[data-parent='${id}']`).not('.sortable-empty-shadow');
  404. if($children.length == 0) return;
  405. const $reversedChildren = $($children.get().reverse());
  406. $reversedChildren.each(function(index, element)
  407. {
  408. $parent.after(element);
  409. moveChildren($(element).attr('data-gid'), processedIds);
  410. });
  411. }