完全理解使用JavaScript 將Json數據導出CSV文件

前言

將數據報表導出,是web數據報告展現經常使用的附帶功能。一般這種功能都是用後端開發人員編寫的。今天咱們主要講的是直接經過前端js將數據導出Excel的CSV格式的文件。前端

原理

首先在本地用Excel新建一個test.csv的文件 ===> 隨便填寫一些數據,保存並用Safari瀏覽打開該文件 ===> 打開瀏覽器的開發者工具,執行JSON.stringify(document.body.innerText);,咱們獲得結果以下圖:
圖片描述git

從圖中,能夠看出:github

  • CSV文件格式單元格之間是經過,隔開的
  • CSV文件格式裏,換行是經過\n實現的

從上面兩條結論,咱們只有把相應的數據轉換成,\n就能夠了。但其實真正的答案應該是把相應的數據轉換成,\r\n
爲何會這樣?且讓我一一道來:
咱們在編輯Excel文件時,當編輯完成當前單元格時,想要編輯下一行緊挨着的單元格,按一下Enter鍵就能夠。而Enter鍵在js字符串中是用\r表示的。那是否是吧\n替換成\r就能夠了呢?
其實不能夠,由於涉及到操做系統的問題:web

  • 在Windows系統中,標準模式採用的是\r\n匹配Enter
  • 在mac系統中,用\r匹配Enter
  • 在Linux系統中,用\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%

那如何使得導出的數據與展現的保持一致呢?
答案是:瀏覽器

  • 把要展現的表頭文字也進行處理
  • 遍歷取對應的key值
  • 設置formatter回調處理的當前值的函數

由此咱們獲得以下代碼: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值,而後調用aclick方法便可下載,IE既不支持adownload屬性也不容許調用aclick方法。代碼以下:

var a = document.querySelector('a');
a.click(); // 在這裏 IE是拒絕執行的,會提示權限問題

那麼對於支持a的download屬性的,直接設置download屬性值和href值,具體代碼以下:

Chrome、Firefox等瀏覽器的的下載方式

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
這樣無論幾萬條仍是幾十萬條數據均可如下載的
這裏涉及到的知識點:encodeURIComponentURL.createObjectURL
到這裏,Chrome、Firefox等瀏覽器解決了。

IE10~Edge瀏覽的下載方式

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下載方式

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文件

相關文章
相關標籤/搜索