feat(wms): 添加钢卷报表导出功能的多行合并选项

- 在异常报表页面添加导出产出钢卷(多行合并)和导出消耗钢卷(多行合并)菜单项
- 新增 mergeCoilCells 工具函数实现 Excel 单元格合并功能
- 实现 exportWithMerge 通用方法处理带合并的 Excel 导出
- 添加导出时的加载提示和错误处理机制
- 优化 MES 质检样本详情加载逻辑,移除不必要的异步请求
This commit is contained in:
2026-07-08 10:56:50 +08:00
parent f0a74c1e1c
commit 3e36d41176
3 changed files with 149 additions and 19 deletions

View File

@@ -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() {

View File

@@ -58,6 +58,8 @@
<el-dropdown-item command="exportDataMore">导出产出钢卷(多行)</el-dropdown-item>
<el-dropdown-item command="exportLossDataMore">导出消耗钢卷(多行)</el-dropdown-item>
<el-dropdown-item command="exportDataMerged" divided>导出产出钢卷(多行合并)</el-dropdown-item>
<el-dropdown-item command="exportLossDataMerged">导出消耗钢卷(多行合并)</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-form-item>
@@ -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.dataBlob与 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()
}
},
// 加载列设置

View File

@@ -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 - 需要合并的钢卷列数,默认 26A-Z列
* @returns {Promise<Blob>} - 合并后的新 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
}