diff --git a/klp-ui/src/views/mes/qc/inspectionSample/index.vue b/klp-ui/src/views/mes/qc/inspectionSample/index.vue
index 84b709aed..d422f538e 100644
--- a/klp-ui/src/views/mes/qc/inspectionSample/index.vue
+++ b/klp-ui/src/views/mes/qc/inspectionSample/index.vue
@@ -344,27 +344,24 @@ export default {
loadMainDetails() {
if (this.mainList.length === 0) return
- const promises = this.mainList.map(main => {
- return listInspectionDetail({ mainId: main.mainId, pageNum: 1, pageSize: 9999 }).then(response => {
- const details = response.rows || []
- details.forEach(detail => {
- if (main._items[detail.itemName]) {
- main._items[detail.itemName] = {
- detailId: detail.detailId,
- mainId: detail.mainId,
- itemName: detail.itemName,
- itemValue: detail.itemValue || '',
- itemUnit: detail.itemUnit || main._items[detail.itemName].itemUnit,
- upperLimit: detail.upperLimit,
- lowerLimit: detail.lowerLimit,
- rangeDesc: detail.rangeDesc
- }
+ this.mainList.forEach(main => {
+ const details = main.detailList || []
+ details.forEach(detail => {
+ if (main._items[detail.itemName]) {
+ main._items[detail.itemName] = {
+ detailId: detail.detailId,
+ mainId: detail.mainId,
+ itemName: detail.itemName,
+ itemValue: detail.itemValue || '',
+ itemUnit: detail.itemUnit || main._items[detail.itemName].itemUnit,
+ upperLimit: detail.upperLimit,
+ lowerLimit: detail.lowerLimit,
+ rangeDesc: detail.rangeDesc
}
- })
- main._originalItems = JSON.parse(JSON.stringify(main._items))
- }).catch(() => {})
+ }
+ })
+ main._originalItems = JSON.parse(JSON.stringify(main._items))
})
- return Promise.all(promises)
},
handleAddMain() {
diff --git a/klp-ui/src/views/wms/report/abnormal.vue b/klp-ui/src/views/wms/report/abnormal.vue
index 9e62b3b1b..66cdf9d96 100644
--- a/klp-ui/src/views/wms/report/abnormal.vue
+++ b/klp-ui/src/views/wms/report/abnormal.vue
@@ -58,6 +58,8 @@
导出产出钢卷(多行)
导出消耗钢卷(多行)
+ 导出产出钢卷(多行合并)
+ 导出消耗钢卷(多行合并)
@@ -144,6 +146,11 @@ import CoilTable from "@/views/wms/report/components/coilTable";
import ColumnsSetting from "@/views/wms/report/components/setting/columns";
import TimeRangePicker from "@/views/wms/report/components/timeRangePicker.vue";
import { saveReportFile } from "@/views/wms/report/js/reportFile";
+import { mergeCoilCells } from "@/views/wms/report/js/mergeExcelCells";
+import { saveAs } from 'file-saver'
+import request from '@/utils/request'
+import { tansParams } from "@/utils/klp";
+import { Loading } from 'element-ui'
export default {
name: 'MergeTemplate',
@@ -362,6 +369,54 @@ export default {
abnormalExportCount: 0
}, `materialCoil_${new Date().getTime()}.xlsx`)
},
+ // 导出产出钢卷(多行合并) — 二次合并钢卷列单元格
+ async exportDataMerged() {
+ if (this.outList.length === 0) {
+ this.$message.warning('暂无数据可导出')
+ return
+ }
+ await this.exportWithMerge('wms/materialCoil/exportAbnormal', {
+ coilIds: this.outList.map(item => item.coilId).join(','),
+ abnormalExportCount: 0
+ }, `abnormalCoil_${new Date().getTime()}.xlsx`)
+ },
+
+ // 导出消耗钢卷(多行合并) — 二次合并钢卷列单元格
+ async exportLossDataMerged() {
+ if (this.lossList.length === 0) {
+ this.$message.warning('暂无数据可导出')
+ return
+ }
+ await this.exportWithMerge('wms/materialCoil/export', {
+ coilIds: this.lossList.map(item => item.coilId).join(','),
+ abnormalExportCount: 0
+ }, `materialCoil_${new Date().getTime()}.xlsx`)
+ },
+
+ // 通用:导出多行 → ExcelJS 二次合并钢卷列 → 保存
+ async exportWithMerge(url, params, filename) {
+ const loading = Loading.service({
+ text: '正在导出并合并单元格,请稍候',
+ spinner: 'el-icon-loading',
+ background: 'rgba(0, 0, 0, 0.7)'
+ })
+ try {
+ const data = await request.post(url, params, {
+ transformRequest: [(params) => { return tansParams(params) }],
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ responseType: 'blob'
+ })
+ // data 是拦截器返回的 res.data(Blob),与 download 函数一致
+ const blob = new Blob([data])
+ const mergedBlob = await mergeCoilCells(blob, 26)
+ saveAs(mergedBlob, filename)
+ } catch (e) {
+ console.error('导出合并失败', e)
+ this.$message.error('导出失败,请联系管理员')
+ } finally {
+ loading.close()
+ }
+ },
// 处理命令
handleCommand(command) {
if (command === 'exportData') {
@@ -376,6 +431,10 @@ export default {
this.exportDataMore()
} else if (command === 'exportLossDataMore') {
this.exportLossDataMore()
+ } else if (command === 'exportDataMerged') {
+ this.exportDataMerged()
+ } else if (command === 'exportLossDataMerged') {
+ this.exportLossDataMerged()
}
},
// 加载列设置
diff --git a/klp-ui/src/views/wms/report/js/mergeExcelCells.js b/klp-ui/src/views/wms/report/js/mergeExcelCells.js
new file mode 100644
index 000000000..3b6e80994
--- /dev/null
+++ b/klp-ui/src/views/wms/report/js/mergeExcelCells.js
@@ -0,0 +1,74 @@
+import ExcelJS from 'exceljs'
+
+/**
+ * 对异常钢卷报表导出的 Excel 进行二次合并处理
+ * 将钢卷信息列(前26列,A-Z)中连续相同值的单元格合并
+ *
+ * 原理:
+ * 1. 用 row.values 数组代替逐格 getCell,避免 9万+ 次对象创建
+ * 2. 合并单元格后用列级别样式统一居中,代替逐格设置
+ *
+ * @param {Blob} blob - 原始 Excel 文件 Blob
+ * @param {number} mergeColumnCount - 需要合并的钢卷列数,默认 26(A-Z列)
+ * @returns {Promise} - 合并后的新 Blob
+ */
+export async function mergeCoilCells(blob, mergeColumnCount = 26) {
+ const workbook = new ExcelJS.Workbook()
+ const arrayBuffer = await blob.arrayBuffer()
+ await workbook.xlsx.load(arrayBuffer)
+
+ const worksheet = workbook.worksheets[0]
+ const rowCount = worksheet.rowCount
+
+ if (rowCount <= 1) return blob
+
+ // === 第一步:按钢卷身份标识分组 ===
+ // 使用 row.values 数组(1-based),字符拼接一次性比较,避免逐格 getCell
+ const groups = []
+ let groupStart = 2
+ let prevFingerprint = rowFingerprint(worksheet.getRow(2), mergeColumnCount)
+
+ for (let row = 3; row <= rowCount; row++) {
+ const curFingerprint = rowFingerprint(worksheet.getRow(row), mergeColumnCount)
+ if (curFingerprint !== prevFingerprint) {
+ groups.push({ start: groupStart, end: row - 1 })
+ groupStart = row
+ prevFingerprint = curFingerprint
+ }
+ }
+ groups.push({ start: groupStart, end: rowCount })
+
+ // === 第二步:合并单元格 ===
+ for (const group of groups) {
+ if (group.end > group.start) {
+ for (let col = 1; col <= mergeColumnCount; col++) {
+ worksheet.mergeCells(group.start, col, group.end, col)
+ }
+ }
+ }
+
+ // === 第三步:列级别统一居中(替换逐格设置,26 次 vs 9万次) ===
+ for (let col = 1; col <= mergeColumnCount; col++) {
+ const column = worksheet.getColumn(col)
+ column.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true }
+ }
+
+ // === 第四步:写回 Blob ===
+ const buffer = await workbook.xlsx.writeBuffer()
+ return new Blob([buffer], {
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ })
+}
+
+/**
+ * 生成行的"指纹"字符串,用于快速比较两行钢卷列是否相同
+ * row.values 是 ExcelJS 的 1-based 数组:values[1]=A列, values[2]=B列 ...
+ */
+function rowFingerprint(row, colCount) {
+ const vals = row.values || []
+ let fp = ''
+ for (let i = 1; i <= colCount; i++) {
+ fp += String(vals[i] ?? '') + '\x00'
+ }
+ return fp
+}