前言

将数据报表导出,是web数据报告展示常用的附带功能。通常这种功能都是用后端开发人员编写的。今天我们主要讲的是直接通过前端js将数据导出Excel的CSV格式的文件。

原理

首先在本地用Excel新建一个test.csv的文件 ===> 随便填写一些数据,保存并用Safari浏览打开该文件 ===> 打开浏览器的开发者工具,执行JSON.stringify(document.body.innerText);,我们得到结果如下图:

从图中,可以看出:

  • CSV文件格式单元格之间是通过,隔开的
  • CSV文件格式里,换行是通过\n实现的

从上面两条结论,我们只有把相应的数据转换成,\n就可以了。但其实真正的答案应该是把相应的数据转换成,\r\n
为什么会这样?且让我一一道来:
我们在编辑Excel文件时,当编辑完成当前单元格时,想要编辑下一行紧挨着的单元格,按一下Enter键就可以。而Enter键在js字符串中是用\r表示的。那是不是吧\n替换成\r就可以了呢?
其实不可以,因为涉及到操作系统的问题:

  • 在Windows系统中,标准模式采用的是\r\n匹配Enter
  • 在mac系统中,用\r匹配Enter
  • 在Linux系统中,用\n匹配Enter

所以,最最最最终终终的结论是:

  • 将相应的数据转换成,\r\n,即:名称,熟练\r\n张三,2
  • 由于单元格之间使用,隔开,所以不支持单元格的合并行、合并列,其实这句话有点多余,CSV格式的文件本身就不支持单元格的合并列和行

实现方式

在编写代码之前,我们先来看一下具体数据和样式。假如当前的JSON数据是这样的

  1. [
  2. {name: '张三', amont: '323433.56', proportion: 33.4},
  3. {name: '李四', amont: '545234.43', proportion: 55.45}
  4. ]

数据报告展示样式如下:

姓名 金额 占比
张三 323,433.56 33.40%
李四 545,234.43 55.45%

那如何使得导出的数据与展示的保持一致呢?
答案是:

  • 把要展示的表头文字也进行处理
  • 遍历取对应的key值
  • 设置formatter回调处理的当前值的函数

由此我们得到如下代码:

  1. var JSonToCSV = {
  2. /*
  3. * obj是一个对象,其中包含有:
  4. * ## data 是导出的具体数据
  5. * ## fileName 是导出时保存的文件名称 是string格式
  6. * ## showLabel 表示是否显示表头 默认显示 是布尔格式
  7. * ## columns 是表头对象,且title和key必须一一对应,包含有
  8. title:[], // 表头展示的文字
  9. key:[], // 获取数据的Key
  10. formatter: function() // 自定义设置当前数据的 传入(key, value)
  11. */
  12. setDataConver: function(obj) {
  13. var data = obj['data'],
  14. ShowLabel = typeof obj['showLabel'] === 'undefined' ? true : obj['showLabel'],
  15. fileName = (obj['fileName'] || 'UserExport') + '.csv',
  16. columns = obj['columns'] || {
  17. title: [],
  18. key: [],
  19. formatter: undefined
  20. };
  21. var ShowLabel = typeof ShowLabel === 'undefined' ? true : ShowLabel;
  22. var row = "", CSV = '', key;
  23. // 如果要现实表头文字
  24. if (ShowLabel) {
  25. // 如果有传入自定义的表头文字
  26. if (columns.title.length) {
  27. columns.title.map(function(n) {
  28. row += n + ',';
  29. });
  30. } else {
  31. // 如果没有,就直接取数据第一条的对象的属性
  32. for (key in data[0]) row += key + ',';
  33. }
  34. row = row.slice(0, -1); // 删除最后一个,号,即a,b, => a,b
  35. CSV += row + '\r\n'; // 添加换行符号
  36. }
  37. // 具体的数据处理
  38. data.map(function(n) {
  39. row = '';
  40. // 如果存在自定义key值
  41. if (columns.key.length) {
  42. columns.key.map(function(m) {
  43. row += '"' + (typeof columns.formatter === 'function' ? columns.formatter(m, n[m]) || n[m] : n[m]) + '",';
  44. });
  45. } else {
  46. for (key in n) {
  47. row += '"' + (typeof columns.formatter === 'function' ? columns.formatter(key, n[key]) || n[key] : n[key]) + '",';
  48. }
  49. }
  50. row.slice(0, row.length - 1); // 删除最后一个,
  51. CSV += row + '\r\n'; // 添加换行符号
  52. });
  53. if(!CSV) return;
  54. this.SaveAs(fileName, CSV);
  55. },
  56. SaveAs: function(fileName, csvData) {
  57. // console.log(fileName, csvData);
  58. }
  59. };

