From c525b81f2340903f36aaa44a934a9876dc7f2261 Mon Sep 17 00:00:00 2001 From: "han\\hanst" Date: Fri, 19 Dec 2025 16:11:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=B9=E9=87=8F=E8=A3=85=E7=AE=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/ecss/ecss.js | 2 + src/views/modules/ecss/codelnotifyConfirm.vue | 1156 ++++++++++++++++- 2 files changed, 1156 insertions(+), 2 deletions(-) diff --git a/src/api/ecss/ecss.js b/src/api/ecss/ecss.js index eba5109..8dba6c5 100644 --- a/src/api/ecss/ecss.js +++ b/src/api/ecss/ecss.js @@ -151,5 +151,7 @@ export const getPartPackageProperties = data => createAPI(`/ecss/coDel/getPartPa // 导入物料包装属性(每卷数量、每箱卷数、每卷重量、箱重量、箱类型) export const importPartPackageProperties = data => createAPI(`/ecss/coDel/importPartPackageProperties`,'post',data) +// 合箱接口 +export const mergeBox = data => createAPI(`/ecss/coDel/mergeBox`,'post',data) diff --git a/src/views/modules/ecss/codelnotifyConfirm.vue b/src/views/modules/ecss/codelnotifyConfirm.vue index 418ae84..372056e 100644 --- a/src/views/modules/ecss/codelnotifyConfirm.vue +++ b/src/views/modules/ecss/codelnotifyConfirm.vue @@ -164,6 +164,7 @@ {{'一键装箱'}} {{'导入装箱单'}} {{'装箱'}} + {{'批量装箱'}} {{'栈板维护'}} {{'导出装箱数据'}} + + +
+ +
+ + 合并选中的箱 ({{ mergeBoxSelection.length }}) + + + 取消合并 + +
+ 总箱数: {{ getUniqueBoxCount() }} + + 已修改 {{ mergeBoxModifiedCount }} 处 + +
+
+ + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
栈板维护 @@ -633,7 +816,7 @@ updateEcssDel, cancerConfirmEcssDel, searchCoDelPalletDataNew, - + selectBoxList, searchEcssCoDelPalletHeaderData, searchEcssCoDelPalletDetailData, savePalletHeader, @@ -646,7 +829,8 @@ searchPalletList, updateExportFlag, updateCodelPalletHeaderPalletQty, - getPartPackageProperties + getPartPackageProperties, + mergeBox }from "@/api/ecss/ecss.js" import {getBuList}from '@/api/factory/site.js' import excel from "@/utils/excel-util.js"; @@ -711,6 +895,18 @@ boxRemnant: false, }, + // 合箱批量编辑相关 + mergeBoxDialogVisible: false, + mergeBoxLoading: false, + mergeBoxSaving: false, + mergeBoxTableData: [], // 扁平化的表格数据 + mergeBoxSelection: [], // 已选择的箱(用于合箱) + mergeBoxOriginalData: {}, // 原始数据快照(用于对比是否修改) + mergeBoxModifiedBoxes: {}, // 修改过的Box记录 + mergeBoxMergeHistory: [], // 合并历史记录(用于取消合并) + mergeBoxModifiedDetails: {}, // 修改过的明细记录 + nextItemNo: 1, // 下一个序号 + // 栈板维护相关 palletMaintenanceModelFlag: false, palletMaintenanceRecords: [], @@ -1957,6 +2153,14 @@ }); // 如果有任何明细有有效的rolls值,则禁用总rolls输入框 return hasValidRolls; + }, + + /** + * 合箱批量编辑修改数量 + */ + mergeBoxModifiedCount() { + return Object.keys(this.mergeBoxModifiedBoxes).length + + Object.keys(this.mergeBoxModifiedDetails).length; } }, @@ -3077,6 +3281,849 @@ }) }, + // ========== 合箱批量编辑相关方法 ========== + + /** + * 打开合箱批量编辑弹出框 + */ + openMergeBoxDialog() { + if(this.currentRow.site===''||this.currentRow.site==null){ + this.$alert('请先选择发货通知单!', '错误', { + confirmButtonText: '确定' + }) + return false + } + this.mergeBoxDialogVisible = true + }, + + /** + * 加载合箱批量编辑数据 + * 将发货通知单明细扁平化为表格数据,每条明细默认是一个箱子 + */ + async loadMergeBoxData() { + this.mergeBoxLoading = true + this.mergeBoxTableData = [] + this.mergeBoxSelection = [] + this.mergeBoxOriginalData = {} + this.mergeBoxModifiedBoxes = {} + this.mergeBoxModifiedDetails = {} + this.mergeBoxMergeHistory = [] + this.nextItemNo = 1 + + try { + // 每次都重新查询最新的发货通知单明细数据 + const detailResponse = await searchEcssCoDelNotifyDetail(this.currentRow) + const detailList = (detailResponse.data && detailResponse.data.code === 0) + ? detailResponse.data.rows || [] + : [] + + if (detailList.length === 0) { + this.$message.warning('当前发货通知单没有明细数据') + this.mergeBoxLoading = false + return + } + + // 处理明细数据 - 每条明细作为一个单独的箱 + const tableData = [] + let itemNo = 1 + + // 只处理剩余数量(surplus_qty)大于0的明细 + const detailsToProcess = [] + detailList.forEach(detail => { + const surplusQty = Number(detail.surplusQty) || 0 + + // 只处理剩余数量大于0的明细 + if (surplusQty > 0) { + detailsToProcess.push({ + detail: detail, + unpackedQty: surplusQty // 使用剩余数量 + }) + } + }) + + if (detailsToProcess.length === 0) { + this.$message.warning('所有明细都已装箱完毕') + this.mergeBoxLoading = false + return + } + + // 创建表格数据 + // searchEcssCoDelNotifyDetail已经返回了rollqty、boxrolls、boxweight,直接使用 + detailsToProcess.forEach(({detail, unpackedQty}) => { + const boxKey = `box_${itemNo}` + + // 直接从detail中读取包装属性(小写字段名) + const rollQty = Number(detail.rollqty) || 0 + const boxRolls = Number(detail.boxrolls) || 0 + const boxWeight = Number(detail.boxweight) || 0 + + // 计算默认值 + let detailRolls = 0 + let box_qty = 1 + let grossWeight = 0 + let netWeight = 0 + let rolls = 0 + + // 1. 根据数量计算明细rolls(与calculateRolls保持一致) + if (rollQty > 0) { + detailRolls = parseFloat((unpackedQty / rollQty).toFixed(4)) + // 总rolls = 明细rolls四舍五入取整(初始时每箱只有一条明细) + rolls = Math.round(detailRolls) + } + + // 2. 根据数量计算箱数 + if (rollQty > 0 && boxRolls > 0) { + const eaPerBox = rollQty * boxRolls + box_qty = Math.ceil(unpackedQty / eaPerBox) + } + + // 3. 根据箱数计算毛重 + if (boxWeight > 0) { + grossWeight = parseFloat((box_qty * boxWeight).toFixed(3)) + } + + // 4. 根据毛重计算净重(与装箱按钮保持一致) + netWeight = parseFloat((grossWeight - (box_qty / 2)).toFixed(3)) + + // 创建一行数据(Box和明细合并在一起) + const rowData = { + // Box信息 + _boxKey: boxKey, + _isFirstRowOfBox: true, + _rowSpan: 1, + item_no: itemNo, + box_qty: box_qty, + grossWeight: grossWeight, + netWeight: netWeight, + rolls: rolls, + // 明细信息 + _detailKey: `${detail.itemNo}_${detail.customerPO}_${detail.pn}`, + _hasDetail: true, + itemNo: detail.itemNo, + poNo: detail.customerPO, + pn: detail.pn, + qty: unpackedQty, + detailRolls: detailRolls, + readyDate: detail.readyDate, + partDesc: detail.partDesc, + // 缓存物料属性(用于后续自动计算) + rollQtyCache: rollQty, + boxRollsCache: boxRolls, + boxWeightCache: boxWeight + } + + tableData.push(rowData) + + // 保存Box原始数据 + this.mergeBoxOriginalData[boxKey] = { + box_qty: box_qty, + grossWeight: grossWeight, + netWeight: netWeight, + rolls: rolls, + boxWeight: boxWeight + } + + // 保存明细原始数据 + this.mergeBoxOriginalData[rowData._detailKey] = { + qty: unpackedQty, + detailRolls: detailRolls + } + + itemNo++ + }) + + this.mergeBoxTableData = tableData + this.nextItemNo = itemNo + + } catch (error) { + console.error('加载合箱数据失败:', error) + this.$message.error('加载数据失败') + } finally { + this.mergeBoxLoading = false + } + }, + + /** + * 选择变化事件处理 + */ + handleMergeBoxSelectionChange(selection) { + this.mergeBoxSelection = selection + }, + + /** + * 合并选中的箱 + */ + doMergeBoxes() { + if (this.mergeBoxSelection.length < 2) { + this.$message.warning('请至少选择2个箱进行合并') + return + } + + // 获取选中箱的所有行数据 + const selectedBoxKeys = this.mergeBoxSelection.map(row => row._boxKey) + const rowsToMerge = this.mergeBoxTableData.filter(row => + selectedBoxKeys.includes(row._boxKey) + ) + + // 保存合并前的状态(用于取消合并) + const mergeHistoryItem = { + timestamp: Date.now(), + boxes: [] + } + + // 记录每个被合并的箱的原始状态 + selectedBoxKeys.forEach(boxKey => { + const boxRows = this.mergeBoxTableData.filter(row => row._boxKey === boxKey) + if (boxRows.length > 0) { + mergeHistoryItem.boxes.push({ + boxKey: boxKey, + item_no: boxRows[0].item_no, + box_qty: boxRows[0].box_qty, + grossWeight: boxRows[0].grossWeight, + netWeight: boxRows[0].netWeight, + rolls: boxRows[0].rolls, + rowCount: boxRows.length, + details: boxRows.map(r => ({ + _detailKey: r._detailKey, + itemNo: r.itemNo, + poNo: r.poNo, + pn: r.pn, + qty: r.qty, + detailRolls: r.detailRolls + })) + }) + } + }) + + this.mergeBoxMergeHistory.push(mergeHistoryItem) + + // 使用第一个箱的序号作为合并后的序号 + const targetItemNo = this.mergeBoxSelection[0].item_no + const targetBoxKey = this.mergeBoxSelection[0]._boxKey + + // 计算合并后的累加值 + let totalBoxQty = 0 + let totalGrossWeight = 0 + let totalNetWeight = 0 + let totalRolls = 0 + + // 按箱Key分组,计算每个箱的值 + const boxMap = new Map() + rowsToMerge.forEach(row => { + if (!boxMap.has(row._boxKey)) { + boxMap.set(row._boxKey, { + box_qty: parseFloat(row.box_qty) || 0, + grossWeight: parseFloat(row.grossWeight) || 0, + netWeight: parseFloat(row.netWeight) || 0, + rolls: parseFloat(row.rolls) || 0 + }) + } + }) + + // 累加所有箱的值 + boxMap.forEach(boxData => { + totalBoxQty += boxData.box_qty + totalGrossWeight += boxData.grossWeight + totalNetWeight += boxData.netWeight + }) + + // 累加所有明细的rolls + rowsToMerge.forEach(row => { + if (row.detailRolls && row.detailRolls > 0) { + totalRolls += parseFloat(row.detailRolls) + } + }) + + // 四舍五入取整(与装箱按钮保持一致) + const finalRolls = Math.round(totalRolls) + + // 更新所有被合并的行 + rowsToMerge.forEach((row, index) => { + row.item_no = targetItemNo + row._boxKey = targetBoxKey + row._isFirstRowOfBox = index === 0 + row._rowSpan = index === 0 ? rowsToMerge.length : 0 + + // 所有行使用累加后的值 + row.box_qty = totalBoxQty + row.grossWeight = parseFloat(totalGrossWeight.toFixed(3)) + row.netWeight = parseFloat(totalNetWeight.toFixed(3)) + row.rolls = finalRolls + }) + + // 合并后重新编号所有箱 + this.renumberBoxes() + + // 按序号重新排序表格数据 + this.sortTableDataByItemNo() + + // 清空选择 + this.$refs.mergeBoxTable.clearSelection() + this.mergeBoxSelection = [] + + this.$message.success(`已合并 ${selectedBoxKeys.length} 个箱(箱数: ${totalBoxQty}, 毛重: ${totalGrossWeight.toFixed(3)}, 净重: ${totalNetWeight.toFixed(3)}),当前共 ${this.getUniqueBoxCount()} 个箱`) + }, + + /** + * 取消选中箱的合并 + */ + doUnmergeSelectedBoxes() { + if (this.mergeBoxSelection.length === 0) { + this.$message.warning('请先选择要取消合并的箱') + return + } + + const selectedBoxKeys = this.mergeBoxSelection.map(row => row._boxKey) + + // 找到选中箱的所有明细 + const rowsToUnmerge = this.mergeBoxTableData.filter(row => + selectedBoxKeys.includes(row._boxKey) && row._hasDetail + ) + + if (rowsToUnmerge.length === 0) { + this.$message.warning('所选箱没有明细数据') + return + } + + // 获取原箱的序号,用于为新箱分配临时序号 + const originalItemNo = rowsToUnmerge[0].item_no + + // 为每条明细创建独立的箱,恢复成初始化时的原始数据 + const newRows = [] + rowsToUnmerge.forEach((row, index) => { + const newBoxKey = `box_${Date.now()}_${index}` + + // 从mergeBoxOriginalData恢复该明细的原始数据 + const detailOriginalData = this.mergeBoxOriginalData[row._detailKey] + + let box_qty, grossWeight, netWeight, rolls, detailRolls + + // 始终使用缓存的包装属性重新计算(确保恢复到初始值) + const rollQty = row.rollQtyCache || 0 + const boxRolls = row.boxRollsCache || 0 + const boxWeight = row.boxWeightCache || 0 + const qty = row.qty || 0 + + // 恢复明细rolls + if (detailOriginalData && detailOriginalData.detailRolls !== undefined) { + detailRolls = detailOriginalData.detailRolls + } else if (rollQty > 0) { + detailRolls = parseFloat((qty / rollQty).toFixed(4)) + } else { + detailRolls = 0 + } + + // 计算总rolls(初始时每箱只有一条明细) + rolls = Math.round(detailRolls) + + // 计算箱数 + if (rollQty > 0 && boxRolls > 0) { + const eaPerBox = rollQty * boxRolls + box_qty = Math.ceil(qty / eaPerBox) + } else { + box_qty = 1 + } + + // 计算毛重 + if (boxWeight > 0) { + grossWeight = parseFloat((box_qty * boxWeight).toFixed(3)) + } else { + grossWeight = 0 + } + + // 计算净重 + netWeight = parseFloat((grossWeight - (box_qty / 2)).toFixed(3)) + + const newRow = { + ...row, + _boxKey: newBoxKey, + _isFirstRowOfBox: true, + _rowSpan: 1, + item_no: originalItemNo + index * 0.001, // 使用原序号加小数,保持顺序 + box_qty: box_qty, + grossWeight: grossWeight, + netWeight: netWeight, + rolls: rolls, + detailRolls: detailRolls // 恢复明细rolls + } + + newRows.push(newRow) + + // 为新箱保存原始数据(用于后续编辑) + this.mergeBoxOriginalData[newBoxKey] = { + box_qty: box_qty, + grossWeight: grossWeight, + netWeight: netWeight, + rolls: rolls, + boxWeight: boxWeight + } + }) + + // 删除旧的行 + selectedBoxKeys.forEach(boxKey => { + for (let i = this.mergeBoxTableData.length - 1; i >= 0; i--) { + if (this.mergeBoxTableData[i]._boxKey === boxKey) { + this.mergeBoxTableData.splice(i, 1) + } + } + }) + + // 添加新的行 + this.mergeBoxTableData.push(...newRows) + + // 重新编号 + this.renumberBoxes() + + // 按序号重新排序表格数据 + this.sortTableDataByItemNo() + + // 清空选择 + this.$refs.mergeBoxTable.clearSelection() + this.mergeBoxSelection = [] + + this.$message.success(`已取消合并,拆分为 ${newRows.length} 个独立的箱,当前共 ${this.getUniqueBoxCount()} 个箱`) + }, + + /** + * 重新编号所有箱 + */ + renumberBoxes() { + // 获取所有唯一的箱,并记录每个箱在表格中第一次出现的位置 + const boxKeysWithIndex = [] + const seenBoxKeys = new Set() + + this.mergeBoxTableData.forEach((row, index) => { + if (!seenBoxKeys.has(row._boxKey)) { + seenBoxKeys.add(row._boxKey) + boxKeysWithIndex.push({ + boxKey: row._boxKey, + item_no: row.item_no, + firstIndex: index // 在表格中第一次出现的位置 + }) + } + }) + + // 按当前序号排序,如果序号相同则按在表格中出现的位置排序 + const sortedBoxKeys = boxKeysWithIndex.sort((a, b) => { + if (a.item_no !== b.item_no) { + return a.item_no - b.item_no + } + // 序号相同时,按在表格中的位置排序 + return a.firstIndex - b.firstIndex + }) + + // 重新分配序号 + sortedBoxKeys.forEach((boxInfo, index) => { + const newItemNo = index + 1 + this.mergeBoxTableData.forEach(row => { + if (row._boxKey === boxInfo.boxKey) { + row.item_no = newItemNo + } + }) + }) + + // 更新下一个序号 + this.nextItemNo = sortedBoxKeys.length + 1 + }, + + /** + * 按序号对表格数据重新排序 + */ + sortTableDataByItemNo() { + this.mergeBoxTableData.sort((a, b) => { + // 先按序号排序 + if (a.item_no !== b.item_no) { + return a.item_no - b.item_no + } + // 序号相同时,保持原有顺序(同一箱内的明细) + return 0 + }) + }, + + /** + * 获取唯一箱数量 + */ + getUniqueBoxCount() { + const boxKeys = new Set(this.mergeBoxTableData.map(row => row._boxKey)) + return boxKeys.size + }, + + /** + * 表格行合并方法 + */ + mergeBoxSpanMethod({ row, column, rowIndex, columnIndex }) { + // Box信息列(前6列:复选框、序号、箱数、毛重、净重、总Rolls)需要合并 + if (columnIndex < 6) { + if (row._isFirstRowOfBox) { + return { + rowspan: row._rowSpan, + colspan: 1 + }; + } else { + return { + rowspan: 0, + colspan: 0 + }; + } + } + return { + rowspan: 1, + colspan: 1 + }; + }, + + /** + * 获取行样式类名 + */ + getMergeBoxRowClassName({ row }) { + const classes = []; + // 检查该行是否有Box修改 + if (this.mergeBoxModifiedBoxes[row._boxKey]) { + classes.push('box-modified-row'); + } + // 检查该行是否有明细修改 + if (row._hasDetail && this.mergeBoxModifiedDetails[row._detailKey]) { + classes.push('detail-modified-row'); + } + return classes.join(' '); + }, + + /** + * 检查Box字段是否被修改 + */ + isBoxFieldModified(row, field) { + const boxKey = row._boxKey; + return this.mergeBoxModifiedBoxes[boxKey] && + this.mergeBoxModifiedBoxes[boxKey][field] !== undefined; + }, + + /** + * 检查明细字段是否被修改 + */ + isDetailFieldModified(row, field) { + if (!row._hasDetail) return false; + const detailKey = row._detailKey; + return this.mergeBoxModifiedDetails[detailKey] && + this.mergeBoxModifiedDetails[detailKey][field] !== undefined; + }, + + /** + * 箱数输入时联动计算毛重和净重 + */ + onMergeBoxQtyInput(row) { + if (this._isMergeCalculating) return; + this._isMergeCalculating = true; + + try { + const boxKey = row._boxKey; + const newBoxQty = parseFloat(row.box_qty) || 0; + + // 先从原始数据获取,如果没有则从row缓存获取 + let boxWeight = 0; + const originalData = this.mergeBoxOriginalData[boxKey]; + if (originalData && originalData.boxWeight) { + boxWeight = parseFloat(originalData.boxWeight); + } else if (row.boxWeightCache) { + boxWeight = parseFloat(row.boxWeightCache); + } + + if (boxWeight > 0 && newBoxQty > 0) { + const newGrossWeight = boxWeight * newBoxQty; + const newNetWeight = newGrossWeight - (newBoxQty / 2); + row.grossWeight = parseFloat(newGrossWeight.toFixed(3)); + row.netWeight = parseFloat(newNetWeight.toFixed(3)); + + // 同步更新所有同Box的行 + this.mergeBoxTableData.forEach(r => { + if (r._boxKey === boxKey) { + r.box_qty = row.box_qty; + r.grossWeight = row.grossWeight; + r.netWeight = row.netWeight; + } + }); + + this.onMergeBoxFieldChange(row, 'grossWeight'); + this.onMergeBoxFieldChange(row, 'netWeight'); + } + } finally { + this._isMergeCalculating = false; + } + }, + + /** + * 毛重输入时联动计算净重 + */ + onMergeGrossWeightInput(row) { + if (this._isMergeCalculating) return; + this._isMergeCalculating = true; + + try { + const grossWeight = parseFloat(row.grossWeight) || 0; + const boxQty = parseFloat(row.box_qty) || 0; + const netWeight = grossWeight - (boxQty / 2); + row.netWeight = netWeight.toFixed(2); + + // 同步更新所有同Box的行 + const boxKey = row._boxKey; + this.mergeBoxTableData.forEach(r => { + if (r._boxKey === boxKey) { + r.grossWeight = row.grossWeight; + r.netWeight = row.netWeight; + } + }); + + this.onMergeBoxFieldChange(row, 'netWeight'); + } finally { + this._isMergeCalculating = false; + } + }, + + /** + * 净重输入时联动计算毛重 + */ + onMergeNetWeightInput(row) { + if (this._isMergeCalculating) return; + this._isMergeCalculating = true; + + try { + const netWeight = parseFloat(row.netWeight) || 0; + const boxQty = parseFloat(row.box_qty) || 0; + const grossWeight = netWeight + (boxQty / 2); + row.grossWeight = grossWeight.toFixed(2); + + // 同步更新所有同Box的行 + const boxKey = row._boxKey; + this.mergeBoxTableData.forEach(r => { + if (r._boxKey === boxKey) { + r.netWeight = row.netWeight; + r.grossWeight = row.grossWeight; + } + }); + + this.onMergeBoxFieldChange(row, 'grossWeight'); + } finally { + this._isMergeCalculating = false; + } + }, + + /** + * Box字段变化处理 + */ + onMergeBoxFieldChange(row, field) { + const boxKey = row._boxKey; + const original = this.mergeBoxOriginalData[boxKey]; + if (!original) return; + + const currentValue = String(row[field]); + const originalValue = String(original[field]); + + if (currentValue !== originalValue) { + if (!this.mergeBoxModifiedBoxes[boxKey]) { + this.$set(this.mergeBoxModifiedBoxes, boxKey, {}); + } + this.$set(this.mergeBoxModifiedBoxes[boxKey], field, { + oldValue: original[field], + newValue: row[field] + }); + + // 同步更新所有同Box的行 + this.mergeBoxTableData.forEach(r => { + if (r._boxKey === boxKey) { + r[field] = row[field]; + } + }); + } else { + if (this.mergeBoxModifiedBoxes[boxKey]) { + this.$delete(this.mergeBoxModifiedBoxes[boxKey], field); + if (Object.keys(this.mergeBoxModifiedBoxes[boxKey]).length === 0) { + this.$delete(this.mergeBoxModifiedBoxes, boxKey); + } + } + } + }, + + /** + * 明细字段变化处理 + */ + onMergeDetailFieldChange(row, field) { + if (!row._hasDetail) return; + + const detailKey = row._detailKey; + const original = this.mergeBoxOriginalData[detailKey]; + if (!original) return; + + const currentValue = String(row[field]); + const originalValue = String(original[field]); + + if (currentValue !== originalValue) { + if (!this.mergeBoxModifiedDetails[detailKey]) { + this.$set(this.mergeBoxModifiedDetails, detailKey, { + row: row + }); + } + this.$set(this.mergeBoxModifiedDetails[detailKey], field, { + oldValue: original[field], + newValue: row[field] + }); + } else { + if (this.mergeBoxModifiedDetails[detailKey]) { + this.$delete(this.mergeBoxModifiedDetails[detailKey], field); + const remainingFields = Object.keys(this.mergeBoxModifiedDetails[detailKey]) + .filter(k => k !== 'row'); + if (remainingFields.length === 0) { + this.$delete(this.mergeBoxModifiedDetails, detailKey); + } + } + } + }, + + /** + * 明细Rolls输入事件处理 - 自动计算总Rolls + */ + onDetailRollsInput(row) { + this.$nextTick(() => { + this.calculateBoxTotalRolls(row); + }); + }, + + /** + * 计算同一Box下所有明细Rolls的总和 + * 计算规则:总Rolls = Sum(所有明细rolls),然后四舍五入取整 + */ + calculateBoxTotalRolls(row) { + const boxKey = row._boxKey; + const boxRows = this.mergeBoxTableData.filter(r => r._boxKey === boxKey && r._hasDetail); + + let totalDetailRolls = 0; + let hasDetailRolls = false; + + boxRows.forEach(r => { + const rollsValue = parseFloat(r.detailRolls); + if (!isNaN(rollsValue) && rollsValue > 0) { + totalDetailRolls += rollsValue; + hasDetailRolls = true; + } + }); + + // 四舍五入取整(与装箱按钮保持一致) + const newRolls = hasDetailRolls ? Math.round(totalDetailRolls) : 0; + + this.mergeBoxTableData.forEach(r => { + if (r._boxKey === boxKey) { + this.$set(r, 'rolls', newRolls); + } + }); + + this.onMergeBoxFieldChange(row, 'rolls'); + }, + + /** + * 检查Box是否有明细Rolls值 + */ + hasDetailRolls(row) { + const boxKey = row._boxKey; + const boxRows = this.mergeBoxTableData.filter(r => r._boxKey === boxKey && r._hasDetail); + + return boxRows.some(r => { + const rollsValue = parseFloat(r.detailRolls); + return !isNaN(rollsValue) && rollsValue > 0; + }); + }, + + /** + * 明细数量变化时重新计算rolls + */ + onDetailQtyChange(row) { + // 如果有缓存的每卷数量,重新计算rolls + if (row.rollQtyCache && row.rollQtyCache > 0 && row.qty > 0) { + const newRolls = parseFloat((row.qty / row.rollQtyCache).toFixed(4)) + this.$set(row, 'detailRolls', newRolls) + + // 更新总Rolls + this.$nextTick(() => { + this.calculateBoxTotalRolls(row) + }) + } + }, + + /** + * 保存合箱批量编辑 + */ + async saveMergeBoxEdit() { + // 允许直接保存,即使没有修改 + this.mergeBoxSaving = true + + try { + // 构造提交数据 - 按箱分组 + const boxMap = new Map() + + // 遍历表格数据,按箱分组 + this.mergeBoxTableData.forEach(row => { + const boxKey = row._boxKey + if (!boxMap.has(boxKey)) { + boxMap.set(boxKey, { + item_no: row.item_no, + box_qty: row.box_qty, + grossWeight: row.grossWeight, + netWeight: row.netWeight, + rolls: row.rolls, + details: [] + }) + } + + // 添加明细 + if (row._hasDetail) { + boxMap.get(boxKey).details.push({ + itemNo: row.itemNo, + poNo: row.poNo, + pn: row.pn, + qty: row.qty, + detailRolls: row.detailRolls, + readyDate: row.readyDate + }) + } + }) + + // 转换为数组 + const boxList = Array.from(boxMap.values()) + + if (boxList.length === 0) { + this.$message.warning('没有可保存的箱数据') + this.mergeBoxSaving = false + return + } + + const mergeData = { + site: this.currentRow.site, + buNo: this.currentRow.buNo, + delNo: this.currentRow.delNo, + createBy: this.$store.state.user.name, + boxList: boxList + } + + // 调用合箱API + const response = await mergeBox(mergeData) + + if (response.data && response.data.code === 0) { + this.$message.success(`成功保存 ${boxList.length} 个箱的数据`) + this.mergeBoxDialogVisible = false + // 刷新当前tab表格 + this.refreshCurrentTabTable() + // 刷新上方的发货通知单列表 + this.searchTable() + } else { + this.$alert(response.data.msg || '保存失败', '错误', { + confirmButtonText: '确定' + }) + } + } catch (error) { + console.error('保存失败:', error) + this.$message.error('保存失败: ' + (error.message || '未知错误')) + } finally { + this.mergeBoxSaving = false + } + }, + openPalletDialog () { //请求 this.searchPalletList(); @@ -3419,4 +4466,109 @@ overflow-y: auto; } +/* ========== 合箱批量编辑弹出框样式 ========== */ + +/* 修复表格滚动条右侧的颜色问题 - 强制设置gutter背景色为白色 */ +.el-dialog__wrapper .batch-edit-dialog .el-table th.gutter { + background-color: #f5f7fa !important; +} + +/* 工具栏样式 */ +.merge-box-toolbar { + display: flex; + align-items: center; + margin-bottom: 15px; + padding: 10px; + background: #f5f7fa; + border-radius: 4px; +} + +.merge-box-toolbar .merge-stats { + margin-left: auto; + display: flex; + align-items: center; +} + +.merge-box-toolbar .modified-count { + color: #e6a23c; + margin-left: 10px; +} + +.merge-box-toolbar .modified-count b { + color: #f56c6c; + font-size: 16px; +} + +/* Box信息单元格样式 */ +.box-info-cell { + font-weight: 600; + color: #409eff; + font-size: 13px; +} + +/* 修改过的输入框样式 */ +/deep/ .batch-edit-container .modified-input .el-input__inner { + background-color: #fff7e6; + border-color: #e6a23c; + color: #e6a23c; +} + +/* 修改过的行样式 */ +/deep/ .batch-edit-container .box-modified-row td:nth-child(-n+6) { + background-color: #fdf6ec !important; +} + +/deep/ .batch-edit-container .detail-modified-row td:nth-child(n+7) { + background-color: #fff7e6 !important; +} + +/* 输入框样式 */ +/deep/ .batch-edit-container .el-input--mini .el-input__inner { + text-align: center; + padding: 0 8px; + border-color: #dcdfe6; +} + +/deep/ .batch-edit-container .el-input--mini .el-input__inner:focus { + border-color: #409eff; +} + +/* 表格单元格内边距 */ +/deep/ .batch-edit-container .el-table td { + padding: 6px 0; +} + +/deep/ .batch-edit-container .el-table .cell { + padding: 0 8px; +} + +/* 数字输入框隐藏上下箭头 */ +/deep/ .batch-edit-container input[type="number"]::-webkit-inner-spin-button, +/deep/ .batch-edit-container input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/deep/ .batch-edit-container input[type="number"] { + -moz-appearance: textfield; +} + +/* 合并单元格的垂直居中 */ +/deep/ .batch-edit-container .el-table__body td[rowspan] { + vertical-align: middle; +} + +/deep/ .batch-edit-container .el-table__body td[rowspan] .cell { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} + +/* 文字样式 */ +/deep/ .batch-edit-container .el-table .cell span { + font-size: 13px; + color: #606266; +} +