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

ASP.NET 6 MVC中用JavaScript循环表格文本框ID的问题求助

问题分析与解决方案

问题根源

循环生成表格行时,手动设置了重复的固定ID(如lstProduct、txtPrice),导致JavaScript通过document.getElementById只会选中页面中第一个匹配的元素,新增行后操作仍修改第一行数据。

修复步骤

1. 修改HTML代码:移除重复ID,使用类名定位

去掉手动设置的id属性,保留asp-for让ASP.NET自动生成唯一ID,同时为需要操作的元素添加专属类名:

<table class="table table-hover MyTable table-bordered" id="MyTable">
    <thead>
        <tr>
            <th style="width:19%;">Product Code</th>
            <th style="width:24%;">Product Name</th>
            <th style="width:15%;">Quantity</th>
            <th style="width:15%;">Price</th>
            <th style="width:15%;">Amount</th>
            <th style="width:12%;">
                <button id='btnAddDetailRow' type="button" class="btn btn-sm btn-secondary visible" onclick="AddItem()">
                    Add
                </button>
            </th>
        </tr>
    </thead>
    <tbody>
        @for (int i = 0; i < Model.InvoiceDetails.Count; i++)
        {
            <tr>
                <td style="width:19%;">
                    <select asp-for="@Model.InvoiceDetails[i].ProductId" class="form-control Product" asp-items="@ViewBag.Products" onchange="handleProductChange(this);"></select>
                    <span asp-validation-for="@Model.InvoiceDetails[i].ProductId" class="text-danger"></span>
                </td>
                <td style="width:24%;">
                    <input asp-for="@Model.InvoiceDetails[i].Product" class="form-control form-control-plaintext product-name" readonly />
                    <span asp-validation-for="@Model.InvoiceDetails[i].Product" class="text-danger"></span>
                </td>
                <td style="width:15%;">
                    <input asp-for="@Model.InvoiceDetails[i].Quantity" class="form-control QtyTotal" />
                    <span asp-validation-for="@Model.InvoiceDetails[i].Quantity" class="text-danger"></span>
                </td>
                <td style="width:15%;">
                    <input asp-for="@Model.InvoiceDetails[i].Price" class="form-control form-control-plaintext PriceTotal product-price" readonly />
                    <span asp-validation-for="@Model.InvoiceDetails[i].Price" class="text-danger"></span>
                </td>
                <td style="width:15%;">
                    <input type="hidden" asp-for="@Model.InvoiceDetails[i].IsDeleted" />
                    <input asp-for="@Model.InvoiceDetails[i].Amount" class="form-control form-control-plaintext AmountTotal" readonly />
                    <span asp-validation-for="@Model.InvoiceDetails[i].Amount" class="text-danger"></span>
                </td>
                <td style="width:12%;">
                    <button id='btnremove-@i' type="button" class="btn btn-sm btn-danger visible" onclick="DeleteItem(this)">
                        Delete
                    </button>
                </td>
            </tr>
        }
    </tbody>
</table>

2. 重构JavaScript函数:基于当前行定位元素

合并原有分散的函数,通过触发事件的元素找到所在行,精准定位该行的输入框:

<script type="text/javascript">
function handleProductChange(selectElement) {
    // 获取当前操作的表格行
    const currentRow = selectElement.closest('tr');
    // 定位该行内的目标输入框
    const productNameInput = currentRow.querySelector('.product-name');
    const productPriceInput = currentRow.querySelector('.product-price');
    const amountInput = currentRow.querySelector('.AmountTotal');
    
    const selectedProductId = selectElement.value;

    // 从全局下拉框获取产品名称
    const productNameDropdown = document.getElementById('dropdownProductName');
    const matchedName = Array.from(productNameDropdown.options).find(opt => opt.value === selectedProductId)?.text || '';
    productNameInput.value = matchedName;

    // 从全局下拉框获取产品价格
    const productPriceDropdown = document.getElementById('dropdownPrice');
    const matchedPrice = Array.from(productPriceDropdown.options).find(opt => opt.value === selectedProductId)?.text || '';
    productPriceInput.value = matchedPrice;

    // 计算金额
    calculateRowAmount(currentRow);
}

// 计算单行金额
function calculateRowAmount(row) {
    const quantity = parseFloat(row.querySelector('.QtyTotal').value) || 0;
    const price = parseFloat(row.querySelector('.product-price').value) || 0;
    row.querySelector('.AmountTotal').value = (quantity * price).toFixed(2);
}

// 监听数量输入变化,自动重新计算金额
document.addEventListener('input', function(e) {
    if (e.target.classList.contains('QtyTotal')) {
        calculateRowAmount(e.target.closest('tr'));
    }
});

function AddItem() {
    const table = document.getElementById('MyTable');
    const rows = table.getElementsByTagName('tr');
    const lastRow = rows[rows.length - 1];
    let rowOuterHtml = lastRow.outerHTML;

    const lastrowIdx = rows.length - 2;
    const nextrowIdx = lastrowIdx + 1;

    // 替换索引,确保新行的绑定属性正确
    rowOuterHtml = rowOuterHtml.replaceAll(`_${lastrowIdx}_`, `_${nextrowIdx}_`);
    rowOuterHtml = rowOuterHtml.replaceAll(`[${lastrowIdx}]`, `[${nextrowIdx}]`);
    rowOuterHtml = rowOuterHtml.replaceAll(`-${lastrowIdx}`, `-${nextrowIdx}`);

    const newRow = table.insertRow();
    newRow.innerHTML = rowOuterHtml;

    // 清空新行的输入值
    const inputs = newRow.querySelectorAll('input');
    inputs.forEach(input => {
        if (input.classList.contains('product-name') || input.classList.contains('product-price') || input.classList.contains('AmountTotal')) {
            input.value = '';
        } else if (input.type === 'number') {
            input.value = 0;
        }
    });
}

function DeleteItem(btn) {
    btn.closest('tr').remove();
}
</script>

关键优化点

  • 消除ID冲突:移除手动固定ID,依赖ASP.NET自动生成的唯一ID,避免DOM选择错误。
  • 行内精准定位:通过closest('tr')锁定当前操作行,再用类名查找元素,确保操作目标是当前行的输入框。
  • 逻辑合并简化:将多个分散的函数整合为一个处理逻辑,减少冗余代码。
  • 自动计算金额:监听数量输入变化,实时更新金额,提升交互体验。

可选优化:直接通过Select选项携带数据

如果产品数据来自后端,可以将名称和价格存储在Select选项的data-*属性中,无需依赖全局下拉框:

<select asp-for="@Model.InvoiceDetails[i].ProductId" class="form-control Product" 
        asp-items="@(ViewBag.Products.Select(p => new SelectListItem { 
            Value = p.Id.ToString(), 
            Text = p.Name, 
            Data = new { price = p.Price.ToString() } 
        }))" 
        onchange="handleProductChange(this);"></select>

然后修改JavaScript函数:

function handleProductChange(selectElement) {
    const currentRow = selectElement.closest('tr');
    const selectedOption = selectElement.options[selectElement.selectedIndex];
    
    currentRow.querySelector('.product-name').value = selectedOption.text;
    currentRow.querySelector('.product-price').value = selectedOption.dataset.price;
    
    calculateRowAmount(currentRow);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 04:20:14