寫在前頭,本人是名Java開發人員,偶爾在前端打打醬油,寫出的代碼或許存在問題,請路過的大神一一指正,不吝感激。
最近公司准備做一些關於Excel 數據導入和導出相關需求,之前有在開源社區看到說比起純后端解析,前端更有優勢,一來是現在的個人電腦的性能已經有了長足的進步,而來,服務端的資源本就金貴,后端服務的瓶頸就是業務系統平台的瓶頸,對於服務端的優化,本就是一個永久的話題,說到這里,基本上也就該說今天的主角了,js-xlsx。
要說js-xlsx,就不得不說xlsx,為什么呢,我也是在剛接觸是一頭霧水,因為在npm上搜索xlsx,第一條就是xlsx,但是進去之后就蒙了,怎么是js-xlsx呢?不信你看看。那在npm上搜索js-xlsx呢,進去之后卻看到,xlsx-style,???如果你足夠細心的話可能還會看到一個包,那就是xlsx-style,看到這里你估計就該問了,xlsx,js-xlsx,xlsx-style這些都是什么鬼,我說下我的理解吧,這些都是xlsx的分支,只不過由於xlsx的部分功能問題,其他人在xlsx的基礎上衍生出了很多版本,比如還未提到的node-xlsx,以及鄙人的sognyz-xlsx,這些都是或多或少的引用了官方代碼,在此基礎上,進行了擴展開發,至於該怎么用,我說下我的看法,xlsx應該是bug最少也最穩定的,至少人家是鼻祖,關於xlsx-style和js-xlsx,它們在原有的功能基礎上,添加了對導出樣式的控制,讓導出的Excel更加滿足業務需要,比如說一些常見的設置字體樣式,大小,顏色等待,但是我使用cdn方式引入xlsx-style時沒有問題,但是使用ES6 import 語法是出現小問題(網上查詢頁面解決),在這個過程中,遇見了node-xlsx和js-xlsx,簡單使用之后,發現js-xlsx是我要找的,node-xlsx是在js-xlsx的基礎之上進行的一層薄薄的封裝,不過這層封裝也大大降低了js-xlsx的上手難度(值得自己學習),啰里啰嗦低講到這里你估計又想說了,那就使用被,還費什么話,額~~~廢話少說,撿重點的~~~
先說關於導入日期處理這塊,導入的文件中包含三種日期格式,截圖如下,關於代碼信息,在文章末尾處
當我看到數據時,我得內心是慌亂的一逼,截圖說明下
js-xlsx將數據直接解析成了個性化數據,瀏覽過源碼就會發現,它是根據excel中的格式進行的格式化,雖然未必能轉換成跟office中一模一樣,但是確實實現了一大部分,但是這種數據丟給我的程序,我豈不是要涼涼,我們當然是希望他們給我們一種統一的格式(yyyy-MM-dd hh:mm或者時間戳格式),這樣才方便自己程序處理,這個問題先記下,
關於數字的問題,Excel截圖如下,我的文件
解析,看數據,截圖說明
哪里有兩個問題,價格的值,莫名的多了個空格,而且還是字符串格式,身份證號的值,竟然使用了科學計數法,這,這,好牛B的程序,然而呢。。。我想靜靜......
js-xlsx雖然很強大哦,但是他並沒有暴露出來一些關於處理數據的入口,哎,思來想去,要不自己改改?
然后就有了,
npm上的 songyz-xlsx
github上的 songyz-xlsx
另外,相關代碼

1 //表頭單元格樣式 2 export const titleStyle = { 3 font: { 4 bold: true, 5 }, 6 alignment: { 7 horizontal: "center", 8 vertical: "center", 9 }, 10 border: { 11 top: { 12 style: "thin", 13 }, 14 bottom: { 15 style: "thin", 16 }, 17 left: { 18 style: "thin", 19 }, 20 right: { 21 style: "thin", 22 }, 23 } 24 }; 25 //內容單元格樣式 26 export const bodyStyle = { 27 alignment: { 28 vertical: "center", 29 }, 30 border: { 31 top: { 32 style: "thin", 33 }, 34 bottom: { 35 style: "thin", 36 }, 37 left: { 38 style: "thin", 39 }, 40 right: { 41 style: "thin", 42 }, 43 } 44 };

