写在前头,本人是名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 }
欢迎大家在评论区指正,不吝赐教!!!