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

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].PropertyName is critical—ASP.NET uses this to map dynamic rows to the List<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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:04:10