Vue3 exceljs库实现前端导入导出Excel

打印 上一主题 下一主题

主题 517|帖子 517|积分 1551

前言

需求场景

最近在开辟项目时需要批量导入和导出Excel数据,在实现这个需求时,我们既可以在前端完成数据解析和文件天生工作,也可以通过前端发起导入以及导出请求后,后端实现解析文件流解析文件内容以及天生文件并提供下载链接的功能。
相较于后端处理Excel数据而言,使用前端导入导出可以提供更高的及时性,用户可以直接在浏览器中触发导出操作,无需等待后端处理。且可以在前端完成数据天生以及数据校验处理工作,大大减轻后端服务器的压力,大幅增强用户体验。
具体的技能方案选型主要看业务场景,如果对于小型数据集、及时性需求较高的导入导出操作,优先思量前端实现。而对于大型数据集、需要业务逻辑处理、以及安全性要求高的场景,则后端处理更为合适。
技能方案

xlsx与xlsx-style组合方案:xlsx 是现在前端最常用的Excel办理方案,又叫做SheetJS,但社区版不支持修改Excel的样式,需要购买Pro版才可以,如果需要修改导出的Excel文件样式,需要联合xlsx-style库一起使用。但遗憾的是xlsx库已经两年多不更新,而xlsx-style上一个版本更是8年前发布,现在已经不再保举使用该方案。
exceljs与file-saver方案:exceljs是一款免费开源支持导入导出Excel 操作工具,并且可以实现样式的修改以及 Excel 的高级功能,是非常值得保举的一个处理 Excel 的库,file-saver可以实现保存文件到当地。本文以exceljs与file-saver操作xlsx格式文件为例先容如何具体上手使用。
exceljs先容

ExcelJS是一个用于在Node.js和浏览器中创建、读取和修改Excel文件的强大JavaScript库。它提供了丰富的功能和灵活的API,使你能够在你的应用步伐中处理和操作Excel文件。
下面是一些ExcelJS库的关键特性和功能:

  • 创建和修改Excel文件:ExcelJS允许你创建新的Excel工作簿,并在其中添加工作表、行和单位格。你可以设置单位格的值、样式、数据范例以及其他属性。
  • 读取息争析Excel文件:ExcelJS支持读取息争析现有的Excel文件。你可以将Excel文件加载到工作簿中,然后访问工作表、行和单位格的数据。
  • 导出和保存Excel文件:ExcelJS可以将工作簿保存为Excel文件,支持多种格式,如XLSX、XLS和CSV。你可以将工作簿保存到当地文件系统或将其发送到客户端以供下载。
  • 处理复杂的Excel功能:ExcelJS支持处理复杂的Excel功能,如公式、图表、数据验证、条件格式和保护工作表等。你可以根据需要设置和操作这些功能。
  • 支持自定义样式和格式:ExcelJS允许你自定义单位格、行、列和工作表的样式和格式。你可以设置字体、颜色、添补、边框、对齐方式以及数字和日期格式等。
参考文档

npm仓库地点:https://www.npmjs.com/package/exceljs
官方中文文档地点:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md
快速上手

安装依赖

exceljs用于Excel数据处理,file-sever用于保存到当地文件。
  1. npm i exceljs
  2. npm i file-saver
复制代码
导出Excel

让我们先从简单的数据导出开始,快速体验如何使用exceljs导出Excel文件,需要注意的是在浏览器环境中运行 JavaScript,浏览器的安全计谋通常不允许直接访问读写当地文件系统。在这种情况下,需要通过其他方式将文件转换为buffer数据,在导出Excel时使用FileSaver.js库将缓冲区数据保存到文件中。
  1. <template>
  2.   <el-button type="primary" @click="exportExcel">导出excel</el-button>
  3. </template>
  4. <script setup>
  5. import ExcelJS from "exceljs";
  6. import FileSaver from "file-saver";
  7. // 导出excel文件
  8. const exportExcel = () => {
  9.   // 创建工作簿
  10.   const workbook = new ExcelJS.Workbook();
  11.   // 添加工作表,名为sheet1
  12.   const sheet1 = workbook.addWorksheet("sheet1");
  13.   // 导出数据列表
  14.   const data = [
  15.     {"姓名": "张三", "年龄": 18, "身高": 175, "体重": 74},
  16.     {"姓名": "李四", "年龄": 22, "身高": 177, "体重": 84},
  17.     {"姓名": "王五", "年龄": 53, "身高": 155, "体重": 64}
  18.   ]
  19.   // 获取表头所有键
  20.   const headers = Object.keys(data[0])
  21.   // 将标题写入第一行
  22.   sheet1.addRow(headers);
  23.   // 将数据写入工作表
  24.   data.forEach((row) => {
  25.     const values = Object.values(row)
  26.     sheet1.addRow(values);
  27.   });
  28.   // 导出表格文件
  29.   workbook.xlsx.writeBuffer().then((buffer) => {
  30.     let file = new Blob([buffer], {type: "application/octet-stream"});
  31.     FileSaver.saveAs(file, "ExcelJS.xlsx");
  32.   }).catch(error => console.log('Error writing excel export', error))
  33. }
  34. </script>
  35. <style scoped lang="scss">
  36. </style>
