当前位置:   article > 正文

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

vue exceljs

前言

需求场景

最近在开发项目时需要批量导入和导出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库的关键特性和功能:

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

参考文档

npm仓库地址:https://www.npmjs.com/package/exceljs
官方中文文档地址:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md

快速上手

安装依赖

exceljs用于Excel数据处理,file-sever用于保存到本地文件。

npm i exceljs
npm i file-saver
  • 1
  • 2

导出Excel

让我们先从简单的数据导出开始,快速体验如何使用exceljs导出Excel文件,需要注意的是在浏览器环境中运行 JavaScript,浏览器的安全策略通常不允许直接访问读写本地文件系统。在这种情况下,需要通过其他方式将文件转换为buffer数据,在导出Excel时使用FileSaver.js库将缓冲区数据保存到文件中。

<template>
  <el-button type="primary" @click="exportExcel">导出excel</el-button>
</template>

<script setup>
import ExcelJS from "exceljs";
import FileSaver from "file-saver";
// 导出excel文件
const exportExcel = () => {
  // 创建工作簿
  const workbook = new ExcelJS.Workbook();
  // 添加工作表,名为sheet1
  const sheet1 = workbook.addWorksheet("sheet1");
  // 导出数据列表
  const data = [
    {"姓名": "张三", "年龄": 18, "身高": 175, "体重": 74},
    {"姓名": "李四", "年龄": 22, "身高": 177, "体重": 84},
    {"姓名": "王五", "年龄": 53, "身高": 155, "体重": 64}
  ]
  // 获取表头所有键
  const headers = Object.keys(data[0])
  // 将标题写入第一行
  sheet1.addRow(headers);
  // 将数据写入工作表
  data.forEach((row) => {
    const values = Object.values(row)
    sheet1.addRow(values);
  });
  // 导出表格文件
  workbook.xlsx.writeBuffer().then((buffer) => {
    let file = new Blob([buffer], {type: "application/octet-stream"});
    FileSaver.saveAs(file, "ExcelJS.xlsx");
  }).catch(error => console.log('Error writing excel export', error))
}
</script>

<style scoped lang="scss">

</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

当我们点击导出excel按钮时,调用exportFile函数,完成excel文件下载,下载后的文件内容如下:
image.png

导入Excel

导入excel文件时,同样使用FileReader的readAsArrayBuffer方法,将文件转换为二进制字符串,然后从buffer中加载数据并解析。

<template>
  <input
      type="file"
      accept=".xls,.xlsx"
      class="upload-file"
      @change="importExcel($event)"/>
</template>

<script setup>
import ExcelJS from "exceljs";
// 导出excel文件
const importExcel = (event) => {
  //获取选择的文件
  const files = event.target.files
  //创建Workbook实例
  const workbook = new ExcelJS.Workbook();
  // 使用FileReader对象来读取文件内容
  const fileReader = new FileReader()
  // 二进制字符串的形式加载文件
  fileReader.readAsArrayBuffer(files[0])
  fileReader.onload = ev => {
    console.log(ev)
    // 从 buffer中加载数据解析
    workbook.xlsx.load(ev.target.result).then(workbook => {
      // 获取第一个worksheet内容
      const worksheet = workbook.getWorksheet(1);
      // 获取第一行的标题
      const headers = [];
      worksheet.getRow(1).eachCell((cell) => {
        headers.push(cell.value);
      });
      console.log("headers", headers)
      // 创建一个空的JavaScript对象数组,用于存储解析后的数据
      const data = [];
      // 遍历工作表的每一行(从第二行开始,因为第一行通常是标题行)
      for (let rowNumber = 2; rowNumber <= worksheet.rowCount; rowNumber++) {
        const rowData = {};
        const row = worksheet.getRow(rowNumber);
        // 遍历当前行的每个单元格
        row.eachCell((cell, colNumber) => {
          // 获取标题对应的键,并将当前单元格的值存储到相应的属性名中
          rowData[headers[colNumber - 1]] = cell.value;
        });
        // 将当前行的数据对象添加到数组中
        data.push(rowData);
      }
      console.log("data", data)
    })
  }
}
</script>

<style scoped lang="scss">

</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

上传文件后,解析内容如下所示:
image.png

进阶操作

添加数据

