
2、这是拖动左边表格行数据的效果

3、拖动一行完成之后表格数据的效果

4、第二次、第三次拖动完成后效果


5、右边表格上面撤销操作点击效果

6、多次点击撤销,表格回到初始状态

二、代码示例
接到需求的第一感觉是应该上Bootstrap table api里面找一下,毕竟开源的力量是强大的,或许有相关的示例呢。经过一番查找,很可惜,Bootstrap Table没有这种两张表格之间的操作。想想其实也可以理解,Bootstrap Table是针对某个动态表格数据绑定的,它的侧重点是表格内部的功能,比如表格内部行的拖拽排序(Reorder Rows)有很好的解决方案,对于像博主这样的特殊需求,似乎也应该自己去实现。
1、需求分析
既然决定自己去写,开始分析需求,似乎这个操作里面比较困难的是拖拽效果,说到拖拽效果,原来使用JsPlumb的时候那使用太多了,于是就想到了我们神奇的JQuery UI里面的draggable.js 和droppable.js。拖拽的问题解决了,那么还有一个难点,就是撤销操作怎么办?我们知道Ctrl+z的意思是还原,什么叫还原?就是返回到上一步的操作,那么前提是要能够保存上一步的状态,说到保存某一步的状态,博主就知道怎么做了,需要一个全局变量Json,里面要有三个键值对,分别是当前步骤的索引、左边表格的数据、右边表格的数据。似乎也不太难嘛,就此着手,开干。
2、代码示例
2.1 cshtml页面代码
<html><head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> @Styles.Render("~/Content/css") @Styles.Render("~/Content/table-css") @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/knockout") @Scripts.Render("~/bundles/bootstrap") @Scripts.Render("~/bundles/bootstrap-table") @RenderSection("Scripts", false)</head><body> @RenderBody()</body></html> @{ ViewBag.Title = "订单插单"; Layout = "~/Views/Shared/_Layout.cshtml";}@Scripts.Render("~/bundles/Order/InsertOrder")@Styles.Render("~/bundles/Order/css")@Scripts.Render("~/Content/bootstrap/datepicker/js")@Styles.Render("~/Content/bootstrap/datepicker/css")<script src="~/Content/jquery-ui-1.11.4.custom/jquery-ui.min.js"></script><div class="panel-body" style="padding-bottom:0px;"><div class="panel panel-default" style="margin-bottom:0px;"><div class="panel-heading">查询条件</div><div class="panel-body container-fluid"><div class="row"> <div class="col-md-3"> <label for="txt_search_ordernumber" class="col-sm-4 control-label" style="margin-top:6px;">订单号</label> <span class="col-sm-8"><input type="text" class="form-control" id="txt_search_ordernumber"> </span> </div> <div class="col-md-3"> <label for="txt_search_bodynumber" class="col-sm-3 control-label" style="margin-top:6px;">车身号</label> <span class="col-sm-8"><input type="text" class="form-control" id="txt_search_bodynumber"> </span> </div> <div class="col-md-3"> <label for="txt_search_vinnumber" class="col-sm-4 control-label" style="margin-top:6px;">VIN码</label> <span class="col-sm-8"><input type="text" class="form-control" id="txt_search_vinnumber"> </span> </div> <div class="col-md-3"> <label for="txt_search_engin_code" class="col-sm-4 control-label" style="margin-top:6px;">发动机号</label> <span class="col-sm-8"><input type="text" class="form-control" id="txt_search_engin_code"> </span> </div></div><div class="collapse" id="div_more_search"> <div class="row" style="margin-top:15px;"> <div class="col-md-3"><label for="txt_search_import_startdate" class="col-sm-4 control-label" style="margin-top:6px;">导入时间</label><span class="col-sm-8"><input type="text" class="form-control datetimepicker" readonly id="txt_search_import_startdate"></span> </div> <div class="col-md-3"><label for="txt_search_import_enddate" class="col-sm-3 control-label" style="margin-top:6px;">至</label><span class="col-sm-8"><input type="text" class="form-control datetimepicker" readonly id="txt_search_import_enddate"></span> </div> <div class="col-md-3"><label for="txt_search_send_startdate" class="col-sm-4 control-label" style="margin-top:6px;">下发时间</label><span class="col-sm-8"><input type="text" class="form-control datetimepicker" readonly id="txt_search_send_startdate"></span> </div> <div class="col-md-3"><label for="txt_search_send_enddate" class="col-sm-4 control-label" style="margin-top:6px;">至</label><span class="col-sm-8"><input type="text" class="form-control datetimepicker" readonly id="txt_search_send_enddate"></span> </div> </div> <div class="row" style="margin-top:15px;"> <div class="col-md-3"><label for="txt_search_carcode" class="col-sm-4 control-label" style="margin-top:6px;">整车编码</label><span class="col-sm-8"><input type="text" class="form-control" id="txt_search_carcode"></span> </div> <div class="col-md-3"><label for="txt_search_vms" class="col-sm-3 control-label" style="margin-top:6px;">VMS号</label><span class="col-sm-8"><input type="text" class="form-control" id="txt_search_vms"></span> </div> <div class="col-md-3"><label for="txt_search_trans_code" class="col-sm-4 control-label" style="margin-top:6px;">变速箱号</label><span class="col-sm-8"><input type="text" class="form-control" id="txt_search_trans_code"></span> </div> </div></div> <div class="row" style="float:right;margin-right:50px;margin-top:13px;"> <div><button type="button" id="btn_query" class="btn btn-primary" style="margin-right:20px;width:100px;">查询</button><button type="submit" id="btn_reset" class="btn btn-default" style="margin-right:20px;width:100px;">重置</button> </div> </div></div> </div> <div class="collapse_div_outside"> <div class="collapse_div_inside"></div> <span id="span_collapse" href="#div_more_search" class="collapse_div_inside_ele">展开<label class="glyphicon glyphicon-menu-down"></label></span> </div></div>@*<div id="toolbar_left" class="btn-group"></div>*@<div id="toolbar_right" class="btn-group"> <button id="btn_cancel" type="button" class="btn btn-default"> <span class="glyphicon glyphicon-backward aria-hidden="true"></span>撤销 </button> <button id="btn_insertorder" type="button" class="btn btn-default"> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>插单 </button></div><div class="panel-body" style="padding-top:0px;"> <div id="div_tableleft" class="col-md-6"> <table id="tb_order_left"></table> </div> <div id="div_tableright" class="col-md-6"> <table id="tb_order_right"></table> </div></div>2.2 js代码var i_statuindex = 0;//此数组用于保存撤销操作每一步的数据var arrdata = [];var m_oTable = null;$(function () { //1.初始化表格 m_oTable = new TableInit(); m_oTable.Init(); //2.初始化按钮事件 var oButtonInit = new ButtonInit(); oButtonInit.Init(); //3.日期控件的初始化 $(".datetimepicker").datetimepicker({ format: "yyyy-mm-dd hh:ii", autoclose: true, todayBtn: true, });});//表格相关事件和方法var TableInit = function () { var oTableInit = new Object(); oTableInit.Init = function () { //初始化左边表格 $("#tb_order_left").bootstrapTable({url: "/api/OrderApi/get",method: "get",striped: true,cache: false,striped: true,pagination: true,height: 600,uniqueId:"TO_ORDER_ID",queryParams: oTableInit.queryParams,queryParamsType: "limit",sidePagination: "server",pageSize: 10,pageList: [10, 25, 50, 100],search: true,strictSearch: true,showColumns: true,showRefresh: true,minimumCountColumns: 2,clickToSelect: true,columns: [{checkbox: true},{field: "ORDER_NO",title: "订单号"},{field: "BODY_NO",title: "车身号"}, {field: "VIN",title: "VIN码"}, {field: "TM_MODEL_MATERIAL_ID",title: "整车编码"},{field: "ORDER_TYPE",title: "订单类型"},{field: "ORDER_STATUS",title: "订单状态"},{field: "CREATE_DATE",title: "订单导入时间"},{field: "PLAN_DATE",title: "订单计划上线日期"},{field: "VMS_NO",title: "VMS号"},{field: "ENGIN_CODE",title: "发动机号"},{field: "TRANS_CODE",title: "变速箱号"},{field: "OFFLINE_DATE_ACT",title: "实际下线日期"},{field: "HOLD_RES",title: "hold理由"},{field: "SPC_FLAG",title: "特殊标记"},],onLoadSuccess: function (data) { //表格加载完成之后初始化拖拽 oTableInit.InitDrag();} }); //初始化右边表格 $("#tb_order_right").bootstrapTable({url: "/api/OrderApi/get",method: "get",toolbar: "#toolbar_right",striped: true,cache: false,striped: true,pagination: true,height: 600,queryParams: oTableInit.queryParamsRight,queryParamsType: "limit",//ajaxOptions: { departmentname: "", statu: "" },sidePagination: "server",pageSize: 10,pageList: [10, 25, 50, 100],search: true,strictSearch: true,showRefresh: true,minimumCountColumns: 2,columns: [{field: "ORDER_NO",title: "订单号"},{field: "BODY_NO",title: "车身号"}, {field: "VIN",title: "VIN码"}, {field: "TM_MODEL_MATERIAL_ID",title: "整车编码"},{field: "ORDER_TYPE",title: "订单类型"},{field: "ORDER_STATUS",title: "订单状态"},{field: "CREATE_DATE",title: "订单导入时间"},{field: "PLAN_DATE",title: "订单计划上线日期"},{field: "VMS_NO",title: "VMS号"},{field: "ENGIN_CODE",title: "发动机号"},{field: "TRANS_CODE",title: "变速箱号"},{field: "OFFLINE_DATE_ACT",title: "实际下线日期"},{field: "HOLD_RES",title: "hold理由"},{field: "SPC_FLAG",title: "特殊标记"},],onLoadSuccess: function (data) {oTableInit.InitDrop();} }); }; //注册表格行的draggable事件 oTableInit.InitDrag = function () { $("#tb_order_left tr").draggable({helper: "clone",start: function (event, ui) {var old_left_data = JSON.stringify($("#tb_order_left").bootstrapTable("getData"));var old_right_data = JSON.stringify($("#tb_order_right").bootstrapTable("getData"));var odata = { index: ++i_statuindex, left_data: old_left_data, right_data: old_right_data };arrdata.push(odata);},stop: function (event, ui) {} }); }; //注册右边表格的droppable事件 oTableInit.InitDrop = function () { $("#tb_order_right").droppable({drop: function (event, ui) {var arrtd = $(ui.helper[0]).find("td");var rowdata = { ORDER_NO: $(arrtd[1]).text(), BODY_NO: $(arrtd[2]).text(), VIN: $(arrtd[3]).text(), TM_MODEL_MATERIAL_ID: $(arrtd[4]).text(), ORDER_TYPE: $(arrtd[5]).text(), ORDER_STATUS: $(arrtd[6]).text(), CREATE_DATE: $(arrtd[7]).text() == "-" ? null : $(arrtd[7]).text(), PLAN_DATE: $(arrtd[8]).text() == "-" ? null : $(arrtd[8]).text(), VMS_NO: $(arrtd[9]).text(), ENGIN_CODE: $(arrtd[10]).text(), TRANS_CODE: $(arrtd[11]).text(), OFFLINE_DATE_ACT: $(arrtd[12]).text() == "-" ? null : $(arrtd[12]).text(), HOLD_RES: $(arrtd[13]).text(), SPC_FLAG: $(arrtd[14]).text(), TO_ORDER_ID: $(ui.helper[0]).attr("data-uniqueid")};var oTop = ui.helper[0].offsetTop;var iRowHeadHeight = 40;var iRowHeight = 37;var rowIndex = 0;if (oTop <= iRowHeadHeight + iRowHeight / 2) { rowIndex = 0;}else { rowIndex = Math.ceil((oTop - iRowHeadHeight) / iRowHeight);} //插入右边表格指定位置行数据$("#tb_order_right").bootstrapTable("insertRow", { index: rowIndex, row: rowdata });$("#tb_order_left").bootstrapTable("removeByUniqueId", $(ui.helper[0]).attr("data-uniqueid"));oTableInit.InitDrag();} }); }; oTableInit.queryParams = function (params) { //配置参数 var temp = { //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的limit: params.limit, //页面大小offset: params.offset, //页码strBodyno: $("#txt_search_bodynumber").val(),strVin: $("#txt_search_vinnumber").val(),strOrderno: $("#txt_search_ordernumber").val(),strEngincode: $("#txt_search_engin_code").val(),strOrderstatus: 0,strTranscode: $("#txt_search_trans_code").val(),strVms: $("#txt_search_vms").val(),strCarcode: $("#txt_search_carcode").val(),strImportStartdate: $("#txt_search_import_startdate").val(),strImportEnddate: $("#txt_search_import_enddate").val(),strSendStartdate: $("#txt_search_send_startdate").val(),strSendEnddate: $("#txt_search_send_enddate").val(), }; return temp; }; oTableInit.queryParamsRight = function (params) { //配置参数 var temp = { //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的limit: params.limit, //页面大小offset: params.offset, //页码strBodyno: "",strVin: "",strOrderno: "",strEngincode: "",strOrderstatus: 5,strTranscode: "",strVms: "",strCarcode: "",strImportStartdate: "",strImportEnddate: "",strSendStartdate: "",strSendEnddate: "", }; return temp; }; return oTableInit;};//页面按钮初始化事件var ButtonInit = function () { var oInit = new Object(); var postdata = {}; oInit.Init = function () { //查询点击事件 $("#btn_query").click(function () {$("#tb_order_left").bootstrapTable("refresh"); }); //重置点击事件 $("#btn_reset").click(function () {$(".container-fluid").find(".form-control").val("");$("#tb_order_left").bootstrapTable("refresh"); }); //撤销操作点击事件 $("#btn_cancel").click(function () {if (i_statuindex <= 0) {return;}for (var i = 0; i < arrdata.length; i++) {if (arrdata[i].index != i_statuindex) { continue;}var arr_left_data = eval(arrdata[i].left_data);var arr_right_data = eval(arrdata[i].right_data);$("#tb_order_left").bootstrapTable("removeAll");$("#tb_order_right").bootstrapTable("removeAll");$("#tb_order_left").bootstrapTable("append", arr_left_data);for (var x = 0; x < arr_right_data.length; x++) { $("#tb_order_right").bootstrapTable("insertRow", { index: x, row: arr_right_data[x] });}//$("#tb_order_right").bootstrapTable("append", arr_right_data);//append之后不能dropbreak;}i_statuindex--;//重新注册可拖拽m_oTable.InitDrag();//m_oTable.InitDrop(); }); //搜索栏展开收起点击事件 $("#span_collapse").click(function () {if ($(this).text() == "收起") {$(this).html("展开<label class="glyphicon glyphicon-menu-down"></label>");$("#div_more_search").collapse("hide");}else {$(this).html("收起<label class="glyphicon glyphicon-menu-up"></label>");$("#div_more_search").collapse("show")} }); }; return oInit;};我们重点来看几个地方的代码:$("#tb_order_left tr").draggable({helper: "clone",start: function (event, ui) {var old_left_data = JSON.stringify($("#tb_order_left").bootstrapTable("getData"));var old_right_data = JSON.stringify($("#tb_order_right").bootstrapTable("getData"));var odata = { index: ++i_statuindex, left_data: old_left_data, right_data: old_right_data };arrdata.push(odata);},stop: function (event, ui) {} });在draggable的start事件中,我们将拖拽之前的左右表格中的数据全部保存到arrdata变量中,i_statuindex这个全局变量用于记录当前这一步的索引,用于撤销操作。$("#tb_order_right").droppable({ drop: function (event, ui) { var arrtd = $(ui.helper[0]).find("td"); var rowdata = { ORDER_NO: $(arrtd[1]).text(), BODY_NO: $(arrtd[2]).text(), VIN: $(arrtd[3]).text(), TM_MODEL_MATERIAL_ID: $(arrtd[4]).text(), ORDER_TYPE: $(arrtd[5]).text(), ORDER_STATUS: $(arrtd[6]).text(), CREATE_DATE: $(arrtd[7]).text() == "-" ? null : $(arrtd[7]).text(), PLAN_DATE: $(arrtd[8]).text() == "-" ? null : $(arrtd[8]).text(), VMS_NO: $(arrtd[9]).text(), ENGIN_CODE: $(arrtd[10]).text(), TRANS_CODE: $(arrtd[11]).text(), OFFLINE_DATE_ACT: $(arrtd[12]).text() == "-" ? null : $(arrtd[12]).text(), HOLD_RES: $(arrtd[13]).text(), SPC_FLAG: $(arrtd[14]).text(), TO_ORDER_ID: $(ui.helper[0]).attr("data-uniqueid") }; var oTop = ui.helper[0].offsetTop; var iRowHeadHeight = 40; var iRowHeight = 37; var rowIndex = 0; if (oTop <= iRowHeadHeight + iRowHeight / 2) { rowIndex = 0; } else { rowIndex = Math.ceil((oTop - iRowHeadHeight) / iRowHeight); } $("#tb_order_right").bootstrapTable("insertRow", { index: rowIndex, row: rowdata }); $("#tb_order_left").bootstrapTable("removeByUniqueId", $(ui.helper[0]).attr("data-uniqueid")); oTableInit.InitDrag(); } });在drop事件时,取到当前拖过来的行数据,计算当前鼠标所在的位置,在右边表格指定位置插入拖过来的行数据。然后删除左边表格拖过来的行数据。//撤销操作点击事件 $("#btn_cancel").click(function () { if (i_statuindex <= 0) { return; } for (var i = 0; i < arrdata.length; i++) { if (arrdata[i].index != i_statuindex) { continue; } var arr_left_data = eval(arrdata[i].left_data); var arr_right_data = eval(arrdata[i].right_data); $("#tb_order_left").bootstrapTable("removeAll"); $("#tb_order_right").bootstrapTable("removeAll"); $("#tb_order_left").bootstrapTable("append", arr_left_data); for (var x = 0; x < arr_right_data.length; x++) { $("#tb_order_right").bootstrapTable("insertRow", { index: x, row: arr_right_data[x] }); } //$("#tb_order_right").bootstrapTable("append", arr_right_data);//append之后不能drop break; } i_statuindex--; //重写注册可拖拽 m_oTable.InitDrag(); });撤销操作主要是通过全局变量arrdata里面的索引判断撤销到哪一步,然后根据索引取出当前步骤的左右表格数据,依次向两表格插入数据,然后i_statuindex依次递减,直至等于零,由于左边表格行数据全部重写加载过,所以需要重新注册可拖拽事件。就是这么简单的三步就能实现想要的效果,是不是很简单~~