前端导出数据到Excel(Excel.js导出数据)

库:Excel.js(版本4.3.0) 和 FileSaver(版本2.0.5)

CDN地址:

html 复制代码
<script src="https://cdn.bootcdn.net/ajax/libs/exceljs/4.3.0/exceljs.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.js"></script>

Excel.js 中文文档:https://gitee.com/alan_scut/exceljs

先看一下效果吧

代码:

使用方法: exportExcal()

javascript 复制代码
// 住宿安排(动态列)
var stay_arr = ['27日晚', '28日晚'];
// 内容数据
var test_data = [
  {
    "guest": "生态伙伴",
    "name": "姓名一",
    "sex": "男",
    "company": "AAAAAA股份有限公司",
    "job": "业务部部长",
    "phone": "12345678912",
    "stay": ["28日晚"]
  },
  {
    "guest": "生态伙伴",
    "name": "姓名二",
    "sex": "女",
    "company": "AAAAAA股份有限公司",
    "job": "业务部部长",
    "phone": "12345678912",
    "stay": ["27日晚","28日晚",]
  }
];
// 导出Excel
function exportExcal() {
  // 文件名称
  var fileName = 'simple.xlsx';
  // 固定内容的列数
  var basic_col_num = 7;
  // 总列数(固定列+住宿动态列的长度)
  var all_column_num = basic_col_num + stay_arr.length;
  // 表单数据数组
  var data_arr = [];
  // 数据单元格起始行数
  var rowStart = 4;
  // 基础单元格边框样式
  var borderStyle = {
    top: { style: "thin", color: { argb: "FF000000" } },
    left: { style: "thin", color: { argb: "FF000000" } },
    bottom: { style: "thin", color: { argb: "FF000000" } },
    right: { style: "thin", color: { argb: "FF000000" } },
  }
  // 基础内容对齐方式
  var basicAlignment = { vertical: 'middle', horizontal: 'center', wrapText: true };
  // 新建工作簿
  var wb = new ExcelJS.Workbook();
  // 设置工作簿属性
  wb.creator = 'Me';                      // 作者
  wb.lastModifiedBy = 'Her';              // 最后修改人
  wb.created = new Date(2023, 10, 10);     // 创建时间
  wb.modified = new Date();               // 修改时间
  wb.lastPrinted = new Date(2023, 10, 10); // 上次打印文档时间
  wb.properties.date1904 = true;          // 将工作簿日期设置为1904日期系统

  // 向新的工作簿中增加一张工作表
  var ws = wb.addWorksheet('邀约名单');
  // 设置默认行高
  ws.properties.defaultRowHeight = 20;
  // 也可以在添加工作表的时候直接设置参数
  // var ws = wb.addWorksheet('邀约名单', {properties:{defaultRowHeight:20}});

  // 表头部分开始

  // 设置单元格第一行
  // 获取单元格内第一行
  var collectRow = ws.getRow(1);
  // 设置单行行高
  collectRow.height = 40;
  // 设置整行的文字样式
  collectRow.style.font = { name: 'Microsoft YaHei', size: 16, bold: true, color: { argb: "ffffffff" } };
  // 设置整行内容对齐方式
  collectRow.alignment = basicAlignment;
  // 获取A1单元格
  var collectcell = ws.getCell(`A1`);
  // 表格第一行标题内容,A1合并后填充的内容
  collectcell.value = `参会信息统计表`;
  // 设置单个单元格的背景色(这里不能设置整行,因为会超出数据的宽度)
  collectcell.fill = {
    type: "pattern",
    pattern: "solid",
    fgColor: { argb: "ff538dd5" },
  }
  // 设置单个单元格的边框样式
  collectcell.border = {
    top: { style: "thin", color: { argb: "FF000000" } },
    left: { style: "thin", color: { argb: "FF000000" } },
    right: { style: "thin", color: { argb: "FF000000" } },
  };
  // 合并单元格,将A1与H1合并(ws.mergeCells(`A1:H1`);)
  ws.mergeCells(`A1:${getLetter(all_column_num)}1`);

  // 设置单元格第二行
  // 获取单元格内第二行
  var collectRow = ws.getRow(2);
  // 设置单行行高
  collectRow.height = 26;
  // 获取A2单元格
  var collectcell2 = ws.getCell(`A2`);
  // 表格第二行标题内容,A2合并后填充的内容
  collectcell2.value = `活动时间:2023年10月10日        活动地点:XXXXXXX会议中心        住宿酒店:XXXX酒店`;
  // 单独设置单元格的文字样式
  collectcell2.font = { name: 'Microsoft YaHei', size: 10, bold: false, color: { argb: "ffffffff" } }; // 字体
  // 单独设置单元格的背景色
  collectcell2.fill = {
    type: "pattern",
    pattern: "solid",
    fgColor: { argb: "ff538dd5" },
  }
  // 单独设置单元格的内容对齐方式
  collectcell2.alignment = basicAlignment;
  // 单独设置单元格的边框样式
  collectcell2.border = {
    left: { style: "thin", color: { argb: "FF000000" } },
    bottom: { style: "thin", color: { argb: "FF000000" } },
    right: { style: "thin", color: { argb: "FF000000" } },
  };
  // 合并单元格,将A2与H2合并(ws.mergeCells(`A2:H2`);)
  ws.mergeCells(`A2:${getLetter(all_column_num)}2`);

  // 处理表单第三行第四行每个单元格表头内容
  for (let i = 1; i <= all_column_num; i++) {
    let r3cell = ws.getCell(`${getLetter(i)}3`); // 获取第三行的单元格
    let r4cell = ws.getCell(`${getLetter(i)}4`); // 获取第三行的单元格

    // 设置内容对齐方式
    r3cell.alignment = basicAlignment;
    r4cell.alignment = basicAlignment;

    // 前面部分固定内容的表头设置
    if (i <= basic_col_num) {
      // 设置单元格样式
      r3cell.border = borderStyle;
      r3cell.fill = {
        type: "pattern",
        pattern: "solid",
        fgColor: { argb: "ff808080" },
      }
      r3cell.font = { name: 'Microsoft YaHei', size: 11, bold: true, color: { argb: "ffffffff" } };

      switch (i) {
        case 1:
          ws.getCell(`${getLetter(i)}3`).value = "序号";
          // 设置单元格宽度(宽度单位是字符宽度,而不是像素宽度,中文是两个字符)
          ws.getColumn(i).width = 6;
          break;
        case 2:
          ws.getCell(`${getLetter(i)}3`).value = "嘉宾类别";
          ws.getColumn(i).width = 16;
          break;
        case 3:
          ws.getCell(`${getLetter(i)}3`).value = "姓名";
          ws.getColumn(i).width = 10;
          break;
        case 4:
          ws.getCell(`${getLetter(i)}3`).value = "性别";
          ws.getColumn(i).width = 6;
          break;
        case 5:
          ws.getCell(`${getLetter(i)}3`).value = "单位";
          ws.getColumn(i).width = 40;
          break;
        case 6:
          ws.getCell(`${getLetter(i)}3`).value = "职务";
          ws.getColumn(i).width = 24;
          break;
        case 7:
          ws.getCell(`${getLetter(i)}3`).value = "手机号";
          ws.getColumn(i).width = 15;
          break;
      }
      // 合并上下对应的单元格
      ws.mergeCells(`${getLetter(i)}3:${getLetter(i)}4`);
    } else {
      // 这部分是动态的单元格
      // 设置单元格宽度
      ws.getColumn(i).width = 12;
      // 住宿部分表头样式需要分开设置(因为不合并)
      r3cell.border = borderStyle;
      r3cell.fill = {
        type: "pattern",
        pattern: "solid",
        fgColor: { argb: "ffebf1de" },
      }
      r4cell.border = borderStyle;
      r4cell.fill = {
        type: "pattern",
        pattern: "solid",
        fgColor: { argb: "ffebf1de" },
      }
      r4cell.font = { name: 'Microsoft YaHei', size: 11, bold: true, color: { argb: "ff595959" } };

      // 住宿第三行的内容
      if (i == (basic_col_num + 1)) {
        ws.getCell(`${getLetter(i)}3`).value = "行程安排";
        // 合并单元格(行程安排)
        ws.mergeCells(`${getLetter(i)}3:${getLetter(i + stay_arr.length - 1)}3`);
      }
      // 设置住宿列第四行的动态内容
      ws.getCell(`${getLetter(i)}4`).value = stay_arr[i - basic_col_num - 1];
    }
  }
 
  // 处理表单数据
  for (let i = 0; i < test_data.length; i++) {
    // 序号,嘉宾类别,姓名,性别,单位,职务,手机号
    data_arr[i] = [
      i + 1,
      test_data[i].guest,
      test_data[i].name,
      test_data[i].sex,
      test_data[i].company,
      test_data[i].job,
      test_data[i].phone
    ]
    // 动态添加住宿(循环住宿内容数组)
    for (let j = 0; j < stay_arr.length; j++) {
      if (test_data[i].stay) {
        // 判断该值是否是列表中数据内存在的值
        if (test_data[i].stay.indexOf(stay_arr[j]) != -1) {
          data_arr[i].push("是");
        } else {
          data_arr[i].push("");
        }
      } else {
        // 如果没有住宿,直接添加空
        data_arr[i].push("");
      }
    }
  }
 
  // 将表单输入插入行
  for (let i = 0; i < data_arr.length; i++) {
    // 获取数据行
    let r_data = ws.getRow(i + rowStart + 1);
    // 设置整行单元格的内容对其方式
    r_data.alignment = basicAlignment;
    // 字体
    r_data.font = { name: 'Microsoft YaHei', size: 11, bold: false, color: { argb: "ff000000" } };
    // 设置整行的数据内容
    r_data.values = data_arr[i];
    // 循环本行插入的每个单元格,然后给单元格设置边框样式(注意,一定要插入数据后,才能循环设置)
    // 这里不能用 设置整行边框,因为单元格会超出,不好看了
    r_data.eachCell({ includeEmpty: true }, function (cell, colNumber) {
      ws.getCell(`${getLetter(colNumber)}${i + rowStart + 1}`).border = borderStyle
    });
  }
 
  // node端才能使用
  // wb.xlsx.writeFile(fileName).then(() => {
  //   console.log('file created');
  // }).catch(err => {
  //   console.log(err.message);
  // });

  wb.xlsx.writeBuffer().then(buffer => {
    // 这里之前是 FileSaver.saveAs 因为报错换成 window.saveAs 原因文章后面有说
    window.saveAs(new Blob([buffer], { type: 'application/octet-stream' }), `${fileName || 'excel.xlsx'}`);
  })
}

