• 纯前端实现 导入 与 导出 Excel


    最近经常在做 不规则Excel的导入,或者一些普通Excel的导出,当前以上说的都是纯前端来实现;下面我们来聊聊经常用到的Excel导出与导入的实现方案,本文实现技术栈以 Vue2 + JS 为例

    导入分类:

    1. 调用 API 完全由后端来解析数据,清洗数据,前端只负责调用 API
    2. 前端解析 Excel ,清洗数据,把对应的数据处理成 API需要的 JSON;(本文主要介绍这个)

    导出分类:

    1. 调用API 完全由后端来生成Excel,前端获得 API 返回的文件名,下载即可;
    2. 前端根据 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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
    • 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

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    1.4 相关参数

    文件读取类型

    类型预期输入
    base64Base64编码类型字符串
    binary二进制字符串(字节n是data.charCodeAt(n))
    stringJS字符串(仅适用于UTF-8文本格式)
    buffernodejs的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;
    }
    
    • 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

    如果大数据的量导出建议还是后端来实现,前端要用 websocket 做优化,避免长时间 loading 带来不好的用户体验

  • 相关阅读:
    【C++】命名空间、缺省参数和函数重载
    vuex的模块化和namespaced
    python浏览器自动化环境搭建selenium
    怎么压缩证件照到200k以下?教你在线压缩图片指定kb
    用sublime测试正则表达式
    报错“this.bookDao“ is null
    软件工程导论第六版 第三章 需求分析知识点总结(上)
    DataFrame,数据列筛选代替遍历每一行数据去判断,大大提高数据过滤速度
    SpringCloud微服务实践之六 Feign最佳实践(抽取)
    啥,要我前端去对接外部?我也不会呀
  • 原文地址:https://blog.csdn.net/weixin_56650035/article/details/132666602