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

ASP.NET MVC 5库存管理系统:动态添加表格行实现商品选购问询

实现ASP.NET MVC 5动态新增采购商品表格行

嘿,作为ASP.NET MVC 5的初学者,我完全懂你想要实现这个动态添加采购商品行的需求——这在库存管理系统里绝对是高频功能!我来给你一步步拆解怎么搞定它:

1. 先搭好后端的模型框架

首先咱们得定义对应的ViewModel,用来承载采购单和每行商品的数据,这样后端才能正确接收表单提交的内容:

// 单条采购商品的ViewModel
public class PurchaseItemViewModel
{
    public int ProductId { get; set; } // 绑定商品下拉框的选中值
    public string ProductName { get; set; } // 可选,用来显示商品名称
    public int Quantity { get; set; } // 采购数量
    public decimal Price { get; set; } // 商品单价
}

// 整个采购单的主ViewModel,包含多行商品
public class PurchaseOrderViewModel
{
    public List<PurchaseItemViewModel> Items { get; set; } = new List<PurchaseItemViewModel>();
    // 你还可以加其他字段,比如供应商ID、采购日期之类的,按需添加就行
}

2. 控制器方法准备

接下来在控制器里写好渲染页面和接收提交的方法:

public class PurchaseController : Controller
{
    // 假设你有一个获取商品列表的服务或方法,这里用占位符代替
    private List<Product> GetAllProducts()
    {
        // 实际项目里这里应该从数据库查商品数据
        return new List<Product>
        {
            new Product { Id = 1, Name = "商品A" },
            new Product { Id = 2, Name = "商品B" }
        };
    }

    // GET请求:打开采购单创建页面
    public ActionResult Create()
    {
        var model = new PurchaseOrderViewModel();
        // 默认先加一行空的商品项,用户进来就能直接填
        model.Items.Add(new PurchaseItemViewModel());
        // 把商品列表放到ViewBag里,供前端下拉框使用
        ViewBag.Products = new SelectList(GetAllProducts(), "Id", "Name");
        return View(model);
    }

    // POST请求:接收提交的采购单数据
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(PurchaseOrderViewModel model)
    {
        if (ModelState.IsValid)
        {
            // 这里写你的保存逻辑,比如把采购单存到数据库
            // 比如 _purchaseService.SavePurchaseOrder(model);
            return RedirectToAction("Index"); // 跳转到采购单列表页
        }
        // 如果验证失败,要重新把商品列表塞回ViewBag,不然下拉框会空
        ViewBag.Products = new SelectList(GetAllProducts(), "Id", "Name");
        return View(model); // 返回页面,保留用户已经输入的内容
    }
}

3. 编写前端Razor视图

咱们把页面分成主视图和行模板的部分视图,这样新增行的时候更方便复用:

主视图 Create.cshtml

@model PurchaseOrderViewModel

@{
    ViewBag.Title = "创建采购单";
}

<h2>创建采购单</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })

        <!-- 采购商品表格 -->
        <table id="purchaseItemsTable" class="table table-bordered">
            <thead>
                <tr>
                    <th>选择商品</th>
                    <th>采购数量</th>
                    <th>商品单价</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <!-- 遍历渲染已有的商品行,默认会有一行空的 -->
                @foreach (var item in Model.Items)
                {
                    @Html.Partial("_PurchaseItemRow", item)
                }
            </tbody>
        </table>

        <!-- 新增商品按钮 -->
        <button type="button" id="addItemBtn" class="btn btn-success mb-3">新增商品</button>

        <!-- 提交按钮 -->
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="提交采购单" class="btn btn-primary" />
            </div>
        </div>
    </div>
}

行模板部分视图 _PurchaseItemRow.cshtml

这个视图就是单一行的模板,不管是默认的还是新增的,都用这个:

@model PurchaseItemViewModel

