📝 问题背景
在Vue3项目中,当我们需要将包含图片的数据导出到Excel时,常用的sheetjs/xlsx
库存在局限性:无法直接导出图片到单元格。本文将提供完整的解决方案,封装可直接复用的工具函数。
🎯 解决方案
技术选型
使用exceljs
+ file-saver
组合:
exceljs
:支持图片插入的Excel操作库file-saver
:前端文件保存工具
功能特性
✅ 支持多图片列导出
✅ 自动识别Base64和DataURL格式
✅ 精确控制图片位置
✅ 异常处理与性能优化
🛠 完整实现代码
步骤1:安装依赖
npm install exceljs file-saver
步骤2:创建工具文件
// utils/excelExport.js
import ExcelJS from 'exceljs';
import { saveAs } from 'file-saver';
/**
*
* @param headers 表头,[表头1,表头2,...] 必填
* @param tableData 内容 [data1,data2,...] 必填
* @param imageIndex 图片下标列 [0, 1, 2 , ...], 非必填
* @returns {Promise<void>}
*/
export const exportToExcel = async (headers, tableData, imageIndex = []) => {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');
// 添加表头
const headerRow = worksheet.addRow(headers);
// 设置表头样式
headerRow.eachCell((cell) => {
cell.font = {
bold: true,
color: { argb: 'FFFFFFFF' },
size: 12
};
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FF007BFF' }
};
cell.alignment = {
horizontal: 'center',
vertical: 'middle'
};
});
// 设置列宽
headers.forEach((_, colIndex) => {
let width = 20; // 默认列宽
if (imageIndex.includes(colIndex)) {
width = 30; // 图片列更宽
}
worksheet.getColumn(colIndex + 1).width = width;
});
// 处理数据行(修改为异步)
for (let rowIdx = 0; rowIdx < tableData.length; rowIdx++) {
const row = tableData[rowIdx];
const dataRow = worksheet.addRow(row);
if (imageIndex.length > 0) {
// 使用Promise.all处理多列图片
await Promise.all(imageIndex.map(async (colIdx) => {
const imgUrl = row[colIdx];
if (!imgUrl) return;
try {
const parsed = await parseImageData(imgUrl);
if (!parsed) return;
const imageId = workbook.addImage({
base64: parsed.base64,
extension: parsed.extension,
});
const colLetter = getExcelColumnLetter(colIdx);
const excelRow = rowIdx + 2;
worksheet.addImage(imageId, {
tl: { col: colIdx, row: excelRow - 1 },
br: { col: colIdx + 1, row: excelRow },
editAs: 'oneCell' // 更稳定的定位方式
});
} catch (error) {
console.error(`图片处理失败(行${rowIdx + 1}列${colIdx + 1}):`, error);
}
}));
}
}
// 生成文件
const buffer = await workbook.xlsx.writeBuffer();
saveAs(new Blob([buffer]), `数据导出_${new Date().getTime()}.xlsx`);
};
// 列索引转Excel字母(如:0 -> A, 1 -> B)
const getExcelColumnLetter = (column) => {
let letter = '';
let col = column + 1;
while (col > 0) {
const remainder = (col - 1) % 26;
letter = String.fromCharCode(65 + remainder) + letter;
col = Math.floor((col - 1) / 26);
}
return letter;
};
// 新增:将图片URL转换为base64
const urlToBase64 = async (url) => {
try {
const response = await fetch(url);
const blob = await response.blob();
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
} catch (error) {
console.error('图片转换失败:', error);
return null;
}
};
// 修改后的解析函数
const parseImageData = async (imgData) => {
if (!imgData) return null;
// 处理URL情况
if (imgData.startsWith('http')) {
const dataURL = await urlToBase64(imgData);
if (!dataURL) return null;
const [header, base64] = dataURL.split(';base64,');
const extension = header.split('/')[1];
return { base64, extension };
}
// 原有处理dataURL的逻辑
if (imgData.startsWith('data:image')) {
const [header, base64] = imgData.split(';base64,');
if (!header || !base64) return null;
const extension = header.split('/')[1];
return { base64, extension };
}
return null;
};
📌 使用示例
Vue3组件调用
<template>
<button @click="handleExport">导出Excel</button>
</template>
<script setup>
import { exportDataToExcel } from '@/utils/excelExport';
const headers = ['名称', '价格', '主图'];
const tableData = [
['商品A', 99.9, 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'],
['商品B', 199.9, '']
];
const imageIndex = [2]; // 第三列为图片列
const handleExport = async () => {
await exportDataToExcel(headers, tableData, imageIndex);
};
</script>
⚙️ 扩展配置
设置列宽
headers.forEach((_, colIndex) => {
let width = 20; // 默认列宽
if (imageIndex.includes(colIndex)) {
width = 30; // 图片列更宽
}
worksheet.getColumn(colIndex + 1).width = width;
});
设置表头样式
headerRow.eachCell((cell) => {
cell.font = {
bold: true,
color: { argb: 'FFFFFFFF' },
size: 12
};
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FF007BFF' }
};
cell.alignment = {
horizontal: 'center',
vertical: 'middle'
};
});
📌 导出样式
⚠️ 注意事项
图片规范
- 建议使用PNG格式
- 单图片尺寸不超过500x500px
- Base64字符串需完整
性能建议
- 推荐数据量:<1000行
- 单文件图片:<50张
- 大文件建议分页导出
常见问题
- 图片不显示:检查Base64格式是否正确
- 位置偏移:调整
tl
/br
定位参数 - 内存溢出:分批次处理数据
📚 实现原理
坐标转换
通过getExcelColumnLetter
函数将数字索引转换为Excel字母坐标图片处理
- 自动剥离DataURL头信息
- 支持纯Base64直接使用
定位系统
// tl: 左上角定位(top-left) // br: 右下角定位(bottom-right) { tl: { col: 0, row: 0 }, // A1单元格 br: { col: 1, row: 1 } // B2单元格 }
💡 总结
本文提供的方案具有以下优势:
- 开箱即用:封装完整工具函数
- 灵活配置:支持多图片列、自定义样式
- 健壮性强:完善的异常处理机制
建议根据实际业务需求调整图片定位参数和样式配置,对于复杂报表建议结合后端生成方案。