• 【HTML5版】导出Table数据并保存为Excel


    首发我的博客 http://blog.meathill.com/tech/js/export-table-data-into-a-excel-file.html

    最近接到这么个需求,要把<table>显示的数据导出成Excel表。类似的需求并不稀罕,过去我通常用PHP输出.csv文件,不过这次似乎不能这么做:数据源表格允许用户筛选和排序,与原始数据表有区别,而传递操作又比较麻烦;另外.csv文件的功能受限严重,难以扩展。所以我准备尝试下别的做法。

    Google之,发现HTML5又成了一座分水岭。之前在IE浏览器下,用户可以利用ActiveXObject创建Excel.application对象来处理——当然不兼容Mac。后来Excel开放标准,可以导出xml格式的文件,dataURI就有了用武之地,导出<table>数据并保存为Excel有了更好的选择。

    (以下内容与StackOverflow中的答案有重合,那个3条赞同的我认为是最佳答案,可惜我没法顶他……)

    准备工作

    1. 创建一个空白的Excel文档
    2. 另存为“XML表格”,xml格式
    3. 好了,模版搞定

    图省事儿的也可以直接使用我的模板(这一段我使用了Handlebars,以便将来填充数据)

    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>';
    

    复制表格数据

    复制数据比较简单了。如前面模版所示,这里我很野蛮的直接复制theadtbody的全部代码,填充内容。当然为了体现用户操作,我只复制显示的tr。这里需要注意的是,jQuery判断一个dom是否处于显示状体基于以下3点:

    1. display:none
    2. 表单元素,type="hidden"
    3. 宽高为0
    4. 父级以上节点不显示,自己也不会显示

    所以,不能先clone()find(':hidden').remove(),因为没添加到主Dom树的节点宽高都是0,也就会被认为还没显示,这下就都干掉了。

    输出内容

    套用模版之后,我们就有了完整的表格数据。接下来,我们需要把其转换成base64格式,以便套用dataURI输出。于是便要使用btoa这个函数(将二进制数据转换成base64格式的字符串,HTML5的大礼之一,操作二进制的API),不过注意,这个函数不能直接转换普通unicode字符,不然大多数浏览器都会抛出异常。所以需要先经过两步转换:

    function base64(string) {
      return window.btoa(unescape(encodeURIComponent(string)));
    }
    

    (MDN中还推荐了另外一种做法,通过Typed Array做中介,我没有实操,有兴趣的可以试下)

    然后配上base64头和mime类型,就可以触发下载了:

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

  • 相关阅读:
    hdu6162 Ch’s gift
    hdu 6166 Senior Pan
    bzoj2818 Gcd
    hdu6127 Hard challenge
    splay伸展树模板
    2016-2017 National Taiwan University World Final Team Selection Contest J
    2016-2017 National Taiwan University World Final Team Selection Contest C
    使用powershell管理域用户
    EGL Driver message (Critical) eglInitialize: No available renderers.
    Python实现telnet命令测试防火墙
  • 原文地址:https://www.cnblogs.com/10manongit/p/12989941.html
Copyright © 2020-2023  润新知