碎碎念--关于excel文件实现预览

  • Excel 文件解析使用 SheetJS

    SheetJS 对应的 npm 包名为:xlsx,安装命令:

    npm install xlsx
    
    //最新版本的安装
    npm i --save https://cdn.sheetjs.com/xlsx-0.19.2/xlsx-0.19.2.tgz
  • 表格渲染采用 canvas-datagrid

    2.
    封装了一个下载文件并转为 ArrayBuffer 实例的工具方法
     

    /**
     * 获取网络文件buffer
     * @param {String} fileUrl 文件完整路径
     * @param {Function} onDownloadProgress 下载进度回调
     * @returns {Promise<Buffer>} data
     */
    export const getFileBuffer = (fileUrl, onDownloadProgress) => {
      return new Promise((resolve, reject) => {
        axios
          .get(fileUrl, {
            withCredentials: true,
            responseType: "arraybuffer",
            timeout: 1000 * 60 * 60,
            onDownloadProgress: onDownloadProgress ? onDownloadProgress : null
          })
          .then(res => {
            resolve(res.data);
          })
          .catch(reject);
      });
    };

    注意的是:

  • 配置参数 responseType 需要指定为 "arraybuffer",这样返回的数据才是 ArrayBuffer 实例;
  • 配置参数 timeout 表示请求超时时间,这里指定为 1 小时,太短的话很容易出现下载大文件时超出默认超时时间配置,导致文件的下载被取消
  • 配置参数 onDownloadProgress 为文件下载进度回调函数,用于通知文件下载进度。

     
  • 解析 excel 文件
     

    将前面下载得到的 ArrayBuffer 数据解析为 SheetJS 的 workbook 对象

    import * as xlsx from "xlsx";
    const workbook = xlsx.read(buffer);

    取 sheet 对象转换为 JSON 格式数据

    sheet 表示 Excel 文件中的一张表,一个文件可以包含多张表


     

    import * as xlsx from "xlsx";
    const sheet = workbook.Sheets[wb.SheetNames[0]]; // 这里取第 0 个 sheet
    const json = xlsx.utils.sheet_to_json(sheet); // 得到的 json 是解析之后的数据


     

    渲染表格到页面

    使用 canvas-datagrid 初始化 grid 对象

    import canvasDatagrid from "canvas-datagrid";
    const grid = canvasDatagrid({
      parentNode: el, // el 是 document 中的一个 DOM 元素
      data: json, // json 是前面解析得到 sheet 对应的数据
      editable: false, // 表示不使用表格编辑
      // ... // 下面是对表格的一些配置项
    });

生成 grid 对象之后,需要设置表格渲染所用的 canvas 的尺寸

grid.style.width = "100%"; // 宽度为视口宽度
grid.style.height = "calc(100vh - 40px)"; // 视口高度减去顶部的 sheet 切换按钮区高度

切换表格 sheet

当切换 sheet 时取对应 sheet 的数据进行解析,然后更新 grid 的数据就可以更新表格中显示的数据。

const sheetIndex = 1;
const sheet = workbook.Sheets[wb.SheetNames[sheetIndex]]; // 这里取第 1 个 sheet
const json = xlsx.utils.sheet_to_json(sheet);
grid.data = json

 

难点解决

在进行上面具体实现过程中遇到了一些难题,下面将依次进行描述。

1. 大文件预览时页面卡顿分析

现象是:当 Excel 文件很大(十几 Mb)时,页面经常卡住不动,有时候表格还不能正常显示。

卡顿原因

  1. JS 的主线程被占用导致其他页面交互无响应

    具体原因有:

    A. 解析 Excel 文件长时间占用主线程

    SeetJS 默认使用 JS 主线程解析文件

    B. 解析后的 JSON 数据的遍历处理长时间占用主线程

    若将 workbook、sheet、grid 等挂载到了 vue 的 data 上会出现,因为 vue 响应式数据会递归遍历这些对象生成 __ob__ 来跟踪这些数据的变化。

  2. 页面生成的 DOM 元素太多

    如果表格渲染使用 handsontable 会遇到,当展示的 sheet 的记录条数很多时 JS 会遍历生成大量 HTML 字符串然后更新到挂载的 parentNode 元素节点下。

    这里大量的 HTML 写入的过程占用 CPU、大量的 HTML 渲染展示占用内存,所以会卡顿或表格显示失败。

  3. 页面生成的 canvas 的尺寸太大浏览器不能显示

    canvas-datagrid 默认会根据要渲染的记录条数生成相应大小的 canvas 来进行绘图,canvas 尺寸过大时会绘图失败。

    不能显示的原因暂时还未找到解释,知道的读者可以留言解答下 :) 。

解决方法

  1. 使用 worker 线程进行文件解析、数据遍历

    涉及文件的解析、数据的遍历过程可以使用 web worker 创建一个新线程来处理。

    不过,可能在不同 web 环境(不同浏览器内核、小程序、APP 的不同 webview)下存在兼容性问题,本文的实现没有采用这种方式。

  2. 不对解析后数据进行响应式处理

    即与 Excel 文件解析、渲染相关的所有对象都不要直接关联到 vue 的响应式数据上,必须用到时可以按需取用并单独存储

  3. 主线程占用期间用加载中占位避免有页面交互行为

    最简单直接,也是本文 demo 采用的方法。在进行文件解析、sheet 数据转换、大数据遍历处理的过程中统一在页面添加全屏的加载中提示,阻止用户的交互行为。

    但是,之前存在的定时器、进行中的接口请求、下载中的图片请求会怎么样到是没有验证,若读者感兴趣可自行跟踪验证下。

  4. 使用虚拟滚动避免一次性渲染大量 DOM、绘制很大的 canvas

    在页面滚动时计算出当前需要显示的数据,只生成当前用户窗口内可见数据对应的 DOM 或者 canvas。

    这是 canvas-datagrid、handsontable 处理大数据渲染的基本原理,感兴趣的读者可以去看看其实现。

    本文的 demo 是通过配置一个相对较小的渲染尺寸给到 canvas-datagrid 来解决的。前文的这段代码至关重要:

    grid.style.width = "100%"; // 宽度为视口宽度
    grid.style.height = "calc(100vh - 40px)"; // 视口高度减去顶部的 sheet 切换按钮区高度
    复制代码

总结

本文实现了一个可以进行常规 Excel 文件的在线预览功能,完全不需要后端任何服务,可以为前端同学开发 Excel 编辑软件提供一种比较可靠的思路。

实现的预览功能在浏览器、小程序、安卓 APP 中进行测试通过,能正常渲染。但是,并未没有提供编辑、嵌套表格展示、单元格样式展示等高级功能,并不代表开源框架 canvas-datagridhandsontable 不能实现。

如果有时间精力的话,完全可以使用本文介绍的开源框架实现一个完整版的 Excel 软件。

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值