复制代码
当我们点击导出excel按钮时,调用exportFile函数,完成excel文件下载,下载后的文件内容如下:

导入Excel

导入excel文件时,同样使用FileReader的readAsArrayBuffer方法,将文件转换为二进制字符串,然后从buffer中加载数据并解析。
  1. <template>
  2.   <input
  3.       type="file"
  4.       accept=".xls,.xlsx"
  5.       class="upload-file"
  6.       @change="importExcel($event)"/>
  7. </template>
  8. <script setup>
  9. import ExcelJS from "exceljs";
  10. // 导出excel文件
  11. const importExcel = (event) => {
  12.   //获取选择的文件
  13.   const files = event.target.files
  14.   //创建Workbook实例
  15.   const workbook = new ExcelJS.Workbook();
  16.   // 使用FileReader对象来读取文件内容
  17.   const fileReader = new FileReader()
  18.   // 二进制字符串的形式加载文件
  19.   fileReader.readAsArrayBuffer(files[0])
  20.   fileReader.onload = ev => {
  21.     console.log(ev)
  22.     // 从 buffer中加载数据解析
  23.     workbook.xlsx.load(ev.target.result).then(workbook => {
  24.       // 获取第一个worksheet内容
  25.       const worksheet = workbook.getWorksheet(1);
  26.       // 获取第一行的标题
  27.       const headers = [];
  28.       worksheet.getRow(1).eachCell((cell) => {
  29.         headers.push(cell.value);
  30.       });
  31.       console.log("headers", headers)
  32.       // 创建一个空的JavaScript对象数组,用于存储解析后的数据
  33.       const data = [];
  34.       // 遍历工作表的每一行(从第二行开始,因为第一行通常是标题行)
  35.       for (let rowNumber = 2; rowNumber <= worksheet.rowCount; rowNumber++) {
  36.         const rowData = {};
  37.         const row = worksheet.getRow(rowNumber);
  38.         // 遍历当前行的每个单元格
  39.         row.eachCell((cell, colNumber) => {
  40.           // 获取标题对应的键,并将当前单元格的值存储到相应的属性名中
  41.           rowData[headers[colNumber - 1]] = cell.value;
  42.         });
  43.         // 将当前行的数据对象添加到数组中
  44.         data.push(rowData);
  45.       }
  46.       console.log("data", data)
  47.     })
  48.   }
  49. }
  50. </script>
  51. <style scoped lang="scss">
  52. </style>
复制代码
上传文件后,解析内容如下所示:

进阶操作

添加数据

我们可以通过columns方法添加列标题并定义列键和宽度,设置好表头后,我们可以直接通过addRow方法,根据key值去添加每一行的数据。
参考文档:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E5%88%97
完整代码如下:
  1. <template>
  2.   <el-button type="primary" @click="exportExcel">导出excel</el-button>
  3. </template>
  4. <script setup>
  5. import ExcelJS from "exceljs";
  6. import FileSaver from "file-saver";
  7. // 导出excel文件
  8. const exportExcel = () => {
  9.   // 创建工作簿
  10.   const workbook = new ExcelJS.Workbook();
  11.   // 添加工作表,名为sheet1
  12.   const sheet1 = workbook.addWorksheet("sheet1");
  13.   // 添加表头列数据
  14.   sheet1.columns = [
  15.     {header: "姓名", key: "name", width: 20},
  16.     {header: "年龄", key: "age", width: 10},
  17.     {header: "身高", key: "height", width: 10},
  18.     {header: "体重", key: "weight", width: 10},
  19.   ];
  20.   // 添加内容列数据
  21.   sheet1.addRow({sort: 1, name: "张三", age: 18, height: 175, weight: 74});
  22.   sheet1.addRow({sort: 2, name: "李四", age: 22, height: 177, weight: 88});
  23.   sheet1.addRow({sort: 3, name: "王五", age: 53, height: 155, weight: 62});
  24.   // 导出表格文件
  25.   workbook.xlsx.writeBuffer().then((buffer) => {
  26.     let file = new Blob([buffer], {type: "application/octet-stream"});
  27.     FileSaver.saveAs(file, "ExcelJS.xlsx");
  28.   }).catch(error => console.log('Error writing excel export', error))
  29. }
  30. </script>
  31. <style scoped lang="scss">
  32. </style>
复制代码
添加数据后导出文件效果如下:

读取数据

