form表单和easyui datagrid表格行编辑 一并提交后台保存

在前端业务实现的时候,经常会遇到对表格进行操作。如果只是单表操作,对表格的增删改会在行编辑完成后立刻提交后台保存。但是当datagrid和form表单数据统合在一起作为一次业务操作,在表单提交之前就更改了关联表的数据,这种操作体验会很差。在操作中把表单数据和表格数据一起提交,在后台保存的时候一并处理——这时又会有另一个问题,关联表数据是否应该先做一次子表数据的删除,然后再保存提交的数据,这种做法的弊端会增加数据库的压力。所以在保存关联表的时候,我们只需要更新编辑过的字表操作不就行了吗?按照这种想法,下面是我在业务场景中的实现;

简述一下业务场景。我需要新增一个合同的模板,模板中包括表头数据(主表)和表明细数据(子表,一对多关系);如下图所示。当我们对子表数据进行操作的时候,给子表数据一个状态;初始、新增、编辑、删除。提交到后台的时候根据这个状态对数据做增删改。理念就是现在页面上操作,用户的操作也会根据“状态”直观的展示出来,提交后根据状态对字表数据做操作。

 页面样式的代码我就不展示了。主要展示js的代码和后台代码。我公司用的前端框架是 layer+easyui。我对表格操作是用的是easyui的方法,layer对逻辑无影响。所以我的前端代码里只展示引入easyui的js包。例如用到了layer.msg的方法,可替换成其它的提示框

<!-- 引入easyui样式 -->
<link rel="stylesheet" type="text/css" href="${ctx}/static/assets/easyui/themes/color.css">
<link rel="stylesheet" type="text/css"
          href="${ctx}/static/assets/easyui/themes/metro/easyui.css">
<link rel="stylesheet" type="text/css" href="${ctx}/static/assets/easyui/themes/icon.css">
<link rel="stylesheet" type="text/css" href="${ctx}/static/css/easyui_cover.css">

<!-- 创建table元素 -->
<table id="tb"></table>

<!-- 引入easyui的js -->
<script src="${ctx}/static/assets/easyui/jquery.easyui.min.js"></script>
<script src="${ctx}/static/assets/easyui/plugins/jquery.edatagrid.js"></script>
<script src="${ctx}/static/assets/easyui/locale/easyui-lang-zh_CN.js"></script>

在写代码之前让我先捋清楚行编辑的流程。表格新增 —> 新增一条数据(状态为’新增‘) —> 表格编辑 —> 编辑初始数据(状态为'编辑');编辑新增数据(状态为'新增') —> 表格删除(删除‘编辑’、‘初始’数据状态为'编辑';删除新增数据则直接删除) —> 取消删除(把数据状态改为‘编辑’) —> 取消行编辑(结束行编辑状态)。根据这个逻辑封装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 '<span style="color:blue;">新增</span>';
                        case 2:
                            return '<span style="color:green;">编辑</span>';
                        case 3:
                            return '<span style="color:red;">删除</span>';
                        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查参数内容,看看定义的属性和实体类定义的属性是否一致。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值