我们可以通过columns方法添加列标题并定义列键和宽度,设置好表头后,我们可以直接通过addRow方法,根据key值去添加每一行的数据。
参考文档:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E5%88%97
完整代码如下:

<template>
  <el-button type="primary" @click="exportExcel">导出excel</el-button>
</template>

<script setup>
import ExcelJS from "exceljs";
import FileSaver from "file-saver";
// 导出excel文件
const exportExcel = () => {
  // 创建工作簿
  const workbook = new ExcelJS.Workbook();
  // 添加工作表,名为sheet1
  const sheet1 = workbook.addWorksheet("sheet1");
  // 添加表头列数据
  sheet1.columns = [
    {header: "姓名", key: "name", width: 20},
    {header: "年龄", key: "age", width: 10},
    {header: "身高", key: "height", width: 10},
    {header: "体重", key: "weight", width: 10},
  ];
  // 添加内容列数据
  sheet1.addRow({sort: 1, name: "张三", age: 18, height: 175, weight: 74});
  sheet1.addRow({sort: 2, name: "李四", age: 22, height: 177, weight: 88});
  sheet1.addRow({sort: 3, name: "王五", age: 53, height: 155, weight: 62});
  // 导出表格文件
  workbook.xlsx.writeBuffer().then((buffer) => {
    let file = new Blob([buffer], {type: "application/octet-stream"});
    FileSaver.saveAs(file, "ExcelJS.xlsx");
  }).catch(error => console.log('Error writing excel export', error))
}

</script>

<style scoped lang="scss">

</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

添加数据后导出文件效果如下:
image.png

读取数据

我们可以使用getRow方法,传入指定行参数读取行数据。
使用getColumn方法,传入键、字母、id参数读取列数据。
使用eachCell方法可以遍历每个单元格内容。
参考文档:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E8%A1%8C
代码如下:

<template>
  <el-button type="primary" @click="exportExcel">导出excel</el-button>
</template>

<script setup>
import ExcelJS from "exceljs";
import FileSaver from "file-saver";
// 导出excel文件
const exportExcel = () => {
  // 创建工作簿
  const workbook = new ExcelJS.Workbook();
  // 添加工作表,名为sheet1
  const sheet1 = workbook.addWorksheet("sheet1");
  // 添加表头列数据
  sheet1.columns = [
    {header: "姓名", key: "name", width: 20},
    {header: "年龄", key: "age", width: 10},
    {header: "身高", key: "height", width: 10},
    {header: "体重", key: "weight", width: 10},
  ];
  // 添加内容列数据
  sheet1.addRow({sort: 1, name: "张三", age: 18, height: 175, weight: 74});
  sheet1.addRow({sort: 2, name: "李四", age: 22, height: 177, weight: 88});
  sheet1.addRow({sort: 3, name: "王五", age: 53, height: 155, weight: 62});
  // 读取行数据
  sheet1.getRow(1).eachCell((cell, rowIdx) => {
    console.log("行数据", cell.value, rowIdx);
  });
  // 读取列数据,可以通过键(name),字母(B)和基于id(1)的列号访问单个列
  sheet1.getColumn("name").eachCell((cell, rowIdx) => {
    console.log("列数据", cell.value, rowIdx);
  });
}

</script>

<style scoped lang="scss">

</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

效果
image.png

样式

在导出excel文件时,默认没有任何样式的,为了美观我们需要添加样式,而exceljs支持修改表格样式,具体内容可参考文档https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E6%A0%B7%E5%BC%8F
例如,我们需要设置所有单元格居中对齐,并添加边框。并分别指定标题行和内容行字体大小、背景颜色、行高属性,代码如下:

<template>
  <el-button type="primary" @click="exportExcel">导出excel</el-button>
</template>

