browse.ui.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. const executionDropdownMap = new Map();
  2. /**
  3. * 计算表格计划信息的统计。
  4. * Set plan summary for table footer.
  5. *
  6. * @param element element
  7. * @param array checkedIDList
  8. * @access public
  9. * @return object
  10. */
  11. window.setStatistics = function(element, checkedIDList, pageSummary)
  12. {
  13. if(!checkedIDList.length) return {html: pageSummary, className: 'text-dark'};
  14. let total = checkedIDList.length;
  15. let totalParent = 0;
  16. let totalChild = 0;
  17. let totalIndependent = 0;
  18. const rows = element.layout.allRows;
  19. rows.forEach((row) => {
  20. if(checkedIDList.length == 0 || checkedIDList.includes(row.id))
  21. {
  22. const plan = row.data;
  23. if(plan.parent > 0) totalChild ++;
  24. if(plan.parent == 0 && plan.isParent) totalParent ++;
  25. }
  26. });
  27. totalIndependent = total - totalParent - totalChild;
  28. let summary = checkedSummary.replace('%total%', total);
  29. summary = summary.replace('%parent%', totalParent);
  30. summary = summary.replace('%child%', totalChild);
  31. summary = summary.replace('%independent%', totalIndependent);
  32. return {html: summary};
  33. }
  34. window.showExecution = function(target, executionList)
  35. {
  36. if(executionDropdownMap.has(target)) return;
  37. let executionItems = new Array();
  38. executionList.forEach(function(execution, index)
  39. {
  40. const link = $.createLink('execution', 'task', `executionID=${execution.id}`);
  41. executionItems.push({text: execution.name, url: link});
  42. });
  43. const dropdown = new zui.Dropdown($(target), {
  44. arrow: true,
  45. placement: 'right',
  46. menu: {items: executionItems},
  47. });
  48. executionDropdownMap.set(target, dropdown);
  49. }
  50. window.renderProductPlanList = function(result, {col, row, value})
  51. {
  52. if(col.name === 'execution')
  53. {
  54. if(result[0].length === 0) return [];
  55. if(result[0].length === 1)
  56. {
  57. const link = $.createLink('execution', 'task', `executionID=${result[0][0].id}`);
  58. result[0] = {html: '<a class="btn ghost toolbar-item text-primary square size-sm" href="' + link + '" title="' + result[0][0].name + '"><i class="icon icon-run"></i></a>'};
  59. return result;
  60. }
  61. result[0] = {html: `<a class="btn ghost toolbar-item text-primary square size-sm" href="javascript:;" onclick='window.showExecution(this, ${JSON.stringify(result[0])})'><i class="icon icon-run"></i></a>`};
  62. }
  63. return result;
  64. }
  65. window.startProductPlan = function(planID)
  66. {
  67. zui.Modal.confirm({message: confirmStart, icon:'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x'}).then((res) =>
  68. {
  69. if(res) $.ajaxSubmit({url: $.createLink('productplan', 'start', 'planID=' + planID)});
  70. });
  71. }
  72. window.finishProductPlan = function(planID)
  73. {
  74. zui.Modal.confirm({message: confirmFinish, icon:'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x'}).then((res) =>
  75. {
  76. if(res) $.ajaxSubmit({url: $.createLink('productplan', 'finish', 'planID=' + planID)});
  77. });
  78. }
  79. window.activateProductPlan = function(planID)
  80. {
  81. zui.Modal.confirm({message: confirmActivate, icon:'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x'}).then((res) =>
  82. {
  83. if(res) $.ajaxSubmit({url: $.createLink('productplan', 'activate', 'planID=' + planID)});
  84. });
  85. }
  86. window.deleteProductPlan = function(planID)
  87. {
  88. zui.Modal.confirm({message: confirmDelete, icon:'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x'}).then((res) =>
  89. {
  90. if(res) $.ajaxSubmit({url: $.createLink('productplan', 'delete', 'planID=' + planID)});
  91. });
  92. }
  93. $(document).off('click', '.batch-btn').on('click', '.batch-btn', function()
  94. {
  95. const dtable = zui.DTable.query($(this).target);
  96. const checkedList = dtable.$.getChecks();
  97. if(!checkedList.length) return;
  98. const url = $(this).data('url');
  99. const form = new FormData();
  100. checkedList.forEach((id) => form.append('planIdList[]', id));
  101. if($(this).hasClass('ajax-btn'))
  102. {
  103. $.ajaxSubmit({url, data: form});
  104. }
  105. else
  106. {
  107. postAndLoadPage(url, form);
  108. }
  109. });
  110. $(document).on('click', '[data-target="#createExecutionModal"]', function()
  111. {
  112. const planID = $(this).data('plan') ? $(this).data('plan') : $(this).closest('.dtable-cell').data('row');
  113. $('#createExecutionModal [name=planID]').val(planID);
  114. const productID = plans[planID].product;
  115. const branch = plans[planID].branch;
  116. const link = $.createLink('productplan', 'ajaxGetProjects', `productID=${productID}&branch=${branch}`);
  117. $.getJSON(link, function(projects)
  118. {
  119. const $projectPicker = $('#createExecutionModal [name=project]').zui('picker');
  120. $projectPicker.render({items: projects, disabled: projects.length == 0});
  121. $('.projectTips').toggleClass('hidden', projects.length != 0);
  122. $projectPicker.$.setValue('');
  123. if(projects.length > 0)
  124. {
  125. $('#createExecutionModal .createExecutionBtn').attr('id', 'createExecutionButton');
  126. $('#createExecutionModal .createExecutionBtn').attr('href', '###');
  127. $('#createExecutionModal .createExecutionBtn').text(nextStep);
  128. }
  129. else
  130. {
  131. $('#createExecutionModal .createExecutionBtn').attr('id', '');
  132. $('#createExecutionModal .createExecutionBtn').attr('href', $.createLink('product', 'project', `status=all&productID=${productID}&branch=${branch}`));
  133. $('#createExecutionModal .createExecutionBtn').text(enterProjectList);
  134. }
  135. })
  136. });
  137. $(document).on('click', '#createExecutionButton', function()
  138. {
  139. const projectID = $('input[name=project]').val();
  140. const planID = $('input[name=planID]').val();
  141. openUrl($.createLink('execution', 'create', 'projectID=' + projectID + '&executionID=&copyExecutionID=&planID=' + planID + '&confirm=&productID=' + productID), {'app': 'execution'});
  142. zui.Modal.hide('#createExecutionModal');
  143. });
  144. /**
  145. * 对部分列进行重定义。
  146. * Redefine the partial column.
  147. *
  148. * @param array result
  149. * @param array info
  150. * @access public
  151. * @return string|array
  152. */
  153. window.renderCell = function(result, info)
  154. {
  155. if(info.col.name == 'execution')
  156. {
  157. const projects = info.row.data.projects;
  158. const projectCount = projects.length;
  159. if(projectCount == 0) return result;
  160. if(projectCount == 1)
  161. {
  162. result[0] = {html: '<a href=' + $.createLink('execution', 'task', 'executionID=' + projects[0].project) + ' title="' + projects[0].name + '"><i class="icon-run text-primary"></i></a>'};
  163. }
  164. else
  165. {
  166. let contentHtml = "<ul class='execution-tip'>";
  167. projects.forEach((project) => {
  168. contentHtml += `<li><a title='${project.name}' href='` + $.createLink('execution', 'task', 'executionID=' + project.project) + `'>${project.name}</a></li>`;
  169. });
  170. contentHtml += "</ul>";
  171. let content = {html: contentHtml};
  172. content = JSON.stringify(content);
  173. content = content.replace(/"/g, '&quot;');
  174. const buttonHtml = `<button type='button' data-toggle='popover' data-trigger='click' data-content="${content}" data-close-btn='false' data-placement='right'><i class='icon-run text-primary'></i></button>`;
  175. result[0] = {html: buttonHtml};
  176. }
  177. }
  178. if(info.col.name == 'title')
  179. {
  180. html = '';
  181. if(info.row.data.parent > 0) html += "<span class='label gray-pale rounded-xl'>" + childrenAB + "</span>";
  182. if(html) result.unshift({html});
  183. html = '';
  184. if(info.row.data.expired && ['wait', 'doing'].includes(info.row.data.status)) html += '<span class="label danger-pale ml-1">' + expiredLang + '</span>';
  185. if(html) result.push({html});
  186. }
  187. return result;
  188. }
  189. $(document).on('click', '.switchButton', function()
  190. {
  191. const type = $(this).data('type');
  192. $.cookie.set('viewType', type, {expires:config.cookieLife, path:config.webRoot});
  193. loadCurrentPage();
  194. })
  195. window.getCol = function(col)
  196. {
  197. col.subtitle = {html: "<span class='text-gray ml-1'>" + col.cards + "</span>"};
  198. }
  199. window.getItem = function(info)
  200. {
  201. if(info.item.delay && ['wait', 'doing'].includes(info.item.status))
  202. {
  203. info.item.suffix = productplanLang.expired;
  204. info.item.suffixClass = 'label danger rounded-xl' + (info.item.status == 'doing' ? ' mr-8' : '');
  205. }
  206. info.item.icon = 'delay';
  207. info.item.titleAttrs = {'class': 'text-black clip', 'title' : info.item.title, 'data-app': currentTab};
  208. info.item.content = {html: info.item.desc};
  209. info.item.contentClass = 'text-gray';
  210. info.item.footer = {html: "<div class='flex'><span class='label label-" + info.item.status + "'>" + info.item.statusLabel + "</span><span class='label gray-pale ml-2'>" + info.item.dateLine + "</span></div>"};
  211. if(privs.canViewPlan) info.item.titleUrl = $.createLink('productplan', 'view', `id=${info.item.id}`);
  212. }
  213. window.canDrop = function(dragInfo, dropInfo)
  214. {
  215. if(!dragInfo) return false;
  216. const column = this.getCol(dropInfo.col);
  217. const lane = this.getLane(dropInfo.lane);
  218. if(!column || !lane) return false;
  219. if(dropInfo.type == 'item') return false;
  220. if(dragInfo.item.lane != lane.name) return false;
  221. if(dragInfo.item.status == 'wait' && dropInfo.col == 'doing') return privs.canStartPlan;
  222. if(dragInfo.item.status == 'wait' && dropInfo.col == 'closed') return privs.canClosePlan;
  223. if(dragInfo.item.status == 'doing' && dropInfo.col == 'done') return privs.canFinishPlan;
  224. if(dragInfo.item.status == 'doing' && dropInfo.col == 'closed') return privs.canClosePlan;
  225. if(dragInfo.item.status == 'done' && dropInfo.col == 'doing') return privs.canActivatePlan;
  226. if(dragInfo.item.status == 'done' && dropInfo.col == 'closed') return privs.canClosePlan;
  227. if(dragInfo.item.status == 'closed' && dropInfo.col == 'doing') return privs.canActivatePlan;
  228. return false;
  229. }
  230. window.onDrop = function(changes, dropInfo)
  231. {
  232. const item = dropInfo['drag']['item'];
  233. const toCol = dropInfo['drop']['col'];
  234. if(item.status == 'wait' && toCol == 'doing')
  235. {
  236. zui.Modal.confirm(productplanLang.confirmStart).then(result =>
  237. {
  238. if(result)
  239. {
  240. const url = $.createLink('productplan', 'start', 'planID=' + item.id)
  241. $.ajaxSubmit({url});
  242. this.update(changes);
  243. }
  244. });
  245. return false;
  246. }
  247. else if(item.status == 'doing' && toCol == 'done')
  248. {
  249. zui.Modal.confirm(productplanLang.confirmFinish).then(result =>
  250. {
  251. if(result)
  252. {
  253. const url = $.createLink('productplan', 'finish', 'planID=' + item.id)
  254. $.ajaxSubmit({url});
  255. this.update(changes);
  256. }
  257. });
  258. return false;
  259. }
  260. else if((item.status == 'done' || item.status == 'closed') && toCol == 'doing')
  261. {
  262. zui.Modal.confirm(productplanLang.confirmActivate).then(result =>
  263. {
  264. if(result)
  265. {
  266. const url = $.createLink('productplan', 'activate', 'planID=' + item.id)
  267. $.ajaxSubmit({url});
  268. this.update(changes);
  269. }
  270. });
  271. return false;
  272. }
  273. zui.Modal.open({url: $.createLink('productplan', 'close', 'planID=' + item.id), size: 'lg'});
  274. return false;
  275. }
  276. window.getItemActions = function(item)
  277. {
  278. return [{
  279. type: 'dropdown',
  280. icon: 'ellipsis-v',
  281. caret: false,
  282. items: buildCardActions(item),
  283. }];
  284. }
  285. window.buildCardActions = function(item)
  286. {
  287. let actions = [];
  288. if(item.actionList.includes('createExecution')) actions.push({text: productplanLang.createExecution, icon: 'plus', url: '#createExecutionModal', 'data-target': '#createExecutionModal', 'data-toggle': 'modal', 'data-on': 'click', 'data-call': 'getPlanID', 'data-params': 'event', 'data-branch': item.branch, 'data-plan': item.id});
  289. if(item.actionList.includes('linkStory')) actions.push({text: productplanLang.linkStory, icon: 'link', url: $.createLink(rawModule, 'view', "planID=" + item.id + "&type=story&orderBy=id_desc&link=true")});
  290. if(item.actionList.includes('linkBug')) actions.push({text: productplanLang.linkBug, icon: 'bug', url: $.createLink(rawModule, 'view', "planID=" + item.id + "&type=bug&orderBy=id_desc&link=true")});
  291. if(item.actionList.includes('edit')) actions.push({text: productplanLang.edit, icon: 'edit', url: $.createLink(rawModule, 'edit', "planID=" + item.id)});
  292. if(item.actionList.includes('start')) actions.push({text: productplanLang.start, icon: 'start', url: $.createLink('productplan', 'start', "planID=" + item.id), 'data-confirm': productplanLang.confirmStart});
  293. if(item.actionList.includes('finish')) actions.push({text: productplanLang.finish, icon: 'checked', url: $.createLink('productplan', 'finish', "planID=" + item.id), 'data-confirm': productplanLang.confirmFinish});
  294. if(item.actionList.includes('close')) actions.push({text: productplanLang.close, icon: 'off', url: $.createLink('productplan', 'close', "planID=" + item.id), 'data-toggle': 'modal'});
  295. if(item.actionList.includes('activate')) actions.push({text: productplanLang.activate, icon: 'magic', url: $.createLink('productplan', 'activate', "planID=" + item.id), 'data-confirm': productplanLang.confirmActivate});
  296. if(item.actionList.includes('delete')) actions.push({text: productplanLang.delete, icon: 'trash', url: $.createLink('productplan', 'delete', "planID=" + item.id), 'data-confirm': productplanLang.confirmDelete});
  297. return actions;
  298. }
  299. window.getPlanID = function(event)
  300. {
  301. const planID = $(event.target).closest('a').data('plan');
  302. $('[name=planID]').val(planID);
  303. }
  304. window.firstRendered = false;
  305. window.toggleCheckRows = function(idList)
  306. {
  307. if(!idList?.length || firstRendered) return;
  308. firstRendered = true;
  309. this._rendered = true;
  310. const dtable = zui.DTable.query($('#productPlans'));
  311. dtable.$.toggleCheckRows(idList.split(','), true);
  312. }