如何在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
相关产品推荐
相关产品推荐