我们可以使用getRow方法,传入指定行参数读取行数据。
使用getColumn方法,传入键、字母、id参数读取列数据。
使用eachCell方法可以遍历每个单位格内容。
参考文档:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E8%A1%8C
代码如下:
  1. <template>
  2.   <el-button type="primary" @click="exportExcel">导出excel</el-button>
  3. </template>
  4. <script setup>
  5. import ExcelJS from "exceljs";
  6. import FileSaver from "file-saver";
  7. // 导出excel文件
  8. const exportExcel = () => {
  9.   // 创建工作簿
  10.   const workbook = new ExcelJS.Workbook();
  11.   // 添加工作表,名为sheet1
  12.   const sheet1 = workbook.addWorksheet("sheet1");
  13.   // 添加表头列数据
  14.   sheet1.columns = [
  15.     {header: "姓名", key: "name", width: 20},
  16.     {header: "年龄", key: "age", width: 10},
  17.     {header: "身高", key: "height", width: 10},
  18.     {header: "体重", key: "weight", width: 10},
  19.   ];
  20.   // 添加内容列数据
  21.   sheet1.addRow({sort: 1, name: "张三", age: 18, height: 175, weight: 74});
  22.   sheet1.addRow({sort: 2, name: "李四", age: 22, height: 177, weight: 88});
  23.   sheet1.addRow({sort: 3, name: "王五", age: 53, height: 155, weight: 62});
  24.   // 读取行数据
  25.   sheet1.getRow(1).eachCell((cell, rowIdx) => {
  26.     console.log("行数据", cell.value, rowIdx);
  27.   });
  28.   // 读取列数据,可以通过键(name),字母(B)和基于id(1)的列号访问单个列
  29.   sheet1.getColumn("name").eachCell((cell, rowIdx) => {
  30.     console.log("列数据", cell.value, rowIdx);
  31.   });
  32. }
  33. </script>
  34. <style scoped lang="scss">
  35. </style>
复制代码
效果

样式

在导出excel文件时,默认没有任何样式的,为了美观我们需要添加样式,而exceljs支持修改表格样式,具体内容可参考文档https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E6%A0%B7%E5%BC%8F
例如,我们需要设置全部单位格居中对齐,并添加边框。并分别指定标题行和内容行字体大小、配景颜色、行高属性,代码如下:
  1. <template>
  2.   <el-button type="primary" @click="exportExcel">导出excel</el-button>
  3. </template>
  4. <script setup>
  5. import ExcelJS from "exceljs";
  6. import FileSaver from "file-saver";
  7. // 导出excel文件
  8. const exportExcel = () => {
  9.   // 创建工作簿
  10.   const workbook = new ExcelJS.Workbook();
  11.   // 添加工作表,名为sheet1
  12.   const sheet1 = workbook.addWorksheet("sheet1");
  13.   // 导出数据列表
  14.   const data = [
  15.     {"姓名": "张三", "年龄": 18, "身高": 175, "体重": 74},
  16.     {"姓名": "李四", "年龄": 22, "身高": 177, "体重": 84},
  17.     {"姓名": "王五", "年龄": 53, "身高": 155, "体重": 64}
  18.   ]
  19.   // 获取表头所有键
  20.   const headers = Object.keys(data[0])
  21.   // 将标题写入第一行
  22.   sheet1.addRow(headers);
  23.   // 将数据写入工作表
  24.   data.forEach((row) => {
  25.     const values = Object.values(row)
  26.     sheet1.addRow(values);
  27.   });
  28.   // 修改所有单元格样式
  29.   // 遍历每一行
  30.   sheet1.eachRow((row, rowNumber) => {
  31.     // 遍历每个单元格
  32.     row.eachCell((cell) => {
  33.       // 设置边框样式
  34.       cell.border = {
  35.         top: {style: 'thin'},
  36.         left: {style: 'thin'},
  37.         bottom: {style: 'thin'},
  38.         right: {style: 'thin'}
  39.       };
  40.       // 设置居中对齐
  41.       cell.alignment = {
  42.         vertical: 'middle',
  43.         horizontal: 'center'
  44.       };
  45.     });
  46.   });
  47.   // 获取标题行数据
  48.   const titleCell = sheet1.getRow(1);
  49.   // 设置行高为30
  50.   titleCell.height = 30
  51.   // 设置标题行单元格样式
  52.   titleCell.eachCell((cell) => {
  53.     // 设置标题行背景颜色为黄色
  54.     cell.fill = {
  55.       type: 'pattern',
  56.       pattern: 'solid',
  57.       fgColor: {argb: 'FFFF00'}
  58.     };
  59.     // 设置标题行字体
  60.     cell.font = {
  61.       color: {argb: 'FF0000'}, //颜色为红色
  62.       bold: true,// 字体粗体
  63.       size: 18 // 设置字体大小为18
  64.     };
  65.   })
  66.   // 获取第二行到最后一行的内容数据
  67.   const bodyRows = sheet1.getRows(2, sheet1.rowCount);
  68.   // 处理内容行的数据
  69.   bodyRows.forEach((bodyRow) => {
  70.     // 设置行高为20
  71.     bodyRow.height = 20
  72.     bodyRow.eachCell((cell) => {
  73.       cell.font = {
  74.         size: 16 // 设置内容行字体大小为16
  75.       };
  76.     });
  77.   });
  78.   // 导出表格文件
  79.   workbook.xlsx.writeBuffer().then((buffer) => {
  80.     let file = new Blob([buffer], {type: "application/octet-stream"});
  81.     FileSaver.saveAs(file, "ExcelJS.xlsx");
  82.   }).catch(error => console.log('Error writing excel export', error))
  83. }
  84. </script>
  85. <style scoped lang="scss">
  86. </style>
