Vue3中使用exceljs和file-saver实现Excel导出(含图片导出)完整方案

📝 问题背景

在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'
        };
    });

📌 导出样式

在这里插入图片描述


⚠️ 注意事项

  1. 图片规范

    • 建议使用PNG格式
    • 单图片尺寸不超过500x500px
    • Base64字符串需完整
  2. 性能建议

    • 推荐数据量:<1000行
    • 单文件图片:<50张
    • 大文件建议分页导出
  3. 常见问题

    • 图片不显示:检查Base64格式是否正确
    • 位置偏移:调整tl/br定位参数
    • 内存溢出:分批次处理数据

📚 实现原理

  1. 坐标转换
    通过getExcelColumnLetter函数将数字索引转换为Excel字母坐标

  2. 图片处理

    • 自动剥离DataURL头信息
    • 支持纯Base64直接使用
  3. 定位系统

    // tl: 左上角定位(top-left)
    // br: 右下角定位(bottom-right)
    { 
      tl: { col: 0, row: 0 }, // A1单元格
      br: { col: 1, row: 1 }  // B2单元格
    }
    

💡 总结

本文提供的方案具有以下优势:

  1. 开箱即用:封装完整工具函数
  2. 灵活配置:支持多图片列、自定义样式
  3. 健壮性强:完善的异常处理机制

建议根据实际业务需求调整图片定位参数和样式配置,对于复杂报表建议结合后端生成方案。

<think>嗯,用户想用exceljs导出Excel文件,需要示例代码。首先,我得确认用户的使用环境,比如是Node.js还是前端,比如Vue或React。不过根据提供的引用,有提到VueJava+JS的例子,所以可能用户是在前端使用,尤其是Vue。不过示例代码可能需要区分环境。 首先,安装exceljsfile-saver。引用23都提到了这两个包,所以必须包括安装步骤。用户可能需要知道如何安装,所以给出npm命令。 然后,基本步骤应该是创建workbook,添加worksheet,设置列头,添加数据行,设置样式,最后导出。引用3中的Vue示例可能是一个好的参考,但需要简化代码,让用户容易理解。 要注意的是,在浏览器端导出可能需要使用ExcelJS的xlsx模块,并且使用FileSaver保存文件。同时,设置表格样式,比如列宽、标题加粗,这些细节能提升用户体验,所以应该包在内。 另外,用户可能需要处理更复杂的情况,比如合并单元格或者添加公式,但基础示例应该先覆盖基本需求。引用3中的代码展示了如何设置单元格样式,比如字体颜色填充色,这些可以作为可选的高级配置。 还要注意异步操作,比如workbook.xlsx.writeBuffer()返回的是Promise,所以在示例中使用async/await处理,确保用户知道需要正确处理异步流程。 最后,相关问题部分需要涵盖常见的问题,比如样式设置、大数据量处理、兼容性等,这样用户遇到进一步问题时可以自行参考。</think>以下是使用ExcelJS导出Excel文件的完整示例代码步骤说明: ### 一、基础导出步骤(浏览器环境) 1. 安装依赖: ```bash npm install exceljs file-saver ``` 2. 基础示例代码: ```javascript import ExcelJS from &#39;exceljs&#39;; import { saveAs } from &#39;file-saver&#39;; // 创建workbook const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet(&#39;Sheet1&#39;); // 设置列头 worksheet.columns = [ { header: &#39;ID&#39;, key: &#39;id&#39;, width: 10 }, { header: &#39;姓名&#39;, key: &#39;name&#39;, width: 20 }, { header: &#39;成绩&#39;, key: &#39;score&#39;, width: 15 } ]; // 添加数据行 worksheet.addRow({ id: 1, name: &#39;张三&#39;, score: 95 }); worksheet.addRow({ id: 2, name: &#39;李四&#39;, score: 88 }); // 设置标题样式 worksheet.getRow(1).eachCell(cell => { cell.font = { bold: true, color: { argb: &#39;FFFFFFFF&#39; } }; cell.fill = { type: &#39;pattern&#39;, pattern: &#39;solid&#39;, fgColor: { argb: &#39;FF4F81BD&#39; } }; }); // 导出文件 workbook.xlsx.writeBuffer().then(buffer => { const blob = new Blob([buffer], { type: &#39;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet&#39; }); saveAs(blob, &#39;学生成绩表.xlsx&#39;); }); ``` ### 二、关键配置说明 1. **列定义**:使用`columns`属性定义表格结构,`width`设置列宽(单位:字符) 2. **数据填充**:支持`addRow()`逐行添加,或`addRows()`批量添加二维数组 3. **样式设置**: - 单元格样式:`font`、`fill`、`border`等属性 - 行高设置:`worksheet.getRow(1).height = 25` - 自动筛选:`worksheet.autoFilter = &#39;A1:C1&#39;` ### 三、高级功能示例 ```javascript // 合并单元格 worksheet.mergeCells(&#39;A1:C1&#39;); worksheet.getCell(&#39;A1&#39;).value = &#39;2023年度成绩汇总表&#39;; // 添加公式 worksheet.addRow({ id: &#39;合计&#39;, score: { formula: &#39;SUM(C2:C3)&#39; } }); // 设置数字格式 worksheet.getColumn(&#39;score&#39;).numFmt = &#39;0.00&#39;; ``` ### 四、最佳实践建议 1. 大数据量导出时建议分页处理,每sheet不超过100万行[^3] 2. 复杂样式建议使用`Style`对象统一管理 3. 中文文件名编码问题处理: ```javascript saveAs(blob, decodeURI(encodeURIComponent(&#39;中文文件名.xlsx&#39;))); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫我OldFe

一分也是鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值