Skip to content

用 TypeScript、React 与 Vite 打造浏览器端表格编辑器 ​

译注:本文编译自 dev.to 文章《How to Create a Browser-based Spreadsheet Editor Using TypeScript, React and Vite》,作者为 Apryse 团队。原文链接见文末。文中涉及的产品为厂商自有 SDK,相关能力与授权方式请以官方文档为准。

表格早已不只是财务人员的工具,而是横跨几乎所有行业的轻量级数据平台。从银行业的财务建模、零售业的库存跟踪,到科研实验记录、建筑项目规划,表格始终是日常运营的关键一环。

过去,查看或编辑表格通常需要先下载文件,在桌面或浏览器应用中修改后再保存。这种方式虽然可行,却会让表格副本残留在硬盘上,带来机密数据在勒索软件攻击或数据泄露中被波及的风险。使用通用编辑器处理表格,还可能因为隐藏列中的机密数据而被无意分享出去。Google Sheets 等在线方案同样存在不容忽视的安全隐患。

Apryse Web SDK 提供了一种替代思路。该 SDK 已推出多年,最初通过 WebViewer 提供 PDF 的查看与编辑能力,2024 年起支持在浏览器内完整编辑 DOCX 文件(无需 Office 授权),如今又新增了表格的查看与编辑支持。对于已有用户而言,只需改动一行代码即可启用该功能。

本文将以 Vite 为例,介绍如何在 React 应用中实现浏览器内的表格查看与编辑。

配置说明 ​

示例环境为 Windows 11,但同样适用于 macOS 与 Linux。需要预先安装 npm 与 Node,本文使用 Node v22。

第一步:用 create-vite 脚手架创建应用 ​

在终端中执行:

shell
npx create-vite@latest

按提示依次指定项目名(示例为 spreadsheet-editor)、框架(选择 React)和变体(选择 TypeScript)。脚手架生成后,进入目录并执行 npm install 安装依赖。此时可以运行项目确认默认的 Vite + React 应用工作正常。

第二步:清理默认代码 ​

脚手架生成的代码大部分并不需要。清空 App.css 与 index.css 的全部内容,并删除 App.tsx 中几乎所有的默认代码,为后续集成 WebViewer 做准备。

第三步:准备安装 WebViewer ​

Apryse 的前身是 PDFTron,于 2023 年更名,因此 WebViewer 的包名中仍保留 pdftron 字样。

WebViewer 使用 JavaScript 编写,运行于浏览器中,因此需要确保运行时可访问其静态文件。出于安全考虑,React 只能访问项目 public 目录下的文件,无法直接读取 node_modules。静态资源的处理方式有多种:手动复制、运行脚本或使用插件。本文选用 vite-plugin-static-copy:

shell
npm i -D vite-plugin-static-copy

同时需要修改 vite.config.ts,指定文件的复制来源与目标位置:

typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { viteStaticCopy } from 'vite-plugin-static-copy'

// https://vite.dev/config/

export default defineConfig({
  plugins: [react(),
    viteStaticCopy({
      targets: [
        {
          src: 'node_modules/@pdftron/webviewer/public/*',
          dest: 'lib/webviewer'
        }
      ]
    })
  ],
})

这样在安装 WebViewer 后,插件会把 node_modules/@pdftron/webviewer/public/ 下的文件复制到 lib/webviewer。如需指定其他目标路径,则需同步修改 WebViewer 的初始化代码。

第四步:安装 WebViewer ​

shell
npm install @pdftron/webviewer

该命令会将包安装到 node_modules 并写入 package.json。若未使用插件,则需要手动将 public 目录下的文件复制到客户端可访问的位置。

第五步:创建挂载用的 HTML 元素 ​

在 App.tsx 中添加一个用于挂载 WebViewer 的 div,通过 useRef 引用,并加上 className 以便用 CSS 控制样式:

javascript
import {useRef} from 'react'
import './App.css'

function App() {
  const viewer = useRef(null)
  return (
    <>
      <div className='webviewer' ref={viewer}></div>
    </>
  )
}
export default App

第六步:实例化 WebViewer ​

WebViewer 可以实例化为 WebComponent(默认)或 Iframe。使用表格编辑器时,必须实例化为 Iframe。

构造函数中需要传入挂载元素(此处为 viewerDiv.current),以及几个关键选项:

  • path:插件复制文件的目标位置。
  • licenseKey:试用或商业授权密钥,可免费申请试用授权。
  • initialMode:指定初始 UI 模式,此处使用 WebViewer.Modes.SPREADSHEET_EDITOR。

此外还可使用 enableFilePicker 允许从 UI 选择文件,以及 initialDoc 指定初始化时加载的文件(示例为 invoice_template.xlsx)。构造函数是异步的,返回 Promise,resolve 后可拿到 instance 对象进行后续操作。

由于使用 React,可将这段代码放入依赖数组为空的 useEffect 中,使其在组件挂载时执行。需要注意的是,在较新版本的 React 中,若开启 strict mode,开发构建下 useEffect 会触发两次。一种处理方式是额外用一个 useRef 变量(示例中为 isInitialized)记录是否已实例化,避免重复初始化:

javascript
import { useRef, useEffect } from 'react'
import './App.css'
import WebViewer from '@pdftron/webviewer'

function App() {
  const viewerDiv = useRef<HTMLDivElement>(null)
  const isInitialized = useRef<boolean>(false)
  useEffect(() => {
    // 生产环境不需要
    if (isInitialized.current) return
    isInitialized.current = true

    WebViewer.Iframe({
      path: "lib/webviewer",
      initialMode: WebViewer.Modes.SPREADSHEET_EDITOR,
      licenseKey: "[Your license key]",
      enableFilePicker: true,
      initialDoc: "invoice_template.xlsx"
    },
      viewerDiv.current as HTMLDivElement).then((instance => {

      })
    );
  }, [])

  return (
    <>
      <div className='webviewer' ref={viewerDiv}>
      </div>
    </>
  )
}

export default App

此时代码已基本完成,可用 npm run dev 启动项目。如果发现表格编辑器加载了但没有网格区域,通常不是 WebViewer 的问题,而是 CSS 问题——浏览器 DevTools 中会显示 WebViewer 已加载,甚至能看到文件名。

第七步:解决 CSS 问题 ​

解决办法很简单,为挂载 WebViewer 的 webviewer 类指定高度:

css
.webviewer{
  height: 100vh;
}

重新加载页面后,即可看到功能完整的 WebViewer。

后续方向 ​

不到 40 行代码、最快约 10 分钟,就能得到一个包含表格编辑器的可运行网页项目。这仅仅是开始。该系列后续文章将介绍如何通过 API 以编程方式操作工作簿内容。

Web SDK 的能力不止于表格。只需少量代码改动,即可查看和编辑 DOCX 或 PDF 文件,甚至可以搭建从模板生成文件、转换为 PDF、再进行数字签名的完整工作流。

原文链接 ​

https://dev.to/apryse/how-to-create-a-browser-based-spreadsheet-editor-using-typescript-react-and-vite-npn