1 // 將指定的自然數轉換為26進制表示。映射關系:[0-25] -> [A-Z]。 2 export const getCharCol = (n) => { 3 let s = '', 4 m = 0 5 while (n > 0) { 6 m = n % 26 + 1 7 s = String.fromCharCode(m + 64) + s 8 n = (n - m) / 26 9 } 10 return s 11 } 12 13 //將數據寫到文件中 14 export const writeFile = (fname, data, enc) => { 15 /*global IE_SaveFile, Blob, navigator, saveAs, URL, document, File, chrome */ 16 if (typeof IE_SaveFile !== 'undefined') return IE_SaveFile(data, fname); 17 if (typeof Blob !== 'undefined') { 18 var blob = new Blob([blobify(data)], { type: "application/octet-stream" }); 19 if (typeof navigator !== 'undefined' && navigator.msSaveBlob) return navigator.msSaveBlob(blob, fname); 20 if (typeof saveAs !== 'undefined') return saveAs(blob, fname); 21 if (typeof URL !== 'undefined' && typeof document !== 'undefined' && document.createElement && URL.createObjectURL) { 22 var url = URL.createObjectURL(blob); 23 if (typeof chrome === 'object' && typeof(chrome.downloads || {}).download == "function") { 24 if (URL.revokeObjectURL && typeof setTimeout !== 'undefined') setTimeout(function() { URL.revokeObjectURL(url); }, 60000); 25 return chrome.downloads.download({ url: url, filename: fname, saveAs: true }); 26 } 27 var a = document.createElement("a"); 28 if (a.download != null) { 29 a.download = fname; 30 a.href = url; 31 document.body.appendChild(a); 32 a.click(); 33 document.body.removeChild(a); 34 if (URL.revokeObjectURL && typeof setTimeout !== 'undefined') setTimeout(function() { URL.revokeObjectURL(url); }, 60000); 35 return url; 36 } 37 } 38 } 39 // $FlowIgnore 40 if (typeof $ !== 'undefined' && typeof File !== 'undefined' && typeof Folder !== 'undefined') try { // extendscript 41 // $FlowIgnore 42 var out = File(fname); 43 out.open("w"); 44 out.encoding = "binary"; 45 if (Array.isArray(payload)) payload = a2s(payload); 46 out.write(payload); 47 out.close(); 48 return payload; 49 } catch (e) { if (!e.message || !e.message.match(/onstruct/)) throw e; } 50 throw new Error("cannot save file " + fname); 51 } 52 53 /* normalize data for blob ctor */ 54 function blobify(data) { 55 if (typeof data === "string") return s2ab(data); 56 if (Array.isArray(data)) return a2u(data); 57 return data; 58 } 59 60 function s2ab(s) { 61 if (typeof ArrayBuffer === 'undefined') return s2a(s); 62 var buf = new ArrayBuffer(s.length), 63 view = new Uint8Array(buf); 64 for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; 65 return buf; 66 } 67 68 function a2u(data) { 69 if (typeof Uint8Array === 'undefined') throw new Error("Unsupported"); 70 return new Uint8Array(data); 71 }

1 import XLSX from 'songyz-xlsx' 2 3 import { titleStyle, bodyStyle } from './xlsx-support/common' 4 import { getCharCol, writeFile } from './xlsx-support/util' 5 6 //導入文件的類型 7 export const xlsxTypes = ["xlsx", "xlc", "xlm", "xls", "xlt", "xlw", "csv"]; 8 9 //導入文件 10 export const importSlsx = (file, opts) => { 11 return new Promise(function (resolve, reject) { 12 const reader = new FileReader() 13 reader.onload = function (e) { 14 opts = opts || {}; 15 16 opts.type = 'binary'; 17 opts._dateType = opts._dateType || 1; //1,"yyyy-MM-dd hh:mm",2,時間戳 18 opts._numberType = opts._numberType || 1; //1,不適用科學計數法,2,使用科學計數法 19 20 const wb = XLSX.read(e.target.result, opts); 21 resolve(Object.keys(wb.Sheets).map(key => XLSX.utils.sheet_to_json(wb.Sheets[key])).reduce((prev, next) => prev.concat(next))) 22 } 23 reader.readAsBinaryString(file.raw) 24 }) 25 } 26 27 //導出數據 28 export const exportXlsx = (dataArray, fileName) => { 29 let type = 'xlsx'; 30 dataArray = dataArray || [{}]; 31 fileName = fileName || 'file'; 32 33 var keyMap = Object.keys(dataArray[0]); 34 var title = {}; 35 keyMap.forEach(key => title[key] = key); 36 dataArray.unshift(title); 37 38 //用來保存轉換好的json 39 var sheetData = []; 40 41 dataArray.map((row, i) => { 42 let style = i == 0 ? titleStyle : bodyStyle; 43 return keyMap.map((key, j) => { 44 return { 45 style: style, 46 value: row[key], 47 position: (j > 25 ? getCharCol(j) : String.fromCharCode(65 + j)) + (i + 1) 48 }; 49 }) 50 }).reduce((prev, next) => prev.concat(next)).forEach((cell, i) => 51 sheetData[cell.position] = { 52 v: cell.value, 53 s: cell.style 54 } 55 ); 56 var outputPos = Object.keys(sheetData); //設置區域,比如表格從A1到D10 57 58 var wb = { 59 SheetNames: ['mySheet'], //保存的表標題 60 Sheets: { 61 'mySheet': Object.assign({}, 62 sheetData, //內容 63 { 64 '!ref': outputPos[0] + ':' + outputPos[outputPos.length - 1] //設置填充區域 65 } 66 ) 67 } 68 }; 69 var buffer = XLSX.write(wb, { bookType: type, bookSST: false, type: 'buffer' }); 70 71 writeFile(fileName + "." + type, buffer); 72 }
歡迎大家在評論區指正,不吝賜教!!!