Vue3中使用exceljs和file-saver實(shí)現(xiàn)Excel導(dǎo)出(含圖片導(dǎo)出)功能完整方案
問題背景
在Vue3項(xiàng)目中,當(dāng)我們需要將包含圖片的數(shù)據(jù)導(dǎo)出到Excel時(shí),常用的sheetjs/xlsx庫存在局限性:無法直接導(dǎo)出圖片到單元格。本文將提供完整的解決方案,封裝可直接復(fù)用的工具函數(shù)。
解決方案
技術(shù)選型
使用exceljs + file-saver組合:
exceljs:支持圖片插入的Excel操作庫file-saver:前端文件保存工具
功能特性
? 支持多圖片列導(dǎo)出
? 自動識別Base64和DataURL格式
? 精確控制圖片位置
? 異常處理與性能優(yōu)化
完整實(shí)現(xiàn)代碼
步驟1:安裝依賴
npm install exceljs file-saver
步驟2:創(chuàng)建工具文件
// utils/excelExport.js
import ExcelJS from 'exceljs';
import { saveAs } from 'file-saver';
/**
*
* @param headers 表頭,[表頭1,表頭2,...] 必填
* @param tableData 內(nèi)容 [data1,data2,...] 必填
* @param imageIndex 圖片下標(biāo)列 [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);
// 設(shè)置表頭樣式
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'
};
});
// 設(shè)置列寬
headers.forEach((_, colIndex) => {
let width = 20; // 默認(rèn)列寬
if (imageIndex.includes(colIndex)) {
width = 30; // 圖片列更寬
}
worksheet.getColumn(colIndex + 1).width = width;
});
// 處理數(shù)據(jù)行(修改為異步)
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' // 更穩(wěn)定的定位方式
});
} catch (error) {
console.error(`圖片處理失?。ㄐ?{rowIdx + 1}列${colIdx + 1}):`, error);
}
}));
}
}
// 生成文件
const buffer = await workbook.xlsx.writeBuffer();
saveAs(new Blob([buffer]), `數(shù)據(jù)導(dǎo)出_${new Date().getTime()}.xlsx`);
};
// 列索引轉(zhuǎn)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轉(zhuǎn)換為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('圖片轉(zhuǎn)換失敗:', error);
return null;
}
};
// 修改后的解析函數(shù)
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組件調(diào)用
<template>
<button @click="handleExport">導(dǎo)出Excel</button>
</template>
<script setup>
import { exportDataToExcel } from '@/utils/excelExport';
const headers = ['名稱', '價(jià)格', '主圖'];
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>
擴(kuò)展配置
設(shè)置列寬
headers.forEach((_, colIndex) => {
let width = 20; // 默認(rèn)列寬
if (imageIndex.includes(colIndex)) {
width = 30; // 圖片列更寬
}
worksheet.getColumn(colIndex + 1).width = width;
});
設(shè)置表頭樣式
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'
};
});
導(dǎo)出樣式

注意事項(xiàng)
圖片規(guī)范
- 建議使用PNG格式
- 單圖片尺寸不超過500x500px
- Base64字符串需完整
性能建議
- 推薦數(shù)據(jù)量:<1000行
- 單文件圖片:<50張
- 大文件建議分頁導(dǎo)出
常見問題
- 圖片不顯示:檢查Base64格式是否正確
- 位置偏移:調(diào)整
tl/br定位參數(shù) - 內(nèi)存溢出:分批次處理數(shù)據(jù)
實(shí)現(xiàn)原理
坐標(biāo)轉(zhuǎn)換
通過getExcelColumnLetter函數(shù)將數(shù)字索引轉(zhuǎn)換為Excel字母坐標(biāo)圖片處理
- 自動剝離DataURL頭信息
- 支持純Base64直接使用
定位系統(tǒng)
// tl: 左上角定位(top-left) // br: 右下角定位(bottom-right) { tl: { col: 0, row: 0 }, // A1單元格 br: { col: 1, row: 1 } // B2單元格 }
總結(jié)
本文提供的方案具有以下優(yōu)勢:
- 開箱即用:封裝完整工具函數(shù)
- 靈活配置:支持多圖片列、自定義樣式
- 健壯性強(qiáng):完善的異常處理機(jī)制
建議根據(jù)實(shí)際業(yè)務(wù)需求調(diào)整圖片定位參數(shù)和樣式配置,對于復(fù)雜報(bào)表建議結(jié)合后端生成方案。
到此這篇關(guān)于Vue3中使用exceljs和file-saver實(shí)現(xiàn)Excel導(dǎo)出(含圖片導(dǎo)出)功能的文章就介紹到這了,更多相關(guān)Vue3導(dǎo)出Excel內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue.js實(shí)現(xiàn)刷新當(dāng)前頁面的方法教程
這篇文章主要給大家介紹了關(guān)于vue.js實(shí)現(xiàn)刷新當(dāng)前頁面的方法教程,文中給出了詳細(xì)的示例代碼供大家參考學(xué)習(xí),對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來學(xué)習(xí)學(xué)習(xí)吧。2017-07-07
vue源碼學(xué)習(xí)之Object.defineProperty對象屬性監(jiān)聽
這篇文章主要介紹了vue源碼學(xué)習(xí)之Object.defineProperty對象屬性監(jiān)聽,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05
VueCli生產(chǎn)環(huán)境打包部署跨域失敗的解決
這篇文章主要介紹了VueCli生產(chǎn)環(huán)境打包部署跨域失敗的解決,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
基于vue3實(shí)現(xiàn)一個(gè)抽獎小項(xiàng)目
在公司年會期間我做了個(gè)抽獎小項(xiàng)目,非常棒,今天把他分享到腳本之家平臺,供大家學(xué)習(xí)參考,對vue3實(shí)現(xiàn)抽獎小項(xiàng)目感興趣的朋友一起看看吧2023-01-01