<script setup>
import ExcelJS from "exceljs";
import FileSaver from "file-saver";
// 导出excel文件
const exportExcel = () => {
  // 创建工作簿
  const workbook = new ExcelJS.Workbook();
  // 添加工作表,名为sheet1
  const sheet1 = workbook.addWorksheet("sheet1");
  // 导出数据列表
  const data = [
    {"姓名": "张三", "年龄": 18, "身高": 175, "体重": 74},
    {"姓名": "李四", "年龄": 22, "身高": 177, "体重": 84},
    {"姓名": "王五", "年龄": 53, "身高": 155, "体重": 64}
  ]
  // 获取表头所有键
  const headers = Object.keys(data[0])
  // 将标题写入第一行
  sheet1.addRow(headers);
  // 将数据写入工作表
  data.forEach((row) => {
    const values = Object.values(row)
    sheet1.addRow(values);
  });
  // 修改所有单元格样式
  // 遍历每一行
  sheet1.eachRow((row, rowNumber) => {
    // 遍历每个单元格
    row.eachCell((cell) => {
      // 设置边框样式
      cell.border = {
        top: {style: 'thin'},
        left: {style: 'thin'},
        bottom: {style: 'thin'},
        right: {style: 'thin'}
      };
      // 设置居中对齐
      cell.alignment = {
        vertical: 'middle',
        horizontal: 'center'
      };
    });
  });
  // 获取标题行数据
  const titleCell = sheet1.getRow(1);
  // 设置行高为30
  titleCell.height = 30
  // 设置标题行单元格样式
  titleCell.eachCell((cell) => {
    // 设置标题行背景颜色为黄色
    cell.fill = {
      type: 'pattern',
      pattern: 'solid',
      fgColor: {argb: 'FFFF00'}
    };
    // 设置标题行字体
    cell.font = {
      color: {argb: 'FF0000'}, //颜色为红色
      bold: true,// 字体粗体
      size: 18 // 设置字体大小为18
    };
  })
  // 获取第二行到最后一行的内容数据
  const bodyRows = sheet1.getRows(2, sheet1.rowCount);
  // 处理内容行的数据
  bodyRows.forEach((bodyRow) => {
    // 设置行高为20
    bodyRow.height = 20
    bodyRow.eachCell((cell) => {
      cell.font = {
        size: 16 // 设置内容行字体大小为16
      };
    });
  });
  // 导出表格文件
  workbook.xlsx.writeBuffer().then((buffer) => {
    let file = new Blob([buffer], {type: "application/octet-stream"});
    FileSaver.saveAs(file, "ExcelJS.xlsx");
  }).catch(error => console.log('Error writing excel export', error))
}

</script>

<style scoped lang="scss">

</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

导出Excel样式效果如下所示,已经成功按我们指定的样式导出了文件:
image.png

筛选

在很多的时候我们需要对表格中每一列的数据进行筛选,比如直接筛选姓名等列信息,我们可以通过 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
代码如下:

<template>
  <el-button type="primary" @click="exportExcel">导出excel</el-button>
</template>

<script setup>
import ExcelJS from "exceljs";
import FileSaver from "file-saver";
// 导出excel文件
const exportExcel = () => {
  // 创建工作簿
  const workbook = new ExcelJS.Workbook();
  // 添加工作表,名为sheet1
  const sheet1 = workbook.addWorksheet("sheet1");
  // 导出数据列表
  const data = [
    {"姓名": "张三", "年龄": 18, "身高": 175, "体重": 74},
    {"姓名": "李四", "年龄": 22, "身高": 177, "体重": 84},
    {"姓名": "王五", "年龄": 53, "身高": 155, "体重": 64}
  ]
  // 获取表头所有键
  const headers = Object.keys(data[0])
  // 将标题写入第一行
  sheet1.addRow(headers);
  // 将数据写入工作表
  data.forEach((row) => {
    const values = Object.values(row)
    sheet1.addRow(values);
  });
  // 单列筛选
  // sheet1.autoFilter = "A1";
  // 多个列筛选
  sheet1.autoFilter = "A1:C1";
  // 导出表格文件
  workbook.xlsx.writeBuffer().then((buffer) => {
    let file = new Blob([buffer], {type: "application/octet-stream"});
    FileSaver.saveAs(file, "ExcelJS.xlsx");
  }).catch(error => console.log('Error writing excel export', error))
}

</script>

<style scoped lang="scss">

</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

导入后的效果如下,在姓名、年龄、身高列添加了筛选按钮:
image.png

公式值

参考文档:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E5%80%BC%E7%B1%BB%E5%9E%8B
我们可以直接对表格中的数据进行公式计算,比如 求和(SUM),平均数(AVERAGE) 等。
例如我们需要计算平均值、最大值、指定公式时,代码如下:

<template>
  <el-button type="primary" @click="exportExcel">导出excel</el-button>
</template>

<script setup>
import ExcelJS from "exceljs";
import FileSaver from "file-saver";
// 导出excel文件
const exportExcel = () => {
  // 创建工作簿
  const workbook = new ExcelJS.Workbook();
  // 添加工作表,名为sheet1
  const sheet1 = workbook.addWorksheet("sheet1");
  // 导出数据列表
  const data = [
    {"姓名": "张三", "年龄": 18, "身高": 1.75, "体重": 74},
    {"姓名": "李四", "年龄": 22, "身高": 1.77, "体重": 84},
    {"姓名": "王五", "年龄": 53, "身高": 1.55, "体重": 64}
  ]
  // 获取表头所有键
  const headers = Object.keys(data[0])
  // 将标题写入第一行
  sheet1.addRow(headers);
  // 将数据写入工作表
  data.forEach((row) => {
    const values = Object.values(row)
    sheet1.addRow(values);
  });
  // 添加单元格
  sheet1.getCell("E1").value = "BMI指数";
  sheet1.getCell("F1").value = "平均身高";
  sheet1.getCell("G1").value = "最大体重";
  // 计算平均身高
  sheet1.getCell("F2").value = {formula: "=AVERAGE(C2:C4)"};
  // 计算最大体重
  sheet1.getCell("G2").value = {formula: "=MAX(D2:D4)"};
  // 计算BMI指数
  // 获取第5列对象
  const BMIRange = sheet1.getColumn(5)
  BMIRange.eachCell((cell) => {
    console.log("cell", cell)
    console.log(cell.row)
    // 从第二列开始添加计算公式
    if (cell.row >= 2) {
      sheet1.getCell("E" + cell.row).value = {formula: "D" + cell.row + "/" + "(C" + cell.row + "*" + "C" + cell.row + ")"};
    }
  })
  // 导出表格文件
  workbook.xlsx.writeBuffer().then((buffer) => {
    let file = new Blob([buffer], {type: "application/octet-stream"});
    FileSaver.saveAs(file, "ExcelJS.xlsx");
  }).catch(error => console.log('Error writing excel export', error))
}

</script>

<style scoped lang="scss">

</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

导出Excel文件效果如下,E列已经自动替换为公式计算。
image.png

合并单元格

表格的合并应该是业务需求中最频繁的功能。当然这一功能使用 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
具体代码实现如下所示:

<template>
  <el-button type="primary" @click="exportExcel">导出excel</el-button>
</template>

<script setup>
import ExcelJS from "exceljs";
import FileSaver from "file-saver";
// 导出excel文件
const exportExcel = () => {
  // 创建工作簿
  const workbook = new ExcelJS.Workbook();
  // 添加工作表,名为sheet1
  const sheet1 = workbook.addWorksheet("sheet1");
  // 导出数据列表
  const data = [
    {"姓名": "张三", "年龄": 18, "身高": 175, "体重": 74},
    {"姓名": "李四", "年龄": 18, "身高": '未知', "体重": '未知'},
    {"姓名": "王五", "年龄": 53, "身高": '未知', "体重": '未知'},
    {"姓名": "赵六", "年龄": 12, "身高": '未知', "体重": '未知'}
  ]
  // 获取表头所有键
  const headers = Object.keys(data[0])
  // 将标题写入第一行
  sheet1.addRow(headers);
  // 将数据写入工作表
  data.forEach((row) => {
    const values = Object.values(row)
    sheet1.addRow(values);
  });
  // 上下合并单元格
  sheet1.mergeCells("B2:B3");
  // 左右合并单元格
  sheet1.mergeCells("C3:D3");
  // 范围合并单元格
  sheet1.mergeCells("C4:D5");
  // 导出表格文件
  workbook.xlsx.writeBuffer().then((buffer) => {
    let file = new Blob([buffer], {type: "application/octet-stream"});
    FileSaver.saveAs(file, "ExcelJS.xlsx");
  }).catch(error => console.log('Error writing excel export', error))
}

</script>

<style scoped lang="scss">

</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

单元格合并后导出文件效果如下:
image.png

数据验证

