JS前端导出Excel的三种实现方案与封装实践

作者:半夏时间:2026-10-31 05:33:05浏览:458

本文详细解析了前端实现Excel导出的三种主流方案:通过后端生成URL下载、直接接收Blob文件流以及基于XLSX库在浏览器端生成文件。文章提供了完整的代码示例,并封装了支持分页轮询下载全部数据的生产级工具函数,帮助开发者快速落地Excel导出功能。

前言

近期在管理后台开发中遇到Excel导出需求。原计划由后端负责,但因后端资源紧张,该任务移交至前端。起初产品认为前端难以实现,为验证可行性,我快速编写了一个Demo供其查看,最终证实前端完全可以胜任,并获得了产品的认可。

接下来,我将分享三种在前端实现Excel文件导出的具体方式。

URL下载

该方案的核心在于后端生成Excel文件并提供访问地址,前端通过请求该地址触发下载流程。

  • 后端接收前端请求,生成所需数据并转换为Excel格式文件。
  • 后端将生成的Excel文件保存至服务器临时目录,并生成一个临时的访问URL。
  • 后端将该临时URL作为响应返回给前端。
  • 前端接收到后端返回的URL后,创建一个隐藏的 <a> 元素,将其 href 属性设置为后端返回的地址,并模拟点击该元素,从而触发浏览器下载文件。
  • 下载完成后,前端可根据业务需求决定是否请求后端删除服务器上的临时文件。
// 后端接口:/api/export/excel
// 请求方式:GET
// 假设后端接口返回导出地址的数据格式为 { url: "https://example.com/excel_exports/exported_file.xlsx" }
export const exportExcelViaURL = () => {
  // 发起后端接口请求获取导出地址
  fetch('/api/export/excel')
    .then((response) => response.json())
    .then((data) => {
      const { url } = data;
      // 创建一个隐藏的<a>标签并设置href属性为后端返回的地址
      const link = document.createElement('a');
      link.href = url;
      link.target = '_blank';
      link.download = `exported_data_${dayjs().format('YYYY-MM-DD_hh.mm.ss_a')}.xlsx`;
      // 触发点击操作,开始下载文件
      link.click();
    })
    .catch((error) => {
      console.error('导出Excel失败:', error);
    });
};

Blob文件流

后端直接返回Blob格式的文件流数据,前端接收该数据后执行下载操作。

  • 后端根据前端请求生成数据,并将其转换为Excel格式文件。
  • 后端以Blob文件流形式返回Excel数据,通常通过设置响应头Content-Type和Content-Disposition,指示浏览器以文件下载方式处理响应。
  • 前端接收到Blob数据后,创建一个Blob URL,随后创建一个隐藏的 <a> 元素,将其 href 属性设置为该Blob URL,并模拟点击操作,实现文件下载。
// 后端接口:/api/export/excel/blob
// 请求方式:GET
export const exportExcelViaBlob = () => {
  // 发起后端接口请求获取Blob文件流数据
  fetch('/api/export/excel/blob')
    .then((response) => response.blob())
    .then((blobData) => {
      // 创建Blob URL
      const blobUrl = URL.createObjectURL(blobData);
      // 创建一个隐藏的<a>标签并设置href属性为Blob URL
      const link = document.createElement('a');
      link.href = blobUrl;
      link.target = '_blank';
      link.download = `exported_data_${dayjs().format('YYYY-MM-DD_hh.mm.ss_a')}.xlsx`;
      // 触发点击操作,开始下载文件
      link.click();
      // 释放Blob URL
      URL.revokeObjectURL(blobUrl);
    })
    .catch((error) => {
      console.error('导出Excel失败:', error);
    });
};

基于XLSX

XLSX是一款功能强大的JavaScript库,支持在浏览器和Node.js环境中读取、解析、处理及写入Excel文件。

1. 安装XLSX

首先,需在项目中安装XLSX库。可通过npm或yarn进行安装:

npm install xlsx

或者

yarn add xlsx

2. 引入XLSX

在代码中引入XLSX库,以便使用其提供的功能:

import * as XLSX from 'xlsx';

3. 读取Excel文件

使用XLSX库可读取现有Excel文件,提取数据及元数据。例如,假设存在名为"data.xlsx"的文件,可通过以下方式读取:

import * as XLSX from 'xlsx';
const file = 'data.xlsx'; // 文件路径或URL
const workbook = XLSX.readFile(file);
const sheetName = workbook.SheetNames[0]; // 假设我们读取第一个工作表
const worksheet = workbook.Sheets[sheetName];
const data = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
console.log(data);

4. 写入Excel文件

除读取外,XLSX库支持将数据写入新Excel文件。例如,可将二维数组数据写入新文件:

import * as XLSX from 'xlsx';
const data = [
  ['Name', 'Age', 'City'],
  ['John Doe', 30, 'New York'],
  ['Jane Smith', 25, 'San Francisco'],
];
const worksheet = XLSX.utils.aoa_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
const fileName = 'output.xlsx'; // 导出的文件名
XLSX.writeFile(workbook, fileName);

项目实践

以下代码为项目中封装好的导出函数,开发者可直接复制使用。

为实现上述两种前端导出Excel的功能,我们将使用XLSX库处理数据并生成文件。

// 基于XLSX的前端导出Excel实现
import dayjs from 'dayjs';
import * as XLSX from 'xlsx';
/**
 * eg: .columns = [
 *    { header: 'Id', key: 'id', wpx: 10 },
 *    { header: 'Name', key: 'name', wch: 32 },
 *    { header: 'D.O.B.', key: 'dob', width: 10, hidden: true }
 * ]
 * data: [{id: 1, name: 'John Doe', dob: new Date(1970,1,1)}]
 * @param columns 定义列属性数组
 * @param data  数据
 * @param name  文件名
 */
export const generateExcel = (columns = [], data = [], name = '') => {
  const headers = columns.map((item) => item.header);
  // https://docs.sheetjs.com/docs/csf/features/#row-and-column-properties
  const otherConfigs = columns.map(({ key, header, ...item }) => item);
  const dataList = data.map((item) => {
    let obj = {};
    columns.forEach((col) => {
      obj[col.header] = item[col.key];
    });
    return obj;
  });
  const workbook = XLSX.utils.book_new();
  workbook.SheetNames.push(name);
  const worksheet = XLSX.utils.json_to_sheet(dataList, {
    header: headers,
  });
  worksheet['!cols'] = otherConfigs;
  workbook.Sheets[name] = worksheet;
  // 生成Blob数据
  const excelData = XLSX.write(workbook, { type: 'array', bookType: 'xlsx' });
  const blobData = new Blob([excelData], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  // 创建Blob URL
  const blobUrl = URL.createObjectURL(blobData);
  // 创建一个隐藏的<a>标签并设置href属性为Blob URL
  const link = document.createElement('a');
  link.href = blobUrl;
  link.target = '_blank';
  link.download = `${name}-${dayjs().format('YYYY-MM-DD_hh.mm.ss_a')}.xlsx`;
  // 触发点击操作,开始下载文件
  link.click();
  // 释放Blob URL
  URL.revokeObjectURL(blobUrl);
};

下载全部

若需一键下载所有表格数据,前端需轮询后端接口获取全量数据。为此,我们需要实现一个loopReuqest函数来处理轮询逻辑。

export default function awaitRequest(limit = 5) {
  let awaitTask = [];
  let currentTaskNum = 0;
  function run(event, ...args) {
    return new Promise((resolve, reject) => {
      function callbackEvent() {
        currentTaskNum++;
        event(...args)
          .then((res) => {
            if (awaitTask.length) {
              const nextTask = awaitTask.shift();
              nextTask();
            }
            resolve(res);
          })
          .catch((e) => {
            console.error(e);
            reject(e);
          })
          .finally(() => {
            currentTaskNum--;
          });
      }
      if (currentTaskNum >= limit) {
        awaitTask.push(callbackEvent);
      } else {
        callbackEvent();
      }
    });
  }
  Object.defineProperties(run, {
    clear: {
      value: () => {
        awaitTask = [];
      },
    },
  });
  return run;
}
/**
 * 循环分页请求,获取全部数据
 * @param {Function} request 请求
 * @param {Number} size 页大小
 * @param {Object} params 其余参数
 * @param {String} listLabel.pageLable 当前页字段名。默认page
 * @param {String} listLabel.sizeLabel 页大小字段名。默认page_size
 * @param {String} listLabel.totalLabel 总条数字段名。默认total
 * @param {String} listLabel.itemsLabel 数据列表字段名。默认list
 * @returns
 */
export async function loopRequest(
  request,
  size,
  params,
  listLabel = {
    totalLabel: 'total',
    pageLable: 'page',
    sizeLabel: 'page_size',
    itemsLabel: 'list',
  },
) {
  const {
    totalLabel = 'total',
    pageLable = 'page',
    sizeLabel = 'page_size',
    itemsLabel = 'list'
  } = listLabel;
  try {
    const firstRes = await request({
      ...params,
      [sizeLabel]: size,
      [pageLable]: 1,
    });
    let list = firstRes.data[itemsLabel] || [];
    const total = firstRes.data[totalLabel];
    if (total > size) {
      const limit = awaitRequest();
      const restRequest = Array.from({
        length: Math.floor(total / size),
      }).map((_, index) =>
        limit(() =>
          request({
            ...params,
            [sizeLabel]: size,
            [pageLable]: index + 2,
          }),
        ),
      );
      const resetRes = await Promise.all(restRequest);
      resetRes.forEach((res) => {
        if (res.code === 0 && res.data[itemsLabel]) {
          list.push(...res.data[itemsLabel]);
        }
      });
    }
    return list;
  } catch (e) {
    console.error(e);
  }
  return [];
}

以上就是JS前端导出Excel的方法详解的详细内容,更多关于JS导出Excel的资料请关注本站其它相关文章!

作者最新文章