將數據報表導出,是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文件