|
@@ -41,18 +41,18 @@
|
|
|
>
|
|
|
<!-- 可用数量自定义模板 -->
|
|
|
<template slot="availableQuantity" slot-scope="scope">
|
|
|
- <span>{{ formatQuantity(scope.row.availableQuantity) }}</span>
|
|
|
+ <span>{{ formatFloatNumber(scope.row.availableQuantity) }}</span>
|
|
|
</template>
|
|
|
|
|
|
<!-- 确认数量自定义模板 -->
|
|
|
<template slot="confirmQuantity" slot-scope="scope">
|
|
|
- <span>{{ formatQuantity(scope.row.confirmQuantity) }}</span>
|
|
|
+ <span>{{ formatFloatNumber(scope.row.confirmQuantity) }}</span>
|
|
|
</template>
|
|
|
|
|
|
<!-- 单价自定义模板 -->
|
|
|
<template slot="unitPrice" slot-scope="scope">
|
|
|
<el-input
|
|
|
- v-if="editMode"
|
|
|
+ v-if="editMode && isRowEditable(scope.row)"
|
|
|
v-model="scope.row.unitPrice"
|
|
|
size="mini"
|
|
|
style="width: 100%"
|
|
@@ -65,7 +65,7 @@
|
|
|
<!-- 订单数量自定义模板 -->
|
|
|
<template slot="orderQuantity" slot-scope="scope">
|
|
|
<el-input
|
|
|
- v-if="editMode"
|
|
|
+ v-if="editMode && isRowEditable(scope.row)"
|
|
|
v-model="scope.row.orderQuantity"
|
|
|
size="mini"
|
|
|
style="width: 100%"
|
|
@@ -79,7 +79,7 @@
|
|
|
<!-- 税率自定义模板 -->
|
|
|
<template slot="taxRate" slot-scope="scope">
|
|
|
<el-input
|
|
|
- v-if="editMode"
|
|
|
+ v-if="editMode && isRowEditable(scope.row)"
|
|
|
v-model="scope.row.taxRate"
|
|
|
size="mini"
|
|
|
style="width: 100%"
|
|
@@ -87,13 +87,13 @@
|
|
|
@input="validateFloatInput($event, scope.row, 'taxRate', 0, 100)"
|
|
|
@blur="validateAndFormatFloatOnBlur(scope.row, 'taxRate', 0, 100); handleTaxRateChange(scope.row, scope.$index)"
|
|
|
/>
|
|
|
- <span v-else>{{ formatQuantity(scope.row.taxRate) }}%</span>
|
|
|
+ <span v-else>{{ formatTaxRate(scope.row.taxRate, false) }}%</span>
|
|
|
</template>
|
|
|
|
|
|
<!-- 税额自定义模板 -->
|
|
|
<template slot="taxAmount" slot-scope="scope">
|
|
|
<el-input
|
|
|
- v-if="editMode"
|
|
|
+ v-if="editMode && isRowEditable(scope.row)"
|
|
|
v-model="scope.row.taxAmount"
|
|
|
size="mini"
|
|
|
style="width: 100%"
|
|
@@ -107,7 +107,7 @@
|
|
|
<!-- 总金额自定义模板 -->
|
|
|
<template slot="totalAmount" slot-scope="scope">
|
|
|
<el-input
|
|
|
- v-if="editMode"
|
|
|
+ v-if="editMode && isRowEditable(scope.row)"
|
|
|
v-model="scope.row.totalAmount"
|
|
|
size="mini"
|
|
|
style="width: 100%"
|
|
@@ -170,11 +170,7 @@ import {
|
|
|
getMaterialDetailStatusColor
|
|
|
} from './constants'
|
|
|
import MaterialImportDialog from './material-import-dialog.vue'
|
|
|
-import {
|
|
|
- formatAmount as legacyFormatAmount,
|
|
|
- formatQuantity as legacyFormatQuantity
|
|
|
-} from './utils'
|
|
|
-import {
|
|
|
+import {
|
|
|
formatAmount,
|
|
|
formatFloatNumber,
|
|
|
formatIntegerNumber,
|
|
@@ -366,19 +362,19 @@ export default {
|
|
|
if (value === '' || value === '-') {
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 移除所有非数字字符(除了负号)
|
|
|
let cleanValue = value.replace(/[^-\d]/g, '')
|
|
|
-
|
|
|
+
|
|
|
// 确保负号只能在开头
|
|
|
if (cleanValue.indexOf('-') > 0) {
|
|
|
cleanValue = cleanValue.replace(/-/g, '')
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 如果有值,转换为整数
|
|
|
if (cleanValue !== '' && cleanValue !== '-') {
|
|
|
const numValue = parseInt(cleanValue, 10)
|
|
|
-
|
|
|
+
|
|
|
// 检查范围
|
|
|
if (numValue < min) {
|
|
|
row[field] = min
|
|
@@ -389,7 +385,7 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 验证浮点数输入(输入时验证)
|
|
|
* @param {string} value - 输入值
|
|
@@ -404,61 +400,77 @@ export default {
|
|
|
row[field] = value
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 移除无效字符,只保留数字、小数点和负号
|
|
|
let cleanValue = value.replace(/[^-\d.]/g, '')
|
|
|
-
|
|
|
+
|
|
|
// 确保负号只能在开头
|
|
|
if (cleanValue.indexOf('-') > 0) {
|
|
|
cleanValue = cleanValue.replace(/-/g, '')
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 确保只有一个小数点
|
|
|
const parts = cleanValue.split('.')
|
|
|
if (parts.length > 2) {
|
|
|
cleanValue = parts[0] + '.' + parts.slice(1).join('')
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 限制小数位数为2位(但允许继续输入)
|
|
|
if (parts.length === 2 && parts[1].length > 2) {
|
|
|
cleanValue = parts[0] + '.' + parts[1].substring(0, 2)
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 更新字段值,但不进行范围检查(留到blur时处理)
|
|
|
row[field] = cleanValue
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
- * 验证并格式化浮点数(失焦时验证)
|
|
|
- * @param {Object} row - 当前行数据
|
|
|
- * @param {string} field - 字段名
|
|
|
- * @param {number} min - 最小值
|
|
|
- * @param {number} max - 最大值
|
|
|
- * @param {number} precision - 小数位数,默认2位
|
|
|
- */
|
|
|
- validateAndFormatFloatOnBlur(row, field, min = 0, max = 999999.99, precision = 2) {
|
|
|
- const value = row[field]
|
|
|
-
|
|
|
- // 如果是空值或无效输入,设置为最小值
|
|
|
- if (value === '' || value === '.' || value === '-' || value === '-.' || isNaN(parseFloat(value))) {
|
|
|
- row[field] = min
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- const numValue = parseFloat(value)
|
|
|
-
|
|
|
- // 范围检查
|
|
|
- if (numValue < min) {
|
|
|
- row[field] = min
|
|
|
- } else if (numValue > max) {
|
|
|
- row[field] = max
|
|
|
- } else {
|
|
|
- // 格式化为指定小数位数
|
|
|
- const multiplier = Math.pow(10, precision)
|
|
|
- const roundedValue = Math.round(numValue * multiplier) / multiplier
|
|
|
- row[field] = roundedValue
|
|
|
- }
|
|
|
- },
|
|
|
+ * 验证并格式化浮点数(失焦时验证)
|
|
|
+ * @param {Object} row - 当前行数据
|
|
|
+ * @param {string} field - 字段名
|
|
|
+ * @param {number} min - 最小值
|
|
|
+ * @param {number} max - 最大值
|
|
|
+ * @param {number} precision - 小数位数,默认2位
|
|
|
+ */
|
|
|
+ validateAndFormatFloatOnBlur(row, field, min = 0, max = 999999.99, precision = 2) {
|
|
|
+ const value = row[field]
|
|
|
+
|
|
|
+ // 如果是空值或无效输入,设置为最小值
|
|
|
+ if (value === '' || value === '.' || value === '-' || value === '-.' || isNaN(parseFloat(value))) {
|
|
|
+ row[field] = min
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const numValue = parseFloat(value)
|
|
|
+
|
|
|
+ // 范围检查
|
|
|
+ if (numValue < min) {
|
|
|
+ row[field] = min
|
|
|
+ } else if (numValue > max) {
|
|
|
+ row[field] = max
|
|
|
+ } else {
|
|
|
+ // 格式化为指定小数位数
|
|
|
+ const multiplier = Math.pow(10, precision)
|
|
|
+ const roundedValue = Math.round(numValue * multiplier) / multiplier
|
|
|
+ row[field] = roundedValue
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断行是否可编辑
|
|
|
+ * @description 根据数据来源判断物料明细行是否允许编辑,远程数据(订单ID获取)不可编辑
|
|
|
+ * @param {MaterialDetailRecord} row - 物料明细行数据
|
|
|
+ * @returns {boolean} 是否可编辑,true表示可编辑,false表示不可编辑
|
|
|
+ */
|
|
|
+ isRowEditable(row) {
|
|
|
+ // 如果没有数据来源信息,默认可编辑
|
|
|
+ if (!row || !row.dataSource) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ // 只有导入的物料可以编辑,远程数据(订单ID获取)不可编辑
|
|
|
+ return row.dataSource === MaterialDetailDataSource.IMPORTED
|
|
|
+ },
|
|
|
|
|
|
/**
|
|
|
* 处理导入物料按钮点击事件
|
|
@@ -526,44 +538,37 @@ export default {
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
- * 格式化金额显示
|
|
|
- * @description 格式化金额为带货币符号的字符串,保留2位小数
|
|
|
- * @param {number|string|null|undefined} amount - 金额数值
|
|
|
- * @returns {string} 格式化后的金额字符串
|
|
|
+ * 格式化浮点数显示
|
|
|
+ * @description 格式化浮点数为4位小数的字符串
|
|
|
+ * @param {number|string|null|undefined} value - 数值
|
|
|
+ * @returns {string} 格式化后的字符串
|
|
|
*/
|
|
|
- formatAmount(amount) {
|
|
|
- return formatAmount(amount, true)
|
|
|
+ formatFloatNumber(value) {
|
|
|
+ return formatFloatNumber(value)
|
|
|
},
|
|
|
-
|
|
|
- /**
|
|
|
- * 格式化数量显示
|
|
|
- * @description 格式化数量为4位浮点型字符串
|
|
|
- * @param {number|string|null|undefined} quantity - 数量数值
|
|
|
- * @param {boolean} [isInteger=false] - 是否为整数类型
|
|
|
- * @returns {string} 格式化后的数量字符串
|
|
|
- */
|
|
|
- formatQuantity(quantity, isInteger = false) {
|
|
|
- return isInteger ? formatIntegerNumber(quantity) : formatFloatNumber(quantity)
|
|
|
- },
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
- * 格式化单价显示
|
|
|
- * @description 格式化单价为4位浮点型字符串
|
|
|
- * @param {number|string|null|undefined} price - 单价数值
|
|
|
- * @returns {string} 格式化后的单价字符串
|
|
|
+ * 格式化金额显示
|
|
|
+ * @description 格式化金额为带货币符号的字符串
|
|
|
+ * @param {number|string|null|undefined} amount - 金额数值
|
|
|
+ * @param {boolean} withSymbol - 是否显示货币符号
|
|
|
+ * @returns {string} 格式化后的金额字符串
|
|
|
*/
|
|
|
- formatUnitPrice(price) {
|
|
|
- return formatUnitPrice(price)
|
|
|
+ formatAmount(amount, withSymbol = true) {
|
|
|
+ return formatAmount(amount, withSymbol)
|
|
|
},
|
|
|
-
|
|
|
+
|
|
|
+ formatUnitPrice,
|
|
|
+ formatTaxRate,
|
|
|
+
|
|
|
/**
|
|
|
- * 格式化税率显示
|
|
|
- * @description 格式化税率为百分比字符串
|
|
|
- * @param {number|string|null|undefined} rate - 税率数值
|
|
|
- * @returns {string} 格式化后的税率字符串
|
|
|
+ * 格式化整数显示
|
|
|
+ * @description 格式化整数为字符串
|
|
|
+ * @param {number|string|null|undefined} value - 整数数值
|
|
|
+ * @returns {string} 格式化后的整数字符串
|
|
|
*/
|
|
|
- formatTaxRate(rate) {
|
|
|
- return formatTaxRate(rate, true)
|
|
|
+ formatIntegerNumber(value) {
|
|
|
+ return formatIntegerNumber(value)
|
|
|
},
|
|
|
|
|
|
/**
|
|
@@ -647,13 +652,13 @@ export default {
|
|
|
try {
|
|
|
// 执行自动计算
|
|
|
const calculatedRow = this.calculateAmounts(row)
|
|
|
-
|
|
|
+
|
|
|
// 触发更新事件,传递计算后的数据
|
|
|
this.$emit('material-update', { row: calculatedRow, index })
|
|
|
-
|
|
|
+
|
|
|
// 完成编辑
|
|
|
done(calculatedRow)
|
|
|
-
|
|
|
+
|
|
|
this.$message.success('物料明细更新成功')
|
|
|
} catch (error) {
|
|
|
this.$message.error('更新失败:' + error.message)
|
|
@@ -753,10 +758,10 @@ export default {
|
|
|
// 如果编辑的是影响计算的字段,执行自动计算
|
|
|
if (['orderQuantity', 'unitPrice', 'taxRate'].includes(prop)) {
|
|
|
const calculatedRow = this.calculateAmounts(row)
|
|
|
-
|
|
|
+
|
|
|
// 更新行数据
|
|
|
Object.assign(row, calculatedRow)
|
|
|
-
|
|
|
+
|
|
|
// 触发更新事件
|
|
|
this.$emit('material-update', { row: calculatedRow, index: this.getCurrentRowIndex(row) })
|
|
|
}
|
|
@@ -770,29 +775,27 @@ export default {
|
|
|
*/
|
|
|
calculateAmounts(row) {
|
|
|
const calculatedRow = { ...row }
|
|
|
-
|
|
|
+
|
|
|
// 验证并获取数值
|
|
|
const quantityValidation = validateNumber(calculatedRow.orderQuantity)
|
|
|
const priceValidation = validateNumber(calculatedRow.unitPrice)
|
|
|
const rateValidation = validateNumber(calculatedRow.taxRate)
|
|
|
-
|
|
|
+
|
|
|
const orderQuantity = quantityValidation.isValid ? Math.round(quantityValidation.value) : 0
|
|
|
const unitPrice = priceValidation.isValid ? priceValidation.value : 0
|
|
|
const taxRate = rateValidation.isValid ? rateValidation.value : 0
|
|
|
-
|
|
|
+
|
|
|
// 使用精确计算:订单数量 * 单价
|
|
|
const totalAmount = preciseMultiply(orderQuantity, unitPrice)
|
|
|
calculatedRow.totalAmount = preciseRound(totalAmount, 2)
|
|
|
-
|
|
|
+
|
|
|
// 使用精确计算:总金额 * 税率 / 100
|
|
|
const taxAmount = preciseMultiply(totalAmount, preciseDivide(taxRate, 100))
|
|
|
calculatedRow.taxAmount = preciseRound(taxAmount, 2)
|
|
|
-
|
|
|
+
|
|
|
return calculatedRow
|
|
|
},
|
|
|
|
|
|
-
|
|
|
-
|
|
|
/**
|
|
|
* 计算税额
|
|
|
* @description 根据总金额和税率计算税额,使用精确计算避免浮点数精度问题
|
|
@@ -802,11 +805,11 @@ export default {
|
|
|
calculateTaxAmount(row) {
|
|
|
const amountValidation = validateNumber(row.totalAmount)
|
|
|
const rateValidation = validateNumber(row.taxRate)
|
|
|
-
|
|
|
+
|
|
|
if (amountValidation.isValid && rateValidation.isValid) {
|
|
|
const totalAmount = amountValidation.value
|
|
|
const taxRate = rateValidation.value
|
|
|
-
|
|
|
+
|
|
|
// 使用精确计算:总金额 * 税率 / 100
|
|
|
const taxAmount = preciseMultiply(totalAmount, preciseDivide(taxRate, 100))
|
|
|
row.taxAmount = preciseRound(taxAmount, 2)
|