feat(calc): 引入高精度计算工具并替换原有精度处理逻辑

- 新增基于 decimal.js 的高精度计算工具类
- 在 main.js 中全局挂载 $calc 对象供所有组件使用
- 替换项目中多处使用的 preciseCurrencyRound 方法
- 统一处理加减乘除及四舍五入运算,提升数值计算准确性
- 移除旧的 ruoyi.js 中的精确货币舍入函数
- 更新 package.json 添加 decimal.js 依赖项
dev_1.0.0
chenhao 2025-12-04 11:03:24 +08:00
parent fb00d486e0
commit 7afab80e0c
8 changed files with 86 additions and 51 deletions

View File

@ -48,7 +48,8 @@
"vue-cropper": "0.5.5",
"vue-router": "3.4.9",
"vuedraggable": "2.24.3",
"vuex": "3.6.0"
"vuex": "3.6.0",
"decimal.js": "10.4.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.6",

View File

@ -4,7 +4,9 @@ import Cookies from 'js-cookie'
import Element from 'element-ui'
import './assets/styles/element-variables.scss'
import * as Calc from '@/utils/calc'
Vue.prototype.$calc = Calc // 全局挂载
import '@/assets/styles/index.scss' // global css
import '@/assets/styles/ruoyi.scss' // ruoyi css
import App from './App'

View File

@ -0,0 +1,38 @@
import Decimal from 'decimal.js'
// 默认保留小数位(你可根据业务修改,比如金额一般 2 位)
const DEFAULT_DP = 2
// 内部方法:统一转换 Decimal避免 null/undefined 报错
function D(n) {
return new Decimal(n || 0)
}
/**
* 保留小数位 & 四舍五入
* @param value 数值
* @param dp 保留位数默认 DEFAULT_DP
*/
export function toFixed(value, dp = DEFAULT_DP) {
return D(value).toDecimalPlaces(dp).toNumber()
}
// 加法
export function add(a, b, dp = DEFAULT_DP) {
return D(a).plus(b).toDecimalPlaces(dp).toNumber()
}
// 减法
export function sub(a, b, dp = DEFAULT_DP) {
return D(a).minus(b).toDecimalPlaces(dp).toNumber()
}
// 乘法
export function mul(a, b, dp = DEFAULT_DP) {
return D(a).times(b).toDecimalPlaces(dp).toNumber()
}
// 除法
export function div(a, b, dp = DEFAULT_DP) {
return D(a).div(b || 1).toDecimalPlaces(dp).toNumber()
}

View File

