在前端业务实现的时候,经常会遇到对表格进行操作。如果只是单表操作,对表格的增删改会在行编辑完成后立刻提交后台保存。但是当datagrid和form表单数据统合在一起作为一次业务操作,在表单提交之前就更改了关联表的数据,这种操作体验会很差。在操作中把表单数据和表格数据一起提交,在后台保存的时候一并处理——这时又会有另一个问题,关联表数据是否应该先做一次子表数据的删除,然后再保存提交的数据,这种做法的弊端会增加数据库的压力。所以在保存关联表的时候,我们只需要更新编辑过的字表操作不就行了吗?按照这种想法,下面是我在业务场景中的实现;
简述一下业务场景。我需要新增一个合同的模板,模板中包括表头数据(主表)和表明细数据(子表,一对多关系);如下图所示。当我们对子表数据进行操作的时候,给子表数据一个状态;初始、新增、编辑、删除。提交到后台的时候根据这个状态对数据做增删改。理念就是现在页面上操作,用户的操作也会根据“状态”直观的展示出来,提交后根据状态对字表数据做操作。
页面样式的代码我就不展示了。主要展示js的代码和后台代码。我公司用的前端框架是 layer+easyui。我对表格操作是用的是easyui的方法,layer对逻辑无影响。所以我的前端代码里只展示引入easyui的js包。例如用到了layer.msg的方法,可替换成其它的提示框
在写代码之前让我先捋清楚行编辑的流程。表格新增 —> 新增一条数据(状态为’新增‘) —> 表格编辑 —> 编辑初始数据(状态为'编辑');编辑新增数据(状态为'新增') —> 表格删除(删除‘编辑’、‘初始’数据状态为'编辑';删除新增数据则直接删除) —> 取消删除(把数据状态改为‘编辑’) —> 取消行编辑(结束行编辑状态)。根据这个逻辑封装js方法。 type枚举值: 1.新增 2.编辑 3.删除 0|null 初始
/**
* 表格删除取消
* @param tb 表格el
*/
function tableCancel(tb) {
let row = $(tb).datagrid('getSelected');
if (row) {
let index = $(tb).datagrid('getRowIndex', row);
if (row.type && row.type === 3) {
$(tb).datagrid('updateRow', {index: index, row: {type: 2}});
}
}
}
/**
* 表格开始编辑
* @param editParam 表格变更参数,传 属性名称,不要传值!!!
* @param index 索引
* @param row 行数据
*/
function tableBeginEdit(editParam, index, row) {
let tb = editTb[editParam]['el'];
let editIndex = editTb[editParam]['index'];
if (row.type === 3) {
return -1;
}
//编辑索引和当前索引判断,不一致,需要把之前的编辑行结束编辑
if (editIndex !== index) {
if (editIndex !== -1) {
//之前行的填写是否通过校验,不通过则取消、删除
let flag = $(tb).datagrid('validateRow', editIndex);
if (flag) {
$(tb).datagrid('endEdit', editIndex);
let kRow = $(tb).datagrid('getRows')[editIndex];
//结束之前的行编辑,并且把行状态改为 编辑
if (!kRow.type) {
$(tb).datagrid('updateRow', {index: editIndex, row: {type: 2}});
}
} else {
layer.msg('检验不通过,取消行编辑数据');
let type = $(tb).datagrid('getRows')[editIndex].type;
if (type && type === 1) {
$(tb).datagrid('deleteRow', editIndex);
//如果是删除行,那么判断删除的行会不会导致当前行的索引前移
if (editIndex < index) {
index--;
}
} else {
$(tb).datagrid('cancelEdit', editIndex);
}
}
}
}
editTb[editParam]['index'] = index;
$(tb).datagrid('beginEdit', index);
}
/**
* 新增行,并触发行编辑事件
* @param editParam 表格变更参数,传 属性名称,不要传值!!!
*/
function tableAddRow(editParam) {
let $tb = $(editTb[editParam]['el']);
let row = {type: 1};
$tb.datagrid('appendRow', row);
let index = $tb.datagrid('getRows').length - 1;
tableBeginEdit(editParam, index, row);
}
/**
* 表格删除行
* @param editParam 表格变更参数,传 属性名称,不要传值!!!
*/
function tableDelete(editParam) {
let tb = $(editTb[editParam]['el']);
let editIndex = editTb[editParam]['index'];
let row = tb.datagrid('getSelected');
if (row) {
let index = tb.datagrid('getRowIndex', row);
if (row.type && row.type === 1) {
tb.datagrid('deleteRow', index);
//删除行的时候,要把编辑行的索引更新
if (index < editIndex) {
editTb['index']--;
} else if (index === editIndex) {
editTb['index'] = -1;
}
} else {
tb.datagrid('updateRow', {index: index, row: {type: 3}});
}
}
tb.datagrid('clearSelections');
}
/**
* 表格关闭行编辑
* @param editParam 表格变更参数,传 属性名称,不要传值!!!
*/
function tableEndEdit(editParam) {
let editIndex = editTb[editParam]['index'];
if (editIndex > -1) {
let $tb = $(editTb[editParam]['el']);
let flag = $tb.datagrid('validateRow', editIndex);
if (flag) {
$tb.datagrid('endEdit', editIndex);
let kRow = $tb.datagrid('getRows')[editIndex];
//结束之前的行编辑,并且把行状态改为 编辑
if (!kRow.type) {
$tb.datagrid('updateRow', {index: editIndex, row: {type: 2}});
}
} else {
layer.msg('检验不通过,取消行编辑数据');
let type = $tb.datagrid('getRows')[editIndex].type;
if (type && type === 1) {
$tb.datagrid('deleteRow', editIndex);
} else {
$tb.datagrid('cancelEdit', editIndex);
}
}
}
editTb[editParam]['index'] = -1;
}
方法已经定义好了。下面贴出完整的js代码
/**
* form表单提交和datagrid表格行编辑
* @Author NZhang
* @Data 2020-5-9
*/
//定义dom元素
let el = {
tb: '#tb',//表格
id: '#id',//主表的id,编辑时才会传值
form: '#form' //form表单元素
};
//定义表格行编辑传参
let editTb = {
productType: {el: '#tb', index: -1}
};
//定义全局变量
let selfData = {
height: 150,
invitationId: '',
url: '/supplier/product/type/template/rest/grid',
evalTemplateTypeUrl: '/admittance/target/template/list/all'
};
$(function () {
selfData.invitationId = $(el.id).val();
let height = document.documentElement.clientHeight - 190;
selfData.height = height > 200 ? height : 150;
loadGrid();
});
/**
* 加载表格
*/
function loadGrid() {
$(el.tb).datagrid({
url: selfData.url,
iconCls: 'icon-list',
height: selfData.height,
singleSelect: true,
autoHeight: true,
loadMsg: "数据加载中......",
rownumbers: true,
fitColumns: true,
nowrap: true,
remoteSort: true,
striped: true,
queryParams: {
id: selfData.invitationId
},
columns: [[
{
field: 'state',
title: '类型',
width: '6%',
align: 'left',
formatter: function (value) {
switch (value) {
case 1:
return '新增';
case 2:
return '编辑';
case 3:
return '删除';
default:
return '初始';
}
}
},
{
field: 'evalTemplateType',
title: '模板类型',
width: '25%',
align: 'left',
formatter: function (value, row) {
return row.evalTemplateTypeName;
},
editor: {
type: 'combobox',
options: {
required: true,
valueField: 'dicId',
textField: 'dicTxt',
panelHeight: 'auto',
editable: true,
multiple: false,
prompt: '-- 请选择 --',
data: [{dicId: '1', dicTxt: 'A类型'}, {dicId: "2", dicTxt: "B类型"}],
onSelect: function (data) {
let index = editTb['productType']['index'];
let row = $(el.tb).datagrid('getRows')[index];
row.evalTemplateTypeName = data.dicTxt;
let evalTemplate = $(el.tb).datagrid('getEditor', {
index: index,
field: 'evalTemplateId'
}).target;
evalTemplate.combobox('clear');
evalTemplate.combobox('options').prompt = '请选择';
evalTemplate.combobox('options').queryParams = {
type: 2,
templateType: data.dicId,
allowMaterialCategoryNull: 1
};
evalTemplate.combobox('options').url = selfData.evalTemplateTypeUrl;
evalTemplate.combobox('reload');
}
}
}
},
{
field: 'evalTemplateId',
title: '注册模板',
width: '35%',
align: 'left',
formatter: function (value, row) {
return row.evalTemplateName;
},
editor: {
type: 'combobox',
options: {
required: true,
valueField: 'id',
textField: 'templateName',
panelHeight: '200',
prompt: '请先选择模板类型',
onLoadSuccess: function (data) {
let index = editTb['productType']['index'];
let row = $(el.tb).datagrid('getRows')[index];
let comBox = $(this);
data.some(function (item) {
if (item.id && item.id === row.evalTemplateId) {
comBox.combobox('setValue', row.evalTemplateId);
return true;
}
});
},
onSelect: function (data) {
let index = editTb['productType']['index'];
let row = $(el.tb).datagrid('getRows')[index];
row.evalTemplateName = data.templateName;
}
}
}
},
{
field: 'finishDate',
title: '结束时间',
width: '20%',
align: 'left',
formatter: function (value) {
return moment(value).format("YYYY-MM-DD");
},
editor: {
type: 'datebox',
options: {
required: true,
parser: function (value) {
if (value) {
return new Date(value);
} else {
return new Date();
}
},
// 验证选择时间 <= 今天
onShowPanel: function () {
$(this).datebox('calendar').calendar({
validator: function (date) {
let now = new Date();
now = now.setDate(now.getDate() - 1);
now = new Date(now);
return date > now;
}
});
}
}
}
}
]],
toolbar: [
{
text: '新增',
iconCls: 'icon-add',
handler: function () {
tableAddRow('productType');
}
},
{
text: '删除',
iconCls: 'icon-remove',
handler: function () {
tableDelete('productType');
}
},
{
text: '取消删除',
iconCls: 'icon-ok',
handler: function () {
tableCancel(editTb['productType']['el']);
}
},
{
text: '结束行编辑',
iconCls: 'icon-book',
handler: function () {
tableEndEdit('productType');
}
}],
onDblClickRow: function (index, row) {
tableBeginEdit('productType', index, row);
}
});
}
/**
* 表单保存提交
*/
function submitForm() {
if ($(el.form).form('validate')) {
//点击提交之前先把所有的表格编辑关闭,不然编辑行的数据读不到
tableEndEdit('productType');
let formData = $(el.form).serializeJson();
formData.productTypeList = getGridRows(el.tb);
//在传到后台前,可以在此处console一下,查看传递回去的数据
layer.load(1);
$.ajax({
url: '/supplier/invitation/rest/save',
method: "post",
data: {supplierInvitaion: JSON.stringify(formData)},
success: function () {
layer.closeAll('loading');
layer.msg('保存成功');
parent.frames['/supplier/invitation/index'].closeDetailWin();
},
error: function (data) {
layer.closeAll('loading');
let obj = data.responseText;
layer.msg(JSON.parse(obj).message);
}
});
}
}
/**
* 表格删除取消
* @param tb 表格el
*/
function tableCancel(tb) {
let row = $(tb).datagrid('getSelected');
if (row) {
let index = $(tb).datagrid('getRowIndex', row);
if (row.type && row.type === 3) {
$(tb).datagrid('updateRow', {index: index, row: {type: 2}});
}
}
}
/**
* 表格开始编辑
* @param editParam 表格变更参数,传 属性名称,不要传值!!!
* @param index 索引
* @param row 行数据
*/
function tableBeginEdit(editParam, index, row) {
let tb = editTb[editParam]['el'];
let editIndex = editTb[editParam]['index'];
if (row.type === 3) {
return -1;
}
//编辑索引和当前索引判断,不一致,需要把之前的编辑行结束编辑
if (editIndex !== index) {
if (editIndex !== -1) {
//之前行的填写是否通过校验,不通过则取消、删除
let flag = $(tb).datagrid('validateRow', editIndex);
if (flag) {
$(tb).datagrid('endEdit', editIndex);
let kRow = $(tb).datagrid('getRows')[editIndex];
//结束之前的行编辑,并且把行状态改为 编辑
if (!kRow.type) {
$(tb).datagrid('updateRow', {index: editIndex, row: {type: 2}});
}
} else {
layer.msg('检验不通过,取消行编辑数据');
let type = $(tb).datagrid('getRows')[editIndex].type;
if (type && type === 1) {
$(tb).datagrid('deleteRow', editIndex);
//如果是删除行,那么判断删除的行会不会导致当前行的索引前移
if (editIndex < index) {
index--;
}
} else {
$(tb).datagrid('cancelEdit', editIndex);
}
}
}
}
editTb[editParam]['index'] = index;
$(tb).datagrid('beginEdit', index);
}
/**
* 新增行,并触发行编辑事件
* @param editParam 表格变更参数,传 属性名称,不要传值!!!
*/
function tableAddRow(editParam) {
let $tb = $(editTb[editParam]['el']);
let row = {type: 1};
$tb.datagrid('appendRow', row);
let index = $tb.datagrid('getRows').length - 1;
tableBeginEdit(editParam, index, row);
}
/**
* 表格删除行
* @param editParam 表格变更参数,传 属性名称,不要传值!!!
*/
function tableDelete(editParam) {
let tb = $(editTb[editParam]['el']);
let editIndex = editTb[editParam]['index'];
let row = tb.datagrid('getSelected');
if (row) {
let index = tb.datagrid('getRowIndex', row);
if (row.type && row.type === 1) {
tb.datagrid('deleteRow', index);
//删除行的时候,要把编辑行的索引更新
if (index < editIndex) {
editTb['index']--;
} else if (index === editIndex) {
editTb['index'] = -1;
}
} else {
tb.datagrid('updateRow', {index: index, row: {type: 3}});
}
}
tb.datagrid('clearSelections');
}
/**
* 表格关闭行编辑
* @param editParam 表格变更参数,传 属性名称,不要传值!!!
*/
function tableEndEdit(editParam) {
let editIndex = editTb[editParam]['index'];
if (editIndex > -1) {
let $tb = $(editTb[editParam]['el']);
let flag = $tb.datagrid('validateRow', editIndex);
if (flag) {
$tb.datagrid('endEdit', editIndex);
let kRow = $tb.datagrid('getRows')[editIndex];
//结束之前的行编辑,并且把行状态改为 编辑
if (!kRow.type) {
$tb.datagrid('updateRow', {index: editIndex, row: {type: 2}});
}
} else {
layer.msg('检验不通过,取消行编辑数据');
let type = $tb.datagrid('getRows')[editIndex].type;
if (type && type === 1) {
$tb.datagrid('deleteRow', editIndex);
} else {
$tb.datagrid('cancelEdit', editIndex);
}
}
}
editTb[editParam]['index'] = -1;
}
/**
* 获取表格中的数据。
* @param elTb 表格el
*/
function getGridRows(elTb) {
let list = [];
if ($(elTb).length > 0) {
let rows = $(elTb).datagrid('getRows');
if (rows.length > 0) {
rows.some(function (item) {
//在此处写过滤数据的方法。提取出需要处理的数据
list.push(item)
})
}
}
return list;
}
以上就是form表单和datagird行编辑保存策略的全部js代码了。从代码量上看其实是很少的。其核心代码逻辑就是用type去标识行编辑的新增、编辑、删除,也就是表格的四个按钮的逻辑,其中行编辑的策略我粗略的带过了。在后台接收这些数据的时候,根据type字段便可以进行对应的处理了。第一次写博客,感觉写的有点乱,一段代码中牵扯的逻辑和各种方法的封装太多了,所以后台的代码就不贴了。如果后台数据映射不上,在提交方法中用console 打印formdata查参数内容,看看定义的属性和实体类定义的属性是否一致。