1.简介

SheetJS的xlsx.js是纯js即可读取/生成excel,功能强大,支持多种格式,兼容性高。
xlsx.js有core和full两个版本,使用xlsx.core.min.js版本基本上就能满足大部分需求,我在项目中选择了core的版本。
其他详细介绍可以去看官方github:https://github.com/SheetJS/sheetjs

2.使用方法

1.XLSX全局对象

引入js文件后,会在window对象上挂载一个全局对象XLSX,所有的操作函数都通过这个全局对象调用。

2.workbook对象与worksheet对象

workbook对象是描述一个excel文件的基本对象。
workbook对象中,主要包含了以下内容:

SheetNames数组:excel文件包含的所有sheets名称列表
Sheets数组:excel文件包含的所有sheets的内容(worksheet对象列表)

SheetJS的xlsx.js,功能强大的前端Excel导出框架_javascript

worksheet对象则包含了一个sheet表格的详细内容。

SheetJS的xlsx.js,功能强大的前端Excel导出框架_sheetjs_02


对象中包含的内容包括:

*    !ref:单元格范围
*    !rows:表格行属性
*    !cols:单元格属性
*    !merges:单元格合并
*    A1/B1/C1...:对应excel中的每一个具体的单元格

想要生成一个excel文件,需要首先构建出一个workbook对象,然后再对这个对象进行其他的操作。
workbook对象与worksheet对象看起来比较复杂,很多人看到后会比较焦虑:这些难道要我自己去组织吗?其实,其中包含的很多项都是可以通过内置方法自动生成的,所以不要太担心。

3.构建一个worksheet对象

在我的项目中,需求是生成一个带有格式要求的excel表格,这里咱们先来说说表格内容部分的生成。
xlsx.js提供了多种函数,将不同结构的数据转换成为worksheet对象,包括:

  • XLSX.utils.aoa_to_sheet:数组转换为worksheet
  • XLSX.utils.json_to_sheet:json对象转换为worksheet
  • XLSX.utils.table_to_sheet:表格转换为worksheet

我的项目中,根据项目情况选择了由数组转换为worksheet的方式,这里我就详细说一下如何构建符合要求的数据数组并将其转换为worksheet对象,其他的方式大家可以移步官方github去查看。

1.内容部分:

基本的数组结构为:

var arr = [
  [单元格A1内容,单元格A2内容,单元格A3内容,...],
  [单元格B1内容,单元格B2内容,单元格B3内容,...],
  [单元格B1内容,单元格B2内容,单元格B3内容,...],
  ...
]

所以,理论上,我们只要不断的向数组里push内容就可以了,数组内容填充完成之后,直接调用var ws = aoa_to_sheet(arr),就能够输出一个worksheet对象到变量ws中了。ws对象中的!ref属性是自动生成的。

2.占位与合并单元格:

我们要输出的表格,并不都是单一罗列式的,往往会有复杂的格式,这就需要我们通过占位与合并单元格来控制表格的格式。

比如,我们希望输出的表格是这个样子的:

SheetJS的xlsx.js,功能强大的前端Excel导出框架_xlsx.js_03


表格中的表头,存在二级分类,比如GPS位置下还包含了纬度、经度两个字段。

这个时候,构建数组表头部分时,我们需要在一些地方,比如表头第二行的开头,使用null来占位,保证表头上下两行列的一一对应。

var arr = [
    ['户主姓名', '性别', '文化程度', '手机号码', 'GPS位置', 'null', '户籍所在地', 'null', 'null', 'null'],
    [null, null, null, null, '纬度', '经度', '盟', '旗', '苏木', '嘎查']
];

这个时候,我们得到的结果是这个样子的:

SheetJS的xlsx.js,功能强大的前端Excel导出框架_Excel导出_04

接下来,我们要做的就是合并单元格,比如A1和B1、A2和B2、A5和A6等。
我们需要向生成的ws对象中手动的加入属性!merges及其对应的数组。

ws['!merges'] = [
    {s: {r: 0, c: 0}, e: {r: 1, c: 0}},
    {s: {r: 0, c: 1}, e: {r: 1, c: 1}},
    {s: {r: 0, c: 2}, e: {r: 1, c: 2}},
    {s: {r: 0, c: 3}, e: {r: 1, c: 3}},
    {s: {r: 0, c: 4}, e: {r: 0, c: 5}},
    {s: {r: 0, c: 6}, e: {r: 0, c: 9}}
]

数组的每一项,代表其中一个单元格的合并要求。每一个对象中:s代表合并的起始位置,e代表合并的结束位置。
s中,r代表行数,c代表列数。
比如数组的第一个对象,表达的含义为:以0行0列(对应单元格A1)作为起始,以1行0列(对应单元格A2)作为结束,合并这些单元格。

3.行高与列宽

当某一个单元格字符数过长的时候,显示的内容会超过单元格边界,非常影响用户的阅读,这个时候,需要通过调整行高或者列宽来保证表格便于阅读。
我们需要向生成的ws对象中手动加入属性!cols来控制列宽,!rows来控制行高。
例如:

ws['!cols'] = [
    {wpx: 90},
    {wpx: 50},
    {wpx: 60},
    {wpx: 100},
    {wpx: 100},
    {wpx: 100},
    {wpx: 70},
    {wpx: 100},
    {wpx: 100},
    {wpx: 100}
];

数组的每一项对应需要控制的列,比如下标0的项代表第一列(A列)的宽度。
wpx表示屏幕像素值,wch代表英文字符数,还有一个width可选,文档描述为 width in Excel's "Max Digit Width",具体含义还没弄明白,求大佬来解释。
行高的控制类似,具体内容请自己查阅文档。

4.构建workbook对象并导出excel文件

对于workbook对象,我们只需要填充SheetNames属性和Sheets属性即可。

var workbook = {
    SheetNames: ['本地户主信息'],
    Sheets: {
        '本地户主信息': ws
    }
};

然后,通过XLSX.write函数生成excel文件的源码(具体应该叫什么我也不清楚,求大佬告知),把文件源码字符串转成arrayBuffer,再通过new Blob方法转换为二进制,最后使用URL.createObjectURL函数将blob对象创建为blob地址,赋给a标签的href属性,就能够进行下载了。
这个部分的具体代码,是我从网上直接找的(对于arrayBuffer和blob这块比较生疏),这里贴出来,感谢最初编写这段代码的大佬。

//导出excel相关函数
 sheet2blob: function(sheet, sheetName) {
     sheetName = sheetName || 'sheet1';
     var workbook = {
         SheetNames: [sheetName],
         Sheets: {}
     };
     workbook.Sheets[sheetName] = sheet;
     // 生成excel的配置项
     var wopts = {
         bookType: 'xlsx', // 要生成的文件类型
         bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
         type: 'binary'
     };
     var wbout = XLSX.write(workbook, wopts);
     var blob = new Blob([s2ab(wbout)], {type:"application/octet-stream"});
     // 字符串转ArrayBuffer
     function s2ab(s) {
         var buf = new ArrayBuffer(s.length);
         var view = new Uint8Array(buf);
         for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
         return buf;
     }
     return blob;
 },

 openDownloadXLSXDialog: function(url, saveName){
     if(typeof url == 'object' && url instanceof Blob){
         url = URL.createObjectURL(url); // 创建blob地址
     }
     var aLink = document.createElement('a');
     aLink.href = url;
     aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
     var event;
     if(window.MouseEvent) event = new MouseEvent('click');
     else{
         event = document.createEvent('MouseEvents');
         event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
     }
     aLink.dispatchEvent(event);
 }

 var blob = sheet2blob(ws, '户主信息');
 openDownloadXLSXDialog(blob, '123.xlsx');

5.遇到的问题

一个很蛋疼的需求,需要我在一个sheet中重复输出带复杂表头的表格。
在上边构建一个worksheet对象中描述的表格,我只需要输出一个表头即可,我能够提前知道合并哪几个单元格,因此我先将数据部分直接循环插入数组就行,在数组内容分填充完毕之后,生成ws对象,再给ws对象增加的!merges属性。
而新需求无法这么做了,因为除了向数组中循环输出数据之外,我还需要循环输出表头到数组中。没有办法,这个时候只能先创建!merges属性的数组,在表格内容数组填充的循环体中,自行计算输出到的行数,缓存在变量里,然后在这个循环体中使用行数变量动态填充!merges数组,保证单元格合并的正确。