<tr class="purchase-item-row">
    <td>
        <!-- 商品下拉框,绑定ProductId -->
        @Html.DropDownListFor(m => m.ProductId, (SelectList)ViewBag.Products, "请选择商品", new { @class = "form-control" })
        @Html.ValidationMessageFor(m => m.ProductId, "", new { @class = "text-danger" })
    </td>
    <td>
        <!-- 数量输入框,限制只能输入正整数 -->
        @Html.TextBoxFor(m => m.Quantity, new { @class = "form-control", type = "number", min = "1", step = "1" })
        @Html.ValidationMessageFor(m => m.Quantity, "", new { @class = "text-danger" })
    </td>
    <td>
        <!-- 单价输入框,支持小数 -->
        @Html.TextBoxFor(m => m.Price, new { @class = "form-control", type = "number", min = "0", step = "0.01" })
        @Html.ValidationMessageFor(m => m.Price, "", new { @class = "text-danger" })
    </td>
    <td>
        <!-- 删除行按钮 -->
        <button type="button" class="btn btn-danger remove-item-btn">删除</button>
    </td>
</tr>

4. 用JavaScript实现动态增删行

这部分是核心,要处理点击新增按钮克隆行、更新索引(保证MVC能正确绑定列表)、删除行,还有重新绑定验证:

@section Scripts {
    // 引入jQuery验证脚本,MVC默认的bundles里有
    @Scripts.Render("~/bundles/jqueryval")
    <script>
        $(document).ready(function () {
            // 新增商品行的点击事件
            $("#addItemBtn").click(function () {
                // 获取最后一行作为模板克隆
                var lastRow = $(".purchase-item-row").last();
                if (!lastRow.length) return; // 防止没有行的情况

                var newRow = lastRow.clone();
                // 清空新行的所有输入值,避免复制上一行的内容
                newRow.find("input, select").val("");

                // 关键:更新name和id的索引,MVC绑定列表需要Items[0], Items[1]这样的格式
                var newIndex = $(".purchase-item-row").length;
                newRow.find("[name]").each(function () {
                    // 替换掉原来的索引,比如把Items[0].ProductId改成Items[1].ProductId
                    var newName = $(this).attr("name").replace(/\[(\d+)\]/, `[${newIndex}]`);
                    $(this).attr("name", newName);
                    // 同时更新id,避免页面上有重复的id
                    var newId = $(this).attr("id").replace(/\_(\d+)\_/, `_${newIndex}_`);
                    $(this).attr("id", newId);
                });

                // 把新行加到表格里
                $("#purchaseItemsTable tbody").append(newRow);
                // 重新绑定验证,不然新行的输入框不会触发验证
                $.validator.unobtrusive.parse(newRow);
            });

            // 删除行的点击事件(用事件委托,因为新增的行是动态的)
            $(document).on("click", ".remove-item-btn", function () {
                var currentRow = $(this).closest(".purchase-item-row");
                // 至少保留一行,不然用户没法提交了
                if ($(".purchase-item-row").length > 1) {
                    currentRow.remove();
                    // 删除后要重新更新所有行的索引,避免索引断层导致绑定失败
                    $(".purchase-item-row").each(function (index) {
                        $(this).find("[name]").each(function () {
                            var updatedName = $(this).attr("name").replace(/\[(\d+)\]/, `[${index}]`);
                            $(this).attr("name", updatedName);
                            var updatedId = $(this).attr("id").replace(/\_(\d+)\_/, `_${index}_`);
                            $(this).attr("id", updatedId);
                        });
                    });
                } else {
                    alert("至少需要保留一行商品哦~");
                }
            });
        });
    </script>
}

几个要注意的关键点

  • 索引正确是核心:MVC绑定列表的时候,必须保证每个行的name属性是Items[0].ProductId、Items[1].ProductId这种连续索引的格式,不然后端接收不到列表数据,所以新增和删除后都要更新索引。
  • 验证要重新绑定:动态新增的DOM元素不会自动带上jQuery验证规则,所以每次新增行后都要调用$.validator.unobtrusive.parse()来解析新行的验证。
  • 删除行的边界处理:一定要保留至少一行,不然用户提交的时候模型会验证失败(因为Items是空的)。

这样你就能实现点击按钮动态新增商品行,每行都能独立选择商品、填数量和价格,提交后后端也能正确拿到所有行的数据啦!

内容的提问来源于stack exchange,提问作者impy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:42:20