前端导出excel 支持 .xls和.xlsx

本文介绍了如何利用exceljs库封装一个Vue组件,实现定制字体、内容居中、多行表头等功能,并通过递归处理超过65536行的数据,以便于导出为.xls格式。同时提及了设置表格样式和解决打开.xls文件可能的错误。
摘要由CSDN通过智能技术生成

此封装具有字体样式、内容居中、多行表头、单元格合并、多表导出功能。

一、开发背景

为响应客户需求的字体样式,试了很多的插件

最终确定用exceljs

github: https://github.com/exceljs/exceljs/tree/v3.10.0

二、封装

安装exceljs

npm install exceljs

导入exceljs 

import ExcelJS from 'exceljs'

放在全局

// exceljs
// github: https://github.com/exceljs/exceljs/tree/v3.10.0

const ExcelJS = require("exceljs");
import FileSaver from "file-saver";
const { i18n } = require("@/utils/i18n.js");
import BUSINESS_CONFIG from "@/business_config.js";
require("script-loader!xlsx/dist/xlsx.core.min");

const twoHeaderRows = 65534;
const oneHeaderRows = 65535;
var rows = [];
export default {
  install(Vue) {
    Vue.prototype.$exportExcelJs = async function (
      columns,
      dataList,
      fileName,
      fileType
    ) {
      // 工作表下标
      var sheetIndex = 0;
      // 创建工作簿
      var workbook = new ExcelJS.Workbook();

      // 设置工作簿属性
      workbook.creator = "Me";
      workbook.lastModifiedBy = "Her";
      workbook.created = new Date(1985, 8, 30);
      workbook.modified = new Date();
      workbook.lastPrinted = new Date(2016, 9, 27);

      // 将工作簿日期设置为 1904 年日期系统
      workbook.properties.date1904 = true;

      // 所有工作表
      var worksheetAll = [];

      // 是否两行标题
      let isTwo = false;

      for (var index = 0; index < columns.length; index++) {
        if ("children" in columns[index]) {
          isTwo = true;
          break;
        }
      }

      //  递归分表
      recursiveSharding(
        isTwo,
        dataList,
        worksheetAll,
        workbook,
        sheetIndex,
        columns
      );

      // 转成buffer
      const buffer = await workbook.xlsx.writeBuffer();

      //   file-saver在保存文件时,需要把Buffer/Blob转换为Blob URL
      const blob = new Blob([buffer], {
        type: "application/vnd.openxmlformats",
      });

      var defaultTitle = fileName || i18n.t("列表");

      if (fileType == ".xls") {
        FileSaver.saveAs(blob, `${defaultTitle}.xls`);
      } else {
        FileSaver.saveAs(blob, `${defaultTitle}.xls`);
      }
      this.$store.commit("saveLoadShow", false);
    };
  },
};

之所以用递归创建表是因为.xls的表超过65536行不显示。

三、递归分表

/*  递归分表
 isTwo 是否两行标题,dataList 数据源, 
 worksheetAll 所有工作表, workbook 工作簿,
 sheetIndex 工作表下标,columns字段配置
 */
function recursiveSharding(
  isTwo,
  dataList,
  worksheetAll,
  workbook,
  sheetIndex,
  columns
) {
  if (
    (isTwo && dataList.length - twoHeaderRows * sheetIndex >= 0) ||
    dataList.length - oneHeaderRows * sheetIndex >= 0
  ) {
    // 获取起始下标、终点下标
    var startIndex = oneHeaderRows * sheetIndex;
    var endIndex = oneHeaderRows * (sheetIndex + 1);
    if (isTwo) {
      startIndex = twoHeaderRows * sheetIndex;
      endIndex = twoHeaderRows * (sheetIndex + 1);
    }
    // 添加工作表
    worksheetAll.push(workbook.addWorksheet(`Sheet${sheetIndex + 1}`));
    // 按 name 提取工作表
    //   var worksheet = workbook.getWorksheet("My Sheet");
    // 不能复制标题,要重新获取标题,不然标题会被第二行覆盖,也没有合并居中
    const { formatColumns } = getColumns(columns, worksheetAll[sheetIndex]);
    worksheetAll[sheetIndex].columns = formatColumns;
    rows = dataList.slice(startIndex, endIndex);
    worksheetAll[sheetIndex].addRows(rows);
    worksheetAll[sheetIndex].columns = setWidth(worksheetAll[sheetIndex]);
    setCellFontStyle(isTwo, worksheetAll[sheetIndex]);
    sheetIndex += 1;
    recursiveSharding(
      isTwo,
      dataList,
      worksheetAll,
      workbook,
      sheetIndex,
      columns
    );
  } else {
    return;
  }
}

 四、获取标题行

