You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:32:02