然后我们分别测试了如下数据:

  1. JSonToCSV.setDataConver({
  2. data: [
  3. {name: '张三', amont: '323433.56', proportion: 33.4},
  4. {name: '李四', amont: '545234.43', proportion: 55.45}
  5. ],
  6. fileName: 'test',
  7. columns: {
  8. title: ['姓名', '金额', '占比'],
  9. key: ['name', 'amont', 'proportion'],
  10. formatter: function(n, v) {
  11. if(n === 'amont' && !isNaN(Number(v))) {
  12. v = v + '';
  13. v = v.split('.');
  14. v[0] = v[0].replace(/(\d)(?=(?:\d{3})+$)/g, '$1,'); // 千分位的设置
  15. return v.join('.');
  16. }
  17. if(n === 'proportion') return v + '%';
  18. }
  19. }
  20. });

到此,数据转换完毕

下载方式

由于浏览器之间的差异,尤其是IE,所以不同的浏览器下载的方式也不一样,如Chrome和Firefox都支持a标签设置download属性和href值,然后调用aclick方法即可下载,IE既不支持adownload属性也不允许调用aclick方法。代码如下:

  1. var a = document.querySelector('a');
  2. a.click(); // 在这里 IE是拒绝执行的,会提示权限问题

那么对于支持a的download属性的,直接设置download属性值和href值,具体代码如下:

Chrome、Firefox等浏览器的的下载方式

  1. SaveAs: function(fileName, csvData) {
  2. var bw = this.browser();
  3. if(!bw['edge'] || !bw['ie']) {
  4. var alink = document.createElement("a");
  5. alink.id = "linkDwnldLink";
  6. alink.href = this.getDownloadUrl(csvData);
  7. document.body.appendChild(alink);
  8. var linkDom = document.getElementById('linkDwnldLink');
  9. linkDom.setAttribute('download', fileName);
  10. linkDom.click();
  11. document.body.removeChild(linkDom);
  12. }
  13. },
  14. getDownloadUrl: function(csvData) {
  15. var _utf = "\uFEFF"; // 为了使Excel以utf-8的编码模式,同时也是解决中文乱码的问题
  16. return 'data:attachment/csv;charset=utf-8,' + _utf + encodeURIComponent(csvData);
  17. },
  18. browser: function() {
  19. var Sys = {};
  20. var ua = navigator.userAgent.toLowerCase();
  21. var s;
  22. (s = ua.indexOf('edge') !== - 1 ? Sys.edge = 'edge' : ua.match(/rv:([\d.]+)\) like gecko/)) ? Sys.ie = s[1]:
  23. (s = ua.match(/msie ([\d.]+)/)) ? Sys.ie = s[1] :
  24. (s = ua.match(/firefox\/([\d.]+)/)) ? Sys.firefox = s[1] :
  25. (s = ua.match(/chrome\/([\d.]+)/)) ? Sys.chrome = s[1] :
  26. (s = ua.match(/opera.([\d.]+)/)) ? Sys.opera = s[1] :
  27. (s = ua.match(/version\/([\d.]+).*safari/)) ? Sys.safari = s[1] : 0;
  28. return Sys;
  29. }

虽然看起来是可以了,但还是有问题。什么问题呢?
就是当数据量大的时候,比如几千条甚至几万条,在数据转换的时候,href的数值自然也就长了。若是超过浏览器自身限制的最大长度,会导致下载失败。具体每个浏览器之前URL最大长度限制如下(HTTP协议并没有限制URL的长度):

浏览器 最大长度(字符数) 备注
IE 2083 如果超过这个数字,提交按钮没有任何反应
Firefox 65,536 -
Chrome 8,182 -
Safari 80,000 -
Opera 190,000 -

所以我们这里借助 Blob(Blob传送门)来将转换好的数据进行处理,代码如下:

  1. getDownloadUrl: function(csvData) {
  2. var _utf = "\uFEFF"; // 为了使Excel以utf-8的编码模式,同时也是解决中文乱码的问题
  3. if (window.Blob && window.URL && window.URL.createObjectURL) {
  4. var csvData = new Blob([_utf + csvData], {
  5. type: 'text/csv'
  6. });
  7. return URL.createObjectURL(csvData);
  8. }
  9. // return 'data:attachment/csv;charset=utf-8,' + _utf + encodeURIComponent(csvData);
  10. }