// 获取标题行
function getColumns(columns, worksheet) {
  var tHeaderChildren = [];
  var isHaveChildren = false;
  let formatColumns = [];

  for (var index = 0; index < columns.length; index++) {
    if ("children" in columns[index]) {
      isHaveChildren = true;
      break;
    }
  }

  if (isHaveChildren) {
    // 设置第一行标题
    let startChar = "A";
    let endChar = "B";
    columns.forEach((item) => {
      // 这里是定义Excel表格里面的表头和内容
      if ("children" in item) {
        endChar = startChar.charCodeAt() + item.children.length - 1;
        endChar = String.fromCharCode(endChar);
        // 合并,保持长度一致
        worksheet.mergeCells(`${startChar}1:${endChar}1`);
        // 设置合并单元格内容
        worksheet.getCell(`${startChar}1`).value = item.title;

        // 设置下一个合并起点
        startChar = endChar.charCodeAt() + 1;
        startChar = String.fromCharCode(startChar);
        item.children.forEach((ele) => {
          tHeaderChildren.push(ele.title);
          formatColumns.push({
            header: ele.title,
            key: ele.dataIndex,
            width: 10,
          });
        });
      }
    });

    // 设置第二行副标题
    worksheet.getRow(2).values = tHeaderChildren;
    return { formatColumns: formatColumns, isTwo: true };
  } else {
    // 添加列标题并定义列键和宽度
    // 注意:这些列结构仅是构建工作簿的方便之处,除了列宽之外,它们不会完全保留。
    columns.forEach((item) => {
      formatColumns.push({
        header: item.title,
        key: item.dataIndex,
        width: 10,
      });
    });
    return { formatColumns: formatColumns, isTwo: false };
  }
}

五、设置worksheet每列的最大宽度

/*设置worksheet每列的最大宽度*/
function setWidth(worksheet) {
  /*以第一行为初始值*/
  var result = [];
  worksheet.columns.forEach((item) => {
    result.push(item.width);
  });

  worksheet._rows.forEach((row) => {
    if (Array.isArray(row._cells)) {
      row._cells.forEach((cell, index) => {
        let cellValue = cell.value;
        if (cellValue == null) {
          cell.tempWidth = 10;
        } else if (cellValue.toString().charCodeAt(0) > 255) {
          // 中文
          cell.tempWidth = cellValue.toString().length * 2 + 2;
        } else {
          cell.tempWidth = cellValue.toString().length + 4;
        }
        // 每列的最大宽度
        if (result[index] < cell.tempWidth) {
          result[index] = cell.tempWidth;
        }
        // 居中对齐
        cell.alignment = { vertical: "middle", horizontal: "center" };
      });
    }
  });

  //   赋值给标题行
  worksheet.columns.forEach((item, index) => {
    if (item.width < result[index]) {
      item.width = result[index];
    }
  });
  return worksheet.columns;
}

 六、根据环境配置字体样式


// 设置字体样式
function setCellFontStyle(isTwo, worksheet) {
  // 4.0字体样式
  var font = {
    name: "宋体",
    family: 4,
    size: 12,
  };
  // 美格字体样式
  if (BUSINESS_CONFIG.isMgBusiness()) {
    font = {
      name: "宋体",
      family: 4,
      size: 10,
    };
  }
  // 根据环境配置字体样式
  worksheet._rows.forEach((row, index) => {
    row.font = {
      name: font.name,
      family: font.family,
      size: font.size,
      bold: isTwo
        ? [0, 1].includes(index)
          ? true
          : false
        : index == 0
        ? true
        : false,
    };
  });
}

七、打开.xls报错问题

可看我另一篇文章

【已解决】xls“的文件格式和扩展名不匹配。文件可能已损坏或不安全。除非您信任其来源,否则请勿打开。是否仍要打开它?-CSDN博客

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值