PAYATHON 2026

如何使用 JavaScript 自动计算账单总价

支付小周

结论

每个金额输入框触发 input 事件时调用 totalIt(),就能同步更新当前行价格和账单总价,无需再设置“Total”按钮。

计算时最好直接遍历表格中的数据行,不要依赖 price1、price2 这类连续编号。删除中间行后,ID 可能出现断号,原来的循环便无法找到对应元素。

推荐实现

为参与计算的三类输入框添加统一的 class,再通过事件委托监听整个表格。初始行和动态新增的行都可以自动参与计算。

HTML

<table id="dataTable">
  <thead>
    <tr>
      <th>Select</th>
      <th>Sr. No.</th>
      <th>Item Name</th>
      <th>Cost</th>
      <th>GST Tax</th>
      <th>Delivery Charges</th>
      <th>Price</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td><input type="checkbox" name="chk[]"></td>
      <td class="row-number">1</td>
      <td><input type="text" name="item[]"></td>
      <td><input type="number" value="0" step="0.001" name="cost[]" class="cost"></td>
      <td><input type="number" value="0" step="0.001" name="qty[]" class="tax"></td>
      <td><input type="number" value="0" step="0.001" name="del[]" class="delivery"></td>
      <td>
        <input type="text" value="0.000" name="price[]" class="price" readonly>
      </td>
    </tr>
  </tbody>
</table>

<div class="total">
  Total:
  <input type="text" id="total" name="total" value="0.000" readonly>
</div>

示例保留了原代码中的 qty[] 字段名,但按照表头含义,这个字段实际表示 GST Tax。如果后端字段可以调整,建议改成含义更清楚的 tax[]。

JavaScript

const table = document.getElementById("dataTable");
const totalField = document.getElementById("total");

function toNumber(value) {
  const number = parseFloat(value);
  return Number.isFinite(number) ? number : 0;
}

function calculateRow(row) {
  const cost = toNumber(row.querySelector(".cost").value);
  const tax = toNumber(row.querySelector(".tax").value);
  const delivery = toNumber(row.querySelector(".delivery").value);

  const price = cost + tax + delivery;
  row.querySelector(".price").value = price.toFixed(3);

  return price;
}

function totalIt() {
  let total = 0;

  table.querySelectorAll("tbody tr").forEach(function (row) {
    total += calculateRow(row);
  });

  totalField.value = total.toFixed(3);
}

table.addEventListener("input", function (event) {
  if (
    event.target.matches(".cost") ||
    event.target.matches(".tax") ||
    event.target.matches(".delivery")
  ) {
    totalIt();
  }
});

totalIt();

这里监听的是 input,而不是 keyup。除了键盘输入,input 还能捕获粘贴、删除和数字输入框箭头调整等操作。

动态添加行

新增行时设置相同的 class 即可。事件已经绑定在 table 上,因此新输入框不需要单独注册 onkeyup。

function addRow() {
  const tbody = table.querySelector("tbody");
  const row = tbody.insertRow();

  row.innerHTML = `
    <td><input type="checkbox" name="chk[]"></td>
    <td class="row-number"></td>
    <td><input type="text" name="item[]"></td>
    <td><input type="number" value="0" step="0.001" name="cost[]" class="cost"></td>
    <td><input type="number" value="0" step="0.001" name="qty[]" class="tax"></td>
    <td><input type="number" value="0" step="0.001" name="del[]" class="delivery"></td>
    <td>
      <input type="text" value="0.000" name="price[]" class="price" readonly>
    </td>
  `;

  updateRowNumbers();
  totalIt();
}

function updateRowNumbers() {
  table.querySelectorAll("tbody .row-number").forEach(function (cell, index) {
    cell.textContent = index + 1;
  });
}

按钮可以这样写:

<button type="button" onclick="addRow()">Add Item</button>

删除行后重新计算

删除选中的行后,需要重新编号并调用 totalIt()。否则,总价可能还包含已删除行的金额。

function deleteSelectedRows() {
  table.querySelectorAll('tbody input[name="chk[]"]:checked')
    .forEach(function (checkbox) {
      checkbox.closest("tr").remove();
    });

  updateRowNumbers();
  totalIt();
}

对应的按钮如下:

<button type="button" onclick="deleteSelectedRows()">Delete Item</button>

如果只想最小幅度修改原代码

可以将各输入框中原来的:

calc(rowCount);

改为:

totalIt();

初始行也改成:

window.onload = function () {
  document.getElementsByName("qty[]")[0].oninput = totalIt;
  document.getElementsByName("cost[]")[0].oninput = totalIt;
  document.getElementsByName("del[]")[0].oninput = totalIt;

  totalIt();
};

动态行使用:

element4.oninput = totalIt;
element5.oninput = totalIt;

这种改法仍依赖连续的数字 ID。删除中间行后,例如页面只剩 price1 和 price3,原来的 totalIt() 仍会尝试读取 price2,可能因此报错。实际项目中,更适合采用前面的“按行遍历 + class + 事件委托”方案。

注意事项

  • readonly 的总价输入框不会由用户触发 keyup,所以在 total 字段上设置 onkeyup="totalIt()" 没有作用。
  • parseFloat("") 的结果是 NaN,空值或非法值应统一按 0 处理。
  • 如果 GST 输入的是税率,例如 18 表示 18%,就不能直接相加,计算公式应改为:
const price = cost + cost * tax / 100 + delivery;
  • 如果 GST 字段输入的是已经计算好的税额,则继续使用:
const price = cost + tax + delivery;
  • 金额通常保留两位小数,当前示例按照原需求保留三位。需要两位时,将 toFixed(3) 改成 toFixed(2)。
  • 浏览器端计算只能改善交互体验。表单提交后,服务端仍需根据原始字段重新计算并校验总价,不能直接信任客户端提交的 price[] 或 total。

备注:内容仅供参考。