// 获取第N个字母
function getLetter(num) {
  return String.fromCharCode(64 + num);
}

代码中用到的方法总结:

新建工作簿 & 向工作簿中增加一张工作表

var workbook = new ExcelJS.Workbook();

var worksheet = workbook.addWorksheet('邀约名单');

合并单元格(将A1与H1合并)

worksheet.mergeCells('A1:H1');

获取行(第一行)

var collectRow = worksheet.getRow(1);

获取列(B或者3)

var collectcell = worksheet.getColumn('B');

var collectcell = worksheet.getColumn(3);

获取单元格(A2)

var collectcell = worksheet.getCell('A2');

设置单元格内容(行、列、单元格均可设置。行、列是赋值数组)

var collectcell = worksheet.getCell('A2');

collectcell.value = '活动时间';

设置单元格宽度(列、单元格均可设置。宽度单位是字符宽度,而不是像素宽度,中文是两个字符。)

var collectcell = worksheet.getCell('A2');

collectcell.width = 6;

设置工作表的默认行高

worksheet.properties.defaultRowHeight = 20;

设置工作表的默认列宽

worksheet.properties.defaultColWidth = 50;

设置文字样式(行,列,单元格均可设置)

var collectcell = worksheet.getCell('A2');

collectcell.font = { name: 'Microsoft YaHei', size: 10, bold: false, color: { argb: "ffffffff" } };

设置背景色(行,列,单元格均可设置)

var collectcell = worksheet.getCell('A2');

collectcell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "ff538dd5" } };

设置内容对齐方式(行,列,单元格均可设置)

var collectcell = worksheet.getCell('A2');

collectcell.alignment = {

top: { style: "thin", color: { argb: "FF000000" } },

left: { style: "thin", color: { argb: "FF000000" } },

bottom: { style: "thin", color: { argb: "FF000000" } },

right: { style: "thin", color: { argb: "FF000000" } },

}

循环行内的每一个单元格(包括空单元格)

let r_data = ws.getRow(i + rowStart + 1);

// 循环本行插入的每个单元格,然后给单元格设置边框样式(注意,一定要插入数据后,才能循环设置)

r_data.eachCell({ includeEmpty: true }, function (cell, colNumber) {

console.log('Cell ' + colNumber + ' = ' + cell.value);

});

遇见的一些问题:

1. 报错:FileSaver.saveAs is not a function.saveAs is not a function

解决方法: 将 FileSaver.saveAs 改成 window.saveAs 即可(原因是 FileSaver 是全局引用了)

2. 为什么不直接用 wb.xlsx.writeFile

答:因为 wb.xlsx.writeFile 只能 node 用,所以需要用 FileSaver

3. eachCell 为什么会无效(不进入循环)

答:一定要插入数据后,才能循环,没有插入数据的时候是无法进入循环的。

相关推荐
T^T尚2 小时前
uniapp H5上传图片前压缩
前端·javascript·uni-app
出逃日志2 小时前
JS的DOM操作和事件监听综合练习 (具备三种功能的轮播图案例)
开发语言·前端·javascript
XIE3922 小时前
如何开发一个脚手架
前端·javascript·git·npm·node.js·github
GISer_Jing3 小时前
React渲染相关内容——渲染流程API、Fragment、渲染相关底层API
javascript·react.js·ecmascript
山猪打不过家猪3 小时前
React(五)——useContecxt/Reducer/useCallback/useRef/React.memo/useMemo
前端·javascript·react.js
前端青山3 小时前
React事件处理机制详解
开发语言·前端·javascript·react.js
科技D人生3 小时前
Vue.js 学习总结(14)—— Vue3 为什么推荐使用 ref 而不是 reactive
前端·vue.js·vue ref·vue ref 响应式·vue reactive
对卦卦上心3 小时前
React-useEffect的使用
前端·javascript·react.js
练习两年半的工程师3 小时前
React的基本知识:事件监听器、Props和State的区分、改变state的方法、使用回调函数改变state、使用三元运算符改变state
前端·javascript·react.js
啵咿傲3 小时前
在React中实践一些软件设计思想 ✅
前端·react.js·前端框架