我们在查看href值为:blob:http://127.0.0.1:3000/9715ca8a-bb9a-4b0c-8546-9bd13e8f0b69
这样不管几万条还是几十万条数据都可以下载的
这里涉及到的知识点:encodeURIComponentURL.createObjectURL
到这里,Chrome、Firefox等浏览器解决了。

IE10~Edge浏览的下载方式

IE10~Edge等浏览器调用windows.navigator.msSaveBlob实现保存文件,msSaveBlob是IE10~Edge的私有方法。
所以SaveAs代码改写如下:

  1. SaveAs: function(fileName, csvData) {
  2. var bw = this.browser();
  3. if(!bw['edge'] || !bw['ie']) {
  4. var alink = document.createElement("a");
  5. alink.id = "linkDwnldLink";
  6. alink.href = this.getDownloadUrl(csvData);
  7. document.body.appendChild(alink);
  8. var linkDom = document.getElementById('linkDwnldLink');
  9. linkDom.setAttribute('download', fileName);
  10. linkDom.click();
  11. document.body.removeChild(linkDom);
  12. }
  13. else if(bw['ie'] >= 10 || bw['edge'] == 'edge') {
  14. var _utf = "\uFEFF";
  15. var _csvData = new Blob([_utf + csvData], {
  16. type: 'text/csv'
  17. });
  18. navigator.msSaveBlob(_csvData, fileName);
  19. }
  20. }

IE9下载方式

IE9使用execCommand方法来保存csv文件,SaveAs改写如下:

  1. SaveAs: function(fileName, csvData) {
  2. var bw = this.browser();
  3. if(!bw['edge'] || !bw['ie']) {
  4. var alink = document.createElement("a");
  5. alink.id = "linkDwnldLink";
  6. alink.href = this.getDownloadUrl(csvData);
  7. document.body.appendChild(alink);
  8. var linkDom = document.getElementById('linkDwnldLink');
  9. linkDom.setAttribute('download', fileName);
  10. linkDom.click();
  11. document.body.removeChild(linkDom);
  12. }
  13. else if(bw['ie'] >= 10 || bw['edge'] == 'edge') {
  14. var _utf = "\uFEFF";
  15. var _csvData = new Blob([_utf + csvData], {
  16. type: 'text/csv'
  17. });
  18. navigator.msSaveBlob(_csvData, fileName);
  19. }
  20. else {
  21. var oWin = window.top.open("about:blank", "_blank");
  22. oWin.document.write('sep=,\r\n' + csvData);
  23. oWin.document.close();
  24. oWin.document.execCommand('SaveAs', true, fileName);
  25. oWin.close();
  26. }
  27. }