@ -226,9 +226,4 @@ export function getNormalPath(p) {
export function blobValidate(data) {
return data.type !== 'application/json'
}
export function preciseCurrencyRound(amount,decimalPlace) {
if (decimalPlace){
return Number(Math.round(amount + `e${decimalPlace}`) + `e-${decimalPlace}`);
}
return Number(Math.round(amount + 'e2') + 'e-2');
}

View File

@ -206,7 +206,6 @@
</template>
<script>
import {preciseCurrencyRound} from "@/utils/ruoyi";
export default {
name: "ConfigInfo",
@ -302,8 +301,9 @@ export default {
if (discount === 1) {
return product.allPrice;
}
const roundedDiscountedUnitPrice = preciseCurrencyRound( product.price * discount);
return roundedDiscountedUnitPrice * product.quantity;
const roundedDiscountedUnitPrice = this.$calc.mul( product.price,discount);
return this.$calc.mul(roundedDiscountedUnitPrice , product.quantity);
},
calculateTotal(productList, discount) {
if (!productList) return 0;

View File

@ -254,7 +254,7 @@
<script>
import SelectProduct from '@/views/system/product/selectProduct.vue';
import {preciseCurrencyRound} from "@/utils/ruoyi";
export default {
name: 'ProductConfig',
@ -322,8 +322,8 @@ export default {
quantity: item.quantity || 0,
taxRate: item.taxRate || 13,
cataloguePriceFormat: this.formatAmount(item.cataloguePrice),
guidanceDiscountFormat: item.guidanceDiscount ? preciseCurrencyRound(item.guidanceDiscount * 100, 2) : '',
discountFormat: item.discount ? preciseCurrencyRound(item.discount * 100, 2) : '',
guidanceDiscountFormat: item.guidanceDiscount ? this.$calc.mul(item.guidanceDiscount, 100) : '',
discountFormat: item.discount ? this.$calc.mul(item.discount, 100) : '',
priceFormat: this.formatAmount(item.price),
allPriceFormat: this.formatAmount(item.allPrice),
catalogueAllPriceFormat: this.formatAmount(item.catalogueAllPrice)
@ -413,9 +413,9 @@ export default {
row.cataloguePrice = product.cataloguePrice;
row.cataloguePriceFormat = this.formatAmount(product.cataloguePrice);
row.guidanceDiscount = product.guidanceDiscount;
row.guidanceDiscountFormat = product.guidanceDiscount ? preciseCurrencyRound(product.guidanceDiscount * 100, 2) : '';
row.guidanceDiscountFormat = product.guidanceDiscount ? this.$calc.mul(product.guidanceDiscount, 100) : '';
row.discount = product.guidanceDiscount || 0;
row.discountFormat = product.guidanceDiscount ? preciseCurrencyRound(product.guidanceDiscount * 100, 2) : '';
row.discountFormat = product.guidanceDiscount ? this.$calc.mul(product.guidanceDiscount, 100) : '';
this.calculateRow(row);
this.emitChange();
@ -439,14 +439,13 @@ export default {
if (isNaN(price)) {
price = 0;
}
row.price = preciseCurrencyRound(price, 2);
row.price = this.$calc.toFixed(price);
row.priceFormat = this.formatAmount(row.price);
// 0
if (row.cataloguePrice && row.cataloguePrice > 0) {
const discount = row.price / row.cataloguePrice;
row.discount = preciseCurrencyRound(discount, 4); // 4
row.discountFormat = preciseCurrencyRound(discount * 100, 2); // 2
row.discount = this.$calc.div(row.price,row.cataloguePrice,4) // 4
row.discountFormat = this.$calc.mul(row.discount ,100, 2); // 2
} else {
// 00
row.discount = 0;
@ -454,9 +453,9 @@ export default {
}
//
row.allPrice = preciseCurrencyRound(row.price * row.quantity, 2);
row.allPrice = this.$calc.mul(row.price , row.quantity, 2);
row.allPriceFormat = this.formatAmount(row.allPrice);
row.catalogueAllPrice = preciseCurrencyRound(row.cataloguePrice * row.quantity, 2);
row.catalogueAllPrice = this.$calc.mul(row.cataloguePrice ,row.quantity, 2);
row.catalogueAllPriceFormat = this.formatAmount(row.catalogueAllPrice);
this.emitChange();
@ -466,17 +465,17 @@ export default {
return;
}
// = *
const discount = row.discountFormat ? row.discountFormat / 100 : 0;
row.discount = preciseCurrencyRound(discount, 4);
row.price =preciseCurrencyRound(row.cataloguePrice * discount, 2);
const discount = row.discountFormat ? this.$calc.div(row.discountFormat , 100,4) : 0;
row.discount = discount;
row.price =this.$calc.mul(row.cataloguePrice , discount, 2);
row.priceFormat = this.formatAmount(row.price);
// = *
row.allPrice = preciseCurrencyRound(row.price * row.quantity, 2);
row.allPrice = this.$calc.mul(row.price , row.quantity, 2);
row.allPriceFormat = this.formatAmount(row.allPrice);
// = *
row.catalogueAllPrice = preciseCurrencyRound(row.cataloguePrice * row.quantity, 2);
row.catalogueAllPrice = this.$calc.mul(row.cataloguePrice , row.quantity, 2);
row.catalogueAllPriceFormat = this.formatAmount(row.catalogueAllPrice);
this.emitChange();
@ -535,7 +534,7 @@ export default {
if (isNaN(cataloguePrice)) {
cataloguePrice = 0;
}
row.cataloguePrice = preciseCurrencyRound(cataloguePrice, 2);
row.cataloguePrice = this.$calc.toFixed(cataloguePrice, 2);
row.cataloguePriceFormat = this.formatAmount(row.cataloguePrice);
// Recalculate based on new catalogue price

View File

@ -188,7 +188,7 @@ import { listAllVendor } from "@/api/base/vendor";
import SelectUser from "@/views/system/user/selectUser";
import SelectProduct from "@/views/system/product/selectProduct";
import { getDicts } from "@/api/system/dict/data";
import {preciseCurrencyRound} from "@/utils/ruoyi";
export default {
name: "PurchaseOrderDetail",
@ -271,7 +271,7 @@ export default {
computed: {
totalAmountWithTax() {
const total = this.form.omsPurchaseOrderItemList?.reduce((acc, cur) => acc + (cur.amountTotal || 0), 0);
this.form.totalAmount=preciseCurrencyRound(total) || 0;
this.form.totalAmount=this.$calc.toFixed(total) || 0;
return this.form.totalAmount;
},
totalAmountWithoutTax() {
@ -280,10 +280,10 @@ export default {
const taxRate = cur.taxRate || 0;
return acc + (amount / (1 + taxRate));
}, 0);
return (preciseCurrencyRound(total)) || 0;
return (this.$calc.toFixed(total)) || 0;
},
totalTaxAmount() {
return (preciseCurrencyRound((this.totalAmountWithTax - this.totalAmountWithoutTax )) ) || 0;
return this.$calc.sub(this.totalAmountWithTax , this.totalAmountWithoutTax ) || 0;
}
},
watch: {
@ -392,8 +392,8 @@ export default {
/** 计算含税小计 */
calculateRowTotal(row) {
if (row.quantity != null && row.price != null) {
row.amountTotal = preciseCurrencyRound(row.quantity * row.price);
row.taxTotal = row.amountTotal - preciseCurrencyRound(row.amountTotal / (1 + row.taxRate));
row.amountTotal = this.$calc.mul(row.quantity , row.price);
row.taxTotal = row.amountTotal - this.$calc.div(row.amountTotal , (1 + row.taxRate));
} else {
row.amountTotal = 0;
row.taxTotal = 0;

View File

@ -226,7 +226,7 @@
import {getPurchaseorder, getPurchaseOrderHistory} from "@/api/sip/purchaseorder";
import {listAllVendor} from "@/api/base/vendor";
import {getDicts} from "@/api/system/dict/data";
import {preciseCurrencyRound} from "@/utils/ruoyi";
export default {
@ -277,7 +277,7 @@ export default {
computed: {
totalAmountWithTax() {
const total = this.form.omsPurchaseOrderItemList?.reduce((acc, cur) => acc + (cur.amountTotal || 0), 0);
return preciseCurrencyRound(total) || 0;
return this.$calc.toFixed(total) || 0;
},
totalAmountWithoutTax() {
const total = this.form.omsPurchaseOrderItemList?.reduce((acc, cur) => {
@ -285,10 +285,10 @@ export default {
const taxRate = cur.taxRate || 0;
return acc + (amount / (1 + taxRate));
}, 0);
return (preciseCurrencyRound(total)) || 0;
return (this.$calc.toFixed(total)) || 0;
},
totalTaxAmount() {
return (preciseCurrencyRound((this.totalAmountWithTax - this.totalAmountWithoutTax))) || 0;
return (this.$calc.sub(this.totalAmountWithTax , this.totalAmountWithoutTax)) || 0;
}
},
watch: {