vue3 excelExport 导出封装

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 = [],
  batchSize = 1000
) => {
  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: "898989" },
    };
    cell.alignment = {
      horizontal: "center",
      vertical: "middle",
    };
  });
  // 设置列宽
  headers.forEach((_, colIndex) => {
    let width = 20; // 默认列宽
    if (imageIndex.includes(colIndex)) {
      width = 30; // 图片列更宽
    }
    worksheet.getColumn(colIndex + 1).width = width;
  });

  // 分批处理数据
  const totalBatches = Math.ceil(tableData.length / batchSize);
  for (let batchNum = 0; batchNum < totalBatches; batchNum++) {
    const batchStart = batchNum * batchSize;
    const batchEnd = Math.min(batchStart + batchSize, tableData.length);
    const batch = tableData.slice(batchStart, batchEnd);

    console.log(`正在处理第 ${batchNum + 1}/${totalBatches} 批数据...`);
    // 处理数据行(修改为异步)
    // 批量处理行数据
    const processRow = async (rowIdx) => {
      const row = batch[rowIdx];
      const newRow =
        imageIndex.length > 0
          ? row.map((cell, idx) => (imageIndex.includes(idx) ? "" : cell))
          : row;

      worksheet.addRow(newRow);
      worksheet.getRow(batchStart + rowIdx + 2).height = 25;

      if (imageIndex.length > 0) {
        await processRowImages(batchStart + rowIdx, row);
        worksheet.getRow(batchStart + rowIdx + 2).height = 80;
      }

      worksheet.getRow(batchStart + rowIdx + 2).alignment = {
        vertical: "middle",
        horizontal: "center",
      };
    };

    // 处理行中的图片
    const processRowImages = async (rowIdx, row) => {
      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,
            });

            worksheet.addImage(imageId, {
              tl: { col: colIdx, row: rowIdx + 1 },
              ext: { width: 200, height: 100 },
              editAs: "oneCell",
            });
            console.log(`图片处理完成(行${rowIdx + 1}列${colIdx + 1})`);
          } catch (error) {
            console.error(
              `图片处理失败(行${rowIdx + 1}列${colIdx + 1}):`,
              error
            );
          }
        })
      );
    };

    // 处理所有行
    for (let rowIdx = 0; rowIdx < batch.length; rowIdx++) {
      await processRow(rowIdx);
    }
    if (batchNum < totalBatches - 1) {
      await new Promise((resolve) => setTimeout(resolve, 0));
      console.log("回收");
      //   if (global.gc) global.gc(); // 显式调用垃圾回收(如果可用)
    }
  }

  // 生成文件
  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;
};

前端使用

js 复制代码
<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>
相关推荐
南北是北北3 小时前
Flow 的 emit 与 tryEmit :它们出现在哪些类型、背压/缓存语义、何时用谁、常见坑
前端·面试
flyliu3 小时前
继承,继承,继承,哪里有家产可以继承
前端·javascript
司宸3 小时前
Cursor 编辑器高效使用与配置全指南
前端
维维酱3 小时前
为什么说 useCallback 实际上是 useMemo 的特例
前端·react.js
王六岁3 小时前
Vue 3 表单验证组合式 API,提供类似 Ant Design Vue Form 的强大表单验证功能
前端·vue.js
机构师3 小时前
<uniapp><日期组件>基于uniapp,编写一个自定义的日期组件
前端·javascript
lypzcgf3 小时前
Coze源码分析-资源库-创建提示词-前端源码
前端·人工智能·typescript·系统架构·开源软件·react·安全架构
fury_1233 小时前
vue3:el-date-picker三十天改成第二十九天的23:59:59
前端·javascript·vue.js
小周同学@3 小时前
DOM常见的操作有哪些?
前端·javascript