一、業務需求及實現效果
項目涉及到訂單模塊,那天突然接到一個需求,說是兩種不同狀態的訂單之間要實現插單的效果,頁面上呈現方式是:左右兩個Table,左邊Table裡面是狀態為1的訂單,右邊Table裡面是狀態為2訂單,左邊Table裡面的行數據拖動到右邊Table裡面指定行的位置,拖動完成後,左邊表格減少一行,右邊表格增加一行。除此之外,還需要撤銷操作(相當於Ctrl + Z操作),能夠返回到上一步的狀態。可能描述會讓大家模擬兩可,反正已經實現了,先來看看效果圖吧。
1、先看看拖動之前的效果

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之後不能drop
break;
}
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;
};
我們重點來看幾個地方的代碼:
2.2.1 左邊表格加載成功之後執行表格行的可拖拽。
$('#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這個全局變量用於記錄當前這一步的索引,用於撤銷操作。
2.2.2 右邊表格在加載成功之後注冊表格的droppable事件
$("#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事件時,取到當前拖過來的行數據,計算當前鼠標所在的位置,在右邊表格指定位置插入拖過來的行數據。然後刪除左邊表格拖過來的行數據。
2.2.3 撤銷操作代碼
//撤銷操作點擊事件
$("#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依次遞減,直至等於零,由於左邊表格行數據全部重寫加載過,所以需要重新注冊可拖拽事件。就是這麼簡單的三步就能實現想要的效果,是不是很簡單~~
如果大家還想深入學習,可以點擊這裡進行學習,再為大家附兩個精彩的專題:Bootstrap學習教程 Bootstrap實戰教程
以上就是本文的全部內容,希望對大家的學習有所幫助。