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

如何在Django动态formset中通过JavaScript实现表单计算逻辑

解决思路与修改方案

核心优化方向

  • 放弃基于固定ID的元素定位,动态新增行的ID会自动带form-N-前缀,和你写死的id_quantity这类固定ID无法匹配
  • 改用类名标记需要参与计算的输入框,通过相对DOM层级定位同属一行的关联元素
  • 采用事件委托绑定输入事件,不需要给新增行单独绑定事件,所有动态生成的元素自动生效
  • 拆分计算逻辑为分层的可复用函数,避免逻辑耦合

第一步:修改HTML模板字段属性

给所有参与计算的输入框添加统一的标记类,修改presupuestos-forms.html中对应的字段渲染代码:

<!-- 原有form和formset.empty_form的字段都按如下规则加类 -->
{% render_field form.quantity class="form-control quantity-input" %}
{% render_field form.unit_price class="form-control unit-price-input" %}
{% render_field form.total_price class="form-control row-total-input" readonly %} <!-- 建议设置只读避免手动修改 -->
{% render_field form.tax_free class="form-check-input tax-free-checkbox" %}
<!-- 折扣和总金额字段也加对应类 -->
{{presupuestosparteform.descuento class="form-control discount-input"}}
{{presupuestosparteform.total class="form-control final-total-input" readonly}}

注意:首行的固定表单字段也要同步加相同的类名


第二步:重构计算逻辑JS

替换原有calculos.js的全部内容:

// 常量统一配置,后续修改税率直接改这里即可
const TAX_RATE = 0.24;

// 1. 计算单行总价
function calcRowTotal($row) {
  const quantity = parseFloat($row.find('.quantity-input').val()) || 0;
  const unitPrice = parseFloat($row.find('.unit-price-input').val()) || 0;
  const rowTotal = (quantity * unitPrice).toFixed(2);
  $row.find('.row-total-input').val(rowTotal);
  return parseFloat(rowTotal);
}

// 2. 计算所有行的税前/免税总合计
function calcAllRowsTotal() {
  let total = 0;
  $('.form-row').each(function() {
    const $row = $(this);
    const rowTotal = calcRowTotal($row);
    const isTaxFree = $row.find('.tax-free-checkbox').is(':checked');
    // 免税行扣除对应税率金额
    const finalRowTotal = isTaxFree ? rowTotal * (1 - TAX_RATE) : rowTotal;
    total += finalRowTotal;
  });
  return total.toFixed(2);
}

// 3. 计算含折扣的最终总金额
function calcFinalTotal() {
  const baseTotal = parseFloat(calcAllRowsTotal()) || 0;
  const discount = parseFloat($('.discount-input').val()) || 0;
  const discountAmount = (baseTotal * discount) / 100;
  const finalTotal = (baseTotal - discountAmount).toFixed(2);
  $('.final-total-input').val(finalTotal);
}

// 绑定事件,用事件委托适配动态新增行
$(document).ready(function() {
  // 每行的数量、单价、免税状态变更时触发重算
  $('tbody').on('input change', '.quantity-input, .unit-price-input, .tax-free-checkbox', function() {
    calcFinalTotal();
  });
  // 折扣变更时触发重算
  $('form').on('input', '.discount-input', function() {
    calcFinalTotal();
  });
  // 页面加载完成先触发一次计算,回填已有数据的结果
  calcFinalTotal();
});

第三步:调整动态新增行逻辑

原有add-parts-row.js可以正常使用,只需要在新增行完成后触发一次重算即可,修改新增行的点击事件:

$( "#add_more" ).click(function(e) {
  var formId="id_form-TOTAL_FORMS";
  var emptyRow=$("#empty-row").clone();
  emptyRow.attr("id",null);
  var totalForms=parseInt($('#'+formId).val());
  var newFormRow;
  emptyRow.find("input, checkbox").each(function(){
    newFormRow=updateEmptyFormIDs($(this),totalForms);
  });
  $(".form-row:last").after(newFormRow);
  $('#'+formId).val(totalForms+1);
  // 新增行后触发一次重算
  calcFinalTotal();
});

如果新增行后输入框默认值为空,也可以不用加这个触发,输入内容时会自动计算


可选优化点

  • 给总价、总金额输入框添加readonly属性,避免用户手动修改和计算结果冲突
  • 可以给数量、单价输入框添加输入限制,只允许输入数字和小数点,避免非法输入
  • 金额计算建议保留2位小数,上面的代码已经加了toFixed(2)处理

内容的提问来源于stack exchange,提问作者Itzel Bracho Molina

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 07:15:04