复制代码
导出Excel样式效果如下所示,已经成功按我们指定的样式导出了文件:

筛选

在许多的时候我们需要对表格中每一列的数据进行筛选,比如直接筛选姓名等列信息,我们可以通过 autoFilter 来添加筛选。参考文档:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E8%87%AA%E5%8A%A8%E7%AD%9B%E9%80%89%E5%99%A8
代码如下:
  1. <template>
  2.   <el-button type="primary" @click="exportExcel">导出excel</el-button>
  3. </template>
  4. <script setup>
  5. import ExcelJS from "exceljs";
  6. import FileSaver from "file-saver";
  7. // 导出excel文件
  8. const exportExcel = () => {
  9.   // 创建工作簿
  10.   const workbook = new ExcelJS.Workbook();
  11.   // 添加工作表,名为sheet1
  12.   const sheet1 = workbook.addWorksheet("sheet1");
  13.   // 导出数据列表
  14.   const data = [
  15.     {"姓名": "张三", "年龄": 18, "身高": 175, "体重": 74},
  16.     {"姓名": "李四", "年龄": 22, "身高": 177, "体重": 84},
  17.     {"姓名": "王五", "年龄": 53, "身高": 155, "体重": 64}
  18.   ]
  19.   // 获取表头所有键
  20.   const headers = Object.keys(data[0])
  21.   // 将标题写入第一行
  22.   sheet1.addRow(headers);
  23.   // 将数据写入工作表
  24.   data.forEach((row) => {
  25.     const values = Object.values(row)
  26.     sheet1.addRow(values);
  27.   });
  28.   // 单列筛选
  29.   // sheet1.autoFilter = "A1";
  30.   // 多个列筛选
  31.   sheet1.autoFilter = "A1:C1";
  32.   // 导出表格文件
  33.   workbook.xlsx.writeBuffer().then((buffer) => {
  34.     let file = new Blob([buffer], {type: "application/octet-stream"});
  35.     FileSaver.saveAs(file, "ExcelJS.xlsx");
  36.   }).catch(error => console.log('Error writing excel export', error))
  37. }
  38. </script>
  39. <style scoped lang="scss">
  40. </style>
复制代码
导入后的效果如下,在姓名、年事、身高列添加了筛选按钮:

公式值

参考文档:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E5%80%BC%E7%B1%BB%E5%9E%8B
我们可以直接对表格中的数据进行公式计算,比如 求和(SUM),平均数(AVERAGE) 等。
例如我们需要计算平均值、最大值、指定公式时,代码如下:
  1. <template>
  2.   <el-button type="primary" @click="exportExcel">导出excel</el-button>
  3. </template>
  4. <script setup>
  5. import ExcelJS from "exceljs";
  6. import FileSaver from "file-saver";
  7. // 导出excel文件
  8. const exportExcel = () => {
  9.   // 创建工作簿
  10.   const workbook = new ExcelJS.Workbook();
  11.   // 添加工作表,名为sheet1
  12.   const sheet1 = workbook.addWorksheet("sheet1");
  13.   // 导出数据列表
  14.   const data = [
  15.     {"姓名": "张三", "年龄": 18, "身高": 1.75, "体重": 74},
  16.     {"姓名": "李四", "年龄": 22, "身高": 1.77, "体重": 84},
  17.     {"姓名": "王五", "年龄": 53, "身高": 1.55, "体重": 64}
  18.   ]
  19.   // 获取表头所有键
  20.   const headers = Object.keys(data[0])
  21.   // 将标题写入第一行
  22.   sheet1.addRow(headers);
  23.   // 将数据写入工作表
  24.   data.forEach((row) => {
  25.     const values = Object.values(row)
  26.     sheet1.addRow(values);
  27.   });
  28.   // 添加单元格
  29.   sheet1.getCell("E1").value = "BMI指数";
  30.   sheet1.getCell("F1").value = "平均身高";
  31.   sheet1.getCell("G1").value = "最大体重";
  32.   // 计算平均身高
  33.   sheet1.getCell("F2").value = {formula: "=AVERAGE(C2:C4)"};
  34.   // 计算最大体重
  35.   sheet1.getCell("G2").value = {formula: "=MAX(D2:D4)"};
  36.   // 计算BMI指数
  37.   // 获取第5列对象
  38.   const BMIRange = sheet1.getColumn(5)
  39.   BMIRange.eachCell((cell) => {
  40.     console.log("cell", cell)
  41.     console.log(cell.row)
  42.     // 从第二列开始添加计算公式
  43.     if (cell.row >= 2) {
  44.       sheet1.getCell("E" + cell.row).value = {formula: "D" + cell.row + "/" + "(C" + cell.row + "*" + "C" + cell.row + ")"};
  45.     }
  46.   })
  47.   // 导出表格文件
  48.   workbook.xlsx.writeBuffer().then((buffer) => {
  49.     let file = new Blob([buffer], {type: "application/octet-stream"});
  50.     FileSaver.saveAs(file, "ExcelJS.xlsx");
  51.   }).catch(error => console.log('Error writing excel export', error))
  52. }
  53. </script>
  54. <style scoped lang="scss">
  55. </style>
