Vue.js发票金额双向绑定及折扣、反向计算功能实现咨询
发票计算功能实现方案
核心计算逻辑
正向计算(修改数量/单价/税率/折扣时自动更新实付净额)
- 先计算无折扣含税总价:
无折扣含税总价 = 数量 × 单价 × (1 + 增值税率 / 100) - 再计算折扣后实付净额:
- 固定金额折扣:
实付净额 = 无折扣含税总价 - 折扣金额 - 百分比折扣:
实付净额 = 无折扣含税总价 × (1 - 折扣比例 / 100)
- 固定金额折扣:
反向计算(修改实付净额时自动更新单价)
- 先根据实付净额反推无折扣含税总价:
- 固定金额折扣:
无折扣含税总价 = 实付净额 + 折扣金额 - 百分比折扣:
无折扣含税总价 = 实付净额 / (1 - 折扣比例 / 100)
- 固定金额折扣:
- 计算单价:
单价 = 无折扣含税总价 / (数量 × (1 + 增值税率 / 100))(数量为0时单价直接设为0,避免除零错误)
修改内容说明
- HTML部分
- 所有数值输入框修改为
type="number",避免非数字输入 - 数量、单价、税率、折扣输入框添加输入事件绑定,触发正向计算
- 实付净额输入框添加输入事件绑定,触发反向计算
- 折扣输入框补充
v-model绑定,修正百分比选项显示符号
- 所有数值输入框修改为
- JS部分
- 新增正向计算方法
calcNetTotal,处理实付净额自动更新逻辑 - 新增反向计算方法
calcUnitPrice,处理单价自动更新逻辑 - 新增数值校验逻辑,避免非法输入导致
NaN问题
- 新增正向计算方法
完整可运行代码
JS代码
const app = new Vue({ el: "#app", data: { invoiceItem: [ { name: "", quantity: 0, unit_price: 0, vat_rate: 18, net_total: 0, showDesc: false, showDiscount: false, description: '', discount_value: 0, discount_rate:'dollar' }, ], }, methods: { addInvoice() { this.invoiceItem.push({ name: "", quantity: 0, unit_price: 0, vat_rate: 18, net_total: 0, showDesc: false, showDiscount: false, description: '', discount_value: 0, discount_rate:'dollar' }); }, removeIncoiceItem(index) { this.invoiceItem.splice(index, 1); }, // 数值安全转换,避免NaN safeNumber(val) { const num = Number(val); return isNaN(num) ? 0 : num; }, // 正向计算:更新实付净额 calcNetTotal(item) { const quantity = this.safeNumber(item.quantity); const unitPrice = this.safeNumber(item.unit_price); const vatRate = this.safeNumber(item.vat_rate); const discountVal = this.safeNumber(item.discount_value); // 计算无折扣含税总价 let totalWithoutDiscount = quantity * unitPrice * (1 + vatRate / 100); // 计算折扣后金额,最低为0 if (item.discount_rate === 'dollar') { item.net_total = Math.max(0, totalWithoutDiscount - discountVal).toFixed(2); } else { item.net_total = Math.max(0, totalWithoutDiscount * (1 - discountVal / 100)).toFixed(2); } }, // 反向计算:更新单价 calcUnitPrice(item) { const quantity = this.safeNumber(item.quantity); const vatRate = this.safeNumber(item.vat_rate); const discountVal = this.safeNumber(item.discount_value); const netTotal = this.safeNumber(item.net_total); // 数量为0直接返回0,避免除零错误 if (quantity === 0) { item.unit_price = 0; return; } // 反推无折扣含税总价 let totalWithoutDiscount; if (item.discount_rate === 'dollar') { totalWithoutDiscount = netTotal + discountVal; } else { totalWithoutDiscount = discountVal >= 100 ? 0 : netTotal / (1 - discountVal / 100); } // 计算单价 item.unit_price = (totalWithoutDiscount / (quantity * (1 + vatRate / 100))).toFixed(2); } }, });
HTML代码
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script> <div id="app"> <section class="container"> <div class="row"> <table class="table"> <thead class="thead-dark"> <tr> <th style="width:17%">商品名称</th> <th style="width:14%">数量</th> <th style="width:14%">单价</th> <th style="width:15%">增值税率</th> <th style="width:14%">实付净额</th> <th style="width:15%">操作</th> </tr> </thead> </table> <div v-for="(item, index) in invoiceItem" :key="index" style="margin-bottom: 10px"> <div class="row"> <div class="col-md-2"> <input type="text" class="form-control" v-model="item.name"> </div> <div class="col-md-2"> <input type="number" class="form-control" v-model="item.quantity" @input="calcNetTotal(item)"> </div> <div class="col-md-2"> <input type="number" class="form-control" v-model="item.unit_price" @input="calcNetTotal(item)"> </div> <div class="col-md-1"> <select class="form-select" v-model="item.vat_rate" @change="calcNetTotal(item)"> <option value="18">%18</option> <option value="8">%8</option> <option value="1">%1</option> <option value="0">%0</option> </select> </div> <div class="col-md-2"> <input type="number" class="form-control" v-model="item.net_total" @input="calcUnitPrice(item)"> </div> <div class="col-md-3"> <button class="btn btn-primary me-1" v-if="!item.showDesc" @click="item.showDesc = !item.showDesc">添加备注 </button> <button class="btn btn-secondary me-1" v-if="!item.showDiscount" @click="item.showDiscount = !item.showDiscount">添加折扣 </button> <button class="btn btn-danger" @click="removeIncoiceItem(index)">删除</button> </div> <div class="col-md-4 mt-2" v-if="item.showDesc"> <input type="text" class="form-control" placeholder="备注信息" v-model="item.description"> </div> <div class="col-md-4 mt-2" v-if="item.showDiscount"> <div class="input-group"> <input type="number" class="form-control" placeholder="折扣数值" v-model="item.discount_value" @input="calcNetTotal(item)"> <select class="form-select" v-model="item.discount_rate" @change="calcNetTotal(item)"> <option value="dollar">USD</option> <option value="percent">%</option> </select> </div> </div> </div> <hr> </div> <hr> <div style="margin-top:10px"> <button class="btn btn-warning" @click="addInvoice()">新增商品行</button> </div> </div> </section> </div>
内容的提问来源于stack exchange,提问作者Selaminko Elam
相关产品推荐
相关产品推荐

