摘要:前言將數(shù)據(jù)報表導(dǎo)出,是數(shù)據(jù)報告展示常用的附帶功能。今天我們主要講的是直接通過前端將數(shù)據(jù)導(dǎo)出的格式的文件。但其實真正的答案應(yīng)該是把相應(yīng)的數(shù)據(jù)轉(zhuǎn)換成和。若是超過瀏覽器自身限制的最大長度,會導(dǎo)致下載失敗。
前言
將數(shù)據(jù)報表導(dǎo)出,是web數(shù)據(jù)報告展示常用的附帶功能。通常這種功能都是用后端開發(fā)人員編寫的。今天我們主要講的是直接通過前端js將數(shù)據(jù)導(dǎo)出Excel的CSV格式的文件。
原理首先在本地用Excel新建一個test.csv的文件 ===> 隨便填寫一些數(shù)據(jù),保存并用Safari瀏覽打開該文件 ===> 打開瀏覽器的開發(fā)者工具,執(zhí)行JSON.stringify(document.body.innerText);,我們得到結(jié)果如下圖:
從圖中,可以看出:
CSV文件格式單元格之間是通過,隔開的
CSV文件格式里,換行是通過 實現(xiàn)的
從上面兩條結(jié)論,我們只有把相應(yīng)的數(shù)據(jù)轉(zhuǎn)換成,和
就可以了。但其實真正的答案應(yīng)該是把相應(yīng)的數(shù)據(jù)轉(zhuǎn)換成,和
。
為什么會這樣?且讓我一一道來:
我們在編輯Excel文件時,當(dāng)編輯完成當(dāng)前單元格時,想要編輯下一行緊挨著的單元格,按一下Enter鍵就可以。而Enter鍵在js字符串中是用
表示的。那是不是吧
替換成
就可以了呢?
其實不可以,因為涉及到操作系統(tǒng)的問題:
在Windows系統(tǒng)中,標(biāo)準(zhǔn)模式采用的是 匹配Enter鍵
在mac系統(tǒng)中,用 匹配Enter鍵
在Linux系統(tǒng)中,用 匹配Enter鍵
所以,最最最最終終終的結(jié)論是:
將相應(yīng)的數(shù)據(jù)轉(zhuǎn)換成,和 ,即:名稱,熟練 張三,2
由于單元格之間使用,隔開,所以不支持單元格的合并行、合并列,其實這句話有點多余,CSV格式的文件本身就不支持單元格的合并列和行
實現(xiàn)方式在編寫代碼之前,我們先來看一下具體數(shù)據(jù)和樣式。假如當(dāng)前的JSON數(shù)據(jù)是這樣的
[ {name: "張三", amont: "323433.56", proportion: 33.4}, {name: "李四", amont: "545234.43", proportion: 55.45} ]
數(shù)據(jù)報告展示樣式如下:
姓名 | 金額 | 占比 |
---|---|---|
張三 | 323,433.56 | 33.40% |
李四 | 545,234.43 | 55.45% |
那如何使得導(dǎo)出的數(shù)據(jù)與展示的保持一致呢?
答案是:
把要展示的表頭文字也進(jìn)行處理
遍歷取對應(yīng)的key值
設(shè)置formatter回調(diào)處理的當(dāng)前值的函數(shù)
由此我們得到如下代碼:
var JSonToCSV = { /* * obj是一個對象,其中包含有: * ## data 是導(dǎo)出的具體數(shù)據(jù) * ## fileName 是導(dǎo)出時保存的文件名稱 是string格式 * ## showLabel 表示是否顯示表頭 默認(rèn)顯示 是布爾格式 * ## columns 是表頭對象,且title和key必須一一對應(yīng),包含有 title:[], // 表頭展示的文字 key:[], // 獲取數(shù)據(jù)的Key formatter: function() // 自定義設(shè)置當(dāng)前數(shù)據(jù)的 傳入(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; // 如果要現(xiàn)實表頭文字 if (ShowLabel) { // 如果有傳入自定義的表頭文字 if (columns.title.length) { columns.title.map(function(n) { row += n + ","; }); } else { // 如果沒有,就直接取數(shù)據(jù)第一條的對象的屬性 for (key in data[0]) row += key + ","; } row = row.slice(0, -1); // 刪除最后一個,號,即a,b, => a,b CSV += row + " "; // 添加換行符號 } // 具體的數(shù)據(jù)處理 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 + " "; // 添加換行符號 }); if(!CSV) return; this.SaveAs(fileName, CSV); }, SaveAs: function(fileName, csvData) { // console.log(fileName, csvData); } };
然后我們分別測試了如下數(shù)據(jù):
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,"); // 千分位的設(shè)置 return v.join("."); } if(n === "proportion") return v + "%"; } } });
到此,數(shù)據(jù)轉(zhuǎn)換完畢
下載方式由于瀏覽器之間的差異,尤其是IE,所以不同的瀏覽器下載的方式也不一樣,如Chrome和Firefox都支持a標(biāo)簽設(shè)置download屬性和href值,然后調(diào)用a的click方法即可下載,IE既不支持adownload屬性也不允許調(diào)用a的click方法。代碼如下:
var a = document.querySelector("a"); a.click(); // 在這里 IE是拒絕執(zhí)行的,會提示權(quán)限問題
那么對于支持a的download屬性的,直接設(shè)置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; }
雖然看起來是可以了,但還是有問題。什么問題呢?
就是當(dāng)數(shù)據(jù)量大的時候,比如幾千條甚至幾萬條,在數(shù)據(jù)轉(zhuǎn)換的時候,href的數(shù)值自然也就長了。若是超過瀏覽器自身限制的最大長度,會導(dǎo)致下載失敗。具體每個瀏覽器之前URL最大長度限制如下(HTTP協(xié)議并沒有限制URL的長度):
瀏覽器 | 最大長度(字符數(shù)) | 備注 |
---|---|---|
IE | 2083 | 如果超過這個數(shù)字,提交按鈕沒有任何反應(yīng) |
Firefox | 65,536 | - |
Chrome | 8,182 | - |
Safari | 80,000 | - |
Opera | 190,000 | - |
所以我們這里借助 Blob(Blob傳送門)來將轉(zhuǎn)換好的數(shù)據(jù)進(jìn)行處理,代碼如下:
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。
這樣不管幾萬條還是幾十萬條數(shù)據(jù)都可以下載的
這里涉及到的知識點:encodeURIComponent、URL.createObjectURL
到這里,Chrome、Firefox等瀏覽器解決了。
IE10~Edge等瀏覽器調(diào)用windows.navigator.msSaveBlob實現(xiàn)保存文件,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=, " + csvData); oWin.document.close(); oWin.document.execCommand("SaveAs", true, fileName); oWin.close(); } }
所以最終代碼整體如下(也可以訪問我的GitHub下載最新的js文件):
var JSonToCSV = { /* * obj是一個對象,其中包含有: * ## data 是導(dǎo)出的具體數(shù)據(jù) * ## fileName 是導(dǎo)出時保存的文件名稱 是string格式 * ## showLabel 表示是否顯示表頭 默認(rèn)顯示 是布爾格式 * ## columns 是表頭對象,且title和key必須一一對應(yīng),包含有 title:[], // 表頭展示的文字 key:[], // 獲取數(shù)據(jù)的Key formatter: function() // 自定義設(shè)置當(dāng)前數(shù)據(jù)的 傳入(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; // 如果要現(xiàn)實表頭文字 if (ShowLabel) { // 如果有傳入自定義的表頭文字 if (columns.title.length) { columns.title.map(function(n) { row += n + ","; }); } else { // 如果沒有,就直接取數(shù)據(jù)第一條的對象的屬性 for (key in data[0]) row += key + ","; } row = row.slice(0, -1); // 刪除最后一個,號,即a,b, => a,b CSV += row + " "; // 添加換行符號 } // 具體的數(shù)據(jù)處理 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 + " "; // 添加換行符號 }); 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=, " + 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文件
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/88758.html
摘要:前言將數(shù)據(jù)報表導(dǎo)出,是數(shù)據(jù)報告展示常用的附帶功能。今天我們主要講的是直接通過前端將數(shù)據(jù)導(dǎo)出的格式的文件。但其實真正的答案應(yīng)該是把相應(yīng)的數(shù)據(jù)轉(zhuǎn)換成和。若是超過瀏覽器自身限制的最大長度,會導(dǎo)致下載失敗。 前言 將數(shù)據(jù)報表導(dǎo)出,是web數(shù)據(jù)報告展示常用的附帶功能。通常這種功能都是用后端開發(fā)人員編寫的。今天我們主要講的是直接通過前端js將數(shù)據(jù)導(dǎo)出Excel的CSV格式的文件。 原理 首先在本地...
摘要:舉個例子,要處理的文件或者文件是以作為分隔符的,每行有這么三個數(shù)據(jù)域,那么首先我們需要在數(shù)據(jù)庫中創(chuàng)建這個表創(chuàng)建成功以后就可以導(dǎo)入了。 文/freenik 將外部數(shù)據(jù)導(dǎo)入(import)數(shù)據(jù)庫是在數(shù)據(jù)庫應(yīng)用中一個很常見的需求。其實這就是在數(shù)據(jù)的管理和操作中的ETL (Extract, transform, load)的L (Load)部分,也就是說,將特定結(jié)構(gòu)(structure)或者格...
閱讀 1164·2021-09-10 10:51
閱讀 896·2019-08-30 15:53
閱讀 2724·2019-08-30 12:50
閱讀 976·2019-08-30 11:07
閱讀 1990·2019-08-30 10:50
閱讀 3598·2019-08-29 18:47
閱讀 1308·2019-08-29 18:44
閱讀 1599·2019-08-29 17:01