前端导出数据到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 为什么会无效(不进入循环)

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

相关推荐
C_心欲无痕1 分钟前
react - useTransition标记低优先级更新
前端·react.js·前端框架
捻tua馔...4 分钟前
antd3的表单实现(HOC解决方案)
前端·javascript·react.js
支付宝体验科技6 分钟前
支付宝 KJS Compose 动态化方案与架构设计
前端·客户端
AllinLin17 分钟前
JS中的call apply bind全面解析
前端·javascript·vue.js
阿乐去买菜21 分钟前
2025 年末 TypeScript 趋势洞察:AI Agent 与 TS 7.0 的原生化革命
前端
POLITE322 分钟前
Leetcode 438. 找到字符串中所有字母异位词 JavaScript (Day 4)
javascript·算法·leetcode
创思通信23 分钟前
STM32F103C8T6采 DS18B20,通过A7680C 4G模块不断发送短信到手机
javascript·stm32·智能手机
海绵宝龙26 分钟前
Vue 中的 Diff 算法
前端·vue.js·算法
zhougl99627 分钟前
vue中App.vue和index.html冲突问题
javascript·vue.js·html
止观止28 分钟前
告别全局污染:深入理解 ES Modules 模块化与构建工具
javascript·webpack·vite·前端工程化·es modules