给大家分享一个前端导出图片到excel中的实现方法。
一、需求
把每个单元格内图片和文字整体绘制成一张图片,再将每张绘制完的图片按照页面的排版导出到excel中,其目的是在导出的excel中便于直接复制图片使用。
图片1![]() | 图片2![]() | 图片3![]() | 图片4![]() | 图片5![]() |
图片1![]() | 图片2![]() | 图片3![]() | 图片4![]() | 图片5![]() |
图片1![]() | 图片2![]() | 图片3![]() | 图片4![]() | 图片5![]() |
二、导出结果
三、实现过程
使用html2canvas将每个单元格里的文字和图片作为一个整体图片绘制出来,使用canvas.toDataURL获取图片的base64数据,使用exceljs中的addImage方法添加图片到excel里,最后使用file-save中的saveAs方法导出。
在实现的过程中,如果遇到图片跨域,需要添加配置config = { useCORS: true }到html2canvas(element, config)中,否则导出的图片不显示。
如果导出的图片很多,可以使用消息队列分批并行导出。
1、安装依赖
npm install exceljs
npm install html2canvas
npm install file-saver
2、页面代码
<div>
<a-button @click="exportToExcel">导出为Excel</a-button>
<div class="groupBox">
<div class="groupItem" v-for="(group, colIndex) in listData" :key="colIndex">
<div
class="cellBox"
ref="imageRef"
v-for="(element, elementIndex) in group"
:key="elementIndex"
>
<div>图片{{elementIndex+1}}</div>
<img
v-if="element?.src"
:width="150"
:height="150"
:src="element?.src"
/>
</div>
</div>
</div>
</div>
3、ts代码
//导出Excel
const imageRef = ref<any>(null)
const config = { useCORS: true } //HTML2canvas绘图时图片允许跨域
// 消息队列的操作接口
interface MessageQueueOperation {
execute(): Promise<void>
}
// 创建一个队列操作的工厂函数
function createOperation(
rowIndex: number,
queueList: any[],
workbook: any,
worksheet: any,
): MessageQueueOperation {
return {
execute: async () => {
console.log(`Operation ${rowIndex} is starting.`)
for (let i = 0; i < queueList.length; i++) {
let canvas = await html2canvas(queueList[i], config)
let context = canvas.getContext('2d')
let imageBase64 = canvas.toDataURL('image/png')
context?.clearRect(0, 0, canvas.width, canvas.height)
if (imageBase64.startsWith('data:image/')) {
const imageId = workbook.addImage({
base64: imageBase64,
extension: 'png',
})
worksheet.addImage(imageId, {
tl: { col: i, row: rowIndex },
ext: { width: 200, height: 200 },
})
}
worksheet.columns[i].width = 27
const row = worksheet.getRow(rowIndex + 1)
row.height = 310 // 设置行高
}
// await new Promise(resolve => setTimeout(resolve, rowIndex * 100))
console.log(`Operation ${rowIndex} is completed.`)
},
}
}
// 并行执行操作
async function executeOperationsConcurrently(operations: MessageQueueOperation[]) {
const promises = operations.map(op => op.execute())
await Promise.all(promises)
}
const batchArray = (array, batchSize, workbook, worksheet) => {
let curRowIndex = 0
let result: any = []
for (let i = 0; i < array.length; i += batchSize) {
let opt = createOperation(curRowIndex, array.slice(i, i + batchSize), workbook, worksheet)
result.push(opt)
curRowIndex++
}
return result
}
//导出
function exportToExcel() {
const workbook = new Workbook()
const worksheet = workbook.addWorksheet('Sheet1')
if (imageRef.value) {
let optList = batchArray(imageRef.value, 10, workbook, worksheet)
executeOperationsConcurrently(optList).then(async () => {
console.log('All operations completed.')
const buffer = await workbook.xlsx.writeBuffer()
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
})
saveAs(blob, '测试.xlsx')
})
}
}
4、css代码
.groupBox {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: flex-start;
.groupItem {
display: flex;
flex-direction: column;
margin: 7px;
.cellBox {
max-width: 60rem;
display: flex;
align-items: center;
text-align: center;
flex-direction: column;
background-color: #f6f8fa;
padding: 8px 16px;
margin-top: 0.5rem;
border: solid 1px #ccc9c9;
cursor: move;
img {
cursor: pointer;
}
}
}
}