将数据报表导出,是web数据报告展现经常使用的附带功能。一般这种功能都是用后端开发人员编写的。今天咱们主要讲的是直接经过前端js将数据导出Excel的CSV格式的文件。前端
首先在本地用Excel新建一个test.csv的文件 ===> 随便填写一些数据,保存并用Safari浏览打开该文件 ===> 打开浏览器的开发者工具,执行JSON.stringify(document.body.innerText);
,咱们获得结果以下图:git
从图中,能够看出:github
,
隔开的\n
实现的从上面两条结论,咱们只有把相应的数据转换成,
和\n
就能够了。但其实真正的答案应该是把相应的数据转换成,
和\r\n
。
为何会这样?且让我一一道来:
咱们在编辑Excel文件时,当编辑完成当前单元格时,想要编辑下一行紧挨着的单元格,按一下Enter
键就能够。而Enter
键在js字符串中是用\r
表示的。那是否是吧\n
替换成\r
就能够了呢?
其实不能够,由于涉及到操做系统的问题:web
\r\n
匹配Enter
键\r
匹配Enter
键\n
匹配Enter
键因此,最最最最终终终的结论是:chrome
,
和\r\n
,即:名称,熟练\r\n张三,2
,
隔开,因此不支持单元格的合并行、合并列,其实这句话有点多余,CSV格式的文件自己就不支持单元格的合并列和行在编写代码以前,咱们先来看一下具体数据和样式。假如当前的JSON数据是这样的windows
[ {name: '张三', amont: '323433.56', proportion: 33.4}, {name: '李四', amont: '545234.43', proportion: 55.45} ]
数据报告展现样式以下:后端
姓名 | 金额 | 占比 |
---|---|---|
张三 | 323,433.56 | 33.40% |
李四 | 545,234.43 | 55.45% |
那如何使得导出的数据与展现的保持一致呢?
答案是:浏览器
由此咱们获得以下代码:app
var JSonToCSV = { /* * obj是一个对象,其中包含有: * ## data 是导出的具体数据 * ## fileName 是导出时保存的文件名称 是string格式 * ## showLabel 表示是否显示表头 默认显示 是布尔格式 * ## columns 是表头对象,且title和key必须一一对应,包含有 title:[], // 表头展现的文字 key:[], // 获取数据的Key formatter: function() // 自定义设置当前数据的 传入(key, value) */ setDataConver: function(obj) { var data = obj['data'], ShowLabel = typeof obj['showLabel'] === 'undefined' ? true : obj['showLabel'], fileName = (obj['fileName'] || 'UserExport') + '.csv', columns = obj['columns'] || { title: [], key: [], formatter: undefined }; var ShowLabel = typeof ShowLabel === 'undefined' ? true : ShowLabel; var row = "", CSV = '', key; // 若是要现实表头文字 if (ShowLabel) { // 若是有传入自定义的表头文字 if (columns.title.length) { columns.title.map(function(n) { row += n + ','; }); } else { // 若是没有,就直接取数据第一条的对象的属性 for (key in data[0]) row += key + ','; } row = row.slice(0, -1); // 删除最后一个,号,即a,b, => a,b CSV += row + '\r\n'; // 添加换行符号 } // 具体的数据处理 data.map(function(n) { row = ''; // 若是存在自定义key值 if (columns.key.length) { columns.key.map(function(m) { row += '"' + (typeof columns.formatter === 'function' ? columns.formatter(m, n[m]) || n[m] : n[m]) + '",'; }); } else { for (key in n) { row += '"' + (typeof columns.formatter === 'function' ? columns.formatter(key, n[key]) || n[key] : n[key]) + '",'; } } row.slice(0, row.length - 1); // 删除最后一个, CSV += row + '\r\n'; // 添加换行符号 }); if(!CSV) return; this.SaveAs(fileName, CSV); }, SaveAs: function(fileName, csvData) { // console.log(fileName, csvData); } };
而后咱们分别测试了以下数据:函数
JSonToCSV.setDataConver({ data: [ {name: '张三', amont: '323433.56', proportion: 33.4}, {name: '李四', amont: '545234.43', proportion: 55.45} ], fileName: 'test', columns: { title: ['姓名', '金额', '占比'], key: ['name', 'amont', 'proportion'], formatter: function(n, v) { if(n === 'amont' && !isNaN(Number(v))) { v = v + ''; v = v.split('.'); v[0] = v[0].replace(/(\d)(?=(?:\d{3})+$)/g, '$1,'); // 千分位的设置 return v.join('.'); } if(n === 'proportion') return v + '%'; } } });
到此,数据转换完毕
因为浏览器之间的差别,尤为是IE,因此不一样的浏览器下载的方式也不同,如Chrome和Firefox都支持a
标签设置download属性和href值,而后调用a
的click
方法便可下载,IE既不支持a
download属性也不容许调用a
的click
方法。代码以下:
var a = document.querySelector('a'); a.click(); // 在这里 IE是拒绝执行的,会提示权限问题
那么对于支持a的download属性的,直接设置download属性值和href值,具体代码以下:
SaveAs: function(fileName, csvData) { var bw = this.browser(); if(!bw['edge'] || !bw['ie']) { var alink = document.createElement("a"); alink.id = "linkDwnldLink"; alink.href = this.getDownloadUrl(csvData); document.body.appendChild(alink); var linkDom = document.getElementById('linkDwnldLink'); linkDom.setAttribute('download', fileName); linkDom.click(); document.body.removeChild(linkDom); } }, getDownloadUrl: function(csvData) { var _utf = "\uFEFF"; // 为了使Excel以utf-8的编码模式,同时也是解决中文乱码的问题 return 'data:attachment/csv;charset=utf-8,' + _utf + encodeURIComponent(csvData); }, browser: function() { var Sys = {}; var ua = navigator.userAgent.toLowerCase(); var s; (s = ua.indexOf('edge') !== - 1 ? Sys.edge = 'edge' : ua.match(/rv:([\d.]+)\) like gecko/)) ? Sys.ie = s[1]: (s = ua.match(/msie ([\d.]+)/)) ? Sys.ie = s[1] : (s = ua.match(/firefox\/([\d.]+)/)) ? Sys.firefox = s[1] : (s = ua.match(/chrome\/([\d.]+)/)) ? Sys.chrome = s[1] : (s = ua.match(/opera.([\d.]+)/)) ? Sys.opera = s[1] : (s = ua.match(/version\/([\d.]+).*safari/)) ? Sys.safari = s[1] : 0; return Sys; }
虽然看起来是能够了,但仍是有问题。什么问题呢?
就是当数据量大的时候,好比几千条甚至几万条,在数据转换的时候,href的数值天然也就长了。如果超过浏览器自身限制的最大长度,会致使下载失败。具体每一个浏览器以前URL最大长度限制以下(HTTP协议并无限制URL的长度):
浏览器 | 最大长度(字符数) | 备注 |
---|---|---|
IE | 2083 | 若是超过这个数字,提交按钮没有任何反应 |
Firefox | 65,536 | - |
Chrome | 8,182 | - |
Safari | 80,000 | - |
Opera | 190,000 | - |
因此咱们这里借助 Blob(Blob传送门)来将转换好的数据进行处理,代码以下:
getDownloadUrl: function(csvData) { var _utf = "\uFEFF"; // 为了使Excel以utf-8的编码模式,同时也是解决中文乱码的问题 if (window.Blob && window.URL && window.URL.createObjectURL) { var csvData = new Blob([_utf + csvData], { type: 'text/csv' }); return URL.createObjectURL(csvData); } // return 'data:attachment/csv;charset=utf-8,' + _utf + encodeURIComponent(csvData); }
咱们在查看href值为:blob:http://127.0.0.1:3000/9715ca8a-bb9a-4b0c-8546-9bd13e8f0b69
。
这样无论几万条仍是几十万条数据均可如下载的
这里涉及到的知识点:encodeURIComponent、URL.createObjectURL
到这里,Chrome、Firefox等浏览器解决了。
IE10~Edge等浏览器调用windows.navigator.msSaveBlob
实现保存文件,msSaveBlob是IE10~Edge的私有方法。
因此SaveAs
代码改写以下:
SaveAs: function(fileName, csvData) { var bw = this.browser(); if(!bw['edge'] || !bw['ie']) { var alink = document.createElement("a"); alink.id = "linkDwnldLink"; alink.href = this.getDownloadUrl(csvData); document.body.appendChild(alink); var linkDom = document.getElementById('linkDwnldLink'); linkDom.setAttribute('download', fileName); linkDom.click(); document.body.removeChild(linkDom); } else if(bw['ie'] >= 10 || bw['edge'] == 'edge') { var _utf = "\uFEFF"; var _csvData = new Blob([_utf + csvData], { type: 'text/csv' }); navigator.msSaveBlob(_csvData, fileName); } }
IE9使用execCommand方法来保存csv文件,SaveAs
改写以下:
SaveAs: function(fileName, csvData) { var bw = this.browser(); if(!bw['edge'] || !bw['ie']) { var alink = document.createElement("a"); alink.id = "linkDwnldLink"; alink.href = this.getDownloadUrl(csvData); document.body.appendChild(alink); var linkDom = document.getElementById('linkDwnldLink'); linkDom.setAttribute('download', fileName); linkDom.click(); document.body.removeChild(linkDom); } else if(bw['ie'] >= 10 || bw['edge'] == 'edge') { var _utf = "\uFEFF"; var _csvData = new Blob([_utf + csvData], { type: 'text/csv' }); navigator.msSaveBlob(_csvData, fileName); } else { var oWin = window.top.open("about:blank", "_blank"); oWin.document.write('sep=,\r\n' + csvData); oWin.document.close(); oWin.document.execCommand('SaveAs', true, fileName); oWin.close(); } }
因此最终代码总体以下(也能够访问个人GitHub下载最新的js文件):
var JSonToCSV = { /* * obj是一个对象,其中包含有: * ## data 是导出的具体数据 * ## fileName 是导出时保存的文件名称 是string格式 * ## showLabel 表示是否显示表头 默认显示 是布尔格式 * ## columns 是表头对象,且title和key必须一一对应,包含有 title:[], // 表头展现的文字 key:[], // 获取数据的Key formatter: function() // 自定义设置当前数据的 传入(key, value) */ setDataConver: function(obj) { var bw = this.browser(); if(bw['ie'] < 9) return; // IE9如下的 var data = obj['data'], ShowLabel = typeof obj['showLabel'] === 'undefined' ? true : obj['showLabel'], fileName = (obj['fileName'] || 'UserExport') + '.csv', columns = obj['columns'] || { title: [], key: [], formatter: undefined }; var ShowLabel = typeof ShowLabel === 'undefined' ? true : ShowLabel; var row = "", CSV = '', key; // 若是要现实表头文字 if (ShowLabel) { // 若是有传入自定义的表头文字 if (columns.title.length) { columns.title.map(function(n) { row += n + ','; }); } else { // 若是没有,就直接取数据第一条的对象的属性 for (key in data[0]) row += key + ','; } row = row.slice(0, -1); // 删除最后一个,号,即a,b, => a,b CSV += row + '\r\n'; // 添加换行符号 } // 具体的数据处理 data.map(function(n) { row = ''; // 若是存在自定义key值 if (columns.key.length) { columns.key.map(function(m) { row += '"' + (typeof columns.formatter === 'function' ? columns.formatter(m, n[m]) || n[m] : n[m]) + '",'; }); } else { for (key in n) { row += '"' + (typeof columns.formatter === 'function' ? columns.formatter(key, n[key]) || n[key] : n[key]) + '",'; } } row.slice(0, row.length - 1); // 删除最后一个, CSV += row + '\r\n'; // 添加换行符号 }); if(!CSV) return; this.SaveAs(fileName, CSV); }, SaveAs: function(fileName, csvData) { var bw = this.browser(); if(!bw['edge'] || !bw['ie']) { var alink = document.createElement("a"); alink.id = "linkDwnldLink"; alink.href = this.getDownloadUrl(csvData); document.body.appendChild(alink); var linkDom = document.getElementById('linkDwnldLink'); linkDom.setAttribute('download', fileName); linkDom.click(); document.body.removeChild(linkDom); } else if(bw['ie'] >= 10 || bw['edge'] == 'edge') { var _utf = "\uFEFF"; var _csvData = new Blob([_utf + csvData], { type: 'text/csv' }); navigator.msSaveBlob(_csvData, fileName); } else { var oWin = window.top.open("about:blank", "_blank"); oWin.document.write('sep=,\r\n' + csvData); oWin.document.close(); oWin.document.execCommand('SaveAs', true, fileName); oWin.close(); } }, getDownloadUrl: function(csvData) { var _utf = "\uFEFF"; // 为了使Excel以utf-8的编码模式,同时也是解决中文乱码的问题 if (window.Blob && window.URL && window.URL.createObjectURL) { var csvData = new Blob([_utf + csvData], { type: 'text/csv' }); return URL.createObjectURL(csvData); } // return 'data:attachment/csv;charset=utf-8,' + _utf + encodeURIComponent(csvData); }, browser: function() { var Sys = {}; var ua = navigator.userAgent.toLowerCase(); var s; (s = ua.indexOf('edge') !== - 1 ? Sys.edge = 'edge' : ua.match(/rv:([\d.]+)\) like gecko/)) ? Sys.ie = s[1]: (s = ua.match(/msie ([\d.]+)/)) ? Sys.ie = s[1] : (s = ua.match(/firefox\/([\d.]+)/)) ? Sys.firefox = s[1] : (s = ua.match(/chrome\/([\d.]+)/)) ? Sys.chrome = s[1] : (s = ua.match(/opera.([\d.]+)/)) ? Sys.opera = s[1] : (s = ua.match(/version\/([\d.]+).*safari/)) ? Sys.safari = s[1] : 0; return Sys; } }; // 测试 JSonToCSV.setDataConver({ data: [ {name: '张三', amont: '323433.56', proportion: 33.4}, {name: '李四', amont: '545234.43', proportion: 55.45} ], fileName: 'test', columns: { title: ['姓名', '金额', '占比'], key: ['name', 'amont', 'proportion'], formatter: function(n, v) { if(n === 'amont' && !isNaN(Number(v))) { v = v + ''; v = v.split('.'); v[0] = v[0].replace(/(\d)(?=(?:\d{3})+$)/g, '$1,'); return v.join('.'); } if(n === 'proportion') return v + '%'; } } });
也能够访问个人GitHub下载最新的js文件