4

 

最近业务上有数据大屏的需求,要求不仅能展示数据,同时能提供所选日期范围的数据下载。本文纯记录实现方案作为笔记,实现细节十分不完备。

工具库

xlsx电子表格的标准规范详见:Full XML Schema。下面两个库都基于这个规范实现了这类格式文件的读写(salute!∠(°ゝ°))。

SheetJS

SheetJS是用于多种电子表格格式的解析器和编写器。通过官方规范、相关文档以及测试文件实现简洁的JS方法。SheetJS强调解析和编写的稳健,其跨格式的特点和统一的JS规范兼容,并且ES3/ES5浏览器向后兼容IE6。

excelize

Go语言编写的可以读写电子表格类型文件的公共库

更静默:webworker

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。

我们将SheetJS处理数据、生成表格数据(book类型数据)的流程另起一个线程实现。(虽然另起一个线程从体验上不会过度影响主UI线程,但本身启动成本比较高)。

该组件的目录如下

NewDashboard
├── components
│   ├── LongCard
│   │   ├── echartsOption.ts
│   │   ├── index.tsx
│   │   └── style.module.less
│   └── ShortCard
│       ├── echartsOption.ts
│       ├── index.tsx
│       └── style.module.less
├── index.tsx                        # 在该文件与webworker通信
├── makeTable.ts                     # 在该文件实现webworker
└── style.module.less

mdn给的samples的worker都是加载外部代码的。在我们这种组织目录下,worker应该在一个文件内实现,并导出一个worker实例。这里需要借助URL.createObjectURL(blob)构造一个外链。

代码如下:

// @file makeTable.ts
const blob = new Blob(
  [
    `
    importScripts('https://g.alicdn.com/code/lib/xlsx/0.17.4/xlsx.full.min.js');
    const GOODS_EFFECT_TITLE = [
      '开播时间',
      '下播时间',
      '直播间',
      '商品名称',
      '商品',
      '点击人数',
      '成交人数',
      '粉丝成交比例',
      '引导成交金额',
    ];
    
    // 接收主进程的表格数据
    onmessage = function({ data }) {
      console.log('from main routine', data);
      const book = XLSX.utils.book_new();
      const sheet = XLSX.utils.aoa_to_sheet([GOODS_EFFECT_TITLE, ...data]);
      XLSX.utils.book_append_sheet(book, sheet, '工作表1');
      
      // book的数据回传给主进程
      postMessage({ book });
    };
`,
  ],
  { type: 'text/javascript' },
);

export const worker = new Worker(URL.createObjectURL(blob));

注意几个点:

  1. 由于在worker内没有DOM、windows等对象,所以没有办法直接使用 XLSX.utils.table_to_book 方法将table元素直接导出为xlsx表格数据。
  2. importScript 方法是并行加载所有列出的资源,但执行是同步的。这里需要将SheetJS的资源加载进worker里。
  3. 主进程的方法:
  // @file index.tsx
import { worker } from './makeTable';

function download() {
   // aoa_to_sheet 方法需要一个二维数组来形成电子表格
    worker.postMessage([[1, 2, 3]]);
    worker.onmessage = ({ data }) => {
      window.XLSX.writeFile(data.book, '测试.xlsx');
    };
  }

更高速:WebAssembly

对于网络平台而言,WebAssembly具有巨大的意义——它提供了一条途径,以使得以各种语言编写的代码都可以以接近原生的速度在Web中运行。在这种情况下,以前无法以此方式运行的客户端软件都将可以运行在Web中。

我们使用Go语言编译为wasm文件,核心代码如下:

// wasm.go
func main() {
    c := make(chan struct{}, 0)
  // js全局方法makeExcel
    js.Global().Set("makeExcel", js.FuncOf(jsMakeExcel))
  // 确保Go程序不退出
    <-c 
}

func makeExcel() []uint8 {
    f := excelize.NewFile()
    f.SetCellValue("Sheet1", "开播时间", now.Format(time.ANSIC))
    f.SetCellValue("Sheet1", "直播间", 1111)
  // 在js环境中无法实现文件的操作
    // if err := f.SaveAs("simple.xlsx"); err != nil {
    //     log.Fatal((err))
    // }
    buf, _ := f.WriteToBuffer()
    res := make([]uint8, buf.Len())
    buf.Read(res)
    return res
}

func jsMakeExcel(arg1 js.Value, arg2 []js.Value) interface{} {
    buf := makeExcel()
    js_uint := js.Global().Get("Uint8Array").New(len(buf))
    js.CopyBytesToJS(js_uint, buf)
  //go的uint8无法直接回传,需要创建js环境的Uint8Array类型数据并回传
    return js_uint
}

将编译好的wasm文件加载进js环境

  1. 引入桥接代码:https://github.com/golang/go/...。此时window下会有一个全局构造函数:Go
  2. 样板代码——实例化webassembly:
// WebAssembly.instantiateStreaming is not currently available in Safari 
if (WebAssembly && !WebAssembly.instantiateStreaming) {
      // polyfill
      WebAssembly.instantiateStreaming = async (resp, importObject) => {
        const source = await (await resp).arrayBuffer();
        return await WebAssembly.instantiate(source, importObject);
      };
    }

    const go = new Go();

    fetch('path/to/wasm.wasm')
      .then((response) => response.arrayBuffer())
      .then((bytes) => WebAssembly.instantiate(bytes, go.importObject))
      .then((res) => go.run(res.instance))
  1. 实现文件下载
function download() {
   // 与普通方法一样调用go写入全局的方法,拿到刚刚回传的uint8array数据
         const buf = makeExcel();
   // 创建下载链接,注意文件类型,并下载文件
    const blob = new Blob([buf], {
      type: 'application/vnd.ms-excel',
    });
    const url = URL.createObjectURL(blob);
    console.log({ blob, str });
    const a = document.createElement('a');
    a.download = 'test.xlsx';
    a.href = url;
    a.click();
}

既要又要

webworker和webassembly是可以一起使用的,待补充……


Marckon
1k 声望169 粉丝

正在成长的小前端...