ASP.NET MVC Core 2.0多交易记录表格录入功能实现咨询
Got it, let's break this down for you. You're building an app where users can add multiple transaction rows to a table before saving, using JavaScript, and you already have your C# view models set up. Here's a practical, step-by-step solution that plays nicely with your existing code:
1. First, Let's Formalize Your Existing View Models
I'll fill in the gaps from your snippet to make the example complete:
public class VolumenConfigViewModel { public int CompanyId { get; set; } // Example global property public List<ConfigItem> ConfigItems { get; set; } = new List<ConfigItem>(); } public class ConfigItem { public decimal TransactionVolume { get; set; } public DateTime TransactionDate { get; set; } public string TransactionDescription { get; set; } // Add any other transaction-specific properties here }
2. Frontend Table Structure with Template Row
We'll create a table with a hidden template row (for cloning new rows) and bind it to your view model:
<form asp-action="SaveVolumeConfig" method="post"> <!-- Global field (e.g., company ID) --> <input type="hidden" asp-for="CompanyId" value="123" /> <table id="transactionTable" class="table table-bordered"> <thead> <tr> <th>交易量</th> <th>交易日期</th> <th>交易描述</th> <th>操作</th> </tr> </thead> <tbody> <!-- Render existing rows from the view model (if any) --> @foreach (var item in Model.ConfigItems) { <tr class="transaction-row"> <td><input type="number" step="0.01" asp-for="@item.TransactionVolume" class="form-control" /></td> <td><input type="date" asp-for="@item.TransactionDate" class="form-control" /></td> <td><input type="text" asp-for="@item.TransactionDescription" class="form-control" /></td> <td><button type="button" class="btn btn-sm btn-danger delete-row">删除</button></td> </tr> } <!-- Hidden template row for new entries --> <tr id="rowTemplate" style="display: none;"> <td><input type="number" step="0.01" name="ConfigItems[__INDEX__].TransactionVolume" class="form-control" /></td> <td><input type="date" name="ConfigItems[__INDEX__].TransactionDate" class="form-control" /></td> <td><input type="text" name="ConfigItems[__INDEX__].TransactionDescription" class="form-control" /></td> <td><button type="button" class="btn btn-sm btn-danger delete-row">删除</button></td> </tr> </tbody> </table> <div class="mt-3"> <button type="button" id="addRowBtn" class="btn btn-primary me-2">添加交易记录</button> <button type="submit" class="btn btn-success">保存所有记录</button> </div> </form>
3. JavaScript to Add/Delete Rows
This script handles cloning the template, updating field names for proper model binding, and deleting rows:
document.addEventListener('DOMContentLoaded', function() { const addRowBtn = document.getElementById('addRowBtn'); const tableBody = document.querySelector('#transactionTable tbody'); const rowTemplate = document.getElementById('rowTemplate'); // Start index at the number of existing rows let currentIndex = document.querySelectorAll('.transaction-row').length; // Add new row logic addRowBtn.addEventListener('click', function() { // Clone the template and remove hidden styling const newRow = rowTemplate.cloneNode(true); newRow.removeAttribute('id'); newRow.style.display = ''; newRow.classList.add('transaction-row'); // Replace the placeholder index with the actual current index const inputs = newRow.querySelectorAll('input'); inputs.forEach(input => { const updatedName = input.getAttribute('name').replace('__INDEX__', currentIndex); input.setAttribute('name', updatedName); input.setAttribute('id', updatedName); // Optional, but helps with client-side validation }); // Add the new row to the table tableBody.appendChild(newRow); currentIndex++; // Bind delete action to the new row's button bindDeleteHandler(newRow.querySelector('.delete-row')); }); // Bind delete handlers to existing rows document.querySelectorAll('.delete-row').forEach(btn => bindDeleteHandler(btn)); // Delete row function function bindDeleteHandler(button) { button.addEventListener('click', function() { button.closest('tr').remove(); // Optional: Re-index rows to keep numbers sequential (ASP.NET allows non-sequential, but this cleans things up) reindexRows(); }); } // Re-index all rows after deletion function reindexRows() { const rows = document.querySelectorAll('.transaction-row'); rows.forEach((row, idx) => { const inputs = row.querySelectorAll('input'); inputs.forEach(input => { const nameParts = input.getAttribute('name').split('['); const newName = `${nameParts[0]}[${idx}].${nameParts[1].split(']')[1]}`; input.setAttribute('name', newName); input.setAttribute('id', newName); }); }); currentIndex = rows.length; } });
4. Backend Action to Receive the Data
This action will automatically bind the submitted rows to your VolumenConfigViewModel:
[HttpPost] public IActionResult SaveVolumeConfig(VolumenConfigViewModel model) { if (ModelState.IsValid) { // Save logic here: loop through model.ConfigItems and persist to your database return RedirectToAction("VolumeConfigIndex"); } // If validation fails, return the view with errors return View(model); }
Key Notes to Remember
- Model Binding Magic: The field name format
ConfigItems[INDEX].PropertyNameis critical—ASP.NET uses this to map dynamic rows to theList<ConfigItem>in your view model. - Validation: Add client-side validation (e.g., using jQuery Validate) to ensure users fill out required fields before saving.
- Non-Sequential Indices: ASP.NET Core doesn't require indices to be sequential—unique numbers work fine. Re-indexing is just for cleanliness.
内容的提问来源于stack exchange,提问作者JuValencia
相关产品推荐
相关产品推荐

