AJAX加载的jQuery DataTables行选择与行转移功能实现求助
问题与解决方案
问题场景
开发基于C#的ASP.NET Core MVC项目,页面包含两个横向排列的jQuery DataTables(Exclude表和Include表),中间有左右箭头按钮。需求是:选中Exclude表的单行/多行后点击右箭头,将选中行转移到Include表;选中Include表的行后点击左箭头,转移回Exclude表。当前两表通过AJAX加载数据,模型一致,但无法实现行选择和转移功能。
解决方案步骤
1. 修复DataTables行选择功能
原代码中实例化DataTables时错误使用了new关键字,导致表格选择功能失效,同时需要保存表格实例以便后续操作:
- 去掉
new关键字,直接通过$("#tableId").DataTable()初始化 - 将表格实例存储在全局变量中,方便后续获取选中行
2. 实现前端行转移逻辑
给左右按钮添加点击事件,获取选中行数据,通过AJAX提交到后端接口完成数据转移,之后重新加载两个表格刷新数据。
3. 后端添加数据转移接口
由于使用ServerSide模式,需要新增接口处理行转移的业务逻辑,更新两个数据源(实际项目中替换为数据库操作),并返回操作结果。
修改后的完整代码
Index.cshtml
@model RowTransfer.Models.DoorDetails @using System.Data @{ ViewData["Title"] = "Index"; } <script src="~/js/jquery-2.2.4.min.js"></script> <script src="~/js/jquery.datatables.min.js"></script> <script type="text/javascript"> // 全局存储表格实例 var excludeTable, includeTable; $(document).ready(function () { LoadGridExclude(); LoadGridInclude(); // 绑定按钮点击事件 $("#btnRight").click(moveToInclude); $("#btnLeft").click(moveToExclude); }); function LoadGridExclude() { excludeTable = $("#excludeTable").DataTable({ stripeClasses: [], select: { style: 'multi' }, ordering: true, paging: false, searching: false, serverSide: true, filter: true, searchDelay: 1000, scrollCollapse: true, ajax: { url: '@Url.Action("LoadExclude", "Home")', type: 'GET', datatype: 'json', headers: { 'RequestVerificationToken': '@Html.AntiForgeryToken()' }, data: (d) => { return { draw: d.draw, start: d.start, length: d.length, search: d.search.value, FilterByColumn: d.columns[d.order[0].column].data, ASC_DSEC: d.order[0].dir } }, dataSrc: (json) => { return json.data; } }, columnDefs: [{ className: "dt-center", targets: [1, 2, 3], width: '2%' }], columns: [ { data: 'SiteCode', title: 'SiteCode', autoWidth: true, searchable: true }, { data: 'LegacyDoor', title: 'Legacy Door', autoWidth: true }, { data: 'BuyingGroup', title: 'Buying Group', autoWidth: false, orderable: false }, { data: 'SettingName', title: 'Setting Name', autoWidth: true } ] }); } function LoadGridInclude() { includeTable = $("#includeTable").DataTable({ stripeClasses: [], select: { style: 'multi' }, ordering: true, paging: false, searching: false, serverSide: true, filter: true, searchDelay: 1000, scrollCollapse: true, ajax: { url: '@Url.Action("LoadInclude", "Home")', type: 'GET', datatype: 'json', headers: { 'RequestVerificationToken': '@Html.AntiForgeryToken()' }, data: (d) => { return { draw: d.draw, start: d.start, length: d.length, search: d.search.value, FilterByColumn: d.columns[d.order[0].column].data, ASC_DSEC: d.order[0].dir } }, dataSrc: (json) => { return json.data; } }, columnDefs: [{ className: "dt-center", targets: [1, 2, 3], width: '2%' }], columns: [ { data: 'SiteCode', title: 'SiteCode', autoWidth: true, searchable: true }, { data: 'LegacyDoor', title: 'Legacy Door', autoWidth: true }, { data: 'BuyingGroup', title: 'Buying Group', autoWidth: false, orderable: false }, { data: 'SettingName', title: 'Setting Name', autoWidth: true } ] }); } // 转移到Include表 function moveToInclude() { var selectedRows = excludeTable.rows({ selected: true }).data().toArray(); if (selectedRows.length === 0) { alert("请选择要转移的行"); return; } // 提取唯一标识(这里用LegacyDoor作为唯一键,实际可根据业务调整) var legacyDoors = selectedRows.map(item => item.LegacyDoor); $.ajax({ url: '@Url.Action("MoveToInclude", "Home")', type: 'POST', contentType: 'application/json', headers: { 'RequestVerificationToken': '@Html.AntiForgeryToken()' }, data: JSON.stringify(legacyDoors), success: function (result) { if (result.success) { // 重新加载表格 excludeTable.ajax.reload(); includeTable.ajax.reload(); // 清除选中状态 excludeTable.rows().deselect(); } else { alert("转移失败:" + result.message); } }, error: function () { alert("转移请求失败"); } }); } // 转移到Exclude表 function moveToExclude() { var selectedRows = includeTable.rows({ selected: true }).data().toArray(); if (selectedRows.length === 0) { alert("请选择要转移的行"); return; } var legacyDoors = selectedRows.map(item => item.LegacyDoor); $.ajax({ url: '@Url.Action("MoveToExclude", "Home")', type: 'POST', contentType: 'application/json', headers: { 'RequestVerificationToken': '@Html.AntiForgeryToken()' }, data: JSON.stringify(legacyDoors), success: function (result) { if (result.success) { includeTable.ajax.reload(); excludeTable.ajax.reload(); includeTable.rows().deselect(); } else { alert("转移失败:" + result.message); } }, error: function () { alert("转移请求失败"); } }); } </script> <div style="vertical-align:top;"> <table id="excludeInclude" width=100% style="vertical-align:top;"> <tr style="vertical-align:top;"> <td valign="top"> <table id="excludeTable" style="width:100%; height:30%;float:left; clear:both; vertical-align:top; border:1px solid;"></table> </td> <td valign="top" style="width:5%;float:left; clear:both;margin-left:5px;margin-right:5px; margin-top:95px; "> <div id="btnPanel"> <button id="btnRight" class="btn-logic"><b> >></b></button> <button id="btnLeft" class="btn-logic"><b> << </b></button> </div> </td> <td valign="top"> <table id="includeTable" style="width:100%; height:30%;float:left; clear:both;vertical-align:top;border:1px solid;"></table> </td> </tr> </table> </div>
HomeController.cs
using Microsoft.AspNetCore.Mvc; using RowTransfer.Models; using System.Collections.Generic; using System.Linq; namespace RowTransfer.Controllers { public class HomeController : Controller { // 模拟数据源,实际项目中替换为数据库操作 private static List<DoorDetails> _excludeDoors = new List<DoorDetails> { new DoorDetails { LegacyDoor = 43443, BuyingGroup = "BuyingGroup1", SiteCode = 11, SettingName = "Setting1" }, new DoorDetails { LegacyDoor = 23123, BuyingGroup = "BuyingGroup2", SiteCode = 22, SettingName = "Setting2" } }; private static List<DoorDetails> _includeDoors = new List<DoorDetails> { new DoorDetails { LegacyDoor = 54483, BuyingGroup = "BuyingGroup3", SiteCode = 33, SettingName = "Setting3" }, new DoorDetails { LegacyDoor = 66543, BuyingGroup = "BuyingGroup4", SiteCode = 44, SettingName = "Setting4" } }; public IActionResult Index() { return View(); } public IActionResult LoadExclude(int draw = 1, int start = 0, int length = 10, string search = "", string FilterByColumn = "", string ASC_DSEC = "") { var filteredData = _excludeDoors.AsQueryable(); // 搜索过滤(示例简单处理,实际可根据需求扩展) if (!string.IsNullOrEmpty(search)) { filteredData = filteredData.Where(d => d.SiteCode.ToString().Contains(search) || d.LegacyDoor.ToString().Contains(search) || d.BuyingGroup.Contains(search) || d.SettingName.Contains(search)); } // 排序 if (!string.IsNullOrEmpty(FilterByColumn)) { filteredData = ASC_DSEC == "asc" ? filteredData.OrderBy(d => d.GetType().GetProperty(FilterByColumn).GetValue(d)) : filteredData.OrderByDescending(d => d.GetType().GetProperty(FilterByColumn).GetValue(d)); } int recordsTotal = _excludeDoors.Count; int recordsFiltered = filteredData.Count(); var data = filteredData.Skip(start).Take(length).ToList(); var jsonData = new { draw = draw, recordsFiltered = recordsFiltered, recordsTotal = recordsTotal, data = data }; return Ok(jsonData); } public IActionResult LoadInclude(int draw = 1, int start = 0, int length = 10, string search = "", string FilterByColumn = "", string ASC_DSEC = "") { var filteredData = _includeDoors.AsQueryable(); if (!string.IsNullOrEmpty(search)) { filteredData = filteredData.Where(d => d.SiteCode.ToString().Contains(search) || d.LegacyDoor.ToString().Contains(search) || d.BuyingGroup.Contains(search) || d.SettingName.Contains(search)); } if (!string.IsNullOrEmpty(FilterByColumn)) { filteredData = ASC_DSEC == "asc" ? filteredData.OrderBy(d => d.GetType().GetProperty(FilterByColumn).GetValue(d)) : filteredData.OrderByDescending(d => d.GetType().GetProperty(FilterByColumn).GetValue(d)); } int recordsTotal = _includeDoors.Count; int recordsFiltered = filteredData.Count(); var data = filteredData.Skip(start).Take(length).ToList(); var jsonData = new { draw = draw, recordsFiltered = recordsFiltered, recordsTotal = recordsTotal, data = data }; return Ok(jsonData); } [HttpPost] public IActionResult MoveToInclude([FromBody] List<int> legacyDoors) { if (legacyDoors == null || legacyDoors.Count == 0) { return Json(new { success = false, message = "未选择任何行" }); } lock (_excludeDoors) { var itemsToMove = _excludeDoors.Where(d => legacyDoors.Contains(d.LegacyDoor)).ToList(); foreach (var item in itemsToMove) { _excludeDoors.Remove(item); _includeDoors.Add(item); } } return Json(new { success = true }); } [HttpPost] public IActionResult MoveToExclude([FromBody] List<int> legacyDoors) { if (legacyDoors == null || legacyDoors.Count == 0) { return Json(new { success = false, message = "未选择任何行" }); } lock (_includeDoors) { var itemsToMove = _includeDoors.Where(d => legacyDoors.Contains(d.LegacyDoor)).ToList(); foreach (var item in itemsToMove) { _includeDoors.Remove(item); _excludeDoors.Add(item); } } return Json(new { success = true }); } } }
DoorDetails.cs(模型类无需修改)
public class DoorDetails { public int SiteCode { get; set; } public int LegacyDoor { get; set; } public string BuyingGroup { get; set; } public string SettingName { get; set; } }
关键说明
- 行选择修复:去掉
new关键字,正确初始化DataTables并保存实例,确保select插件生效 - 转移逻辑:通过
rows({ selected: true }).data()获取选中行数据,提交到后端完成数据迁移后重新加载表格 - 后端处理:使用静态列表模拟数据源,实际项目中需替换为数据库的增删操作,同时添加线程锁保证并发安全
- 防伪造令牌:使用
@Html.AntiForgeryToken()生成验证令牌,防止CSRF攻击
内容的提问来源于stack exchange,提问作者KitKat
相关产品推荐
相关产品推荐

