需求說明
前端頁(yè)面通過表格呈現(xiàn)查詢數(shù)據(jù),并由一個(gè)按鈕點(diǎn)擊事件將表格的數(shù)據(jù)以xlsx的文件格式導(dǎo)出下載。
開發(fā)環(huán)境
Chrome + vue 2.0 + Element UI
方案1:XLSX + FILE-SAVER
由于文件下載需求的普遍性,以及vue和Element的流行性,網(wǎng)上類似的方案很多。稍作整理后的形式如下:
// 導(dǎo)出按鈕綁定的函數(shù)
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;
}
該函數(shù)流程大致為:
- 通過ID找到表格
- 利用XLSX的table_to_book方法將表格數(shù)據(jù)直接生成到工作簿
- 將工作簿包裝為blob對(duì)象
- 通過FileSaver實(shí)現(xiàn)文件保存功能
問題1:可能導(dǎo)出重復(fù)數(shù)據(jù)
當(dāng)使用Element UI的el-table中的fixed屬性時(shí),實(shí)際生成了2張表格,因此以上方案導(dǎo)出的數(shù)據(jù)會(huì)出現(xiàn)重復(fù)的現(xiàn)象。就像這樣:

既然原因很清楚了,那么這個(gè)問題的解決辦法也容易找到:在生成工作簿的時(shí)候,首先去除帶有fix屬性的表格,在工作簿對(duì)象生成后再添加上。
這樣,生成工作簿的代碼更新為:
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:分頁(yè)數(shù)據(jù)只導(dǎo)出頁(yè)面顯示的部分
如果表格存在分頁(yè),上述方法導(dǎo)出的是當(dāng)前顯示的部分。網(wǎng)上也有幾種解決辦法:
- 前端隱藏一個(gè)全量的table,專門用于導(dǎo)出(個(gè)人不推薦)。
- 導(dǎo)出的時(shí)候臨時(shí)修改pageSize,使全量數(shù)據(jù)都在表格上。完成后還原。
這兩個(gè)我都沒嘗試,也就不貼代碼了,通過搜索都可以找到。
方案2:只使用XLSX解決問題
之前兩個(gè)問題的解決辦法要么觸發(fā)了前端變更的操作,要么保存了多余的內(nèi)容。雖然能解決問題,但總有些變扭。
重新看了看SheetJS項(xiàng)目的文檔,發(fā)現(xiàn)有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"]});
如果不顯式定義列名,則以第一個(gè)對(duì)象的Object.keys作為列名。
The default column order is determined by the first appearance of the field using Object.keys
這不正和el-table的表單數(shù)據(jù)格式一模一樣么。本地已經(jīng)有tableData,且是全量數(shù)據(jù),不存在分頁(yè)問題,直接用tableData加上json_to_sheet方法生成sheet便可。但這個(gè)方法返回的是worksheet,而非workbook,因此需要添加到一個(gè)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');
}
代碼可以如此簡(jiǎn)潔,且用不著file-saver的原因是,其實(shí)writeFile方法包裝了很多行為,包括使瀏覽器生成文件的鏈接并強(qiáng)制點(diǎn)擊,觸發(fā)文件保存的動(dòng)作等。
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版本完全可行。