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
相关产品推荐
相关产品推荐