有时候我们需要为某个单元格添加数据可以方便直接下拉选择指定的值,此时就需要使用数据验证功能,传入可填写的选项列表。
参考文档:https://github.com/exceljs/exceljs/blob/HEAD/README_zh.md#%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81
例如我们对是否注册列添加数据验证,可填值为"是、否、未知",具体代码如下:

<template>
  <el-button type="primary" @click="exportExcel">导出excel</el-button>
</template>

<script setup>
import ExcelJS from "exceljs";
import FileSaver from "file-saver";
// 导出excel文件
const exportExcel = () => {
  // 创建工作簿
  const workbook = new ExcelJS.Workbook();
  // 添加工作表,名为sheet1
  const sheet1 = workbook.addWorksheet("sheet1");
  // 导出数据列表
  const data = [
    {"姓名": "张三", "年龄": 18, "身高": 1.75, "体重": 74, "是否注册": ''},
    {"姓名": "李四", "年龄": 22, "身高": 1.77, "体重": 84, "是否注册": ''},
    {"姓名": "王五", "年龄": 53, "身高": 1.55, "体重": 64, "是否注册": ''}
  ]
  // 获取表头所有键
  const headers = Object.keys(data[0])
  // 将标题写入第一行
  sheet1.addRow(headers);
  // 将数据写入工作表
  data.forEach((row) => {
    const values = Object.values(row)
    sheet1.addRow(values);
  });
  // 获取第5列对象
  const VerificationRange = sheet1.getColumn(5)
  VerificationRange.eachCell((cell) => {
    // 从第二列开始添加数据验证规则
    if (cell.row >= 2) {
      sheet1.getCell("E" + cell.row).dataValidation = {
        type: "list",
        allowBlank: true,
        formulae: ['"是,否,未知"']
      };
    }
  })
  // 导出表格文件
  workbook.xlsx.writeBuffer().then((buffer) => {
    let file = new Blob([buffer], {type: "application/octet-stream"});
    FileSaver.saveAs(file, "ExcelJS.xlsx");
  }).catch(error => console.log('Error writing excel export', error))
}

</script>

<style scoped lang="scss">

</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

导出的excel文件效果如下:
image.png

条件格式化

我们可以为指定单元格添加条件格式,对满足条件的单元格设置指定的样式。
参考文档: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岁单元格进行红色标注,代码如下:

<template>
  <el-button type="primary" @click="exportExcel">导出excel</el-button>
</template>

<script setup>
import ExcelJS from "exceljs";
import FileSaver from "file-saver";
// 导出excel文件
const exportExcel = () => {
  // 创建工作簿
  const workbook = new ExcelJS.Workbook();
  // 添加工作表,名为sheet1
  const sheet1 = workbook.addWorksheet("sheet1");
  // 导出数据列表
  const data = [
    {"姓名": "张三", "年龄": 18, "身高": 1.75, "体重": 74},
    {"姓名": "李四", "年龄": 22, "身高": 1.77, "体重": 84},
    {"姓名": "王五", "年龄": 53, "身高": 1.55, "体重": 64}
  ]
  // 获取表头所有键
  const headers = Object.keys(data[0])
  // 将标题写入第一行
  sheet1.addRow(headers);
  // 将数据写入工作表
  data.forEach((row) => {
    const values = Object.values(row)
    sheet1.addRow(values);
  });
  // 年龄大于18岁红色标注
  sheet1.addConditionalFormatting({
    ref: "B2:B4",
    rules: [
      {
        type: "cellIs",
        operator: "greaterThan",
        priority: 1,
        formulae: [18],
        style: {
          fill: {
            type: "pattern",
            pattern: "solid",
            bgColor: { argb: "FFFFC0CB" },
          },
        },
      },
    ],
  });
  // 导出表格文件
  workbook.xlsx.writeBuffer().then((buffer) => {
    let file = new Blob([buffer], {type: "application/octet-stream"});
    FileSaver.saveAs(file, "ExcelJS.xlsx");
  }).catch(error => console.log('Error writing excel export', error))
}

</script>

<style scoped lang="scss">

</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

导出后的文件效果如下:
image.png

封装exceljs

封装导入导出函数

为了提高项目代码的复用性,通常会将excel导入导出功能封装到单独的函数中方便调用,封装后的函数如下:

import ExcelJS from "exceljs";
import FileSaver from "file-saver";
import {timeFile} from "@/utils/timeFormat";
// 导出excel文件
export function exportFile(export_data, filename) {
    // 创建工作簿
    const workbook = new ExcelJS.Workbook();
    // 添加工作表,名为sheet1
    const sheet1 = workbook.addWorksheet("sheet1");
    // 获取表头所有键
    const headers = Object.keys(export_data[0])
    // 将标题写入第一行
    sheet1.addRow(headers);
    // 将数据写入工作表
    export_data.forEach((row) => {
        const values = Object.values(row)
        sheet1.addRow(values);
    });
    // 设置默认宽高属性
    sheet1.properties.defaultColWidth = 20
    sheet1.properties.defaultRowHeight = 20
    // 修改所有单元格样式
    // 遍历每一行
    sheet1.eachRow((row, rowNumber) => {
        // 遍历每个单元格
        row.eachCell((cell) => {
            // 设置边框样式
            cell.border = {
                top: {style: 'thin'},
                left: {style: 'thin'},
                bottom: {style: 'thin'},
                right: {style: 'thin'}
            };
            // 设置居中对齐
            cell.alignment = {
                vertical: 'middle',
                horizontal: 'center'
            };
        });
    });
    // 获取标题行数据
    const titleCell = sheet1.getRow(1);
    // 设置标题行单元格样式
    titleCell.eachCell((cell) => {
        // 设置标题行背景颜色
        cell.fill = {
            type: 'pattern',
            pattern: 'solid',
            fgColor: {argb: '3498db'}
        };
        // 设置标题行字体
        cell.font = {
            bold: true,// 字体粗体
        };
    })
    // 导出表格文件
    workbook.xlsx.writeBuffer().then((buffer) => {
        let file = new Blob([buffer], {type: "application/octet-stream"});
        FileSaver.saveAs(file, filename + timeFile() + ".xlsx");
    }).catch(error => console.log('Error writing excel export', error))
}

