ASP.NET MVC预算应用:如何动态增删多份收入表单?
实现ASP.NET MVC多收入表单动态增删功能
我正在使用ASP.NET MVC开发月度预算应用,需要允许用户同时创建多条收入记录,要求大屏最多显示5个表单,小屏最多3个。目前通过ViewModel维护收入表单列表,但在视图中实现动态增删遇到困难,希望实现用户点击"+"按钮添加新表单,点击"x"按钮删除表单的交互效果。
现有后台代码
// GET: Incomes/Create public IActionResult Create() { var model = new MultipleIncomeVM(); ViewData["BudgetId"] = new SelectList(_context.Budgets, "BudgetId", "Name"); ViewData["CategoryId"] = new SelectList(_context.Categories, "CategoryId", "Name"); return View(model); } // POST: Incomes/Create [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create(MultipleIncomeVM incomevm) { try { // Validate only the incomes with Amount greater than 0 var validIncomes = incomevm.Incomes.Where(i => i.Amount > 0); if (ModelState.IsValid) { foreach (var i in validIncomes) { // Add each income _context.Add(i); await _context.SaveChangesAsync(); // A new transaction var transaction = new Transaction { Date = i.DateReceived, Type = ControllersName.GetControllerName(this.GetType().Name), Detail = i.Source, Amount = i.Amount, BudgetId = i.BudgetId, CategoryId = i.CategoryId }; _context.Add(transaction); await _context.SaveChangesAsync(); } return RedirectToAction(nameof(Index)); } } catch (DbUpdateException) { ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator."); } ViewData["BudgetId"] = new SelectList(_context.Budgets, "BudgetId", "Name"); ViewData["CategoryId"] = new SelectList(_context.Categories, "CategoryId", "Name"); return View(incomevm); }
ViewModel代码
namespace My_Budget.ViewModels { public class MultipleIncomeVM { public List<Income> Incomes { get; set; } = new List<Income> { new Income { DateReceived = DateTime.Today } }; } }
现有视图代码
@model My_Budget.ViewModels.MultipleIncomeVM @{ ViewData["Title"] = "Create Incomes"; } <h1>Create Multiple Incomes</h1> <hr /> <div class="row"> <div class="col-md-12"> <form asp-action="Create"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <table class="table table-bordered"> <thead> <tr> <th>Source</th> <th>Amount</th> <th>Date Received</th> <th>Category</th> <th></th> <th></th> </tr> </thead> <tbody> @for (int i = 0; i < Model.Incomes.Count; i++) { <tr> <td> <input asp-for="@Model.Incomes[i].Source" class="form-control" /> <span asp-validation-for="@Model.Incomes[i].Source" class="text-danger"></span> </td> <td> <input asp-for="@Model.Incomes[i].Amount" class="form-control" /> <span asp-validation-for="@Model.Incomes[i].Amount" class="text-danger"></span> </td> <td> <input asp-for="@Model.Incomes[i].DateReceived" class="form-control" type="date" /> <span asp-validation-for="@Model.Incomes[i].DateReceived" class="text-danger"></span> </td> <td> <select asp-for="@Model.Incomes[i].CategoryId" class="form-control" asp-items="ViewBag.CategoryId"></select> <span asp-validation-for="@Model.Incomes[i].CategoryId" class="text-danger"></span> </td> <td> <i class="bi bi-plus-circle" id="add-form"></i> <i class="bi bi-x-circle" disabled></i> </td> <td> @* Hidden value *@ <select asp-for="@Model.Incomes[i].BudgetId" hidden class="form-control" asp-items="ViewBag.BudgetId" ></select> </td> </tr> } </tbody> </table> <div class="form-group"> <input type="submit" value="Create" class="btn btn-primary" /> </div> </form> </div> </div> <div> <a asp-action="Index">Back to List</a> </div> @section Scripts { @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } }
已尝试的操作
- 在ViewModel中创建列表存储多条收入记录
- 可初始展示多表单,但不知如何在视图中处理动态增删
解决方案
步骤1:修改视图结构,添加模板行
在视图的<tbody>中添加隐藏的模板行,用于新增表单时克隆,同时调整现有行的类和按钮属性:
<tbody> @for (int i = 0; i < Model.Incomes.Count; i++) { <tr class="income-row"> <td> <input asp-for="@Model.Incomes[i].Source" class="form-control" /> <span asp-validation-for="@Model.Incomes[i].Source" class="text-danger"></span> </td> <td> <input asp-for="@Model.Incomes[i].Amount" class="form-control" /> <span asp-validation-for="@Model.Incomes[i].Amount" class="text-danger"></span> </td> <td> <input asp-for="@Model.Incomes[i].DateReceived" class="form-control" type="date" /> <span asp-validation-for="@Model.Incomes[i].DateReceived" class="text-danger"></span> </td> <td> <select asp-for="@Model.Incomes[i].CategoryId" class="form-control" asp-items="ViewBag.CategoryId"></select> <span asp-validation-for="@Model.Incomes[i].CategoryId" class="text-danger"></span> </td> <td> <i class="bi bi-plus-circle add-btn" style="cursor:pointer;color:green;"></i> <i class="bi bi-x-circle remove-btn" style="cursor:pointer;color:red;" @(i == 0 ? "disabled" : "")></i> </td> <td> <select asp-for="@Model.Incomes[i].BudgetId" hidden class="form-control" asp-items="ViewBag.BudgetId"></select> </td> </tr> } <!-- 隐藏的模板行 --> <tr id="income-template" style="display:none;"> <td> <input name="Incomes[__INDEX__].Source" class="form-control" /> <span class="text-danger field-validation-valid" data-valmsg-for="Incomes[__INDEX__].Source" data-valmsg-replace="true"></span> </td> <td> <input name="Incomes[__INDEX__].Amount" class="form-control" /> <span class="text-danger field-validation-valid" data-valmsg-for="Incomes[__INDEX__].Amount" data-valmsg-replace="true"></span> </td> <td> <input name="Incomes[__INDEX__].DateReceived" class="form-control" type="date" /> <span class="text-danger field-validation-valid" data-valmsg-for="Incomes[__INDEX__].DateReceived" data-valmsg-replace="true"></span> </td> <td> <select name="Incomes[__INDEX__].CategoryId" class="form-control" asp-items="ViewBag.CategoryId"></select> <span class="text-danger field-validation-valid" data-valmsg-for="Incomes[__INDEX__].CategoryId" data-valmsg-replace="true"></span> </td> <td> <i class="bi bi-plus-circle add-btn" style="cursor:pointer;color:green;"></i> <i class="bi bi-x-circle remove-btn" style="cursor:pointer;color:red;"></i> </td> <td> <select name="Incomes[__INDEX__].BudgetId" hidden class="form-control" asp-items="ViewBag.BudgetId"></select> </td> </tr> </tbody>
步骤2:添加JavaScript实现动态增删
在Scripts区域添加以下代码,实现增删逻辑、响应式限制和验证绑定:
@section Scripts { @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } <script> $(function() { // 获取当前屏幕允许的最大表单数量 function getMaxRows() { return $(window).width() >= 768 ? 5 : 3; } // 新增表单逻辑 $(document).on('click', '.add-btn', function() { const currentRows = $('.income-row').length; const maxRows = getMaxRows(); if (currentRows >= maxRows) { alert(`最多只能添加${maxRows}条记录`); return; } // 克隆模板行并替换索引 const template = $('#income-template').clone(); template.removeAttr('id').show().addClass('income-row'); const newIndex = currentRows; template.html(template.html().replace(/__INDEX__/g, newIndex)); $('tbody').append(template); // 重新绑定客户端验证 $.validator.unobtrusive.parse('form'); }); // 删除表单逻辑 $(document).on('click', '.remove-btn', function() { const row = $(this).closest('.income-row'); // 至少保留一条记录 if ($('.income-row').length <= 1) { alert('至少保留一条记录'); return; } row.remove(); // 重新索引所有表单元素,保证模型绑定正确 $('.income-row').each(function(index) { $(this).find('input, select').each(function() { const name = $(this).attr('name').replace(/Incomes\[\d+\]/, `Incomes[${index}]`); $(this).attr('name', name); // 更新验证消息的关联属性 const msgSpan = $(this).next('.text-danger'); if (msgSpan.length) { msgSpan.attr('data-valmsg-for', name); } }); }); // 重新绑定客户端验证 $.validator.unobtrusive.parse('form'); }); // 窗口大小变化时自动调整表单数量(可选) $(window).resize(function() { const currentRows = $('.income-row').length; const maxRows = getMaxRows(); if (currentRows > maxRows) { alert(`屏幕尺寸变化,最多只能显示${maxRows}条记录,已自动移除多余行`); $('.income-row').slice(maxRows).remove(); // 重新索引表单元素 $('.income-row').each(function(index) { $(this).find('input, select').each(function() { const name = $(this).attr('name').replace(/Incomes\[\d+\]/, `Incomes[${index}]`); $(this).attr('name', name); const msgSpan = $(this).next('.text-danger'); if (msgSpan.length) { msgSpan.attr('data-valmsg-for', name); } }); }); $.validator.unobtrusive.parse('form'); } }); }); </script> }
步骤3:可选调整ViewModel初始化
如果希望初始显示多条记录,修改ViewModel的默认列表:
public List<Income> Incomes { get; set; } = new List<Income> { new Income { DateReceived = DateTime.Today }, new Income { DateReceived = DateTime.Today } };
核心说明
- 模型绑定兼容:通过动态更新表单元素的
name属性索引,确保ASP.NET MVC能正确绑定到MultipleIncomeVM.Incomes列表 - 响应式限制:根据屏幕宽度自动切换最大允许的表单数量
- 验证支持:每次增删后重新解析表单验证规则,保证客户端验证正常工作
- 操作限制:禁止删除最后一条表单,避免提交空数据
内容的提问来源于stack exchange,提问作者NicolasMartinezK
相关产品推荐
相关产品推荐