复制代码
导出Excel文件效果如下,E列已经主动替换为公式计算。

合并单位格

表格的合并应该是业务需求中最频繁的功能。当然这一功能使用 xlsx 也可以实现,我们只需要使用mergeCells方法,传入合并单位格范围参数即可。
参考文档:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E5%90%88%E5%B9%B6%E5%8D%95%E5%85%83%E6%A0%BC
具体代码实现如下所示:
  1. <template>
  2.   <el-button type="primary" @click="exportExcel">导出excel</el-button>
  3. </template>
  4. <script setup>
  5. import ExcelJS from "exceljs";
  6. import FileSaver from "file-saver";
  7. // 导出excel文件
  8. const exportExcel = () => {
  9.   // 创建工作簿
  10.   const workbook = new ExcelJS.Workbook();
  11.   // 添加工作表,名为sheet1
  12.   const sheet1 = workbook.addWorksheet("sheet1");
  13.   // 导出数据列表
  14.   const data = [
  15.     {"姓名": "张三", "年龄": 18, "身高": 175, "体重": 74},
  16.     {"姓名": "李四", "年龄": 18, "身高": '未知', "体重": '未知'},
  17.     {"姓名": "王五", "年龄": 53, "身高": '未知', "体重": '未知'},
  18.     {"姓名": "赵六", "年龄": 12, "身高": '未知', "体重": '未知'}
  19.   ]
  20.   // 获取表头所有键
  21.   const headers = Object.keys(data[0])
  22.   // 将标题写入第一行
  23.   sheet1.addRow(headers);
  24.   // 将数据写入工作表
  25.   data.forEach((row) => {
  26.     const values = Object.values(row)
  27.     sheet1.addRow(values);
  28.   });
  29.   // 上下合并单元格
  30.   sheet1.mergeCells("B2:B3");
  31.   // 左右合并单元格
  32.   sheet1.mergeCells("C3:D3");
  33.   // 范围合并单元格
  34.   sheet1.mergeCells("C4:D5");
  35.   // 导出表格文件
  36.   workbook.xlsx.writeBuffer().then((buffer) => {
  37.     let file = new Blob([buffer], {type: "application/octet-stream"});
  38.     FileSaver.saveAs(file, "ExcelJS.xlsx");
  39.   }).catch(error => console.log('Error writing excel export', error))
  40. }
  41. </script>
  42. <style scoped lang="scss">
  43. </style>
复制代码
单位格合并后导出文件效果如下:

数据验证

有时候我们需要为某个单位格添加数据可以方便直接下拉选择指定的值,此时就需要使用数据验证功能,传入可填写的选项列表。
参考文档:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81
例如我们对是否注册列添加数据验证,可填值为"是、否、未知",具体代码如下:
  1. <template>
  2.   <el-button type="primary" @click="exportExcel">导出excel</el-button>
  3. </template>
  4. <script setup>
  5. import ExcelJS from "exceljs";
  6. import FileSaver from "file-saver";
  7. // 导出excel文件
  8. const exportExcel = () => {
  9.   // 创建工作簿
  10.   const workbook = new ExcelJS.Workbook();
  11.   // 添加工作表,名为sheet1
  12.   const sheet1 = workbook.addWorksheet("sheet1");
  13.   // 导出数据列表
  14.   const data = [
  15.     {"姓名": "张三", "年龄": 18, "身高": 1.75, "体重": 74, "是否注册": ''},
  16.     {"姓名": "李四", "年龄": 22, "身高": 1.77, "体重": 84, "是否注册": ''},
  17.     {"姓名": "王五", "年龄": 53, "身高": 1.55, "体重": 64, "是否注册": ''}
  18.   ]
  19.   // 获取表头所有键
  20.   const headers = Object.keys(data[0])
  21.   // 将标题写入第一行
  22.   sheet1.addRow(headers);
  23.   // 将数据写入工作表
  24.   data.forEach((row) => {
  25.     const values = Object.values(row)
  26.     sheet1.addRow(values);
  27.   });
  28.   // 获取第5列对象
  29.   const VerificationRange = sheet1.getColumn(5)
  30.   VerificationRange.eachCell((cell) => {
  31.     // 从第二列开始添加数据验证规则
  32.     if (cell.row >= 2) {
  33.       sheet1.getCell("E" + cell.row).dataValidation = {
  34.         type: "list",
  35.         allowBlank: true,
  36.         formulae: ['"是,否,未知"']
  37.       };
  38.     }
  39.   })
  40.   // 导出表格文件
  41.   workbook.xlsx.writeBuffer().then((buffer) => {
  42.     let file = new Blob([buffer], {type: "application/octet-stream"});
  43.     FileSaver.saveAs(file, "ExcelJS.xlsx");
  44.   }).catch(error => console.log('Error writing excel export', error))
  45. }
  46. </script>
  47. <style scoped lang="scss">
  48. </style>
