前端導出文件大部分還是通過服務器端的方式生成文件,然后傳遞到客戶端。但很多情況下當我們導出CSV時並不需要后端參與,甚至沒有后端。
做過WebGIS的同學經常會碰到這種場景,用戶的興趣點數據以csv文件形式上傳到web應用中以表格形式展示,並可以編輯屬性信息,編輯完成后需要將數據下載到本地。特別是對一些敏感數據,用戶不希望傳遞到應用服務器端,整個過程完全在客戶端進行。
上傳過程我們暫且不討論,只討論生成CSV以及下載過程。
CSV的生成
問題一:如何分行分列?
思路:分行使用“\n”,分列使用","
var str = "col1,col2,col3\nvalue1,value2,value3";
實際應用中發現導出的csv用excel打開后,列可以分開但行無法分開。
解決方法是,將生成的csv字符串使用encodeURIComponent編碼;但是IE8/9中不能使用encodeURIComponent,而是:'sep=,\r\n' + str;
str = encodeURIComponent(str);
問題二:字段值中含有特殊符號影響csv文件的正確解讀,如:“,”,"\n"
思路:將含有特殊符號的字段用雙引號包裝起來,如:a,b => "a,b"
var textField = '"'; if (value && /[,\r\n]/g.test(value)) { value = textField + value + textField; }
實際應用發現少考慮了一種情況,如果字段值中含有‘ " ’這個符號,經過上方代碼處理反而會出現問題:a"b => "a"b"。顯然是語法錯誤。
解決方法是將"換成"",a"b => "a""b"
var textField = '"'; if (value && /[",\r\n]/g.test(value)) { value = textField + value.replace(/(")/g, '""') + textField; }
在解決以上問題后生成CSV字符串代碼如下
//data: 數據數組,每個元素都包含_outFields中指定的字段名 //_outFields: 字段名稱數組 exports.createCSVStr = function(data, _outFields) { var textField = '"'; var content = ""; var len = 0, n = 0, comma = "", value = ""; try { array.forEach(_outFields, function(_field) { content = content + comma + _field; comma = ","; }); content = content + "\r\n"; len = data.length; n = _outFields.length; for (var i = 0; i < len; i++) { comma = ""; for (var m = 0; m < n; m++) { var _field = _outFields[m]; value = data[i][_field]; if (!value && typeof value !== "number") { value = ""; } if (value && /[",\r\n]/g.test(value)) { value = textField + value.replace(/(")/g, '""') + textField; } content = content + comma + value; comma = ","; } content = content + "\r\n"; } } catch (err) { console.error(err); content = ""; } return content; };
問題三:如果字段中含有希伯來文、法語、德語等文字('éà; ça; 12\nà@€; çï; 13'
),導出的csv文件在Excel中打開后,這些文字呈現出亂碼
解決方法:嚴格來說這並不是csv文件的問題,而是Excel處理文件編碼方式問題,Excel默認並不是以UTF-8來打開文件,所以在csv開頭加入BOM,告訴Excel文件使用utf-8的編碼方式。
var BOM = "\uFEFF"; var csvStr = BOM + csvStr;
實際應用中發現,這種處理方式在windows中的Excel中打開后可以正常顯示,但在mac上的Excel無法正確顯示。目前沒有完全的解決方案,但mac中可以使用自帶的Numbers軟件打開,不會出現亂碼問題。
CSV的下載方式
問題一:如何在解決不同瀏覽器中的下載問題?
思路:
- IE10以下,利用execCommand方法來保存csv文件
var oWin = window.top.open("about:blank", "_blank"); oWin.document.write('sep=,\r\n' + text); oWin.document.close(); oWin.document.execCommand('SaveAs', true, filename); oWin.close();
在實際應用中瀏覽器會打開一個新窗口,並彈出保存文件對話框,而對話框中保存類型時,只有html和text兩項可選,此時需要在文件名中手動加上“.csv”后綴
- IE10以及Edge瀏覽器使用navigator.msSaveBlob(blob);雖然這些瀏覽器也支持上面的方法,但可以避免上面遇到的問題。
var BOM = "\uFEFF"; var csvData = new Blob([BOM + text], { type: 'text/csv' }); navigator.msSaveBlob(csvData, filename);
msSaveBlob是IE的私有方法,只有IE10及以上和Edge瀏覽器支持。
- Firefox、Chrome、Safari瀏覽器中使用a標簽,利用html5中增加的download屬性來下載csv
var link = html.create("a", { href: 'data:attachment/csv;charset=utf-8,' + BOM + encodeURIComponent(text), target: '_blank', download: filename }, this.domNode); if (has('safari')) { // # First create an event var click_ev = document.createEvent("MouseEvents"); // # initialize the event click_ev.initEvent("click", true /* bubble */ , true /* cancelable */ ); // # trigger the evevnt/ link.dispatchEvent(click_ev); } else { link.click(); }
Safari中並不支持除了input外的元素直接調用click方法,所以我們利用自定義事件,模擬用戶點擊來下載文件。實際應用中發現,如果csv字符串太大,以上方式在下載csv時會導致瀏覽器崩潰。解決的方法是利用URL.createObjectURL(blob)創建一個連接給a標簽。
_getDownloadUrl: function(text) { var BOM = "\uFEFF"; // Add BOM to text for open in excel correctly if (window.Blob && window.URL && window.URL.createObjectURL) { var csvData = new Blob([BOM + text], { type: 'text/csv' }); return URL.createObjectURL(csvData); } else { return 'data:attachment/csv;charset=utf-8,' + BOM + encodeURIComponent(text); } },
綜合上述方式,下載csv文件的代碼如下
_isIE11: function() { var iev = 0; var ieold = (/MSIE (\d+\.\d+);/.test(navigator.userAgent)); var trident = !!navigator.userAgent.match(/Trident\/7.0/); var rv = navigator.userAgent.indexOf("rv:11.0"); if (ieold) { iev = Number(RegExp.$1); } if (navigator.appVersion.indexOf("MSIE 10") !== -1) { iev = 10; } if (trident && rv !== -1) { iev = 11; } return iev === 11; }, _isEdge: function() { return /Edge\/12/.test(navigator.userAgent); }, _getDownloadUrl: function(text) { var BOM = "\uFEFF"; // Add BOM to text for open in excel correctly if (window.Blob && window.URL && window.URL.createObjectURL) { var csvData = new Blob([BOM + text], { type: 'text/csv' }); return URL.createObjectURL(csvData); } else { return 'data:attachment/csv;charset=utf-8,' + BOM + encodeURIComponent(text); } }, download: function(filename, text) { if (has('ie') && has('ie') < 10) { // has module unable identify ie11 and Edge var oWin = window.top.open("about:blank", "_blank"); oWin.document.write('sep=,\r\n' + text); oWin.document.close(); oWin.document.execCommand('SaveAs', true, filename); oWin.close(); }else if (has("ie") === 10 || this._isIE11() || this._isEdge()) { var BOM = "\uFEFF"; var csvData = new Blob([BOM + text], { type: 'text/csv' }); navigator.msSaveBlob(csvData, filename); } else { var link = html.create("a", { href: this._getDownloadUrl(text), target: '_blank', download: filename }, this.domNode); if (has('safari')) { // # First create an event var click_ev = document.createEvent("MouseEvents"); // # initialize the event click_ev.initEvent("click", true /* bubble */ , true /* cancelable */ ); // # trigger the evevnt/ link.dispatchEvent(click_ev); } else { link.click(); } html.destroy(link); } }
如果您覺得這篇文章對您有幫助,請不吝點擊下方推薦,您的鼓勵是我分享的動力!
參考資料: