最近经常在做 不规则Excel
的导入,或者一些普通Excel
的导出,当前以上说的都是纯前端来实现;下面我们来聊聊经常用到的Excel导出与导入的实现方案,本文实现技术栈以 Vue2 + JS 为例
导入分类:
调用API
完全由后端来解析数据,清洗数据,前端只负责调用 API
;前端解析 Excel
,清洗数据,把对应的数据处理成 API
需要的 JSON;(本文主要介绍这个) 导出分类:
调用API
完全由后端来生成Excel
,前端获得 API
返回的文件名,下载即可;前端根据 JSON 数据来生成 Excel
, 然后利用第三方库 file-saver
进行下载;(本文主要介绍这个) 导入 Excel
需要用到 xlsx
这个 npm 库
导出 Excel
需要用到 exceljs
, file-saver
这两个
直接 npm install 对应库即可;
1. 导入Excel,处理数据
1.1 需求示例
假如我现在有一个这种 Excel 需要导入,前端负责解析 Excel,清洗数据,API 只需要 4-5 个有用的字段
1.2 具体实现 – html 部分
<section> <el-button @click="handleUpload" size="mini" type="primary">{{l("ChooseFile")}}</el-button> <input v-show="false" @change="handleFileChange" ref="inputFile" type="file" /> <el-alert type="warning" :closable="false" style="margin-top:6px;"> {{'Please Upload (xls | xlsx) Type File'}} </el-alert></section>
import XLSX from "xlsx";handleUpload() { if (!this.importResult) { this.$refs["inputFile"].click(); }},handleFileChange(e) { const file = e.target.files[0]; const fileName = file.name.substring(file.name.lastIndexOf(".") + 1); if (fileName !== "xlsx" && fileName !== "xls") { this.$message.error(this.l("FileTypeError,PleaseTryAgain")); return; } const reader = new FileReader(); reader.readAsBinaryString(file); reader.onload = (e) => { const result = e.target.result; if (!result) { this.errorMsg = this.l("NoData"); this.step = 1; return; } if (this.importType === 1) { this.handleSinglePageExcel(result); } else { this.handleMultiplePageExcel(result); } }; reader.onerror = (err) => { throw new Error("UpLoadError: " + err.stack); }; },
1.3 具体实现 – 单个 sheet
handleSinglePageExcel(data) { const wb = XLSX.read(data, { type: "binary", cellDates: true, }); const sheet = wb.SheetNames[0]; const importData = XLSX.utils.sheet_to_json(wb.Sheets[sheet], { range: -1, }); const arr = []; for (let i = 3; i < importData.length; i++) { // 处理业务逻辑 } this.importResult = arr;},
1.4 具体实现 – 多个 sheet
handleMultiplePageExcel(data) { const wb = XLSX.read(data, { type: "binary", cellDates: true, }); const sheetList = wb.SheetNames; const arrMap = {}; // 多 Sheet 页数据; sheetList.forEach((t) => { const importData = XLSX.utils.sheet_to_json(wb.Sheets[t], { range: 2, }); arrMap[t] = importData; }); const arr = []; for (let t in arrMap) { const importData = arrMap[t]; // importData : 代表每个 Sheet 页的 Excel 数据 } this.importResult = arr;},
1.4 相关参数
文件读取类型
类型 | 预期输入 |
---|---|
base64 | Base64编码类型字符串 |
binary | 二进制字符串(字节n是data.charCodeAt(n)) |
string | JS字符串(仅适用于UTF-8文本格式) |
buffer | nodejs的buffer类型 |
array | 数组 |
file | 将被读取的文件路径(仅限nodejs) |
常用方法
sheet_to_*
函数接受一个工作表和一个可选的options对象,主要是将excel文件转化为对应的数据格式,一般导入excel文件的时候使用*_to_sheet
函数接受一个数据对象和一个可选的options对象,主要是将数据格式转化为excel文件,一般导出文件的时候使用sheet_add_*
函数接受工作表、数据和可选选项。主要用途是更新一个现有的工作表对象 2. 根据已有数据,按需导出Excel
1.1 需求示例
假如我现在有一个这种查询表格需要导出,因为所有的数据都在表格中,所以不需要调用API
也可以实现
1.2 具体实现
import { Workbook } from "exceljs";import { saveAs } from "file-saver";try { this.loading = true; // 创建一个工作簿 const workbook = new Workbook(); // columns 需要生成的Excel列 { prop, label, width, sheetName | Detail } // sheetName 需要生成的 Sheet 页, 如果只生成一个 Sheet Excel 不用考虑这里 const sheets = _.uniq(this.columns.map((t) => t.sheetName || "Detail")); for (let i = 0; i < sheets.length; i++) { const columns = this.columns.filter( (t) => (t.sheetName || "Detail") === sheets[i] ); // addWorksheet 添加一个 Sheet 页 const worksheet = workbook.addWorksheet(sheets[i]); worksheet.columns = columns.map((t) => { // 需求处理 const label = t.label ? t.label : this.propToLabel(t.prop); return { header: this.l(label), // Excel 第一行标题 key: t.prop, width: label.length * 2, // Excel 列的宽度 }; }); // this.list -> 当前 table 数据 this.list.forEach((t) => { const row = []; columns.forEach((x) => { row.push(t[x.prop] || ""); }); // 生成的 Excel Sheet 添加数据 worksheet.addRow(row); }); // 第一行 Header 行添加自定义样式 worksheet.getRow(1).eachCell((cell, colNumber) => { cell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "cccccc", }, bgColor: { argb: "#96C8FB", }, }; }); } // 导出的文件名 const code = this.exportTemple.code || new Date().getTime(); workbook.xlsx.writeBuffer().then((buffer) => { // 调用 第三方库 下载刚生成好的Excel saveAs( new Blob([buffer], { type: "application/octet-stream", }), code + "." + "xlsx" ); this.loading = false; });} catch (e) { console.error("clinet export error", e);} finally { this.loading = false;}
如果大数据的量导出
建议还是后端来实现,前端要用 websocket
做优化,避免长时间 loading 带来不好的用户体验