Files
workspace/code/g3hd/frontend/src/utils/excel.js
T
2026-07-11 17:30:09 +08:00

343 lines
9.1 KiB
JavaScript

import ExcelJS from "exceljs";
import { VxeUI } from "vxe-pc-ui";
import { formatValueByFullFormat, getAlignByFormat, updateTableFooterData } from "@/utils/column";
export function ExportVxeTableExcel(tableRef) {
if (!tableRef) {
return;
}
// columns
const fullColumns = tableRef.getFullColumns();
const exportColumns = buildColumns(fullColumns) || [];
const exportDatas =
tableRef.getCheckboxRecords().length > 0
? tableRef.getCheckboxRecords()
: tableRef.getFullData();
const footerDatas = updateTableFooterData(exportColumns, exportDatas);
ExportExcel(exportColumns, exportDatas, footerDatas);
}
export function ExportExcel(columns, data, footerData = []) {
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet("Sheet1");
const exportColumns = columns;
const exportDatas = data;
if (!exportColumns || exportColumns.length === 0) {
console.error("没有找到列数据");
return;
}
VxeUI.modal.message({
content: `正在导出中 . . .`,
status: "loading",
duration: -1,
});
// 1. 提取所有叶子节点(数据列)
const leafColumns = [];
const headerData = [];
const mergeInfo = [];
// 2. 解析列结构,构建表头数据
function parseColumns(cols, currentLevel = 0, startCol = 0) {
let colIndex = startCol;
cols.forEach((col) => {
// 确保当前层级的数组存在
if (!headerData[currentLevel]) {
headerData[currentLevel] = [];
}
if (col.children && col.children.length > 0) {
// 处理有子级的列
const childLeafCount = parseColumns(col.children, currentLevel + 1, colIndex);
// 记录合并信息(水平合并)
if (childLeafCount > 1) {
mergeInfo.push({
startRow: currentLevel + 1, // ExcelJS 行号从1开始
startCol: colIndex + 1, // ExcelJS 列号从1开始
endRow: currentLevel + 1,
endCol: colIndex + childLeafCount,
});
}
// 设置当前行的表头
headerData[currentLevel][colIndex] = col.label || "";
// 填充子级占据的列
for (let i = 1; i < childLeafCount; i++) {
headerData[currentLevel][colIndex + i] = "";
}
colIndex += childLeafCount;
} else {
// 处理叶子节点
leafColumns.push(col);
// 设置当前行和所有下层的表头
for (let level = currentLevel; level < headerData.length; level++) {
if (!headerData[level]) headerData[level] = [];
if (level === currentLevel) {
headerData[level][colIndex] = col.label || "";
} else {
headerData[level][colIndex] = col.label || ""; // 对于垂直合并,需要填充标签
}
}
// 记录垂直合并信息
if (headerData.length - currentLevel > 1) {
mergeInfo.push({
startRow: currentLevel + 1,
startCol: colIndex + 1,
endRow: headerData.length, // 合并到最后一行
endCol: colIndex + 1,
});
}
colIndex++;
}
});
return colIndex - startCol; // 返回该组列占用的列数
}
// 初始化 headerData 数组
const maxDepth = getMaxDepth(exportColumns);
for (let i = 0; i < maxDepth; i++) {
headerData[i] = [];
}
parseColumns(exportColumns);
// 3. 添加表头行
headerData.forEach((rowData, rowIndex) => {
const row = sheet.addRow(rowData);
// 设置表头样式
row.eachCell((cell, colNumber) => {
cell.style = {
font: { bold: true, size: 10 },
alignment: { horizontal: "center", vertical: "middle" },
fill: {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFE0E0E0" },
},
border: {
top: { style: "thin" },
left: { style: "thin" },
bottom: { style: "thin" },
right: { style: "thin" },
},
};
});
});
// 4. 合并单元格(先排序,避免重复合并)
// 按照从左上到右下的顺序排序
mergeInfo.sort((a, b) => {
if (a.startRow !== b.startRow) return a.startRow - b.startRow;
return a.startCol - b.startCol;
});
// 记录已合并的单元格
const mergedCells = new Set();
mergeInfo.forEach((merge) => {
// 检查这个合并区域是否与已合并的区域冲突
let canMerge = true;
for (let r = merge.startRow; r <= merge.endRow; r++) {
for (let c = merge.startCol; c <= merge.endCol; c++) {
const cellKey = `${r}-${c}`;
if (mergedCells.has(cellKey)) {
canMerge = false;
break;
}
}
if (!canMerge) break;
}
if (canMerge) {
try {
sheet.mergeCells(merge.startRow, merge.startCol, merge.endRow, merge.endCol);
// 记录已合并的单元格
for (let r = merge.startRow; r <= merge.endRow; r++) {
for (let c = merge.startCol; c <= merge.endCol; c++) {
mergedCells.add(`${r}-${c}`);
}
}
} catch (error) {
console.warn(
`无法合并单元格 ${merge.startRow}:${merge.startCol}-${merge.endRow}:${merge.endCol}`,
error,
);
}
}
});
// 5. 设置列宽
sheet.columns = leafColumns.map((col) => ({
key: col.prop,
width: pxToExcelWidth(col.width) || 20,
}));
// 6. 填充数据
exportDatas.forEach((item, rowIndex) => {
const rowData = {};
leafColumns.forEach((col, colIndex) => {
let value = item[col.prop];
// 如果是空值,设置为空字符串
rowData[col.prop] = formatValueByFullFormat(value, col.displayFormat);
});
const row = sheet.addRow(rowData);
// 设置数据行样式
row.eachCell((cell, colNumber) => {
const leafCol = leafColumns[colNumber - 1];
const style = {
font: { size: 10 },
alignment: { vertical: "middle" },
border: {
top: { style: "thin" },
left: { style: "thin" },
bottom: { style: "thin" },
right: { style: "thin" },
},
};
style.alignment.horizontal = getAlignByFormat(leafCol.displayFormat);
cell.style = style;
});
});
// 7. 添加汇总行
if (footerData.length > 0) {
footerData.forEach((footerItem, rowIndex) => {
const summaryRowData = {};
// 遍历所有列,计算每个列的汇总值
leafColumns.forEach((col) => {
summaryRowData[col.prop] = formatValueByFullFormat(footerItem[col.prop], col.displayFormat);
});
// 添加汇总行
const summaryRow = sheet.addRow(summaryRowData);
summaryRow.eachCell((cell) => {
// 设置单元格样式
const style = {
font: { bold: true, size: 10 },
alignment: { vertical: "middle" },
fill: {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF0F0F0" },
},
border: {
top: { style: "thin" },
left: { style: "thin" },
bottom: { style: "thin" },
right: { style: "thin" },
},
};
style.alignment.horizontal = "right";
// 应用样式
cell.style = style;
});
});
}
// 8. 保存文件
workbook.xlsx
.writeBuffer()
.then((buffer) => {
const blob = new Blob([buffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `export-${new Date().getTime()}.xlsx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
VxeUI.modal.close();
VxeUI.modal.message({
content: `导出成功`,
status: "success",
});
})
.catch((err) => {
console.error("生成 Excel 文件失败:", err);
VxeUI.modal.message({
content: `导出失败`,
status: "error",
});
});
}
// 获取最大深度
function getMaxDepth(columns, currentDepth = 1) {
let maxDepth = currentDepth;
columns.forEach((col) => {
if (col.children && col.children.length > 0) {
const childDepth = getMaxDepth(col.children, currentDepth + 1);
maxDepth = Math.max(maxDepth, childDepth);
}
});
return maxDepth;
}
function pxToExcelWidth(px) {
return Number(px) / 8;
}
function buildColumns(arr) {
return arr
.filter((item) => {
if (
(item.type === "checkbox" || item.type === "seq") &&
(!item.children || item.children.length === 0)
) {
return false;
} else if (item.title === "操作") {
return false;
}
return true;
})
.map((item) => {
let column = {};
if (item.params) {
column = { ...item.params };
}
column.label = item.title;
column.width = item.renderWidth || item.width;
if (item.children && item.children.length > 0) {
column.children = buildColumns(item.children);
}
return column;
});
}