-
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)时,页面经常卡住不动,有时候表格还不能正常显示。
卡顿原因
-
JS 的主线程被占用导致其他页面交互无响应
具体原因有:
A. 解析 Excel 文件长时间占用主线程
SeetJS 默认使用 JS 主线程解析文件
B. 解析后的 JSON 数据的遍历处理长时间占用主线程
若将
workbook、sheet、grid
等挂载到了 vue 的 data 上会出现,因为 vue 响应式数据会递归遍历这些对象生成__ob__
来跟踪这些数据的变化。 -
页面生成的 DOM 元素太多
如果表格渲染使用
handsontable
会遇到,当展示的 sheet 的记录条数很多时 JS 会遍历生成大量 HTML 字符串然后更新到挂载的 parentNode 元素节点下。这里大量的 HTML 写入的过程占用 CPU、大量的 HTML 渲染展示占用内存,所以会卡顿或表格显示失败。
-
页面生成的 canvas 的尺寸太大浏览器不能显示
canvas-datagrid
默认会根据要渲染的记录条数生成相应大小的 canvas 来进行绘图,canvas 尺寸过大时会绘图失败。不能显示的原因暂时还未找到解释,知道的读者可以留言解答下 :) 。
解决方法
-
使用 worker 线程进行文件解析、数据遍历
涉及文件的解析、数据的遍历过程可以使用
web worker
创建一个新线程来处理。不过,可能在不同 web 环境(不同浏览器内核、小程序、APP 的不同 webview)下存在兼容性问题,本文的实现没有采用这种方式。
-
不对解析后数据进行响应式处理
即与 Excel 文件解析、渲染相关的所有对象都不要直接关联到 vue 的响应式数据上,必须用到时可以按需取用并单独存储。
-
主线程占用期间用加载中占位避免有页面交互行为
最简单直接,也是本文 demo 采用的方法。在进行文件解析、sheet 数据转换、大数据遍历处理的过程中统一在页面添加全屏的加载中提示,阻止用户的交互行为。
但是,之前存在的定时器、进行中的接口请求、下载中的图片请求会怎么样到是没有验证,若读者感兴趣可自行跟踪验证下。
-
使用虚拟滚动避免一次性渲染大量 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-datagrid
和 handsontable
不能实现。
如果有时间精力的话,完全可以使用本文介绍的开源框架实现一个完整版的 Excel 软件。