运维咖啡吧

享受技术带来的乐趣,体验生活给予的感动

简洁表单替代复杂JSON,完成开始完美结束

上篇文章「简洁表单替代复杂JSON,配置直观又简单」讲了我们将任务参数由定义并修改JSON的方式升级为了表格+表单的方式,理解更加简单,上手难度降低,对用户也更为友好,使用成本明显降低

通常情况下一个新功能开发完成,做到能用就快速上线了,往往体验并不十分完美,后续会进行持续的优化,以提升使用体验。这个功能也不例外,上线前都知道一些体验有待提升的地方,例如:

1.对于一些参数,会存在默认值,并且这些默认值同一任务是不变的,例如基于Java项目部署模板创建了A、B、C三个任务流,模板中有一个Project的参数,对应项目名字,对于特定的任务流项目名字固定,不需要修改,此时我们会给任务流的Project参数一个固定的默认值,并且在后续点击任务流执行时也不需要显示Project参数的填写或选择。此时的话我们就需要一个隐藏属性以标识参数是否需要在任务流执行时渲染

2.参数因为各种各样的原因需要修改属性,而我们只提供了参数删除功能,并不能修改参数属性,那还需要加个参数属性修改的功能,以满足参数属性的修改需要

3.不仅参数属性需要修改,参数位置也有修改的需求,我们会根据参数位置来渲染最终执行时的展示顺序,例如一个任务流有B、C、D三个参数,我们习惯于在执行任务时B在前C次之D最后,这样我们在新建参数时只需要先建B再建C最后建D即可,如果此时要添加个新参数A,默认新加的参数A在最后,而习惯于在任务执行时A渲染在第一个参数位置,此时我们就需要修改参数位置了

对于这几个问题,该如何处理

参数隐藏

参数隐藏这个最简单,给参数加个hidden属性即可,我们在执行渲染时判断参数是否有hidden属性,如果有则不渲染,此时的数据结构将变为:

[
    {
        "name": "branch",
        "type": "string",
        "value": "main",
        "helper": "git仓库的分支,例如main",
        "hidden": true
    },
    {
        "name": "environment",
        "type": "list",
        "value": "dev,test,prod",
        "helper": "",
        "hidden": false
    }
],

而对于前端代码的渲染则多加个object.hidden的判断即可

属性修改

参数展示的Table我们通过Datatables插件渲染,foot里添加了个form表单,默认就从这里新建

<table class="table table-bordered" id="id_table_params" style="width: 100%; border-collapse: collapse !important;">
  <thead class="table-light">
  <tr>
    <th>参数名称</th>
    <th>参数类型</th>
    <th>参数值</th>
    <th>帮助信息</th>
    <th>隐藏</th>
    <th>操作</th>
  </tr>
  </thead>
  <tfoot>
  <tr>
    <td><input type="text" autocomplete="off" class="form-control" id="param-name" placeholder="参数名"></td>
    <td><select class="form-select col-auto" id="param-type">
      <option value="string">string</option>
      <option value="integer">integer</option>
      <option value="list">list</option>
    </select></td>
    <td><input type="text" autocomplete="off" class="form-control" id="param-value" placeholder="参数值,默认为空"></td>
    <td><input type="text" autocomplete="off" class="form-control" id="param-helper" placeholder="帮助说明"></td>
    <td><select class="form-select col-auto" id="param-hidden">
      <option value="是">是</option>
      <option value="否">否</option>
    </select></td>
    <td id="id-args-button"><button type="button" class="btn btn-info btn-sm" onclick="addParam()"><i class="fa fa-plus"></i> 添加</button></td>
  </tr>
  </tfoot>
</table>

当点击修改按钮时,则触发editParam函数将这行数据直接给填充进form表单,同时获取这行数据在datatables里的index,最后将原本的添加按钮给替换成保存

function editParam(btn) {
  var data = table_params.row($(btn).closest('tr')).data();

  $('#param-name').val(data[1]);
  $('#param-type').val(data[2]);
  $('#param-value').val(data[3]);
  $('#param-helper').val(data[4]);
  $('#param-hidden').val(data[5]);

  //获取btn所在的tr的rowIndex
  var rowIndex = table_params.row($(btn).closest('tr')).index();

  $('#id-args-button').html('<button type="button" class="btn btn-warning btn-sm" onclick="addParam('+ rowIndex +')"><i class="fa fa-save"></i> 保存</button>');
}