所以最终代码整体如下(也可以访问我的GitHub下载最新的js文件):

  1. var JSonToCSV = {
  2. /*
  3. * obj是一个对象,其中包含有:
  4. * ## data 是导出的具体数据
  5. * ## fileName 是导出时保存的文件名称 是string格式
  6. * ## showLabel 表示是否显示表头 默认显示 是布尔格式
  7. * ## columns 是表头对象,且title和key必须一一对应,包含有
  8. title:[], // 表头展示的文字
  9. key:[], // 获取数据的Key
  10. formatter: function() // 自定义设置当前数据的 传入(key, value)
  11. */
  12. setDataConver: function(obj) {
  13. var bw = this.browser();
  14. if(bw['ie'] < 9) return; // IE9以下的
  15. var data = obj['data'],
  16. ShowLabel = typeof obj['showLabel'] === 'undefined' ? true : obj['showLabel'],
  17. fileName = (obj['fileName'] || 'UserExport') + '.csv',
  18. columns = obj['columns'] || {
  19. title: [],
  20. key: [],
  21. formatter: undefined
  22. };
  23. var ShowLabel = typeof ShowLabel === 'undefined' ? true : ShowLabel;
  24. var row = "", CSV = '', key;
  25. // 如果要现实表头文字
  26. if (ShowLabel) {
  27. // 如果有传入自定义的表头文字
  28. if (columns.title.length) {
  29. columns.title.map(function(n) {
  30. row += n + ',';
  31. });
  32. } else {
  33. // 如果没有,就直接取数据第一条的对象的属性
  34. for (key in data[0]) row += key + ',';
  35. }
  36. row = row.slice(0, -1); // 删除最后一个,号,即a,b, => a,b
  37. CSV += row + '\r\n'; // 添加换行符号
  38. }
  39. // 具体的数据处理
  40. data.map(function(n) {
  41. row = '';
  42. // 如果存在自定义key值
  43. if (columns.key.length) {
  44. columns.key.map(function(m) {
  45. row += '"' + (typeof columns.formatter === 'function' ? columns.formatter(m, n[m]) || n[m] : n[m]) + '",';
  46. });
  47. } else {
  48. for (key in n) {
  49. row += '"' + (typeof columns.formatter === 'function' ? columns.formatter(key, n[key]) || n[key] : n[key]) + '",';
  50. }
  51. }
  52. row.slice(0, row.length - 1); // 删除最后一个,
  53. CSV += row + '\r\n'; // 添加换行符号
  54. });
  55. if(!CSV) return;
  56. this.SaveAs(fileName, CSV);
  57. },
  58. SaveAs: function(fileName, csvData) {
  59. var bw = this.browser();
  60. if(!bw['edge'] || !bw['ie']) {
  61. var alink = document.createElement("a");
  62. alink.id = "linkDwnldLink";
  63. alink.href = this.getDownloadUrl(csvData);
  64. document.body.appendChild(alink);
  65. var linkDom = document.getElementById('linkDwnldLink');
  66. linkDom.setAttribute('download', fileName);
  67. linkDom.click();
  68. document.body.removeChild(linkDom);
  69. }
  70. else if(bw['ie'] >= 10 || bw['edge'] == 'edge') {
  71. var _utf = "\uFEFF";
  72. var _csvData = new Blob([_utf + csvData], {
  73. type: 'text/csv'
  74. });
  75. navigator.msSaveBlob(_csvData, fileName);
  76. }
  77. else {
  78. var oWin = window.top.open("about:blank", "_blank");
  79. oWin.document.write('sep=,\r\n' + csvData);
  80. oWin.document.close();
  81. oWin.document.execCommand('SaveAs', true, fileName);
  82. oWin.close();
  83. }
  84. },
  85. getDownloadUrl: function(csvData) {
  86. var _utf = "\uFEFF"; // 为了使Excel以utf-8的编码模式,同时也是解决中文乱码的问题
  87. if (window.Blob && window.URL && window.URL.createObjectURL) {
  88. var csvData = new Blob([_utf + csvData], {
  89. type: 'text/csv'
  90. });
  91. return URL.createObjectURL(csvData);
  92. }
  93. // return 'data:attachment/csv;charset=utf-8,' + _utf + encodeURIComponent(csvData);
  94. },
  95. browser: function() {
  96. var Sys = {};
  97. var ua = navigator.userAgent.toLowerCase();
  98. var s;
  99. (s = ua.indexOf('edge') !== - 1 ? Sys.edge = 'edge' : ua.match(/rv:([\d.]+)\) like gecko/)) ? Sys.ie = s[1]:
  100. (s = ua.match(/msie ([\d.]+)/)) ? Sys.ie = s[1] :
  101. (s = ua.match(/firefox\/([\d.]+)/)) ? Sys.firefox = s[1] :
  102. (s = ua.match(/chrome\/([\d.]+)/)) ? Sys.chrome = s[1] :
  103. (s = ua.match(/opera.([\d.]+)/)) ? Sys.opera = s[1] :
  104. (s = ua.match(/version\/([\d.]+).*safari/)) ? Sys.safari = s[1] : 0;
  105. return Sys;
  106. }
  107. };
  108. // 测试
  109. JSonToCSV.setDataConver({
  110. data: [
  111. {name: '张三', amont: '323433.56', proportion: 33.4},
  112. {name: '李四', amont: '545234.43', proportion: 55.45}
  113. ],
  114. fileName: 'test',
  115. columns: {
  116. title: ['姓名', '金额', '占比'],
  117. key: ['name', 'amont', 'proportion'],
  118. formatter: function(n, v) {
  119. if(n === 'amont' && !isNaN(Number(v))) {
  120. v = v + '';
  121. v = v.split('.');
  122. v[0] = v[0].replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
  123. return v.join('.');
  124. }
  125. if(n === 'proportion') return v + '%';
  126. }
  127. }
  128. });

