|
@@ -30,7 +30,6 @@
|
|
|
<div class="material-detail-content">
|
|
|
<avue-crud
|
|
|
ref="materialDetailCrud"
|
|
|
- v-model="formData"
|
|
|
:data="currentPageData"
|
|
|
:option="tableOption"
|
|
|
:page.sync="page"
|
|
@@ -38,11 +37,62 @@
|
|
|
@current-change="handleCurrentChange"
|
|
|
@size-change="handleSizeChange"
|
|
|
@row-del="handleRowDelete"
|
|
|
+ @row-update="handleRowUpdate"
|
|
|
>
|
|
|
+ <!-- 订单数量自定义模板 -->
|
|
|
+ <template slot="orderQuantity" slot-scope="scope">
|
|
|
+ <el-input
|
|
|
+ v-if="editMode"
|
|
|
+ v-model="scope.row.orderQuantity"
|
|
|
+ size="mini"
|
|
|
+ style="width: 100%"
|
|
|
+ placeholder="请输入订单数量"
|
|
|
+ @input="validateIntegerInput($event, scope.row, 'orderQuantity')"
|
|
|
+ @blur="handleQuantityChange(scope.row, scope.$index)"
|
|
|
+ />
|
|
|
+ <span v-else>{{ scope.row.orderQuantity }}</span>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 税率自定义模板 -->
|
|
|
+ <template slot="taxRate" slot-scope="scope">
|
|
|
+ <el-input
|
|
|
+ v-if="editMode"
|
|
|
+ v-model="scope.row.taxRate"
|
|
|
+ size="mini"
|
|
|
+ style="width: 100%"
|
|
|
+ placeholder="请输入税率"
|
|
|
+ @input="validateFloatInput($event, scope.row, 'taxRate', 0, 100)"
|
|
|
+ @blur="handleTaxRateChange(scope.row, scope.$index)"
|
|
|
+ />
|
|
|
+ <span v-else>{{ scope.row.taxRate }}%</span>
|
|
|
+ </template>
|
|
|
|
|
|
- <!-- 总金额列自定义渲染 -->
|
|
|
- <template slot="totalAmount" slot-scope="{ row }">
|
|
|
- <span class="amount-text">{{ formatAmount(row.totalAmount) }}</span>
|
|
|
+ <!-- 税额自定义模板 -->
|
|
|
+ <template slot="taxAmount" slot-scope="scope">
|
|
|
+ <el-input
|
|
|
+ v-if="editMode"
|
|
|
+ v-model="scope.row.taxAmount"
|
|
|
+ size="mini"
|
|
|
+ style="width: 100%"
|
|
|
+ placeholder="请输入税额"
|
|
|
+ @input="validateFloatInput($event, scope.row, 'taxAmount')"
|
|
|
+ @blur="handleTaxAmountChange(scope.row, scope.$index)"
|
|
|
+ />
|
|
|
+ <span v-else>{{ scope.row.taxAmount }}</span>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 总金额自定义模板 -->
|
|
|
+ <template slot="totalAmount" slot-scope="scope">
|
|
|
+ <el-input
|
|
|
+ v-if="editMode"
|
|
|
+ v-model="scope.row.totalAmount"
|
|
|
+ size="mini"
|
|
|
+ style="width: 100%"
|
|
|
+ placeholder="请输入总金额"
|
|
|
+ @input="validateFloatInput($event, scope.row, 'totalAmount')"
|
|
|
+ @blur="handleTotalAmountChange(scope.row, scope.$index)"
|
|
|
+ />
|
|
|
+ <span v-else>{{ scope.row.totalAmount }}</span>
|
|
|
</template>
|
|
|
|
|
|
<!-- 明细状态列自定义渲染 -->
|
|
@@ -145,6 +195,14 @@ export default {
|
|
|
*/
|
|
|
props: {
|
|
|
/**
|
|
|
+ * 是否为编辑模式 - 控制表格是否可编辑
|
|
|
+ * @type {boolean}
|
|
|
+ */
|
|
|
+ editMode: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ /**
|
|
|
* 订单ID - 关联的订单唯一标识符
|
|
|
* @type {string|number|null}
|
|
|
*/
|
|
@@ -196,7 +254,19 @@ export default {
|
|
|
* 导入弹窗显示状态 - 控制物料导入弹窗的显示/隐藏
|
|
|
* @type {boolean}
|
|
|
*/
|
|
|
- importDialogVisible: false
|
|
|
+ importDialogVisible: false,
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 正在编辑的行数据 - 用于记录编辑前的状态
|
|
|
+ * @type {MaterialDetailRecord|null}
|
|
|
+ */
|
|
|
+ editingRow: null,
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 正在编辑的属性名 - 用于记录当前编辑的字段
|
|
|
+ * @type {string|null}
|
|
|
+ */
|
|
|
+ editingProp: null
|
|
|
}
|
|
|
},
|
|
|
|
|
@@ -206,10 +276,10 @@ export default {
|
|
|
computed: {
|
|
|
/**
|
|
|
* 表格配置选项 - 获取AvueJS表格的配置对象
|
|
|
- * @returns {AvueTableOption} AvueJS表格配置对象,已配置为只读模式
|
|
|
+ * @returns {AvueTableOption} AvueJS表格配置对象,根据编辑模式配置
|
|
|
*/
|
|
|
tableOption() {
|
|
|
- return getMaterialDetailOption()
|
|
|
+ return getMaterialDetailOption(this.editMode)
|
|
|
},
|
|
|
|
|
|
/**
|
|
@@ -246,6 +316,93 @@ export default {
|
|
|
*/
|
|
|
methods: {
|
|
|
/**
|
|
|
+ * 验证整数输入
|
|
|
+ * @param {string} value - 输入值
|
|
|
+ * @param {Object} row - 当前行数据
|
|
|
+ * @param {string} field - 字段名
|
|
|
+ * @param {number} min - 最小值
|
|
|
+ * @param {number} max - 最大值
|
|
|
+ */
|
|
|
+ validateIntegerInput(value, row, field, min = 0, max = 999999) {
|
|
|
+ // 允许空值和部分输入(如正在输入的数字)
|
|
|
+ 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
|
|
|
+ } else if (numValue > max) {
|
|
|
+ row[field] = max
|
|
|
+ } else {
|
|
|
+ row[field] = numValue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证浮点数输入
|
|
|
+ * @param {string} value - 输入值
|
|
|
+ * @param {Object} row - 当前行数据
|
|
|
+ * @param {string} field - 字段名
|
|
|
+ * @param {number} min - 最小值
|
|
|
+ * @param {number} max - 最大值
|
|
|
+ */
|
|
|
+ validateFloatInput(value, row, field, min = 0, max = 999999.99) {
|
|
|
+ // 允许空值和部分输入
|
|
|
+ if (value === '' || value === '-' || 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)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有有效数值,进行范围检查
|
|
|
+ if (cleanValue !== '' && cleanValue !== '-' && cleanValue !== '.') {
|
|
|
+ const numValue = parseFloat(cleanValue)
|
|
|
+ if (!isNaN(numValue)) {
|
|
|
+ if (numValue < min) {
|
|
|
+ row[field] = min
|
|
|
+ } else if (numValue > max) {
|
|
|
+ row[field] = max
|
|
|
+ } else {
|
|
|
+ // 限制为2位小数
|
|
|
+ const roundedValue = Math.round(numValue * 100) / 100
|
|
|
+ row[field] = roundedValue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
* 处理导入物料按钮点击事件
|
|
|
* @description 打开物料导入弹窗,允许用户选择和导入物料
|
|
|
* @returns {void}
|
|
@@ -384,6 +541,173 @@ export default {
|
|
|
*/
|
|
|
handleRowDelete(row, index) {
|
|
|
this.handleDeleteMaterial(row, index)
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理行更新事件
|
|
|
+ * @description 当用户编辑表格行数据时触发,执行自动计算逻辑
|
|
|
+ * @param {MaterialDetailRecord} row - 更新后的行数据
|
|
|
+ * @param {number} index - 行索引
|
|
|
+ * @param {boolean} done - 完成回调函数
|
|
|
+ * @returns {void}
|
|
|
+ * @emits material-update
|
|
|
+ */
|
|
|
+ async handleRowUpdate(row, index, done) {
|
|
|
+ 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)
|
|
|
+ done(false)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理订单数量变更
|
|
|
+ * @description 当订单数量发生变化时,自动计算总金额和税额
|
|
|
+ * @param {MaterialDetailRecord} row - 行数据
|
|
|
+ * @param {number} index - 行索引
|
|
|
+ * @returns {void}
|
|
|
+ */
|
|
|
+ handleQuantityChange(row, index) {
|
|
|
+ const calculatedRow = this.calculateAmounts(row)
|
|
|
+ Object.assign(row, calculatedRow)
|
|
|
+ this.$emit('material-update', { row, index })
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理税率变更
|
|
|
+ * @description 当税率发生变化时,重新计算税额
|
|
|
+ * @param {MaterialDetailRecord} row - 行数据
|
|
|
+ * @param {number} index - 行索引
|
|
|
+ * @returns {void}
|
|
|
+ */
|
|
|
+ handleTaxRateChange(row, index) {
|
|
|
+ this.calculateTaxAmount(row)
|
|
|
+ this.$emit('material-update', { row, index })
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理税额变更
|
|
|
+ * @description 当税额手动修改时,反推税率
|
|
|
+ * @param {MaterialDetailRecord} row - 行数据
|
|
|
+ * @param {number} index - 行索引
|
|
|
+ * @returns {void}
|
|
|
+ */
|
|
|
+ handleTaxAmountChange(row, index) {
|
|
|
+ // 当税额手动修改时,反推税率
|
|
|
+ if (row.totalAmount && row.totalAmount > 0) {
|
|
|
+ row.taxRate = ((row.taxAmount || 0) / row.totalAmount * 100).toFixed(2)
|
|
|
+ }
|
|
|
+ this.$emit('material-update', { row, index })
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理总金额变更
|
|
|
+ * @description 当总金额手动修改时,重新计算税额
|
|
|
+ * @param {MaterialDetailRecord} row - 行数据
|
|
|
+ * @param {number} index - 行索引
|
|
|
+ * @returns {void}
|
|
|
+ */
|
|
|
+ handleTotalAmountChange(row, index) {
|
|
|
+ // 当总金额手动修改时,重新计算税额
|
|
|
+ this.calculateTaxAmount(row)
|
|
|
+ this.$emit('material-update', { row, index })
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理单元格编辑开始事件
|
|
|
+ * @description 当用户开始编辑单元格时触发
|
|
|
+ * @param {MaterialDetailRecord} row - 编辑的行数据
|
|
|
+ * @param {string} prop - 编辑的属性名
|
|
|
+ * @param {*} value - 当前值
|
|
|
+ * @returns {void}
|
|
|
+ */
|
|
|
+ handleCellEditStart(row, prop, value) {
|
|
|
+ // 记录编辑前的值,用于计算变化
|
|
|
+ this.editingRow = { ...row }
|
|
|
+ this.editingProp = prop
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理单元格编辑结束事件
|
|
|
+ * @description 当用户结束编辑单元格时触发,执行实时计算
|
|
|
+ * @param {MaterialDetailRecord} row - 编辑后的行数据
|
|
|
+ * @param {string} prop - 编辑的属性名
|
|
|
+ * @param {*} value - 新值
|
|
|
+ * @returns {void}
|
|
|
+ */
|
|
|
+ handleCellEditEnd(row, prop, value) {
|
|
|
+ // 如果编辑的是影响计算的字段,执行自动计算
|
|
|
+ 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) })
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自动计算金额
|
|
|
+ * @description 根据订单数量、单价和税率自动计算总金额和税额
|
|
|
+ * @param {MaterialDetailRecord} row - 物料明细记录
|
|
|
+ * @returns {MaterialDetailRecord} 计算后的物料明细记录
|
|
|
+ */
|
|
|
+ calculateAmounts(row) {
|
|
|
+ const calculatedRow = { ...row }
|
|
|
+
|
|
|
+ // 获取数值,确保为有效数字
|
|
|
+ const orderQuantity = parseInt(calculatedRow.orderQuantity) || 0 // 订单数量为整数
|
|
|
+ const unitPrice = parseFloat(calculatedRow.unitPrice) || 0
|
|
|
+ const taxRate = parseFloat(calculatedRow.taxRate) || 0
|
|
|
+
|
|
|
+ // 计算总金额:订单数量 * 单价(保留2位小数)
|
|
|
+ const totalAmount = orderQuantity * unitPrice
|
|
|
+ calculatedRow.totalAmount = Math.round(totalAmount * 100) / 100
|
|
|
+
|
|
|
+ // 计算税额:总金额 * 税率 / 100(保留2位小数)
|
|
|
+ const taxAmount = totalAmount * (taxRate / 100)
|
|
|
+ calculatedRow.taxAmount = Math.round(taxAmount * 100) / 100
|
|
|
+
|
|
|
+ return calculatedRow
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算税额
|
|
|
+ * @description 根据总金额和税率计算税额
|
|
|
+ * @param {MaterialDetailRecord} row - 物料明细记录
|
|
|
+ * @returns {void}
|
|
|
+ */
|
|
|
+ calculateTaxAmount(row) {
|
|
|
+ if (row.totalAmount && row.taxRate) {
|
|
|
+ const totalAmount = parseFloat(row.totalAmount) || 0
|
|
|
+ const taxRate = parseFloat(row.taxRate) || 0
|
|
|
+ const taxAmount = totalAmount * (taxRate / 100)
|
|
|
+ row.taxAmount = Math.round(taxAmount * 100) / 100 // 保留2位小数
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前行索引
|
|
|
+ * @description 根据行数据获取在当前页中的索引
|
|
|
+ * @param {MaterialDetailRecord} row - 行数据
|
|
|
+ * @returns {number} 行索引
|
|
|
+ */
|
|
|
+ getCurrentRowIndex(row) {
|
|
|
+ return this.currentPageData.findIndex(item => item.id === row.id)
|
|
|
}
|
|
|
}
|
|
|
}
|