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>
相关推荐
jay神35 分钟前
基于 YOLOv8 的轴承缺陷检测(Web 版)
前端·人工智能·yolo
jay神43 分钟前
基于 YOLOv8 的森林火灾检测系统(Web 版)
前端·yolo
code2cat1 小时前
HTML基础篇之第一张网页:从index.html到浏览器里的任务清单
前端·html
miss2 小时前
AI 改完不敢上线?OpenSpec 规约驱动开发实战
前端·代码规范·前端工程化
AiMan2 小时前
腾讯ima还是不可以导出左侧所有对话,只能单条导出,方法不对吗
前端
去伪存真2 小时前
并发量一高就装死?Agent 全链路“防猝死”手册
前端·agent
Moment2 小时前
AI 编程时代,聊聊我的 Mac mini 双屏桌面
前端·后端
AiMan2 小时前
先确认能不能一次把Microsofat Copilot所有内容打包一个文件导出
前端
总要学点什么3 小时前
第97篇:三维数据加载-城市建筑白模自定义着色器——让一条光带沿高度扫上去,快慢却由帧率说了算
前端·gis·webgl·cesium·着色器·空间分析·白模
上海懒图科技3 小时前
工厂厂区电子地图定位导航:厂区人车精准定位,安全合规双保障
前端·arcgis·vue