概述
SheetJS用于读写各种电子表格,免费版不支持样式调整。
官方说明文档:https://github.com/SheetJS/sheetjs
xlsx-style基于SheetJS二次开发,使其支持样式调整,但其开发停留在2017年,所基于的SheetJS版本老旧,缺失许多后续新增方法。
官方说明文档:https://github.com/protobi/js-xlsx
这两者默认文件名都为xlsx.core.min.js和xlsx.full.min.js,一般使用xlsx.core.min.js即可;
SheetJS所暴露出来的对象在最后一个语句, var XLS=XLSX,ODS=XLSX; 分别为XLS,XLSX,ODS;
xlsx-style所暴露出来的对象在最后一个语句, var XLS=XLSX; 分别为XLS,XLSX;
由于两者所暴露出来的对象一样(都是XLS和XLSX),同时引用时会覆盖掉另一个,故要将xlsx-style所暴露的对象改为xlsxStyle,参考这个项目https://github.com/Ctrl-Ling/XLSX-Style-Utils;所暴露出来的对象在最后一个语句, var XLS = xlsxStyle; 分别为XLS,xlsxStyle;使用SheetJS功能时用XLSX对象,使用xlsx-style功能时用xlsxStyle对象;
导出
项目采用异步提交后返回json,回调处理生成并下载Excel,其中sdata为导出字段信息,包括字段名称、字段描述、是否合并,合并是指一列中相邻单元格内容一样;
xhr.onload = function() { if (xhr.readyState == 4 && xhr.status == 200) { //传入参数 var sdata = this.sdata; //接收响应数据 var content = this.response; //解析为json格式 //var jsonStuExt = JSON.parse(content); var jsonStuExt = content; //删除无用列 for (var i = 0, ilen = jsonStuExt.length; i < ilen; i++) { var json = {}; for (var j = 0, jlen = sdata.length; j < jlen; j++) { json[sdata[j].field] = jsonStuExt[i][sdata[j].field]; //日期格式转换,2020-04-12 if (/Date\((-?\d+)\)/.test(json[sdata[j].field])) { json[sdata[j].field] = getFDate(json[sdata[j].field]); } } jsonStuExt[i] = json; } //添加空行,2020-04-08 jsonStuExt.unshift({}); //转为工作表 var sheet = XLSX.utils.json_to_sheet(jsonStuExt); //获取行列数 var range = XLSX.utils.decode_range(sheet['!ref']); var ncols = range.e.c - range.s.c + 1, nrows = range.e.r - range.s.r + 1; //第二行改为标题内容 for (var i = 0; i < ncols; i++) { var curr = getColumnNameByIndex(i) + '2', prev = getColumnNameByIndex(i) + '1'; //复制首行 sheet[curr] = sheet[prev]; //单元格内容改为文本 sheet[curr].v = sdata[i].text; //加批注 sheet[curr].c = [{ hidden: true, a: "SheetJS", t: sdata[i].field }]; //第一行清空 delete sheet[prev]; } var merge = []; //合并单元格,2020-05-06 for (var i = 0; i < ncols; i++) { //获取导出字段名,设置为列批注 if (sdata[i].merge) { merge = merge.concat(MergeColumn(sheet, range.s.r + 2, i, range.e.r)); } } if (merge.length != 0) { sheet['!merges'] = merge; } sheet["!rows"] = []; //单元格行高 for (var i = 1; i < nrows + 1; i++) { //设置列宽 sheet["!rows"].push({ hpt: 15 }); } //原文链接:https://blog.csdn.net/tian_i/java/article/details/84327329 sheet["!cols"] = []; //单元格列宽,2020-05-11 for (var i = 1; i < ncols + 1; i++) { //设置列宽 sheet["!cols"].push({ wpx: 80 }); for (var k = 2; k < nrows + 1; k++) { var curr = getColumnNameByIndex(i - 1) + k; //console.log(curr); console.log(sheet); sheet[curr].s = { font: { name: '宋体', sz: 11, bold: false, color: { rgb: "000" }, }, //fill: { // bgColor: { indexed: 64 }, fgColor: { rgb: "FFFF00" } //}, alignment: { horizontal: "center", vertical: "center", //wrapText: true, }, border: { top: { style: "thin", }, bottom: { style: "thin", }, left: { style: "thin", }, right: { style: "thin", }, } }; //<====设置xlsx单元格样式 } } //转为blob var blob = sheet2blob(sheet); var fileName = "成绩管理" + getFullTime() + ".xlsx"; //下载 saveAs(blob, fileName); } };
合并单元格
function MergeColumn(sheet, StartRowNum, ColumnIndex, LastRowNum) { var sheetMerge = []; var rowNum = 0; for (var i = StartRowNum; i < LastRowNum + 1; i++) { rowNum = i + 1; var StartValue = sheet[getColumnNameByIndex(ColumnIndex) + rowNum].v; for (var k = i + 1; k < LastRowNum + 1; k++) { rowNum = k + 1; var CurrValue = sheet[getColumnNameByIndex(ColumnIndex) + rowNum].v; // console.log(CurrValue); if (StartValue != CurrValue) { if (k != i + 1) { //合并 sheetMerge.push({ s: { r: i, c: ColumnIndex }, e: { r: k - 1, c: ColumnIndex } }); i = k - 1; } break; } else if (k == LastRowNum) { //连同最后一行合并 sheetMerge.push({ s: { r: i, c: ColumnIndex }, e: { r: LastRowNum, c: ColumnIndex } }); i = LastRowNum; break; } } } return sheetMerge; }
格式化日期,根据索引输出列名
//js转换 /Date(1464671903000)/ 格式的日期,转换成:2016-07-11 function getFDate(date) { var d = eval('new ' + date.substr(1, date.length - 2)); var ar_date = [d.getFullYear(), d.getMonth() + 1, d.getDate()]; for (var i = 0; i < ar_date.length; i++) ar_date[i] = dFormat(ar_date[i]); return ar_date.join('-'); } function dFormat(i) { return i < 10 ? "0" + i.toString() : i; } //Excel/Sheet根据索引输出列名,2020-04-09 function getColumnNameByIndex(i) { var result = String.fromCharCode('A'.charCodeAt() + i % 26); while (i >= 26) { i /= 26; i--; result = String.fromCharCode('A'.charCodeAt() + i % 26) + result; } return result; }
转化,导出
其中write方法一定要用xlsxStyle对象调用,才能输出带格式文件;
//https://www.cnblogs.com/liuxianan/p/js-excel.html // 将一个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' }; //转换成二进制 使用xlsx-style(XS)进行转换才能得到带样式Excel,2020-05-11 var wbout = xlsxStyle.write(workbook, wopts); //console.log(wbout); 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; }
//获取当前日期,2019-12-25 function getFullTime() { var now = new Date(); var yy = now.getFullYear(); //年 var mm = now.getMonth() + 1; //月 var dd = now.getDate(); //日 var hh = now.getHours(); //时 var ii = now.getMinutes(); //分 var ss = now.getSeconds(); //秒 var ms = now.getMilliseconds(); //毫秒 var clock = yy + "-"; if (mm < 10) clock += "0"; clock += mm + "-"; if (dd < 10) clock += "0"; clock += dd + "-"; if (hh < 10) clock += "0"; clock += hh + "-"; if (ii < 10) clock += '0'; clock += ii + "-"; if (ss < 10) clock += '0'; clock += ss + "-"; clock += ms; return clock; //获取当前日期 }
function saveAs(blob, fileName) { //生成一个a标签 var a = document.createElement('a'); a.style.display = 'none'; a.download = fileName; a.id = "aexport"; //生成一个label标签,用于触发a标签点击事件 var lb = document.createElement('label'); lb. for = "aexport"; a.appendChild(lb); //创建一个URL对象,指向Blob对象 var objectURL = window.URL.createObjectURL(blob); a.href = objectURL; //把a标签加入body document.body.appendChild(a); //触发a标签点击事件 lb.click(); //IE不支持createObjectURL,特殊处理 if (_isIE()) { window.navigator.msSaveOrOpenBlob(blob, fileName); } //删除a标签 document.body.removeChild(a); //回收内存 URL.revokeObjectURL(objectURL); }
解析
读取本地Excel,生成workbook,方法一,使用 { type: 'binary' } ;
// 读取本地excel文件 function readWorkbookFromLocalFile(file, callback) { var reader = new FileReader(); reader.onload = function(e) { var data; if (reader.result) { //读取后内容,适用FireFox data = reader.result; } else { //读取后内容,适用IE data = reader.content; } var workbook =XLSX.read(data, { type: 'binary' }); if (callback) callback(workbook); }; reader.readAsBinaryString(file); } //readAsBinaryString 兼容IE11,2020-04-03 //https://www.cnblogs.com/badprogrammer/p/10728024.html if (!FileReader.prototype.readAsBinaryString) { FileReader.prototype.readAsBinaryString = function(fileData) { //解决ie11 大文件堆栈溢出的问题(for arrayBufferToString) var binary = ""; var pt = this; var reader = new FileReader(); reader.onload = function(e) { var bytes = new Uint8Array(reader.result); var length = bytes.byteLength; for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } pt.content = binary; //console.log("binary length:" + binary.length); pt.onload(pt); //页面内data取pt.content文件内容 } reader.readAsArrayBuffer(fileData); } }
读取本地Excel,生成workbook,方法二,使用 { type: 'base64' } ;
function fixdata(data) { //文件流转BinaryString var o = "", l = 0, w = 10240; for (; l < data.byteLength / w; ++l) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w))); o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w))); return o; } // 读取本地excel文件 function readWorkbookFromLocalFile(file, callback) { var reader = new FileReader(); reader.onload = function(e) { var data; if (reader.result) { //读取后内容,适用FireFox data = reader.result; } else { //读取后内容,适用IE data = reader.content; } var workbook = XLSX.read(btoa(fixdata(data)), { type: 'base64' }); if (callback) callback(workbook); }; reader.readAsArrayBuffer(file); }
处理表单内容
readWorkbookFromLocalFile(fileObj.files[0],readWorkbook); function readWorkbook(workbook) { var sheetNames = workbook.SheetNames; // 工作表名称集合 var worksheet = workbook.Sheets[sheetNames[0]]; // 这里我们只读取第一张sheet var range = XLSX.utils.decode_range(worksheet['!ref']); var ncols = range.e.c - range.s.c + 1, nrows = range.e.r - range.s.r + 1; //修改列名为批注内容 for (var i = 0; i < ncols; i++) { var key = getColumnNameByIndex(i) + '2'; worksheet[key].w = worksheet[key].c[0].t; } var json = XLSX.utils.sheet_to_json(worksheet, { range: 1 }); //IE不支持,注释掉,2020-04-03 //var json = Object.values(XLSX.utils.sheet_to_row_object_array(worksheet));
/* DO SOMETHING HERE */ } //Excel/Sheet根据索引输出列名,2020-04-09 function getColumnNameByIndex(i) { var result = String.fromCharCode('A'.charCodeAt() + i % 26); while (i >= 26) { i /= 26; i--; result = String.fromCharCode('A'.charCodeAt() + i % 26) + result; } return result; }
总结
- 本插件对浏览器要求较高,部分功能可能有兼容性问题;
- 替代方案一,NPOI,导出时在后端生成workbook,写入输出流在前端下载,项目参考;导入时在服务器读取流为DataTable,再用SqlBulkCopy批量复制到数据库,项目参考;
- 替代方案二,使用商业软件,Wijmo,项目可参考https://www.cnblogs.com/likeFlyingFish/p/5794467.html;
参考资料:
- https://www.cnblogs.com/likeFlyingFish/p/5794467.html
- https://www.jianshu.com/p/877631e7e411
- https://www.jianshu.com/p/74d405940305
- https://www.jianshu.com/p/869375439fee
- https://www.cnblogs.com/liuxianan/p/js-excel.html
- https://blog.csdn.net/tian_i/article/details/84327329