复制代码
导出的excel文件效果如下:

条件格式化

我们可以为指定单位格添加条件格式,对满足条件的单位格设置指定的样式。
参考文档:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E6%9D%A1%E4%BB%B6%E6%A0%BC%E5%BC%8F%E5%8C%96
例如为年事大于18岁单位格进行赤色标注,代码如下:
  1. <template>
  2.   <el-button type="primary" @click="exportExcel">导出excel</el-button>
  3. </template>
  4. <script setup>
  5. import ExcelJS from "exceljs";
  6. import FileSaver from "file-saver";
  7. // 导出excel文件
  8. const exportExcel = () => {
  9.   // 创建工作簿
  10.   const workbook = new ExcelJS.Workbook();
  11.   // 添加工作表,名为sheet1
  12.   const sheet1 = workbook.addWorksheet("sheet1");
  13.   // 导出数据列表
  14.   const data = [
  15.     {"姓名": "张三", "年龄": 18, "身高": 1.75, "体重": 74},
  16.     {"姓名": "李四", "年龄": 22, "身高": 1.77, "体重": 84},
  17.     {"姓名": "王五", "年龄": 53, "身高": 1.55, "体重": 64}
  18.   ]
  19.   // 获取表头所有键
  20.   const headers = Object.keys(data[0])
  21.   // 将标题写入第一行
  22.   sheet1.addRow(headers);
  23.   // 将数据写入工作表
  24.   data.forEach((row) => {
  25.     const values = Object.values(row)
  26.     sheet1.addRow(values);
  27.   });
  28.   // 年龄大于18岁红色标注
  29.   sheet1.addConditionalFormatting({
  30.     ref: "B2:B4",
  31.     rules: [
  32.       {
  33.         type: "cellIs",
  34.         operator: "greaterThan",
  35.         priority: 1,
  36.         formulae: [18],
  37.         style: {
  38.           fill: {
  39.             type: "pattern",
  40.             pattern: "solid",
  41.             bgColor: { argb: "FFFFC0CB" },
  42.           },
  43.         },
  44.       },
  45.     ],
  46.   });
  47.   // 导出表格文件
  48.   workbook.xlsx.writeBuffer().then((buffer) => {
  49.     let file = new Blob([buffer], {type: "application/octet-stream"});
  50.     FileSaver.saveAs(file, "ExcelJS.xlsx");
  51.   }).catch(error => console.log('Error writing excel export', error))
  52. }
  53. </script>
  54. <style scoped lang="scss">
  55. </style>
复制代码
导出后的文件效果如下:

封装exceljs

封装导入导出函数

