目录
  • 前言
  • 一、插件安装
  • 二、数据准备
  • 三、插件引入
  • 四、导出前数据处理
    • 1、按exceljs格式创建导出函数
    • 2、表头及列宽数据预处理
      • 2.1 处理逻辑
    • 3、单元格样式处理函数
      • 4、表头数据填充及样式设置
        • 5、横向单元格合并处理
          • 6、纵向单元格合并处理
            • 7、添加数据集
            • 五、调用导出函数
              • 总结

                前言

                因为项目需求要用到前端导出功能,粗略比较了几个导出插件之后,决定使用exceljs插件,至少能够满足样式、数据格式等的需求。

                鉴于个人水平和时间限制,目前只是初步实现了组件化,将表头、数据按要求格式传递到组件中即可导出需要的excel文件。代码质量、实现思路存在一定的缺陷,后续有时间会继续优化,如果大家有更好的实现,感谢留言!

                组件导出文件效果如下:

                 一级表头

                vue2项目使用exceljs多表头导出功能详解

                二级表头

                vue2项目使用exceljs多表头导出功能详解

                三级表头

                vue2项目使用exceljs多表头导出功能详解

                一、插件安装

                yarn add exceljs
                yarn add file-saver

                二、数据准备

                1、前台表格表头数据

                const data = [
                    {
                      prop: "AA",
                      label: "地区",
                      minWidth: "150px"
                    },
                    {
                      prop: "BB",
                      label: "归集额",
                      minWidth: "100px"
                    },
                    {
                      label: "对下调入",
                      children: [
                        {
                          prop: "CC",
                          label: "资金",
                          minWidth: "90px"
                        },
                        {
                          prop: "DD",
                          label: "占比",
                          minWidth: "80px"
                        },
                      ],
                    },
                    {
                      label: "本级调入",
                      children: [
                        {
                          prop: "EE",
                          label: "资金",
                          minWidth: "100px"
                        },
                        {
                          prop: "FF",
                          label: "占比",
                          minWidth: "80px"
                        }
                      ]
                    }
                  ]

                2、传递到组件前的数据处理

                // 表头数据预处理函数
                const headersPreprocess = function (businessAttr) {
                  let columns = deepClone(businessAttr);
                  let maxLevel = 0;
                  let baseCols = [];
                  function columnsProcess(columns = [], level = 0, path = "") {
                    ++level;
                    maxLevel = maxLevel < level ? level : maxLevel;
                    for (let i = 0; i < columns.length; i++) {
                      // 获取列的路径,用于后面表头数据格式处理
                      columns[i].path = emptyCheck(path) ? path + "_" + columns[i].label : columns[i].label;
                      columnProcess(columns[i]);
                      if (columns[i].children) {
                        columnsProcess(columns[i].children, level, columns[i].path);
                      } else {
                        baseCols.push({
                          prop: columns[i].prop,    // 当前字段的prop值
                          path: columns[i].path,    // 当前字段的路径
                          level: level,             // 当前列所处层级
                          width: columns[i].width || columns[i].minWidth,    // 列宽
                        });
                        delete columns[i].path;
                      }
                    }
                  }
                  columnsProcess(columns, 0);
                  return {
                    columns: columns,     // 前台表格表头用到的数据
                    depth: maxLevel,      // 树形结构表头的最大层级数--导出功能所需
                    baseCols: baseCols    // 底层字段数据集--导出功能所需
                  };
                };

                得到的baseCols数据

                //得到的baseCols数据
                [
                    {
                        level: 1,
                        path:  "地区",
                        prop: "AA",
                        width: "150px"
                    },
                    {
                        level: 1,
                        path:  "资金归集额",
                        prop: "BB",
                        width: "100px"
                    },
                    {
                        level: 2,
                        path:  "对下调入_资金",
                        prop: "CC",
                        width: "90px"
                    },
                    {
                        level: 2,
                        path:  "对下调入_占比",
                        prop: "DD",
                        width: "80px"
                    },
                    {
                        level: 2,
                        path:  "本级调入_资金",
                        prop: "DD",
                        width: "100px"
                    },
                    {
                        level: 2,
                        path:  "本级调入_占比",
                        prop: "DD",
                        width: "80px"
                    },
                ]

                三、插件引入

                1、新建ExcelJS.js文件

                2、ExcelJS.js文件中引入需要的库

                import ExcelJS from "exceljs";
                import FileSaver from "file-saver";

                四、导出前数据处理

                1、按exceljs格式创建导出函数

                conf参数包含的就是上面获取到的baseCols和depth数据,及自定义的其他属性,如文件名等

                /**
                 * @description: excel导出
                 * @param  dataList 需要导出的数据集
                 * @param  conf 导出函数配置数据,包括上面处理得到的底层字段信息baseCols,表头层级数据depth,文件名fileName
                 * @return 
                 */
                export const downloadExcel = function (dataList = [], conf = {}) {
                  const workbook = new ExcelJS.Workbook();
                  // 设置信息
                  workbook.creator = "Me";
                  workbook.title = conf.fileName;
                  workbook.created = new Date();
                  workbook.modified = new Date();
                  // 创建工作表
                  const worksheet = workbook.addWorksheet(conf.fileName)
                  // 表头信息处理
                  let columns = columnsPreprocess(conf)    
                  // 表头数据填充及样式设置
                  headerFillAndSet(columns.headers, columns.widthList, worksheet) 
                  // 横向单元格合并处理
                  sheetMergesForCross(worksheet, columns.headers);
                  // 纵向单元格合并处理
                  sheetMergesForVertical(worksheet, columns.headers);
                  // 添加数据集
                  worksheetDataProcess(conf.baseCols, dataList, worksheet)
                  const _titleCell = titleStyleProcess(worksheet);
                  // 写入文件
                  workbook.xlsx.writeBuffer().then((buffer) => {
                    let _file = new Blob([buffer], {
                      type: "application/octet-stream",
                    });
                    FileSaver.saveAs(_file, "ExcelJS.xlsx");
                  });
                };

                2、表头及列宽数据预处理

                2.1 处理逻辑

                // 获取表头及列宽数据集
                function columnsPreprocess(conf = {}) {
                  let underlayCols = conf.baseCols;
                  let paths = [];
                  let widthArr = [];
                  // 将各列的路径信息及列宽分别放到数组中
                  for (let i = 0; i < underlayCols.length; i++) {
                    paths.push(underlayCols[i].path);
                    // 列宽放入数组前,使用excelColumnWidthProcess函数提前处理
                    widthArr.push(excelColumnWidthProcess(underlayCols[i].width));
                  }
                  return {
                    // excel使用到的表头数据没有可以直接用到的,需要用到headersProcess进行转换
                    headers: paths.length > 0 ? headersProcess(paths, conf.depth) : [],
                    widthList: widthArr,
                  };
                }
                // 表头数据处理
                // 从最上面的界面表格的表头树形结构数据可以看到,有6个底层列,数据结构有2层,转为excel数据格式的 // 话,就需要转为2行6列。即表头层级有多少,这里就转为多少行,表头底层字段有多少,这里就有多少列。
                function headersProcess(pathList = [], depth = 1) {
                  let headers = [];
                  for (let i = 0; i < depth; i++) {
                    headers.push([]);
                  }
                  let paths = [];
                  for (let i = 0; i < pathList.length; i++) {
                    let arr = pathList[i].split("_");
                    for (let j = arr.length; j < depth; j++) {
                      arr.push("");
                    }
                    paths.push(arr);
                  }
                  for (let i = 0; i < depth; i++) {
                    for (let j = 0; j < paths.length; j++) {
                      headers[i].push(paths[j][i]);
                    }
                  }
                  return headers;
                }
                // 列宽处理
                // 宽度数据需要转为数字,同时前端的宽度与excel中的列宽存在较大的区别,所以此处除以了5
                function excelColumnWidthProcess(width) {
                  let result = 40;
                  if (emptyCheck(width)) {
                    if (getDataType(width) === "string") {
                      if (width.includes("px")) {
                        width = width.replace("px", "");
                        width = parseFloat(width);
                        result = parseInt(width / 5);
                      }
                    } else if (getDataType(width) === "number") {
                      result = parseInt(width / 5);
                    }
                  }
                  return result;
                };

                2.2 处理结果

                // 得到的headers处理结果
                [
                  ['地区', '资金归集额', '省对下调入', '省对下调入', '本级调入', '本级调入'],
                  ['',     '',          '资金',      '占比',       '资金',     '占比']
                ]

                3、单元格样式处理函数

                // 单元格样式处理,按需设置
                const titleStyleProcess = function (sheet, index) {
                  const titleCell = sheet.getRow(index);
                  titleCell.eachCell({ includeEmpty: true }, (cell, colNumber) => {
                    titleCell.getCell(colNumber).fill = {
                      type: "pattern",
                      pattern: "solid",
                      fgColor: { argb: "FFF5F7FA" },
                    };
                    titleCell.getCell(colNumber).border = {
                      top: { style: "thin" },
                      left: { style: "thin" },
                      bottom: { style: "thin" },
                      right: { style: "thin" },
                    };
                  });
                  titleCell.height = 30;
                  titleCell.font = {
                    name: "黑体",
                    bold: true,
                    size: 14,
                    color: {
                      argb: "FF999999",
                    },
                  };
                  // // 设置第一行的对齐方式(水平垂直)
                  titleCell.alignment = {
                    vertical: "middle",
                    horizontal: "center",
                  };
                  return titleCell;
                };

                4、表头数据填充及样式设置

                // 设置表头的方法还有 worksheet.columns = headers等方式,但似乎只适用于只有一层的表头,也可能是 // 自己没有找对方法。在排除直接设置的方式后,直接将表头数据当做普通行数据进行处理。同时进行行样式设 // 置,此时也就可以理解为表头样式设置。
                function headerFillAndSet(headers = [], widthList = [], worksheet) {
                  for (let i = 1; i <= headers.length; i++) {
                    worksheet.getRow(i).values = headers[i - 1];
                    titleStyleProcess(worksheet, i);
                  }
                  for (let i = 1; i <= widthList.length; i++) {
                    worksheet.getColumn(i).width = widthList[i - 1];
                  }
                }

                5、横向单元格合并处理

                // Excel的列名集合,用于合并单元格时定位单元格位置
                const colNames = [
                  "A",
                  "B",
                  ...,
                  "Y",
                  "Z",
                  "AA",
                  "AB",
                  ...,
                  "AY",
                  "AZ",
                  ....
                ];
                // 
                function sheetMergesForCross(worksheet, headers) {
                  for (let i = 0; i < headers.length; i++) {
                    let arr = [null, null];
                    for (let j = 1; j <= headers[i].length; j++) {
                      if (headers[i][j - 1] === headers[i][j]) {    // 前后元素相同,表示可以合并
                        if (!emptyCheck(arr[0]) && emptyCheck(headers[i][j - 1])) {
                          arr[0] = colNames[j - 1] + (i + 1);
                        }
                        arr[1] = colNames[j] + (i + 1);
                      } else {    // 前后元素不相同,j是从1开始,所以表示没有相同列或者此次相同列已结束
                        if (emptyCheck(arr[0]) && emptyCheck(arr[1])) { // arr[0]或者arr[1]为空,表示相邻元素不同,均有值表示有相同列,arr[1]便是最后一个相同的列
                          worksheet.mergeCells(arr[0] + ":" + arr[1]);
                        }
                        arr = [null, null];    // 相邻元素不同,arr重置,准备下一批可合并元素
                      }
                    }
                  }
                }

                6、纵向单元格合并处理

                function sheetMergesForVertical(worksheet, headers) {
                  let col = headers[0];
                  // 第一层循环具体元素
                  for (let i = 0; i < col.length; i++) {
                    let sd = ""; // 开始元素
                    let ed = ""; // 结束元素
                    // 第二层循环,比较层级不同下标相同的元素
                    for (let j = 1; j < headers.length; j++) {
                      if (headers[j][i] === "") {  // 元素为空,表示可与上层元素合并
                        sd = emptyCheck(sd) ? sd : colNames[i] + j;
                        ed = colNames[i] + (j + 1);
                      }
                    }
                    if (emptyCheck(sd) && emptyCheck(ed)) {
                      worksheet.mergeCells(sd + ":" + ed);
                    }
                  }
                }

                7、添加数据集

                function worksheetDataProcess(columns = [], dataList = [], worksheet = null) {
                  let len = 1;
                  for (let i = 0; i < columns.length; i++) {
                    len = len < columns[i].level ? columns[i].level : len;
                  }
                  for (let i = 0; i < dataList.length; i++) {
                    let list = [];
                    for (let j = 0; j < columns.length; j++) {
                      list.push(dataList[i][columns[j].prop]);
                    }
                    worksheet.getRow(len + i + 1).values = list;
                  }
                }

                五、调用导出函数

                // conf = {
                //   baseCols: baseCols,    表头信息数组
                //   depth: 2,    // 表头最大层级数
                //   fileName: 'xxxx',  导出文件名、sheet名等,自定
                //   ....  其他自定义属性
                // }
                
                // dataList 需要导出的数据集
                downloadExcel(dataList, conf)

                总结

                声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。