点击保存按钮则触发与新建同样的addParam函数,获取到form的数据,更新到表格中

function addParam(rowIndex=null) {
  if (! $('#param-name').val() ) {
    toastr.error("参数名不能为空");
  } else if (table_params.column(0).data().toArray().includes($('#param-name').val()) && rowIndex == null) {
    toastr.error("参数名不能相同");
  } else {
    tr_data = [
      rowIndex == null ? (table_params.rows().count() + 1) : (rowIndex + 1),
      $('#param-name').val(),
      $('#param-type').val(),
      $('#param-value').val(),
      $('#param-helper').val(),
      $('#param-hidden').val(),
      '<div class="btn-group" role="group" aria-label="Basic mixed styles example">' +
        '<button type="button" class="btn btn-warning btn-sm" onclick="editParam(this)"><i class="fa fa-edit"></i></button>' +
        '<button type="button" class="btn btn-danger btn-sm" onclick="deleteParam(this)"><i class="fa fa-trash"></i></button>' +
      '</div>'
    ]

    if (rowIndex === null) {
      table_params.row.add(tr_data).draw(false);
    } else {
      // 根据rowIndex更新tr
      table_params.row(rowIndex).data(tr_data).draw(false);
    }

    $("#param-name, #param-value, #param-helper").val("");
    $('#id-args-button').html('<button type="button" class="btn btn-info btn-sm" onclick="addParam()"><i class="fa fa-plus"></i> 添加</button>');
  }
}

参数修改也顺利完成

位置拖动

最后就是参数位置的修改了,对于参数位置修改,我想过加一个sort属性,通过修改sort来实现排序,但感觉比较麻烦,要用户自己来修改属性数据,并且数据结构还复杂了。并且列表本身就是有顺序的,加个sort必要性不大,后边搜了下datatables有一个RowReorder扩展,可以实现行级别的拖动排序

RowReorder用法看官方非常简单,就JS/CSS引入之后增加一个rowReorder:true的配置即可,但实际用下来却发现了不少问题,例如:

1.拖动仅能拖动一个单元格数据,而与单元格同行的数据位置不变

2.虽然能整行拖动了,但保存之后位置并没有更新

因此差点放弃,但总感觉不能拖动体验不完美,于是查了rowReorder的各种参数、文档以及datatables的论坛,终于发现了其中的奥秘,datatables使用rowReorder的前提是必须启用ordering排序,且第一列要是顺序数字编号,官方原话是:

The data point in the row that is modified is defined by the rowReorder.dataSrc. Normally you will want this to be a sequential number! The data reorder can potentially confuse end users otherwise!

英文不好或者理解不到的就会遇到上边提到的奇怪问题。知道了这两个前提,那在渲染数据时Table的第一列自动填充为行号Index,问题就顺利解决,可以随意拖动了

$.each(v, function (index, obj) {
  table_params.row.add([
      index + 1,
      obj.name.trim(),
      obj.type.trim(),
      obj.value.trim(),
      obj.helper.trim(),
      obj.hidden ? obj.hidden : '否',
      '<div class="btn-group" role="group" aria-label="Basic mixed styles example">' +
        '<button type="button" class="btn btn-warning btn-sm" onclick="editParam(this)"><i class="fa fa-edit"></i></button>' +
        '<button type="button" class="btn btn-danger btn-sm" onclick="deleteParam(this)"><i class="fa fa-trash"></i></button>' +
      '</div>'
  ]).draw(false);
})

最终的效果就是参数增加了隐藏属性同时也可以修改属性或者拖动位置重新排序了

几个都是使用体验方面的小问题,不阻塞功能使用,但体验不佳。影响用户体验是大问题,这将直接导致用户的使用情绪,体验好能增加信任,促进系统的使用,反之则可能影响到系统的落地。反复说过,这些功能或是系统本身没有什么意义,落地使用对大伙有帮助才是真的有意义

这么简单的内容为啥还要记录,第一是我自己需要整理,写出来能加深记忆,第二则是万一你就恰好需要呢