首发个人博客 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带来的优秀体验,咱们的开发成本也能降得更低。