Web端導出CSV


  前端導出文件大部分還是通過服務器端的方式生成文件,然后傳遞到客戶端。但很多情況下當我們導出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);
        }
      }

  如果您覺得這篇文章對您有幫助,請不吝點擊下方推薦,您的鼓勵是我分享的動力!

參考資料:

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM