需求说明

前端页面通过表格呈现查询数据,并由一个按钮点击事件将表格的数据以xlsx的文件格式导出下载。

开发环境

Chrome + vue 2.0 + Element UI

方案1:XLSX + FILE-SAVER

由于文件下载需求的普遍性,以及vue和Element的流行性,网上类似的方案很多。稍作整理后的形式如下:

// 导出按钮绑定的函数
exportXLSX() {
// 从表格生成workbook
let wb = XLSX.utils.table_to_book(document.querySelector('#tableID'));
let wbout = XLSX.write(wb, {
bookType: "xlsx",
bookSST: true,
type: "array"
});
try {
// 下载
let b = new Blob([wbout], { type: "application/octet-stream" });
FileSaver.saveAs(b, "filename.xlsx");
} catch (e) {
if (typeof console !== "undefined") {
console.log(e, wbout);
}
}
return wbout;
}

该函数流程大致为:

通过ID找到表格

利用XLSX的table_to_book方法将表格数据直接生成到工作簿

将工作簿包装为blob对象

通过FileSaver实现文件保存功能

问题1:可能导出重复数据

当使用Element UI的el-table中的fixed属性时,实际生成了2张表格,因此以上方案导出的数据会出现重复的现象。就像这样:


重复的数据

既然原因很清楚了,那么这个问题的解决办法也容易找到:在生成工作簿的时候,首先去除带有fix属性的表格,在工作簿对象生成后再添加上。

这样,生成工作簿的代码更新为:

let fix = document.querySelector('.el-table__fixed');
if (fix) {
let wb = XLSX.utils.table_to_book(document.querySelector('#tableID').removeChild(fix));
document.querySelector('#tableID').appendChild(fix);
} else {
let wb = XLSX.utils.table_to_book(document.querySelector('#tableID'));
}

问题2:分页数据只导出页面显示的部分

如果表格存在分页,上述方法导出的是当前显示的部分。网上也有几种解决办法:

前端隐藏一个全量的table,专门用于导出(个人不推荐)。

导出的时候临时修改pageSize,使全量数据都在表格上。完成后还原。

这两个我都没尝试,也就不贴代码了,通过搜索都可以找到。

方案2:只使用XLSX解决问题

之前两个问题的解决办法要么触发了前端变更的操作,要么保存了多余的内容。虽然能解决问题,但总有些变扭。

重新看了看SheetJS项目的文档,发现有json_to_sheet方法,官方的示例如下:

var ws = XLSX.utils.json_to_sheet([
{ S:1, h:2, e:3, e_1:4, t:5, J:6, S_1:7 },
{ S:2, h:3, e:4, e_1:5, t:6, J:7, S_1:8 }
], {header:["S","h","e","e_1","t","J","S_1"]});

如果不显式定义列名,则以第一个对象的Object.keys作为列名。

The default column order is determined by the first appearance of the field using Object.keys

这不正和el-table的表单数据格式一模一样么。本地已经有tableData,且是全量数据,不存在分页问题,直接用tableData加上json_to_sheet方法生成sheet便可。但这个方法返回的是worksheet,而非workbook,因此需要添加到一个workbook。最后使用workbook的writeFile方法即可完成下载。最终代码如下:

exportXLSX() {
let ws = XLSX.utils.json_to_sheet(this.tableData);
let wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'sheetname');
return XLSX.writeFile(wb, 'filename.xlsx');
}

代码可以如此简洁,且用不着file-saver的原因是,其实writeFile方法包装了很多行为,包括使浏览器生成文件的链接并强制点击,触发文件保存的动作等。

XLSX.writeFile wraps a few techniques for triggering a file save:

URL browser API creates an object URL for the file, which the library uses by creating a link and forcing a click. It is supported in modern browsers.

msSaveBlob is an IE10+ API for triggering a file save.

IE_FileSave uses VBScript and ActiveX to write a file in IE6+ for Windows XP and Windows 7. The shim must be included in the containing HTML page.

不知道这样的兼容性如何,至少Chrome 80版本完全可行。