为了进步项目代码的复用性,通常会将excel导入导出功能封装到单独的函数中方便调用,封装后的函数如下:
  1. import ExcelJS from "exceljs";
  2. import FileSaver from "file-saver";
  3. import {timeFile} from "@/utils/timeFormat";
  4. // 导出excel文件
  5. export function exportFile(export_data, filename) {
  6.     // 创建工作簿
  7.     const workbook = new ExcelJS.Workbook();
  8.     // 添加工作表,名为sheet1
  9.     const sheet1 = workbook.addWorksheet("sheet1");
  10.     // 获取表头所有键
  11.     const headers = Object.keys(export_data[0])
  12.     // 将标题写入第一行
  13.     sheet1.addRow(headers);
  14.     // 将数据写入工作表
  15.     export_data.forEach((row) => {
  16.         const values = Object.values(row)
  17.         sheet1.addRow(values);
  18.     });
  19.     // 设置默认宽高属性
  20.     sheet1.properties.defaultColWidth = 20
  21.     sheet1.properties.defaultRowHeight = 20
  22.     // 修改所有单元格样式
  23.     // 遍历每一行
  24.     sheet1.eachRow((row, rowNumber) => {
  25.         // 遍历每个单元格
  26.         row.eachCell((cell) => {
  27.             // 设置边框样式
  28.             cell.border = {
  29.                 top: {style: 'thin'},
  30.                 left: {style: 'thin'},
  31.                 bottom: {style: 'thin'},
  32.                 right: {style: 'thin'}
  33.             };
  34.             // 设置居中对齐
  35.             cell.alignment = {
  36.                 vertical: 'middle',
  37.                 horizontal: 'center'
  38.             };
  39.         });
  40.     });
  41.     // 获取标题行数据
  42.     const titleCell = sheet1.getRow(1);
  43.     // 设置标题行单元格样式
  44.     titleCell.eachCell((cell) => {
  45.         // 设置标题行背景颜色
  46.         cell.fill = {
  47.             type: 'pattern',
  48.             pattern: 'solid',
  49.             fgColor: {argb: '3498db'}
  50.         };
  51.         // 设置标题行字体
  52.         cell.font = {
  53.             bold: true,// 字体粗体
  54.         };
  55.     })
  56.     // 导出表格文件
  57.     workbook.xlsx.writeBuffer().then((buffer) => {
  58.         let file = new Blob([buffer], {type: "application/octet-stream"});
  59.         FileSaver.saveAs(file, filename + timeFile() + ".xlsx");
  60.     }).catch(error => console.log('Error writing excel export', error))
  61. }
  62. // 导入excel文件
  63. export function importFile(content) {
  64.     return new Promise((resolve, reject) => {
  65.         // 创建一个空的JavaScript对象数组,用于存储解析后的数据
  66.         const data = [];
  67.         //创建Workbook实例
  68.         const workbook = new ExcelJS.Workbook();
  69.         workbook.xlsx.load(content).then(workbook => {
  70.             // 获取第一个worksheet内容
  71.             const worksheet = workbook.getWorksheet(1);
  72.             // 获取第一行的标题
  73.             const headers = [];
  74.             worksheet.getRow(1).eachCell((cell) => {
  75.                 headers.push(cell.value);
  76.             });
  77.             // console.log("headers", headers)
  78.             // 遍历工作表的每一行(从第二行开始,因为第一行通常是标题行)
  79.             for (let rowNumber = 2; rowNumber <= worksheet.rowCount; rowNumber++) {
  80.                 const rowData = {};
  81.                 const row = worksheet.getRow(rowNumber);
  82.                 // 遍历当前行的每个单元格
  83.                 row.eachCell((cell, colNumber) => {
  84.                     // 获取标题对应的键,并将当前单元格的值存储到相应的属性名中
  85.                     rowData[headers[colNumber - 1]] = cell.value;
  86.                 });
  87.                 // 将当前行的数据对象添加到数组中
  88.                 data.push(rowData);
  89.             }
  90.             // console.log("data", data)
  91.             resolve(data);
  92.         }).catch(error => {
  93.             reject(error);
  94.         });
  95.     })
  96. }
复制代码
vue组件调用