也可以访问我的GitHub下载最新的js文件

彻底理解使用JavaScript 将Json数据导出CSV文件的更多相关文章

  1. C#将DataTable数据导出CSV文件

    C#将DataTable数据导出CSV文件通用方法! //导出按钮调用导出方法    protected void btnCSV_Click(object sender, EventArgs e)   ...

  2. JavaScript 上万条数据 导出Excel文件(改装版)

    最近项目要js实现将数据导出excel文件,网上很多插件实现~~那个开心呀,谁知道后面数据量达到上万条时出问题:浏览器不仅卡死,导出的excel文件一直提示网络失败.... debug调试发现var  ...

  3. JavaScript 上万条数据 导出Excel文件 页面卡死

    最近项目要js实现将数据导出excel文件,网上很多插件实现~~那个开心呀,谁知道后面数据量达到上万条时出问题:浏览器不仅卡死,导出的excel文件一直提示网络失败.... debug调试发现var  ...

  4. 数据库数据导出CSV文件,浏览器下载

    直接上代码: def download(request): # 从数据库查询数据 data_list = Info.objects.all() # 定义返回对象 response = HttpResp ...

  5. DataTable数据导出CSV文件

    public static void SaveAsExcel(DataTable dt1) { //System.Windows.Forms.SaveFileDialog sfd = new Syst ...

  6. l如何把SQLServer表数据导出CSV文件,并带列名

    http://jingyan.baidu.com/article/4b07be3c466b5d48b280f37f.html 微信公众号:

  7. JS 在页面上直接将json数据导出到excel,支持chrome,edge,IE10+,IE9,IE8,Safari,Firefox

    JS 在页面上直接将json数据导出到excel,支持chrome,edge,IE10+,IE9,IE8,Safari,Firefox <html> <head> </h ...

  8. 第一百二十七节,JavaScript,JSON数据类型转换,数据转换成字符串,字符串转换成数据

    第一百二十七节,JavaScript,JSON数据类型转换,数据转换成字符串,字符串转换成数据 学习要点: 1.JSON语法 2.解析和序列化 前两章我们探讨了XML的结构化数据,但开发人员还是觉得这 ...

  9. javascript导出csv文件(excel)

    这里贴出JavaScript导出csv文件(excel)的代码. /** * 导出excel * @param {Object} title 标题列key-val * @param {Object} ...

随机推荐

  1. RedBlack-Tree(红黑树)原理及C++代码实现

    众所周知,红黑树是用途很广的平衡二叉搜索树,用过的都说好.所以我们来看看红黑树的是怎么实现的吧. 红黑树顾名思义,通过红与黑两种颜色来给每个节点上色.其中根结点和叶子结点一定是黑色的,并且红色结点的两 ...

  2. MySql 按日期条件查询数据

    本周内: select * from wap_content where week(created_at) = week(now) 查询一天: select * from table where to ...

  3. mysql中datetime时间转字符串(避免java层映射为数字串)

    -- in_date datetime NULLDATE_FORMAT(ls.`in_date`,'%Y-%m-%d %T')AS create_time

  4. PAT甲级——1058 A+B in Hogwarts

    1058 A+B in Hogwarts If you are a fan of Harry Potter, you would know the world of magic has its own ...

  5. qsub|pasta|

    cd /xxx/genome_stat/Annotation ln -s /xxx/02.annotation/gff_v2/*.homolog.v2.gff /xxx/genome_stat/Ann ...

  6. [LC] 46. Permutations

    Given a collection of distinct integers, return all possible permutations. Example: Input: [1,2,3] O ...

  7. php配置memcached的扩展。

    (一)安装memcached服务器 1根据系统下载相应版本的memcached服务器版本:如win7(64位=====>memcached-win64/memcached.exe 2.解压到目录 ...

  8. generate的使用verilog

    根据项目设计的需要,要实例化多个类似的模块,这些类似的模块包括方波波形发生器,这几个模块基本相同,除了参数传递值不同,其他他部分都是相同的 具体实现代码如下: 此外有计数模块的例化,这个模块例化多个的 ...

  9. [LC] 485. Max Consecutive Ones

    Given a binary array, find the maximum number of consecutive 1s in this array. Example 1: Input: [1, ...

  10. [LC] 129. Sum Root to Leaf Numbers

    Given a binary tree containing digits from 0-9 only, each root-to-leaf path could represent a number ...