diff --git a/module/programplan/css/create.ui.css b/module/programplan/css/create.ui.css index 71b45faa9a..9d5168e54e 100644 --- a/module/programplan/css/create.ui.css +++ b/module/programplan/css/create.ui.css @@ -1,3 +1,4 @@ .panel-actions {display: flex; width: 100%; flex-direction: row-reverse; justify-content: space-between;} menu .panel-actions {width: auto;} td .check-list {flex-direction:row;} +th.form-batch-head[data-name=ACTIONS]{width:120px;} \ No newline at end of file diff --git a/module/programplan/js/create.ui.js b/module/programplan/js/create.ui.js index fcb321313a..ac22462700 100644 --- a/module/programplan/js/create.ui.js +++ b/module/programplan/js/create.ui.js @@ -1,13 +1,92 @@ -window.onRenderRow = function(row, rowIdx, data) +window.handleClickBatchFormAction = function(action, $row, rowIndex) { - if(row.children('[data-name=milestone]').find('input[type=radio]:checked').length == 0) row.children('[data-name=milestone]').find('input[type=radio]').eq(1).prop('checked', true); - row.children('[data-name=type]').find('[name^=type]').picker({disabled: true}); - if(typeof data != 'undefined' && typeof data.id != 'undefined') row.find('[data-type="delete"]').css('display', 'none'); + if(action !== 'addSub' && action !== 'addSibling') return; + + if(!this.nestedLevelMap) this.nestedLevelMap = {}; + const level = this.nestedLevelMap[$row.attr('data-gid')] || 0; + const nextGid = this._idSeed++; + this.nestedLevelMap[nextGid] = action === 'addSub' ? level + 1 : level; + $row.find('input[data-name="estimate"]').prop('readonly', true); // 如果有子任务,不允许修改预计工时 + + const nextLevel = level + 1; + while(true) + { + $nextRow = $row.next(); + if($nextRow.length == 0 || $nextRow.attr('data-level') < nextLevel) + { + rowIndex = $nextRow.length == 0 ? $row.index() : $nextRow.index() - 1; + break; + } + + $row = $nextRow; + } + this.addRow(rowIndex, nextGid); +}; + +window.handleRenderRow = function($row, index, data) +{ + if(!this.nestedLevelMap) this.nestedLevelMap = {}; + + /* 上一行: */ + const $prevRow = $row.prev(); + + /* 添加序号。 */ + const $nameTd = $row.find('td[data-name="name"]'); + if($nameTd.find('.input-group').length == 0) + { + $nameTd.find('input.form-control').wrap('
'); + $nameTd.find('.input-group').prepend('
'); + } + + /* 从行中查找层级文本展示元素: */ + const nestedTextSelector = 'td[data-name="name"] .input-group-addon'; + + /* 获取当前行的层级,下面可能会根据上一行层级修改当前行层级: */ + let level = this.nestedLevelMap[$row.attr('data-gid')] || 0; + + /* 当前行层级信息文本: */ + let text = '1'; + + /* 处理有上一行的情况: */ + if($prevRow.length) + { + /* 根据上一行层级,重新计算当前行层级: */ + const prevLevel = +$prevRow.attr('data-level') || 0; + if(prevLevel < level) level = prevLevel + 1; + + /* 根据上一行的层级文本,生成当前行的层级文本: */ + const prevText = $prevRow.find(nestedTextSelector).text(); + const parts = prevText.split('.'); + if(prevLevel === level) parts[level] = +parts[level] + 1; + else if(prevLevel > level) + { + parts.length = level + 1; + parts[level] = +parts[level] + 1; + } + else parts[level] = 1; + text = parts.join('.'); + } + else + { + /* 如果没有上一行,当前行层级为 0: */ + level = 0; + } + + /* 存储当前行层级信息: */ + this.nestedLevelMap[$row.attr('data-gid')] = level; + $row.attr('data-level', level); + + /* 创建隐藏表单域用于向服务器提交当前行层级信息。 */ + $row.find(nestedTextSelector).attr('title', text).text(text).append(``); + $row.find('.form-batch-col-actions').addClass('is-pinned'); + + if(typeof data != 'undefined' && typeof data.id != 'undefined') $row.find('[data-name="ACTIONS"]').find('[data-type="delete"]').addClass('hidden'); + if($row.find('input[data-name="milestone"]:checked').length == 0) $row.find('input[data-name="milestone"]').eq(1).prop('checked', true); if(project.model == 'ipd' && planID == '0') { const $attribute = data.attribute; - const $point = row.find('[data-name="point"]'); + const $point = $row.find('[data-name="point"]'); $point.find('.picker-box').on('inited', function(e, info) { @@ -35,8 +114,8 @@ window.onRenderRow = function(row, rowIdx, data) }); $('thead [data-name="ACTIONS"]').css('display', 'none'); - row.find('[data-name="ACTIONS"]').css('display', 'none'); - row.find('[data-name="attribute"]').find('.picker-box').on('inited', function(e, info) + $row.find('[data-name="ACTIONS"]').css('display', 'none'); + $row.find('[data-name="attribute"]').find('.picker-box').on('inited', function(e, info) { let $attributePicker = info[0]; $attributePicker.render({disabled: true}); @@ -44,19 +123,19 @@ window.onRenderRow = function(row, rowIdx, data) if(data.hasOwnProperty('status') && data.status != 'wait') { - row.find('[data-name=enabled] input').attr('disabled', 'disabled'); - row.find('[data-name=enabled]').attr('title', cropStageTip); + $row.find('[data-name=enabled] input').attr('disabled', 'disabled'); + $row.find('[data-name=enabled]').attr('title', cropStageTip); } if(data.enabled == 'off') { /* 需要等该行所有input元素加载完再执行changeEnabled方法 */ - window.waitDom("tr[data-index='" + rowIdx + "'] [data-name='PM'] input", function(){changeEnabled(row.find('[data-name=enabled] [name^=enabled]'));}); + window.waitDom("tr[data-index='" + index + "'] [data-name='PM'] input", function(){changeEnabled($row.find('[data-name=enabled] [name^=enabled]'));}); } window.waitDom("div.picker-multi-selection", function() { - let selection = row.find('div.picker-multi-selection'); + let selection = $row.find('div.picker-multi-selection'); selection.each(function(index, element) { /* 获取title。*/ let $title = $(element).attr('title'); @@ -75,7 +154,7 @@ window.onRenderRow = function(row, rowIdx, data) { if(data.hasOwnProperty('type')) { - row.find('[data-name="type"]').find('.picker-box').on('inited', function(e, info) + $row.find('[data-name="type"]').find('.picker-box').on('inited', function(e, info) { let $type = info[0]; $type.render({disabled: true}); @@ -87,15 +166,13 @@ window.onRenderRow = function(row, rowIdx, data) { let name = ''; let value = ''; - const checkBoxList = row.children('[data-name=milestone]').find('input'); - Object.values(checkBoxList).forEach(function(ele){ + const checkBoxList = $row.children('[data-name=milestone]').find('input'); + Object.values(checkBoxList).forEach(function(ele) + { if(typeof ele !== 'object') return; name = ele.name; - if(ele.checked) - { - value = ele.value; - } + if(ele.checked) value = ele.value; }); checkBoxList.attr('disabled', 'disabled'); @@ -105,12 +182,8 @@ window.onRenderRow = function(row, rowIdx, data) inputEle.setAttribute('name', name); inputEle.setAttribute('value', value); inputEle.setAttribute('type', 'hidden'); - row.children('[data-name=milestone]').append(inputEle); + $row.children('[data-name=milestone]').append(inputEle); } - if(!data || !data.planIDList) return; - - row.children('.form-batch-row-actions').children('[data-type=delete]').addClass('hidden'); - row.children('[data-name=type]').children('select').attr('disabled', 'disabled'); }; window.onChangeExecutionType = function(event) diff --git a/module/programplan/ui/create.html.php b/module/programplan/ui/create.html.php index 397f173e61..6011cd7666 100644 --- a/module/programplan/ui/create.html.php +++ b/module/programplan/ui/create.html.php @@ -20,6 +20,7 @@ $reviewedPoints = isset($reviewedPoints) ? $reviewedPoints : array(); $canParallel = isset($canParallel) ? $canParallel : false; $customKey = 'createFields'; $section = 'custom'; +unset($fields['level']); /* Generate custom config key by project model. */ if(in_array($project->model, array('waterfallplus', 'ipd', 'waterfall'))) $customKey = 'create' . ucfirst($project->model) . 'Fields'; @@ -142,8 +143,7 @@ $fnGenerateSubPlanManageFields = function() use ($lang, $planID, $project, $exec /* Generate form fields. */ $fnGenerateFields = function() use ($config, $lang, $requiredFields, $showFields, $fields, $PMUsers, $enableOptionalAttr, $programPlan, $planID, $executionType, $project) { - $items = array(); - $items[] = $project->model == 'ipd' ? null : array('name' => 'index', 'label' => $lang->programplan->idAB, 'control' => 'index', 'width' => '40px'); + $items = array(); $fields['attribute']['required'] = $fields['acl']['required'] = true; if(isset($requiredFields['code'])) $fields['code']['required'] = true; @@ -297,11 +297,17 @@ toolbar backBtn(set::icon('back'), setClass('primary'), $lang->goback), ); +$batchFormOptions = array(); +$batchFormOptions['fixedActions'] = true; // 滚动时固定操作列。 +$batchFormOptions['actions'] = array('sort', array('type' => 'addSibling', 'icon' => 'icon-plus', 'text' => $lang->task->addSibling), array('type' => 'addSub', 'icon' => 'icon-split', 'text' => $lang->task->addSub), 'delete'); +$batchFormOptions['onClickAction'] = jsRaw('window.handleClickBatchFormAction'); // 定义操作列按钮点击事件处理函数。 +$batchFormOptions['onRenderRow'] = jsRaw('window.handleRenderRow'); // 定义行渲染事件处理函数。 + formBatchPanel ( setID('dataform'), set::idKey('index'), - set::onRenderRow(jsRaw('window.onRenderRow')), + set::batchFormOptions($batchFormOptions), to::headingActions(array($fnGenerateSubPlanManageFields())), set::customFields(array('list' => $customFields, 'show' => explode(',', $showFields), 'key' => 'createFields')), set::customUrlParams("module=programplan§ion=$section&key=$customKey"),