前端使用SheetJS的xlsx.js實現excel表格生成

在作以前一個項目的時候,須要有生成excel表格的功能,在網上查詢一番後,發現不少人都推薦Sheetjs的xlsx.js這款工具,本身使用了一下,感受也不錯,上手簡單,功能結構清晰。所以,在這裏就我本身使用到的功能部分和使用時候出現問題簡單總結一下。前端

1.爲何由前端生成excel

過去的時候,excel表格的生成一般是由後端完成的,主要緣由是以前我的PC的性能較低,完成數據量過大的excel表格生成工做比較困難,耗時太長,會形成長時間瀏覽器的卡頓,對用戶體驗形成不好的影響。
而如今,我的PC的性能已經有很大的提高,數據量較大的excel表格生成不會對用戶體驗形成較大的影響(多少仍是會有影響的,可是已經沒有那麼誇張了,並且生成大數據量excel的功能通常是在網站的後臺,用戶每每可以接受必定時間的等待)。讓用戶PC機去處理excel的生成,可以解放服務器的很大壓力,因此如今不少網站選擇把生成excel的工做交付給前端去完成。git

2.SheetJS的xlsx.js簡介

純js便可讀取/生成excel,功能強大,支持多種格式,兼容性高。
xlsx.js有core和full兩個版本,使用xlsx.core.min.js版本基本上就能知足大部分需求,我在項目中選擇了core的版本。
其餘詳細介紹能夠去看官方github:https://github.com/SheetJS/sheetjsgithub

3.XLSX對象

引入js文件後,會在window對象上掛載一個全局對象XLSX,全部的操做函數都經過這個全局對象調用。json

4.workbook對象與worksheet對象

workbook對象是描述一個excel文件的基本對象。
workbook對象中,主要包含了如下內容:後端

*      SheetNames數組:excel文件包含的全部sheets名稱列表
*      Sheets數組:excel文件包含的全部sheets的內容(worksheet對象列表)

1576139832423.jpg

worksheet對象則包含了一個sheet表格的詳細內容。
1576140264474.jpg
對象中包含的內容包括:數組

*    !ref:單元格範圍
*    !rows:表格行屬性
*    !cols:單元格屬性
*    !merges:單元格合併
*    A1/B1/C1...:對應excel中的每個具體的單元格

想要生成一個excel文件,須要首先構建出一個workbook對象,而後再對這個對象進行其餘的操做。
workbook對象與worksheet對象看起來比較複雜,不少人看到後會比較焦慮:這些難道要我本身去組織嗎?其實,其中包含的不少項都是能夠經過內置方法自動生成的,因此不要太擔憂。瀏覽器

5.構建一個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.內容部分:

基本的數組結構爲:app

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

因此,理論上,咱們只要不斷的向數組裏push內容就能夠了,數組內容填充完成以後,直接調用var ws = aoa_to_sheet(arr),就可以輸出一個worksheet對象到變量ws中了。ws對象中的!ref屬性是自動生成的。

2.佔位與合併單元格:

咱們要輸出的表格,並不都是單一羅列式的,每每會有複雜的格式,這就須要咱們經過佔位與合併單元格來控制表格的格式。
好比,咱們但願輸出的表格是這個樣子的:
1576143791466.jpg
表格中的表頭,存在二級分類,好比GPS位置下還包含了緯度、經度兩個字段。
這個時候,構建數組表頭部分時,咱們須要在一些地方,好比表頭第二行的開頭,使用null來佔位,保證表頭上下兩行列的一一對應。

var arr = [
    ['戶主姓名', '性別', '文化程度', '手機號碼', 'GPS位置', 'null', '戶籍所在地', 'null', 'null', 'null'],
    [null, null, null, null, '緯度', '經度', '盟', '旗', '蘇木', '嘎查']
];

這個時候,咱們獲得的結果是這個樣子的:
1576144821697.jpg

接下來,咱們要作的就是合併單元格,好比A1和B一、A2和B二、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",具體含義還沒弄明白,求大佬來解釋。
行高的控制相似,具體內容請本身查閱文檔。

6.構建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');

7.遇到的問題

一個很蛋疼的需求,須要我在一個sheet中重複輸出帶複雜表頭的表格。
在上邊構建一個worksheet對象中描述的表格,我只須要輸出一個表頭便可,我可以提早知道合併哪幾個單元格,所以我先將數據部分直接循環插入數組就行,在數組內容分填充完畢以後,生成ws對象,再給ws對象增長的!merges屬性。
而新需求沒法這麼作了,由於除了向數組中循環輸出數據以外,我還須要循環輸出表頭到數組中。沒有辦法,這個時候只能先建立!merges屬性的數組,在表格內容數組填充的循環體中,自行計算輸出到的行數,緩存在變量裏,而後在這個循環體中使用行數變量動態填充!merges數組,保證單元格合併的正確。

相關文章
相關標籤/搜索