首發個人博客 http://blog.meathill.com/tech/js/export-table-data-into-a-excel-file.htmlhtml
最近接到這麼個需求,要把<table>
顯示的數據導出成Excel表。相似的需求並不稀罕,過去我一般用PHP輸出.csv文件,不過此次彷佛不能這麼作:數據源表格容許用戶篩選和排序,與原始數據表有區別,而傳遞操做又比較麻煩;另外.csv文件的功能受限嚴重,難以擴展。因此我準備嘗試下別的作法。web
Google之,發現HTML5又成了一座分水嶺。以前在IE瀏覽器下,用戶能夠利用ActiveXObject
建立Excel.application
對象來處理——固然不兼容Mac。後來Excel開放標準,能夠導出xml格式的文件,dataURI
就有了用武之地,導出<table>
數據並保存爲Excel有了更好的選擇。瀏覽器
(如下內容與StackOverflow中的答案有重合,那個3條贊同的我認爲是最佳答案,惋惜我無法頂他……)app
圖省事兒的也能夠直接使用個人模板(這一段我使用了Handlebars,以便未來填充數據)dom
template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">\ <head><!--[if gte mso 9]>\ <xml>\ <x:ExcelWorkbook>\ <x:ExcelWorksheets>\ <x:ExcelWorksheet>\ <x:Name>{{worksheet}}</x:Name>\ <x:WorksheetOptions>\ <x:DisplayGridlines/>\ </x:WorksheetOptions>\ </x:ExcelWorksheet>\ </x:ExcelWorksheets>\ </x:ExcelWorkbook>\ </xml><![endif]-->\ </head>\ <body>\ {{#each tables}}<table>{{{this}}}</table>{{/each}}\ </body>\ </html>';
複製數據比較簡單了。如前面模版所示,這裏我很野蠻的直接複製thead
和tbody
的所有代碼,填充內容。固然爲了體現用戶操做,我只複製顯示的tr
。這裏須要注意的是,jQuery判斷一個dom是否處於顯示狀體基於如下3點:函數
因此,不能先clone()
再find(':hidden').remove()
,由於沒添加到主Dom樹的節點寬高都是0,也就會被認爲還沒顯示,這下就都幹掉了。this
套用模版以後,咱們就有了完整的表格數據。接下來,咱們須要把其轉換成base64格式,以便套用dataURI
輸出。因而便要使用btoa
這個函數(將二進制數據轉換成base64格式的字符串,HTML5的大禮之一,操做二進制的API),不過注意,這個函數不能直接轉換普通unicode字符,否則大多數瀏覽器都會拋出異常。因此須要先通過兩步轉換:excel
function base64(string) { return window.btoa(unescape(encodeURIComponent(string))); }
(MDN中還推薦了另一種作法,經過Typed Array
作中介,我沒有實操,有興趣的能夠試下)code
而後配上base64頭和mime類型,就能夠觸發下載了:xml
var uri = 'data:application/vnd.ms-excel;base64,'; location.href = uri + base64(template(tables));
貌似到這裏就完成了,不過做爲一名掛職產品總監的碼農,我很難容忍下載的文件文件名是「下載」,並且尚未擴展名(Windows 8下沒有;Windows 7 和 Mac下會有.xls的擴展名,我認爲和已裝軟件註冊過的mime類型有關)。
這是個用在內部管理後臺的需求,我以前曾要求你們必須使用Chrome訪問後臺;並且我知道,Chrome已經支持<a>
裏的download
屬性。那麼這就好辦了,由於onclick
事件會先於系統默認行爲觸發,因此我能夠在這個事件的處理函數中將生成的Base64放在被點擊按鈕的href
裏,並將其download
屬性設爲容易理解的「某年某月末日至某年某月某日廣告數據分析.xls」。至此,此項功能宣告圓滿。
HTML部分(使用到Bootstrap和Handlebars):
<a href="#" title="點擊下載" class="btn btn-primary export-button" download="{{start}}至{{end}}廣告數據分析.xls"><i class="icon-download-alt icon-white"></i> 導出</a>
JavaScript部分
tableToExcel: function (tableList, name) { var tables = [], uri = 'data:application/vnd.ms-excel;base64,', template = Handlebars.compile('<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{{worksheet}}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body>{{#each tables}}<table>{{{this}}}</table>{{/each}}</body></html>'); for (var i = 0; i < tableList.length; i++) { tables.push(tableList[i].innerHTML); } var data = { worksheet: name || 'Worksheet', tables: tables }; return uri + base64(template(data)); }, exportHandler: function (event) { var tables = this.$('table'), table = null; tables.each(function (i) { var t = $('<table><thead></thead><tbody></tobdy></table>'); t.find('thead').html(this.tHead.innerHTML); t.find('tbody').append($(this.tBodies).children(':visible').clone()); t.find('.not-print').remove(); // not-print 是@media print中不會打印的部分 t.find('a').replaceWith(function (i) { // 表格中再也不須要的超連接也移除了 return this.innerHTML; }); table = table ? table.add(t) : t; }); event.currentTarget.href = Dianjoy.utils.tableToExcel(table, '廣告數據'); }
說是圓滿,其實也不盡然,由於URL有2M的長度限制,遇到真正的大表仍然可能出問題(我沒實測)。
最後例行吐槽:老闆(領導)想提高工做效率,光逼員工沒啥意義,必須關注員工平常使用的軟件:不準用亂七八糟的瀏覽器,統一Chrome;360一率禁用(最近遇到N起升級Chrome Dev 30版致使各類bug的問題);所有裝Windows 8(自帶殺毒,幾乎全部外設秒配)。能作到這幾點,公司辦公效率提高1倍不止。
再多說兩句:咱們對外的後臺雖然作到了基本兼容,但若是用戶使用非Chrome訪問,仍然會建議他換用Chrome。目前Chrome訪問佔比已經上升到90%,IE678不到5%,但願不久的未來,咱們的用戶都能盡情享受HTML5帶來的優秀體驗,咱們的開發成本也能降得更低。