前言
最近写管理端的需求,发现有一个excel导出的需求,本来是后端同学负责,但是因为他们太忙了,把这块任务交给前端了,起初产品觉得前端实现不了,一听这话,这我哪里受得了,赶紧写了个demo给她看,前端是可以实现的。enen,产品看了直夸牛逼
接下来,我来分享导出excel文件的三种实现方式
url下载
在这种方式中,我们的目标是后端生成Excel文件并提供一个地址,前端通过访问这个地址来下载导出的Excel文件。
- 后端根据前端的请求,生成需要导出的数据,并将数据转换为Excel格式的文件。
- 后端将生成的Excel文件保存到服务器的某个临时目录,并为该文件生成一个临时的访问地址。
- 后端将生成的临时地址返回给前端作为响应。
- 前端收到后端返回的地址后,可以通过创建一个隐藏的
<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文件流数据,前端通过接收到的Blob数据进行文件下载。
- 后端根据前端的请求,生成需要导出的数据,并将数据转换为Excel格式的文件。
- 后端将生成的Excel数据以Blob文件流的形式返回给前端,通常是通过设置响应的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来安装:
基于XLSX XLSX是一款功能强大的JavaScript库,用于在浏览器和Node.js中读取、解析、处理和写入Excel文件。 1. 安装XLSX 首先,你需要在你的项目中安装XLSX库。你可以通过npm或yarn来安装:
或者
yarn add xlsx
2. 引入XLSX
在你的代码中,你需要引入XLSX库,以便使用其中的功能:
import * as XLSX from 'xlsx';
3. 读取Excel文件
使用XLSX库,你可以读取现有的Excel文件,提取其中的数据和元数据。例如,假设你有一个名为"data.xlsx"的Excel文件,你可以通过以下方式读取它:
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文件
除了读取现有的Excel文件,XLSX库还允许你将数据写入到新的Excel文件中。例如,你可以将一个二维数组的数据写入到一个新的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);
项目实践
下面代码是我在项目中封装好的代码,有需要的同学直接copy使用就行
为了实现前述两种方式的前端导出Excel功能,我们将使用XLSX库来处理数据并导出Excel文件。
// 基于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 []; }