以element plus为例,调用函数完成Excel文件导入与导出,代码如下:
  1. <template>
  2.   <el-button type="primary" @click="exportExcel">导出excel</el-button>
  3.   <el-button type="success" @click="importExcel">导入excel</el-button>
  4.   <p>导入数据预览</p>
  5.   {{ uploadData}}
  6.   <el-dialog
  7.       v-model="uploadDialogVisible"
  8.       title="批量添加数据"
  9.       width="40%"
  10.   >
  11.     <el-form label-width="120px">
  12.       <el-form-item label="模板下载:">
  13.         <el-button type="info" @click="downloadTemplate">
  14.           <el-icon>
  15.             <Download/>
  16.           </el-icon>
  17.           点击下载
  18.         </el-button>
  19.       </el-form-item>
  20.       <el-form-item label="文件上传:">
  21.         <el-upload drag accept=".xls,.xlsx" :auto-upload="false" :on-change="handleChange">
  22.           <el-icon class="el-icon--upload">
  23.             <upload-filled/>
  24.           </el-icon>
  25.           <div class="el-upload__text">
  26.             将文件拖到此处,或<em>点击上传</em>
  27.           </div>
  28.           <template #tip>
  29.             <div class="el-upload__tip">
  30.               请上传.xls,.xlsx格式文件,文件最大为500kb
  31.             </div>
  32.           </template>
  33.         </el-upload>
  34.       </el-form-item>
  35.     </el-form>
  36.     <template #footer>
  37.       <span class="dialog-footer">
  38.         <el-button @click="uploadDialogVisible = false">取消</el-button>
  39.         <el-button type="primary" @click="submitUpload">
  40.           导入
  41.         </el-button>
  42.       </span>
  43.     </template>
  44.   </el-dialog>
  45. </template>
  46. <script setup>
  47. import {ref} from "vue";
  48. import {ElMessage} from "element-plus";
  49. import {Download, UploadFilled} from "@element-plus/icons-vue"
  50. import {getDemo} from "@/api/home";
  51. import {timeFormatConversion} from "@/utils/timeFormat";
  52. import {exportFile, importFile} from "@/utils/excel";
  53. // 表格字段配置
  54. const fieldConfig = ref([
  55.   {
  56.     'label': 'ID', // 标签
  57.     'model': 'id',// 字段名
  58.     'is_export': true,// 是否导出该字段
  59.   },
  60.   {
  61.     'label': '用户名', // 标签
  62.     'model': 'username',// 字段名
  63.     'is_export': true, // 是否导出该字段
  64.   },
  65.   {
  66.     'label': '省份', // 标签
  67.     'model': 'province',// 字段名
  68.     'is_export': true // 是否导出该字段
  69.   },
  70.   {
  71.     'label': '性别', // 标签
  72.     'model': 'sex_name',// 字段名
  73.     'is_export': true // 是否导出该字段
  74.   },
  75.   {
  76.     'label': '生日', // 标签
  77.     'model': 'birthday',// 字段名
  78.     'is_export': true, // 是否导出该字段
  79.   },
  80.   {
  81.     'label': '身高(cm)', // 标签
  82.     'model': 'height',// 字段名
  83.     'is_export': true, // 是否导出该字段
  84.   },
  85.   {
  86.     'label': '体重(kg)', // 标签
  87.     'model': 'weight',// 字段名
  88.     'is_export': true,// 是否导出该字段
  89.   },
  90.   {
  91.     'label': '注册时间', // 标签
  92.     'model': 'created_time',// 字段名
  93.     'is_export': true, // 是否导出该字段
  94.   },
  95.   {
  96.     'label': '个人介绍', // 标签
  97.     'model': 'introduction',// 字段名
  98.     'is_export': true,// 是否导出该字段
  99.   }
  100. ])
  101. // 导出Excel事件
  102. const exportExcel = () => {
  103.   ElMessage({
  104.     message: '开始导出数据,请稍候!',
  105.     type: 'success',
  106.   })
  107.   // 导出数据查询参数
  108.   const printParams = {
  109.     'size': 1000,
  110.     'page': 1,
  111.   }
  112.   // 获取需要导出的字段配置
  113.   const export_fields = fieldConfig.value
  114.       .filter(obj => obj['is_export'])
  115.       .map(({label, model}) => ({[model]: label}))
  116.   // 处理数据结构
  117.   getDemo(printParams).then((response) => {
  118.     // console.log(response.results)
  119.     const export_data = response.results.map(obj => {
  120.       const newObj = {};
  121.       export_fields.forEach(field => {
  122.         const [key, value] = Object.entries(field)[0];
  123.         if (key === 'created_time') {
  124.           newObj[value] = timeFormatConversion((obj[key]), 'YYYY-MM-DD HH:mm:ss');
  125.         } else {
  126.           newObj[value] = obj[key];
  127.         }
  128.       });
  129.       return newObj;
  130.     });
  131.     let filename = '示例用户'
  132.     exportFile(export_data, filename);
  133.   }).catch(response => {
  134.     //发生错误时执行的代码
  135.     console.log(response)
  136.     ElMessage.error('获取列表数据失败!')
  137.   });
  138. }
  139. // 导入excel弹窗是否显示
  140. const uploadDialogVisible = ref(false)
  141. // 点击导入excel按钮事件
  142. const importExcel = () => {
  143.   uploadDialogVisible.value = true
  144. }
  145. // 下载模板文件
  146. const downloadTemplate = () => {
  147.   window.open('https://api.cuiliangblog.cn/static/demo-template.xlsx')
  148. }
  149. // 文件数据
  150. const uploadData=ref([])
  151. // 文件上传事件
  152. const handleChange = (file) => {
  153.   const reader = new FileReader();
  154.   reader.onload = () => {
  155.     const content = reader.result;
  156.     importFile(content).then((data) => {
  157.       console.log(data)
  158.       uploadData.value = data
  159.     }).catch(response => {
  160.       //发生错误时执行的代码
  161.       console.log(response)
  162.       ElMessage.error('获取列表数据失败!')
  163.     });
  164.   };
  165.   reader.readAsBinaryString(file.raw);
  166. };
  167. // 点击导入excel提交数据事件
  168. const submitUpload = () => {
  169.   uploadDialogVisible.value =false
  170. }
  171. </script>
  172. <style scoped lang="scss">
  173. </style>
复制代码
页面效果

封装后的页面效果如下,至此,一个简单的vue前端实现Excel文件导入导出功能便开辟完成了。

完整代码

gitee:https://gitee.com/cuiliang0302/vue3_vite_element-plus
github:https://github.com/cuiliang0302/vue3-vite-template
检察更多

微信公众号

微信公众号同步更新,欢迎关注微信公众号《崔亮的博客》第一时间获取最近文章。
博客网站

崔亮的博客-专注devops主动化运维,流传优秀it运维技能文章。更多原创运维开辟相关文章,欢迎访问https://www.cuiliangblog.cn

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

冬雨财经

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表