摘要:前言最近公司需要將幾張統計表格導出到由于公司現有導出功能是前后端配合的導出,覺得麻煩,所以想找一個純前端導出的工具,最后找到了,評價還是挺高的,但是中文文檔沒找到百度也沒有找到一個比較全面的教程所以踩了很多坑,自己記錄下,方便以后使用。
前言
最近公司需要將幾張統計表格導出到excel,由于公司現有導出excel功能是前后端配合的導出,覺得麻煩,所以想找一個純前端導出的工具,最后找到了js-xlsx,評價還是挺高的,但是中文文檔沒找到,百度也沒有找到一個比較全面的教程,所以踩了很多坑,自己記錄下,方便以后使用。
環境由于我業務只用到將table標簽內的內容導出到excel,所以只會寫如何將一個table元素里的內容導出到excel。也可以通過json導出,貌似還會更簡單些。
安裝GitHub地址
npm安裝
npm install xlsx
安裝后dist文件夾下有一個文件xlsx.full.min.js,就是它了,引入到項目中
第一個例子先上代碼
Document
序號 | 姓名 | 年齡 | 興趣 |
1 | 張三 | 18 | 打游戲 |
2 | 李四 | 88 | 看電影 |
3 | 王五 | 81 | 睡覺 |
運行效果
導出結果:
你可能注意到了,我這里引入了一個export.js文件,這個export.js文件里面只有2個方法,就是上面代碼用到的openDownloadDialog(sheet2blob(sheet),"下載.xlsx");
這是export.js的代碼:
// 將一個sheet轉成最終的excel文件的blob對象,然后利用URL.createObjectURL下載 function sheet2blob(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; } function openDownloadDialog(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); }
PS: 這2個方法是網上當的,原文地址。作者寫的挺好,也是從這里找到了頭緒。
如果你的table標簽內有合并單元格的操作,XLSX.utils.table_to_sheet(*)也能夠讀取出來,并且你打印出來的結果也能夠顯示出來,效果圖:
可以看到,excel中的表格也已經合并了。
但是實際的情況,客戶覺得這行字沒有居中,他就會向你嘮叨,為啥不居中,所以我們現在解決文字不居中的問題。
這里就不繞圈子了,設置樣式的話,上面的xlsx.full.min.js是無法生效的,
必須安裝xlsx-style
好像只有npm安裝,github我沒找到地址
npm install xlsx-style
同樣,安裝目錄下dist文件夾下有一個xlsx.full.min.js,嗯?名字一模一樣?怎么用?好吧,無從下手,只好硬著頭皮引入了,注意,我將xlsx-style的js文件放在下方:
還有btn_export()方法要變一下,加一下樣式。
具體的單元格樣式說明可以看下這篇文章 xlsx-style單元格樣式參考表
function btn_export() { var table1 = document.querySelector("#table1"); var sheet = XLSX.utils.table_to_sheet(table1); //這個就是修改格式的代碼 sheet["A5"].s = { font: { sz: 13, bold: true, }, alignment: { horizontal: "center", vertical: "center", wrap_text: true } }; openDownloadDialog(sheet2blob(sheet),"下載.xlsx"); }
改完之后,點擊運行,果不其然,報錯了:
原因是什么呢,原因是2個js文件暴露出來的變量都叫‘XLSX’,但是xlsx-style這個js文件里沒有XLSX.utils這個方法,而且xlsx-style這個js文件是后引入的,就把前面的XLSX給覆蓋了,所以報錯。
XLSX.utils里面有很多可用的方法,但是按照這種方式無法進行調用:
你可能想到把2個js文件調換一下位置,但是結果是xlsx暴露的變量覆蓋了xlsx-style暴露的變量。你的樣式還是改變不了。
不用XLSX.utils的方式
由于這2個js都是加密之后的內容,無法解讀,不能在這2個js上找到什么有用的東西。好在在xlsx dist文件夾下找到了xlsx.extendscript.js,看這個文件就像個工具類,由于我上面用到了table_to_sheet方法,在xlsx.extendscript上面的搜索了一下,果然發現了這個方法,二話不說,將xlsx的js引用刪除,引入xlsx.extendscript:
運行。結果你應該已經猜到了,樣式并沒有發生改變。什么原因呢,xlsx.extendscript.js暴露出來的變量仍然是"XLSX",下面的變量還是覆蓋了上面的變量。
注意!!! 如果你的項目中使用了webpack、babel等,可以直接import,不用改變變量名好在這個xlsx.extendscript.js不是壓縮版本,可以對內容進行修改,就把暴露出來的變量修改為"XLSX2"吧。這樣我們只有在使用utils工具的時候才用到xlsx.extendscript.js,其余都用的是xlsx-style這個js,這樣總該可以了吧 。
修改完之后別忘了將XSLX.utils.table_to_sheet()改成XLSX2.utils.table_to_sheet()。
(不建議修改源碼,由于工作需要不修改源碼無法使用才做的修改)
function btn_export() { var table1 = document.querySelector("#table1"); var sheet = XLSX2.utils.table_to_sheet(table1); sheet["A5"].s = { font: { sz: 13, bold: true, color: { rgb: "FFFFAA00" } }, alignment: { horizontal: "center", vertical: "center", wrap_text: true } }; openDownloadDialog(sheet2blob(sheet), "下載.xlsx"); }
運行:
可以看到,你所做的樣式更改已經生效了。
客戶需求增加:我想要前面幾行空出來,并且寫上打印公司名。
觀察xlsx.extendscript.js源碼,發現table_to_sheet,也就是parse_dom_table,并沒有設置起始行的參數,下面給出parse_dom_table的代碼:
function parse_dom_table(table, _opts) { var opts = _opts || {}; if(DENSE != null) opts.dense = DENSE; var ws = opts.dense ? ([]) : ({}); var rows = table.getElementsByTagName("tr"); var sheetRows = opts.sheetRows || 10000000; var range = {s:{r:0,c:0},e:{r:0,c:0}}; var merges = [], midx = 0; var rowinfo = []; var _R = 0, R = 0, _C, C, RS, CS; for(; _R < rows.length && R < sheetRows; ++_R) { var row = rows[_R]; if (is_dom_element_hidden(row)) { if (opts.display) continue; rowinfo[R] = {hidden: true}; } var elts = (row.children); for(_C = C = 0; _C < elts.length; ++_C) { var elt = elts[_C]; if (opts.display && is_dom_element_hidden(elt)) continue; var v = htmldecode(elt.innerHTML); for(midx = 0; midx < merges.length; ++midx) { var m = merges[midx]; if(m.s.c == C && m.s.r <= R && R <= m.e.r) { C = m.e.c+1; midx = -1; } } /* TODO: figure out how to extract nonstandard mso- style */ CS = +elt.getAttribute("colspan") || 1; if((RS = +elt.getAttribute("rowspan"))>0 || CS>1) merges.push({s:{r:R,c:C},e:{r:R + (RS||1) - 1, c:C + CS - 1}}); var o = {t:"s", v:v}; var _t = elt.getAttribute("t") || ""; if(v != null) { if(v.length == 0) o.t = _t || "z"; else if(opts.raw || v.trim().length == 0 || _t == "s"){} else if(v === "TRUE") o = {t:"b", v:true}; else if(v === "FALSE") o = {t:"b", v:false}; else if(!isNaN(fuzzynum(v))) o = {t:"n", v:fuzzynum(v)}; else if(!isNaN(fuzzydate(v).getDate())) { o = ({t:"d", v:parseDate(v)}); if(!opts.cellDates) o = ({t:"n", v:datenum(o.v)}); o.z = opts.dateNF || SSF._table[14]; } } if(opts.dense) { if(!ws[R]) ws[R] = []; ws[R][C] = o; } else ws[encode_cell({c:C, r:R})] = o; if(range.e.c < C) range.e.c = C; C += CS; } ++R; } if(merges.length) ws["!merges"] = merges; if(rowinfo.length) ws["!rows"] = rowinfo; range.e.r = R - 1; ws["!ref"] = encode_range(range); if(R >= sheetRows) ws["!fullref"] = encode_range((range.e.r = rows.length-_R+R-1,range)); // We can count the real number of rows to parse but we don"t to improve the performance return ws; }
那自己加一個吧
可以看到,里面的R變量 這是控制起始行的關鍵所在,好吧,我們再做一下修改:
var _R = 0, R = _opts.rowIndex || 0, _C, C, RS, CS;
這里我們給_opts增加一個屬性rowIndex,在調用table_to_sheet方法的時候傳入這個屬性。下面是變更后的代碼:
function btn_export() { var table1 = document.querySelector("#table1"); var opt = { rowIndex: 4 }; //開頭空4行 var sheet = XLSX2.utils.table_to_sheet(table1, opt); sheet["A1"] = { t: "s", v: "三鹿集團有限公司" }; //給A1單元格賦值 sheet["A1"].s = { font: { name: "宋體", sz: 24, bold: true, underline: true, color: { rgb: "FFFFAA00" } }, alignment: { horizontal: "center", vertical: "center", wrap_text: true }, fill: { bgColor: { rgb: "ffff00" } } }; //["!merges"]這個屬性是專門用來進行單元格合并的 sheet["!merges"].push({//如果不為空push 為空 = 賦值 //合并單元格 index都從0開始 s: { //s開始 c: 0, //開始列 r: 0 //開始行 }, e: { //e結束 c: 3, //結束列 r: 2 //結束行 } }); sheet["A9"].s = { //樣式 font: { sz: 13, bold: true, color: { rgb: "FFFFAA00" } }, alignment: { horizontal: "center", vertical: "center", wrap_text: true } }; openDownloadDialog(sheet2blob(sheet), "下載.xlsx"); }
運行結果:
可以看到,你所做的更改生效了。
客戶又提新需求了,要加上2個字段,身份證號和手機號。
這還不簡單?加上2個字段不就好了。2分鐘搞定,導出:
???
身份證號怎么變成了科學計數法,什么鬼(后來發現百分比也會直接給你換算成0~1的小數,統計沒法搞)
怎么回事?還是parse_dom_table的杰作!
注意這一行:
else if(!isNaN(fuzzynum(v))) o = {t:"n", v:fuzzynum(v)};
意思是只要從td的text里讀取到的值,只要轉換之后是一個number,(不管你是string類型),都會給你來一個fuzzynum(v),轉換成一個number類型。
做下修改,結果:
function parse_dom_table(table, _opts) { var opts = _opts || {}; if(DENSE != null) opts.dense = DENSE; var ws = opts.dense ? ([]) : ({}); var rows = table.getElementsByTagName("tr"); var sheetRows = opts.sheetRows || 10000000; var range = {s:{r:0,c:0},e:{r:0,c:0}}; var merges = [], midx = 0; var rowinfo = []; var _R = 0, R = _opts.rowIndex || 0, _C, C, RS, CS; for(; _R < rows.length && R < sheetRows; ++_R) { var row = rows[_R]; if (is_dom_element_hidden(row)) { if (opts.display) continue; rowinfo[R] = {hidden: true}; } var elts = (row.children); for(_C = C = 0; _C < elts.length; ++_C) { var elt = elts[_C]; if (opts.display && is_dom_element_hidden(elt)) continue; var v = htmldecode(elt.innerHTML); for(midx = 0; midx < merges.length; ++midx) { var m = merges[midx]; if(m.s.c == C && m.s.r <= R && R <= m.e.r) { C = m.e.c+1; midx = -1; } } /* TODO: figure out how to extract nonstandard mso- style */ CS = +elt.getAttribute("colspan") || 1; if((RS = +elt.getAttribute("rowspan"))>0 || CS>1) merges.push({s:{r:R,c:C},e:{r:R + (RS||1) - 1, c:C + CS - 1}}); var o = {t:"s", v:v}; var _t = elt.getAttribute("t") || ""; if(v != null) { if(v.length == 0) o.t = _t || "z"; else if(opts.raw || v.trim().length == 0 || _t == "s"){} else if(v === "TRUE") o = {t:"b", v:true}; else if(v === "FALSE") o = {t:"b", v:false}; //else if(!isNaN(fuzzynum(v))) o = {t:"n", v:fuzzynum(v)}; else if(!isNaN(fuzzynum(v))) o = {t:"s", v:v};//不自動格式化number類型 else if(!isNaN(fuzzydate(v).getDate())) { o = ({t:"d", v:parseDate(v)}); if(!opts.cellDates) o = ({t:"n", v:datenum(o.v)}); o.z = opts.dateNF || SSF._table[14]; } } if(opts.dense) { if(!ws[R]) ws[R] = []; ws[R][C] = o; } else ws[encode_cell({c:C, r:R})] = o; if(range.e.c < C) range.e.c = C; C += CS; } ++R; } if(merges.length) ws["!merges"] = merges; if(rowinfo.length) ws["!rows"] = rowinfo; range.e.r = R - 1; ws["!ref"] = encode_range(range); if(R >= sheetRows) ws["!fullref"] = encode_range((range.e.r = rows.length-_R+R-1,range)); // We can count the real number of rows to parse but we don"t to improve the performance return ws; }
將轉換的語句注釋掉,重寫這行代碼,如果是number類型,不做任何修改,該是什么值還是什么值。
現在再重新運行,結果:
可以看到,數字能夠正常顯示了。但是這個單元格好像并不會自動展開,永遠都這么大,xlsx-style 也提供了控制單元格寬度的方法:
sheet["!cols"] = [{ wpx: 70 }, { wpx: 70 }, { wpx: 70 }, { wpx: 70 }, { wpx: 150 }, { wpx: 120 }]; //單元格列寬
注意,設置單元格列寬要從第一行開始設置
結果:
完整前端代碼:
Document
序號 | 姓名 | 年齡 | 興趣 | 身份證號 | 手機號 |
1 | 張三 | 18 | 打游戲 | 320322184087562589 | 1374569821 |
2 | 李四 | 88 | 看電影 | 420322184087562589 | 2374569821 |
3 | 王五 | 81 | 睡覺 | 520322184087562589 | 3374569821 |
這是一個合并單元格 |
github地址 完整實例
總結不是特殊情況不建議修改源碼
因為畢竟修改了代碼,所以這種方法只能面向小眾
聽過收費版功能很全,建議如果有需要的話還是購買收費版本,但是地址沒找到...
可以根據自己需求對xlsx源碼進行修改,以便滿足自己工作的需求。但是這樣較難以維護,如何取舍還是自行斟酌。
我這里只列取了我實際工作中所需要的功能,xlsx的功能很豐富,有空可以多琢磨琢磨。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/114939.html
摘要:使用時,前端可以將后端返回的數據拼接成自己需要導出的格式,下載到電腦中,完全不依賴后端。 前言 github: https://github.com/stardew516... 以往做excel表格下載功能的時候,都是后端生成好表格后,存儲在某個地方,然后給前端一個鏈接,前端使用a標簽加download下載,或者使用node。其實純前端也是可以做表格下載的,有一個很好用的javascr...
摘要:簡介是前端操作以及類似的二維表的最佳選擇之一而是它的社區版本將注意力集中到了數據轉換和導出上所以它支持相當多種類的數據解析和導出不僅僅局限于支持格式支持的導入格式支持的導出格式它可以解析符合格式的數據導出符合格式的數據利用中間層操作數據 簡介 SheetJS是前端操作Excel以及類似的二維表的最佳選擇之一,而js-xlsx是它的社區版本. js-xlsx將注意力集中到了數據轉換和導出...
摘要:前言最近公司需要將幾張統計表格導出到由于公司現有導出功能是前后端配合的導出,覺得麻煩,所以想找一個純前端導出的工具,最后找到了,評價還是挺高的,但是中文文檔沒找到百度也沒有找到一個比較全面的教程所以踩了很多坑,自己記錄下,方便以后使用。 前言 最近公司需要將幾張統計表格導出到excel,由于公司現有導出excel功能是前后端配合的導出,覺得麻煩,所以想找一個純前端導出的工具,最后找到了...
摘要:前言最近公司需要將幾張統計表格導出到由于公司現有導出功能是前后端配合的導出,覺得麻煩,所以想找一個純前端導出的工具,最后找到了,評價還是挺高的,但是中文文檔沒找到百度也沒有找到一個比較全面的教程所以踩了很多坑,自己記錄下,方便以后使用。 前言 最近公司需要將幾張統計表格導出到excel,由于公司現有導出excel功能是前后端配合的導出,覺得麻煩,所以想找一個純前端導出的工具,最后找到了...
閱讀 2671·2021-11-25 09:43
閱讀 2579·2021-11-22 09:34
閱讀 2823·2021-11-12 10:34
閱讀 1430·2021-10-20 13:46
閱讀 2300·2019-08-30 13:21
閱讀 929·2019-08-30 11:21
閱讀 483·2019-08-30 11:20
閱讀 2186·2019-08-29 17:20