// 导入excel文件
export function importFile(content) {
    return new Promise((resolve, reject) => {
        // 创建一个空的JavaScript对象数组,用于存储解析后的数据
        const data = [];
        //创建Workbook实例
        const workbook = new ExcelJS.Workbook();
        workbook.xlsx.load(content).then(workbook => {
            // 获取第一个worksheet内容
            const worksheet = workbook.getWorksheet(1);
            // 获取第一行的标题
            const headers = [];
            worksheet.getRow(1).eachCell((cell) => {
                headers.push(cell.value);
            });
            // console.log("headers", headers)
            // 遍历工作表的每一行(从第二行开始,因为第一行通常是标题行)
            for (let rowNumber = 2; rowNumber <= worksheet.rowCount; rowNumber++) {
                const rowData = {};
                const row = worksheet.getRow(rowNumber);
                // 遍历当前行的每个单元格
                row.eachCell((cell, colNumber) => {
                    // 获取标题对应的键,并将当前单元格的值存储到相应的属性名中
                    rowData[headers[colNumber - 1]] = cell.value;
                });
                // 将当前行的数据对象添加到数组中
                data.push(rowData);
            }
            // console.log("data", data)
            resolve(data);
        }).catch(error => {
            reject(error);
        });
    })
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

vue组件调用

以element plus为例,调用函数完成Excel文件导入与导出,代码如下:

<template>
  <el-button type="primary" @click="exportExcel">导出excel</el-button>
  <el-button type="success" @click="importExcel">导入excel</el-button>
  <p>导入数据预览</p>
  {{ uploadData}}
  <el-dialog
      v-model="uploadDialogVisible"
      title="批量添加数据"
      width="40%"
  >
    <el-form label-width="120px">
      <el-form-item label="模板下载:">
        <el-button type="info" @click="downloadTemplate">
          <el-icon>
            <Download/>
          </el-icon>
          点击下载
        </el-button>
      </el-form-item>
      <el-form-item label="文件上传:">
        <el-upload drag accept=".xls,.xlsx" :auto-upload="false" :on-change="handleChange">
          <el-icon class="el-icon--upload">
            <upload-filled/>
          </el-icon>
          <div class="el-upload__text">
            将文件拖到此处,或<em>点击上传</em>
          </div>
          <template #tip>
            <div class="el-upload__tip">
              请上传.xls,.xlsx格式文件,文件最大为500kb
            </div>
          </template>
        </el-upload>
      </el-form-item>
    </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="uploadDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitUpload">
          导入
        </el-button>
      </span>
    </template>
  </el-dialog>
</template>

<script setup>
import {ref} from "vue";
import {ElMessage} from "element-plus";
import {Download, UploadFilled} from "@element-plus/icons-vue"
import {getDemo} from "@/api/home";
import {timeFormatConversion} from "@/utils/timeFormat";
import {exportFile, importFile} from "@/utils/excel";
// 表格字段配置
const fieldConfig = ref([
  {
    'label': 'ID', // 标签
    'model': 'id',// 字段名
    'is_export': true,// 是否导出该字段
  },
  {
    'label': '用户名', // 标签
    'model': 'username',// 字段名
    'is_export': true, // 是否导出该字段
  },
  {
    'label': '省份', // 标签
    'model': 'province',// 字段名
    'is_export': true // 是否导出该字段
  },
  {
    'label': '性别', // 标签
    'model': 'sex_name',// 字段名
    'is_export': true // 是否导出该字段
  },
  {
    'label': '生日', // 标签
    'model': 'birthday',// 字段名
    'is_export': true, // 是否导出该字段
  },
  {
    'label': '身高(cm)', // 标签
    'model': 'height',// 字段名
    'is_export': true, // 是否导出该字段
  },
  {
    'label': '体重(kg)', // 标签
    'model': 'weight',// 字段名
    'is_export': true,// 是否导出该字段
  },
  {
    'label': '注册时间', // 标签
    'model': 'created_time',// 字段名
    'is_export': true, // 是否导出该字段
  },
  {
    'label': '个人介绍', // 标签
    'model': 'introduction',// 字段名
    'is_export': true,// 是否导出该字段
  }
])
// 导出Excel事件
const exportExcel = () => {
  ElMessage({
    message: '开始导出数据,请稍候!',
    type: 'success',
  })
  // 导出数据查询参数
  const printParams = {
    'size': 1000,
    'page': 1,
  }
  // 获取需要导出的字段配置
  const export_fields = fieldConfig.value
      .filter(obj => obj['is_export'])
      .map(({label, model}) => ({[model]: label}))
  // 处理数据结构
  getDemo(printParams).then((response) => {
    // console.log(response.results)
    const export_data = response.results.map(obj => {
      const newObj = {};
      export_fields.forEach(field => {
        const [key, value] = Object.entries(field)[0];
        if (key === 'created_time') {
          newObj[value] = timeFormatConversion((obj[key]), 'YYYY-MM-DD HH:mm:ss');
        } else {
          newObj[value] = obj[key];
        }
      });
      return newObj;
    });
    let filename = '示例用户'
    exportFile(export_data, filename);
  }).catch(response => {
    //发生错误时执行的代码
    console.log(response)
    ElMessage.error('获取列表数据失败!')
  });
}
// 导入excel弹窗是否显示
const uploadDialogVisible = ref(false)
// 点击导入excel按钮事件
const importExcel = () => {
  uploadDialogVisible.value = true
}
// 下载模板文件
const downloadTemplate = () => {
  window.open('https://api.cuiliangblog.cn/static/demo-template.xlsx')
}
// 文件数据
const uploadData=ref([])
// 文件上传事件
const handleChange = (file) => {
  const reader = new FileReader();
  reader.onload = () => {
    const content = reader.result;
    importFile(content).then((data) => {
      console.log(data)
      uploadData.value = data
    }).catch(response => {
      //发生错误时执行的代码
      console.log(response)
      ElMessage.error('获取列表数据失败!')
    });
  };
  reader.readAsBinaryString(file.raw);
};
// 点击导入excel提交数据事件
const submitUpload = () => {
  uploadDialogVisible.value =false
}
</script>

<style scoped lang="scss">

</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176

页面效果

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

完整代码

gitee:https://gitee.com/cuiliang0302/vue3_vite_element-plus
github:https://github.com/cuiliang0302/vue3-vite-template

查看更多

微信公众号

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

博客网站

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/490075
推荐阅读
相关标签
  

闽ICP备14008679号