npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ooxml-excel-editor

v1.15.1

Published

High-fidelity OOXML (.xlsx) preview & editor — canvas renderer, framework-agnostic core with Vue 3, Vue 2 and React shells (1:1 UI across all shells). Read-only by default; opt-in editing with undo/redo, formula recalc, and export back to .xlsx/JSON/CSV.

Downloads

1,250

Readme

ooxml-excel-editor

npm version npm downloads CI license

Vue 3 + Vue 2 + React 高保真 .xlsx 预览 / 编辑组件 —— Canvas 渲染,默认只读预览,可选开启编辑。从零实现解析与渲染,尽量还原微软 Excel 打开工作簿的观感。三个壳 UI 1:1 对齐(Vue 3 SFC 是标准,Vue 2 / React 复刻)。

🔗 在线 demo(直接试用):https://ojadeveloper.github.io/ooxml-excel-editor/ · English · 中文

⚡ 快速开始

(按框架二选一):

npm i ooxml-excel-editor vue                          # Vue 3 (默认入口)
npm i ooxml-excel-editor react react-dom              # React 壳 (/react 子入口)
npm i ooxml-excel-editor [email protected] @vue/composition-api # Vue 2.6/2.7+ (/vue2 子入口, 1.3.0+)

1.3.2+ exceljs / jspdf / hyperformulainline 编进 dist chunks/ (ES2017 降级 + 老打包器零解析), echarts 仍 external 走 dependencies (npm 自动装, 避免主题 dual instance). 消费方仅装 framework (vue / react / @vue/composition-api), 其他 lib 完全不用手动装. dist ~5.4 MB, tgz ~1.33 MB.

(Vue,容器要给高度;src 可传 File / Blob / ArrayBuffer / Uint8Array / URL 字符串):

<script setup lang="ts">
import { ref } from 'vue'
import { ExcelViewer } from 'ooxml-excel-editor'
import 'ooxml-excel-editor/style.css'
const src = ref<File>() // 绑个 <input type="file" @change> 给它即可
</script>

<template>
  <ExcelViewer :src="src" style="height: 100vh" />
</template>

默认只读预览;想编辑加 :editable="true"(React / Vue 2 同名 editable)。React / Vue 2 写法、props/事件表、编辑 / 导出 API 见下文对应章节。Vue 2 用法详见 docs/Vue2.md

纯使用者只需读 安装 / 使用 / API / 编辑 / 导出 几节即可接入,无需看源码;类型随包发 .d.ts(IDE 自动补全)。「扩展 API / 插件 / 开发」是进阶,可跳过。

特性

  • 📊 Canvas 高保真渲染:DPR 高清、虚拟滚动(万行流畅)、冻结窗格四象限、滚动自动延伸空行/列(像 Excel 无限网格,但不写进 dimension/文件)

  • 🔢 自写数字格式引擎:千分位/货币/百分比/科学计数/分数、四段格式(正;负;零;文本)、[Red] 颜色、[>=100] 条件段、中文日期 yyyy"年"[h]:mm 经过时间

  • 🗓 日期序列号:含 Excel 1900 闰年 bug、1904 系统

  • 🎨 主题色 + tint、indexed 调色板、合并单元格、边框(细/粗/虚/双线)、填充(纯色/图案/渐变)

  • 🌈 条件格式:色阶 / 数据条 / 图标集 / cellIs / top10 渲染;开 conditionalFormat 后可新建/编辑/删除全 6 类规则(工具栏入口 + API,导出回写 .xlsx)

  • 🖼 图片 + 图表(DrawingML → ECharts 近似还原)、形状/文本框迷你图(sparklines)、批注数据验证(列表型点格内箭头弹选可撤销;整数/小数/日期/文本长度等在编辑模式拦截非法输入 + WPS 式出错/输入提示)、自动筛选样式

  • 📌 WPS 单元格内嵌图(DISPIMG):识别并展示 WPS 私有的"嵌在格里的图"(普通工具会缺图);编辑模式下支持一键浮动 ⇄ 嵌入互转。见 WPS 单元格内嵌图

  • 🔍 图片点击放大 + 下载原图:网格里的图(内嵌图/浮动图)点开看大图、下载原件。只读模式单击图放大、编辑模式右键「查看大图」。imageLightbox prop 控制(默认开),openImageLightbox(src) 命令式打开。

  • 📋 从 Excel/WPS 富粘贴:Ctrl+V 解析剪贴板 HTML → 还原字体/颜色/填充/边框/对齐/合并单元格,整块单次撤销。Excel/WPS 把格式放在 <style> 块的 CSS 类里(<td class="xl65">),解析时会把类规则合并进每格 —— 不只读内联 style=Ctrl+Vpaste 事件拿原始 HTML;navigator.clipboard.read()(右键菜单粘贴用)会净化删掉 <style>/注释,所以右键粘贴从 WPS 拿的格式不如 Ctrl+V 全。图片走多通道:data-uri <img> / WPS VML o:gfxdata(区域复制的内嵌图藏在 VML 里,是个 zip,解出来落格)/ 单图 blob / 拖文件;数字格式(日期/货币)也从 mso-number-format 解析还原,不再变成裸序列号。:Excel 某些版本只给 file:/// 本地路径的 <img>(浏览器读不到)而不带 o:gfxdata,那种区域图仍救不回。

  • 📋 应用内 1:1 复制粘贴:本组件自己 Ctrl+C 的内容会把完整模型快照嵌进剪贴板(<table data-ooxml-clip>),Ctrl+V 时识别并 1:1 还原 —— 数字不会退化成文本、边框/数字格式/合并/DISPIMG 图片/行高全保留;因为快照随剪贴板走,Vue3/Vue2/React 三壳之间、跨标签页互相复制结果都一致。粘到外部应用(Excel/WPS/Word)则读可见 <table>(近似)。列宽以目标现有表头为准、不被源覆盖(列宽整列共享,改了会动表头;同 Excel 默认粘贴)。

  • 📝 文本溢出到相邻空格、自动行高

  • 🖱 交互:单元格选区(合并感知)、拖选、公式栏、状态栏(计数/求和/均值/最值)、超链接可点、裁切文本悬停看全文、Ctrl+C 复制(同应用内 1:1 保真:含数字原始值/数字格式/边框/合并/图片/行高,跨 Vue3/Vue2/React 实例互相复制都一致;列宽以目标表头为准不覆盖;另带 TSV/HTML 供贴进 Excel/WPS)、Ctrl+F 查找替换(高亮 + 上/下定位 + 计数 + 区分大小写/全字匹配;编辑模式带替换 / 全部替换)、自动筛选(点下拉真能筛:去重值多选 + 搜值 + 清除)、自动填充柄(编辑模式拖选区右下角填序列:等差/日期/星期月份/文本递增,见 编辑)、不连续多选(Ctrl/⌘ 点击行头/列头/格 加选不相邻区域,复制堆叠 + 状态栏跨区统计)

  • 🖨 导出 / 打印:整表/选区/多表导出 PNG/JPEGPDF(位图 + 矢量·文字可选可搜两种)、系统打印(可另存 PDF);默认还原原生 pageSetup(纸张/方向/页边距/缩放/打印区域/打印标题行列每页重复);宽表横向跨页(页矩阵);beforeRenderPage 注入页眉/页脚/水印、configureDoc 注册字体;内置「导出设置」对话框

  • 按需加载(无图表文件不下载 echarts、不导出 PDF 不下载 jspdf)、友好错误兜底(损坏/加密/旧 .xls)、解析失败自动给出可读提示

  • 📤 数据读取 API:不必自己再解析 —— getCellText/getSheetData/sheetToJSON/getRangeData(独立函数 + 组件 ref 方法),值/显示文本可选,合并/日期/数字格式都处理好

  • ✏️ 编辑(可选,默认只读):开 editable 即可编辑 —— 单元格值 / 样式(粗体/对齐/填充)/ 列宽行高 / 浮动图片(拖拽移改)/ 增删行列;撤销重做(Ctrl+Z/Y)、前后完整快照事件脏状态 + 一键还原原件;可换公式引擎自动重算依赖格;可注入自定义编辑器(下拉/日期/图片选择器);导出回 .xlsx / JSON / CSV(所见即所得)。见 编辑

纯预览不需要公式引擎 —— .xlsx 缓存了公式结果,直接显示;仅开启编辑 + 重算时才用(可选 hyperformula)。详见 EXCEL还原难点.md

安装

一个包,四个子入口 —— 框架无关的 core 引擎被 Vue 3 / React 两个壳共享(dist/core.js 只打一份),Vue 2 因 SFC 编译器跟 Vue 3 冲突独立打包(内嵌 core)。只需按框架装对应 framework,exceljs / fflate / jspdf / hyperformula 等重依赖 1.3.2+ 已内联进 dist,无需手动装:

# Vue 3 项目
npm i ooxml-excel-editor vue

# React 项目
npm i ooxml-excel-editor react react-dom

# Vue 2.6.x 或 2.7+ 项目 (1.3.0+) — 必装 @vue/composition-api (兼容 2.6 + 2.7)
npm i ooxml-excel-editor [email protected] @vue/composition-api
# Vue 2.6.x 还需 main.js: Vue.use(require('@vue/composition-api').default)

# 纯 Node / 只解析读数据 / 导出(不渲染 UI,无框架)
npm i ooxml-excel-editor

四个入口:

| import | 内容 | 需要装的 framework | 体积 (gzip) | |---|---|---|---| | ooxml-excel-editor | Vue 3 组件 <ExcelViewer> (参考实现 Standard) | vue@3 | ~19 KB + 共享 chunks | | ooxml-excel-editor/react | React 组件 <ExcelViewer> (1:1 复刻 Vue 3) | react + react-dom | ~11 KB + 共享 chunks | | ooxml-excel-editor/vue2 | Vue 2.6 / 2.7+ 组件 <ExcelViewer> (1:1 复刻 Vue 3) | [email protected]+ + @vue/composition-api | ~124 KB (内嵌 core) | | ooxml-excel-editor/core | 框架无关引擎(解析/渲染/控制器/导出/读数据) | 无(纯 Node 也可用,见 Node 用法) | ~1 KB + 共享 chunks |

vue / react / vue@2 按框架三选一(均为可选 peer);exceljs / fflate / jspdf / hyperformula 已内联进 dist(无需装、也绝不重复进你的产物,运行时才动态从 chunk 加载);echarts 是 external 依赖(npm 自动装,仅图表渲染才真正加载,避免主题 dual instance)。

三壳 UI 1:1: Vue 3 SFC 是参考实现 (Standard), Vue 2 / React 1:1 复刻视觉与交互 (工具栏 SVG 图标 / 下拉子菜单 / 公式栏 / 状态栏 / dialog / 浮层 / 演示 demo 全部对齐). 详见 docs/Vue2.md 跟 Vue 3 的差异速查 + CLAUDE.md 第 7 中心原则。

公式重算的引擎(1.14.0 起):默认是内置 MIT 引擎(零依赖,覆盖 ~60 常用函数,无许可证负担)。需要更全函数集时,formulaEngine prop 注入 HyperFormula(hyperFormulaEngineFactory,GPL-3.0/商业双授权,~395 函数)或自研引擎(实现 FormulaEngine 接口)。

Node / 服务端 (headless) 用法

不渲染 UI、纯在 Node(无浏览器、无 canvas)里处理 .xlsx —— 走 ooxml-excel-editor/core(框架无关、ESM)。装包只需 npm i ooxml-excel-editor(exceljs 等已内联)。可跑示例见 examples/

适合 Node 的两件事(比裸 exceljs 多了"显示文本渲染 / 合并 / 日期 / 富文本"的保真):

① 解析取数 —— 拿"人看到的"数据(显示文本 / JSON / CSV):

import { readFileSync } from 'node:fs'
import { openWorkbook, getSheetData, sheetToJSON, getCellText, toCsv } from 'ooxml-excel-editor/core'

// openWorkbook 直接吃 Node Buffer —— 不必手动转 ArrayBuffer
const wb = await openWorkbook(readFileSync('input.xlsx'))
const sheet = wb.sheets[0]

getCellText(sheet, 0, 0)               // 单格显示文本(数字格式/日期已渲染,如 "2021年1月")
getSheetData(sheet, { format: true })  // 2D 数组(format:false 给原始值)
sheetToJSON(sheet)                      // 对象数组(首行当表头)
toCsv(sheet)                            // CSV 文本

② 高保真往返编辑 —— 打开真实 .xlsx → 程序化改值/样式 → 保样式回写(overlay 保留原件的样式/条件格式/图片/透视表,裸 exceljs 会丢):

import { readFileSync, writeFileSync } from 'node:fs'
import { openWorkbook, setCellValue, applyStyleOverride, workbookToXlsxBytes } from 'ooxml-excel-editor/core'

const src = readFileSync('input.xlsx')
const wb = await openWorkbook(src)
const sheet = wb.sheets[0]

setCellValue(sheet, 1, 2, 123.45)
applyStyleOverride(sheet, 1, 2, { font: { bold: true, color: '#FF0000' } })

// workbookToXlsxBytes 返回 Uint8Array(不是浏览器 Blob),直接 fs 落盘
const bytes = await workbookToXlsxBytes(wb, {
  fidelity: 'overlay',
  sourceBuffer: src.buffer.slice(src.byteOffset, src.byteOffset + src.byteLength),
})
writeFileSync('output.xlsx', bytes)

③ 从数据建表(可选;纯建表裸 exceljs 也行)—— jsonToWorkbookworkbookToXlsxBytes:

import { writeFileSync } from 'node:fs'
import { jsonToWorkbook, workbookToXlsxBytes } from 'ooxml-excel-editor/core'

const wb = jsonToWorkbook(
  [{ name: '张三', age: 25 }, { name: '李四', age: 30 }],
  { sheetName: 'People' },
)
writeFileSync('new.xlsx', await workbookToXlsxBytes(wb))

Node headless 下不可用(硬依赖浏览器 canvas / DOM,需在浏览器或 Electron 渲染进程跑):图片/PNG/JPEG 与 PDF 导出print()downloadBlob、内置 DefaultEditor 编辑器、<ExcelViewer> 组件渲染。另:finalizeImages 在 Node 会安全跳过(图片保留 bytes/mime,不生成 blob URL);URL 字符串入参走 fetch,Node 用 fs 读 Buffer,别传本地路径字符串。纯 Node 可用的导出面见 EXTENDING.md

使用

Vue

<script setup lang="ts">
import { ref } from 'vue'
import { ExcelViewer } from 'ooxml-excel-editor'
import 'ooxml-excel-editor/style.css'

const file = ref<File>()
</script>

<template>
  <ExcelViewer
    :src="file"
    :file-name="file?.name"
    style="height: 100vh"
    @rendered="(wb) => console.log('已渲染', wb.sheets.length, '个工作表')"
    @error="(msg) => console.error(msg)"
  />
</template>

React

同一套 core 引擎,React 薄壳。命令式 API 走 ref(getSheetData / setSelection / downloadPdf …,与 Vue 组件 ref 对齐):

import { useRef, useState } from 'react'
import { ExcelViewer, type ExcelViewerHandle } from 'ooxml-excel-editor/react'

export function Preview() {
  const [file, setFile] = useState<File>()
  const viewer = useRef<ExcelViewerHandle>(null)
  return (
    <>
      <input type="file" accept=".xlsx" onChange={(e) => setFile(e.target.files?.[0])} />
      <ExcelViewer
        ref={viewer}
        src={file}
        fileName={file?.name}
        style={{ height: '100vh' }}
        onRendered={(wb) => console.log('已渲染', wb.sheets.length, '个工作表')}
        onSelectionChange={({ range, active }) => console.log(range, active)}
      />
    </>
  )
}

Vue 2 (1.3.0+)

跟 Vue 3 1:1 复刻 (工具栏 / 公式栏 / dialog / 浮层 / events / API 全对齐). Vue 2 用 Options API + Composition API 都行:

<template>
  <ExcelViewer
    ref="viewer"
    :src="src"
    :file-name="fileName"
    :editable="editMode"
    style="height: 100vh"
    @rendered="(wb) => console.log('已渲染', wb.sheets.length, '个工作表')"
    @cell-change="(p) => console.log(p.before.text, '→', p.after.text)"
  />
</template>

<script>
import ExcelViewer from 'ooxml-excel-editor/vue2'
import 'ooxml-excel-editor/style.css'
import 'ooxml-excel-editor/vue2.css'

export default {
  components: { ExcelViewer },
  data: () => ({ src: null, fileName: '', editMode: false }),
  methods: {
    download() { this.$refs.viewer.downloadXlsx() },
  },
}
</script>

完整 Vue 2 用法 + 跟 Vue 3 的差异表 见 docs/Vue2.md

仅引擎(不渲染 UI)

// 解析 + 读数据 + 导出,全程不依赖任何框架
import { parseWorkbook, loadArrayBuffer, getSheetData, WorkbookExporter } from 'ooxml-excel-editor/core'

全局注册(Vue 插件)

import OoxmlExcelPreview from 'ooxml-excel-editor'
import 'ooxml-excel-editor/style.css'

app.use(OoxmlExcelPreview) // 注册全局组件 <ExcelViewer />

读取数据(好用的数据访问 API)

不必自己再解析。两条路径,都拿同一份数据:

A. 独立函数(配 parseWorkbook,不渲染也能用):

import { parseWorkbook, loadArrayBuffer, getSheetData, sheetToJSON, getCellText, getWorkbookJSON } from 'ooxml-excel-editor'

const wb = await parseWorkbook(await loadArrayBuffer(file))
const sheet = wb.sheets[0]

getCellText(sheet, 1, 0, wb.date1904)         // 单格显示文本,如 '产品' / '¥1,234.50'
getSheetData(sheet, { date1904: wb.date1904 })            // 二维数组(显示文本)
getSheetData(sheet, { format: false, date1904: wb.date1904 }) // 二维数组(原始 number/Date)
sheetToJSON(sheet, { headerRow: 0, date1904: wb.date1904 })   // 首行作表头 → [{ 产品:'鼠标', 单价:89 }, ...]
getWorkbookJSON(wb)                            // 全簿 → { 表名: 对象数组 }(自动带 date1904)

B. 组件 ref(自动带 date1904 + 默认当前表;插件 ctx.viewer 同样可用):

const viewer = ref()  // <ExcelViewer ref="viewer" />
viewer.value.getCellText(1, 0)                 // 显示文本
viewer.value.getCellValue(1, 0)                // 原始值
viewer.value.getSheetData()                    // 当前表 2D(默认显示文本)
viewer.value.getSheetJSON({ headerRow: 1 })    // 对象数组
viewer.value.getRangeData(viewer.value.getSelection())  // 取"我选中的"区域

| 函数 / 方法 | 返回 | |---|---| | getCellValue / getCellText / getCellStyle / getCell | 单格 原始值 / 显示文本 / 解析样式 / 模型 | | getSheetData | 二维数组(稠密 rows×cols) | | getRangeData(range) | 区域二维数组 | | sheetToJSON | 对象数组(首行作 key,空表头回退列字母,全空行跳过) | | getWorkbookJSON | { 表名: 对象数组 } |

  • 值 vs 文本:format 默认 true → 套了数字/日期格式的显示文本(所见即所得);{ format: false } → 原始 number/Date/boolean
  • 合并单元格:2D/JSON 里锚点(左上)持值,其余为空(单格 getCellValue 仍返回模型里的字面值)。
  • 公式:不重算,沿用 Excel 缓存结果(公式串见 cell.formula)。
  • 也 re-export 了底层 formatValue / cellKey

想要更底层的渲染模型,仍可直接用 parseWorkbook 的返回值 / getWorkbook() / @rendered(WorkbookModel:sheets[].cells: Map<"row:col">styles[styleId])。

UI 区域速查(给调用方 & 二开者)

<ExcelViewer> 渲染出的 chrome 自上而下分这几块,每块都有名字 / DOM 选择器 / 文件 / 替换方式,改样式或替换某块时按这张表找位置。

┌─ <ExcelViewer> .excel-viewer (Vue) / .rxl (React) ─────────────────────────┐
│ ① Header (顶栏)            <ViewerToolbar>             ← #header slot 整条替换│
│    文件名 · 表数 · 缩放 · 导出 ▾                                             │
├────────────────────────────────────────────────────────────────────────────┤
│ ② ActionToolbar (操作栏)   .action-toolbar             ← :toolbar 配置/插件   │
│    [查找][筛选][复制][自动换行][冻结][缩放][导出] … 自动「⋯ 更多」           │
├────────────────────────────────────────────────────────────────────────────┤
│ ③ FormulaBar (公式栏 Fx)   .formula-bar                ← (无 slot,可全局 CSS)│
├────────────────────────────────────────────────────────────────────────────┤
│ ④ RenderArea (网格区)      .render-area + canvas.grid-canvas              │
│    ├ OverlayManager        图片/图表/形状/插件 overlay(DOM 叠加,随滚动)    │
│    ├ ContextMenu           .ooxml-context-menu(body 级,框架无关)          │
│    ├ LightboxHost          .ooxml-lightbox(body 级,图片放大+下载)         │
│    └ CellEditorHost        .editor-slot(自定义/内置 cell 编辑器)           │
├────────────────────────────────────────────────────────────────────────────┤
│ ⑤ SheetTabs (表标签)       .sheet-tabs                                    │
└────────────────────────────────────────────────────────────────────────────┘

| 区域 | DOM / class | 文件(壳) | 替换 / 自定义方式 | |---|---|---|---| | ① Header 顶栏 | <ViewerToolbar> (Vue) | components/ViewerToolbar.vue | <template #header> slot 整条替换;插槽签名见 README「分层 UI」 | | ② 操作栏 | .action-toolbar | components/ActionToolbar.vue / [react/RxlActionToolbar.tsx — 暂无,React 用内置按钮行]builtin 渲染在 components/ExcelViewer.vue builtinTool() | :toolbar="[...]" 数组 prop(内置 id + 自定义 ToolbarItem);插件 definePlugin({ toolbar }) 追加项 | | ③ 公式栏 | .formula-bar / .rxl-formula-bar | components/ExcelViewer.vue / react/ExcelViewer.tsx | 暂无 slot;CSS 直接覆写(.excel-viewer .formula-bar)。命令式:getCellEditString / commitActiveCellValue | | ④ 网格区 canvas | .render-area > canvas.grid-canvas | core: render/canvas-renderer.ts | 渲染钩子 cellStyle / transformModel / 插件 overlay 返回 DOM;:theme 改配色 | | ④a Overlay 层 | .overlay-host 四象限(主/冻结行/冻结列/冻结角) | core: viewer/overlay-manager.ts | <template #overlay> slot (Vue) / overlay prop 返回 DOM (React)/ 插件 overlay | | ④b 右键菜单 | .ooxml-context-menu(body 级) | core: edit/context-menu.ts | Plan C 三层开放::contextMenu prop(false 关闭 / (ctx,items)=>MenuItem[] transform) · @before-context-menu/@context-menu 事件(preventDefault() 接管渲染) · 命令式 openContextMenu(x,y,items?) / closeContextMenu();插件 definePlugin({ contextMenu }) 链式贡献 | | ④c 图片灯箱 | .ooxml-lightbox(body 级) | core: viewer/lightbox-host.ts | imageLightbox={false} 关闭;命令式 openImageLightbox(src,fileName?) | | ④d 单元格编辑器 | .editor-slot | core: edit/editor-host.ts + 内置默认 edit/default-editor.ts | :editor prop = EditorResolver(按格返回工厂);插件 editor;详见 README「自定义编辑器」 | | ⑤ 表标签 | .sheet-tabs | components/SheetTabs.vue / 内置于 React 壳 | 暂无 slot;.excel-viewer .sheet-tabs CSS 覆写 |

重要:demo 顶栏不是组件的。仓库里 npm run dev 看到的绿色顶栏(.app-bar)是 src/App.vue 的 demo 框架栏(选 xlsx / 加载示例 / 编辑模式开关 / 演示按钮),React demo 同形见 src/react-demo/main.tsx。它独立于 <ExcelViewer>,用户 import 组件时不会出现

API

<ExcelViewer>

| Prop | 类型 | 说明 | |---|---|---| | src | File \| Blob \| ArrayBuffer \| Uint8Array \| string(URL) | 要预览的 .xlsx 数据源 | | workbook | WorkbookModel \| JsonInput | JSON 直渲(P3) 优先于 src。WorkbookModel 直用;JsonInput 走 jsonToWorkbook 自动构造:① unknown[][] 二维数组 ② Record<string,unknown>[] 对象数组(首行表头) ③ { sheets:[...] } 多表 | | jsonOptions | JsonLoadOptions | JSON 直渲选项(workbook = JsonInput 时生效):headerRow / sheetName / autoInfer(数字串→数字、ISO 日期串→Date,默认 on) | | templateFile | ExcelSource | 模板样式 overlay(P3 重设计 2026-06-08) 一份 .xlsx 当样式捐赠者 —— 模板贡献 styling(styles / merges / 列宽 / 行高 / freeze / theme),JSON / CSV 数据在 A1 自然位置渲染,模板的 raw 文字 / 占位符 / 图 / 图表 / 条件格式 全部丢弃仅在 :workbook (JSON / 模型) 数据源下生效;:src (xlsx) 数据源自带格式,给 :templateFile 会被忽略并 console.warn。工具栏内置 template 项可在运行时切换 / 导入 / 清除 | | templateName | string | 模板显示名(标题栏 · 模板: xxx 后缀);不给则取运行时 File.name。:fileName 同步:JSON 源未给名时默认 "JSON 数据" | | exportProgress | boolean(默认 true)| 内置导出进度遮罩(P1.5)viewer.downloadPdf / downloadImage / downloadXlsx / print / 选区图片批量转换 时,壳自动建 AbortController + 接 onProgress → 显示居中模态(stage 标签 + 进度条 + 取消)。关闭false(纯回调走 opts.onProgress/signal);自渲染#export-progress 插槽(Vue)/ renderExportProgress (React) | | contextMenu | false \| ContextMenuTransform | 右键菜单(Plan C) — 三层开放:① 默认 = 内置菜单(editable 时弹) ② false = 不弹内置(@before-context-menu / @context-menu 事件仍触发,自渲染) ③ (ctx, items) => MenuItem[] \| undefined = transform 回调,在内置 items 上加 / 减 / 重排 | | fileName | string | 标题栏显示的文件名(可选) | | editable | boolean | 开启编辑(默认 false = 只读,行为与历史一致) | | pivotTable | boolean | 透视表功能开关(默认 false = 关闭)。开启后(还需 editable):工具栏 pivot-table 入口可见、createPivotTable/openPivotTableDialog 等 API 生效、导出 .xlsx 回注真实 OOXML 透视表零件(overlay 模式同时保留原文件透视表) | | conditionalFormat | boolean | 条件格式编辑开关(默认 false = 关闭、只读渲染)。开启后(还需 editable):工具栏 conditional-format 入口可见、openConditionalFormatDialog/addConditionalRule/updateConditionalRule/removeConditionalRule/setConditionalRules/getConditionalRules API 生效、导出 .xlsx 回写条件格式(overlay 保留原件未编辑规则原样,只增改用户改的)。支持全 6 类规则(cellIs / 公式 / 色阶 / 数据条 / 图标集 / top10)新建·编辑·删除,整体单次撤销 | | cellReadOnly | (cell, pos) => boolean | 按格只读判定(编辑时) | | readOnlyRanges | MergeRange[] | 只读区域(命中即只读,黑名单) | | editableTargets | EditableTarget \| EditableTarget[] | 可编辑白名单(2026-06-08)— 设了就是白名单语义:默认只读,命中任一 target 的格可编辑。4 种 target 形状自动识别:{row,col} 单格 / {row} 整行 / {col} 整列 / MergeRange 矩形。单值或数组都行,允许不相邻多 target。undefined (不传) = 不启用白名单 = 老行为(默认全可编辑);[] (显式空) = 全只读。与 readOnlyRanges / cellReadOnly 叠加 — 白名单命中后仍可被二次"黑"掉。运行时改:命令式 viewer.setEditableTargets(targets) | | strictDimensions | boolean | 严格尺寸闸门(Phase B, 2026-06-08)— 默认 false:setColumnWidth / setRowHeight / autoFit 仅受全局 editable 控制。设 true + 启用了 editableTargets → 该列/行至少有 1 格在白名单内才能改尺寸,否则 skip + emit permission-denied (reason='dimension')。 | | readOnlyCellStyle | boolean \| CellStyleOverride \| CellStyleFn | 只读视觉钩子(Phase C, 2026-06-08)— 默认 false 无视觉差异(老行为);true 套内置浅灰底 #f5f7fa;对象 = 固定样式给所有只读格;函数 = 按格自定义。仅在该格 editable=false 时套用,跟 editableTargets 配合一眼看出哪些格可编辑。鼠标光标: 编辑模式下悬停只读格自动变 not-allowed(内置,不可关)。 | | editor | EditorResolver | 自定义单元格编辑器工厂(返回任意 DOM) | | recalc | boolean | 公式重算(默认 false;需 editable) | | formulaEngine | FormulaEngineFactory | 自定义/自研公式引擎(默认 = 内置 MIT 引擎;可注入 hyperFormulaEngineFactory 或自研) | | pasteBehavior | Partial<PasteBehavior> | 粘贴行为(默认 = 覆盖式 1:1)。逐项可配:cellStyle/fill(overwrite/merge/skiprowHeight(source/keepcolWidth(firstRowOnly/source/keepsourceMerges(apply/skiptargetMerges(clear/keep,默认清=修旧合并吞列)·images(apply/skip)。缺项回落默认。也可运行时 viewer.setPasteBehavior(cfg) / 右键「选择性粘贴」/ 工具栏「⚙ 粘贴配置」面板 | | readOnlyPrompt | 'dialog' \| 'toast' \| 'none' | 粘贴撞只读格的内置提醒(默认 'dialog'):dialog 弹窗列出具体哪些格只读 / toast 顶部气泡 / none 只发 permission-denied 事件。逐格精确(编辑模式下也可能有只读格) | | cellImageFit | 'fill' \| 'contain' \| 'cover' | WPS 单元格内嵌图贴合方式(默认 contain 等比,与 WPS 渲染一致) | | imageLightbox | boolean | 图片点击放大灯箱(默认 true;只读单击图放大、编辑右键「查看大图」) | | toolbar | false \| Array<string \| ToolbarItem> | 操作工具栏配置(默认 ['find','filter','sort'])。内置 id 见 操作工具栏;false 不渲染。可混入自定义项 | | plugins | ExcelPlugin[] | 插件数组(definePlugin 打包 theme/cellStyle/transformModel/events/overlay/toolbar/setup);见 插件 | | openLinks | boolean | 单击超链接是否自动打开(默认 true;false 只发 @hyperlink 事件,自己处理) |

编辑相关 props 详见下方 编辑 章节;数据验证(开 editable编辑时拦截非法输入 + 列表型下拉选值)无需额外 prop,解析到的规则自动生效。

| 事件 | 载荷 | 触发时机 | |---|---|---| | rendered | WorkbookModel | 解析并首次渲染完成 | | error | string | 解析失败(友好文案) | | cell-change | { before, after, source } | 单元格变更(编辑/撤销/重做/公式级联);before/after完整快照(底层 cell + 解析 style + raw/computed/text) | | edit-start / edit-commit | { cell, ... } | 进入 / 提交编辑 | | dim-change | { axis, index, before, after } | 列宽/行高变更 | | image-change | { index, before, after } | 图片增删移改(前后 ImageAnchor) | | struct-change | { op, at, count } | 增删行列 | | dirty-change | { dirty } | 有/无未保存修改 |

容器需要有明确高度(组件填满父容器),例如 style="height: 100vh"

具名导出

四个入口同源:ooxml-excel-editor(Vue 3)、/react/vue2/corere-export 同一套框架无关 core 公共 API —— 任一入口都能拿到下表全部(各入口再各自加自己的组件)。所以 React/Vue2 用方不必绕到 /core

| 导出 | 说明 | |---|---| | ExcelViewer | 预览/编辑组件(各框架入口各自的) | | default | Vue 3 / Vue 2 入口默认导出 = Vue 插件(app.use) | | 解析/加载 | openWorkbook(src)(一行门面 = 归一化 + 解析,Node/浏览器通用)· parseWorkbook(buffer)(ArrayBuffer\|Uint8Array → Promise<WorkbookModel>,优先 Worker)· loadArrayBuffer(src)(多种输入归一)· jsonToWorkbook(data)(数据直建模型) | | 读数据 API | getCellValue / getCellText / getCellStyle / getSheetData / getRangeData / sheetToJSON / getWorkbookJSON / cellDisplayText | | 格式/工具 | formatValue(数字格式)· cellKey · colIndexToLetters | | 插件 / 主题 | definePlugin · DEFAULT_THEME / mergeTheme | | 公式引擎(1.14.0) | builtinFormulaEngineFactory(默认,MIT)· hyperFormulaEngineFactory(HyperFormula,GPL/商业)· FUNCTION_NAMES(已支持函数名)· BuiltinFormulaEngine | | 导出工具 | workbookToXlsxBytes(→ Uint8Array,纯 Node 落盘用workbookToXlsxBlob(→ Blob,浏览器下载用)· toCsv / toWorkbookJson · canvasToBlob / canvasToDataURL / downloadBlob(后三者需浏览器) | | 类型 | WorkbookModel / SheetModel / CellModel / CellStyle / CellStyleOverride / MergeRange / ConditionalRule / DataValidationRule / ChartSpec / ImageAnchor / PivotTableModel / CssColor / ExcelSource / ViewerApi / ExcelPlugin / FormulaEngine / FormulaEngineFactory / EditConfig / 导出选项类型(PdfExportOptions/ImageExportOptions/…)等 |

想看完整出口清单见 src/core/index.ts;深度二开(直接用 ViewerController / CanvasRenderer / EditController / 模型 mutations 等内部件)也都从这些入口导出,见 ARCHITECTURE.md

编辑(可选,默认只读)

组件默认是只读预览,行为与历史完全一致、零额外成本。传 :editable 才进入编辑模式,所有编辑能力建在一份可变内存模型(WorkbookModel)上,读 / 写 / 事件 / 导出共用同一层。

开启 + 只读控制

<ExcelViewer
  :src="src"
  :editable="true"
  :read-only-ranges="[{ top: 0, left: 0, bottom: 0, right: 4 }]"   <!-- 表头整行只读 -->
  :cell-read-only="(cell, pos) => pos.col === 0"                    <!-- 第 0 列只读 -->
  @cell-change="onCellChange"
/>

开编辑后:双击 / F2 / 直接打字进格编辑(Enter 提交下移、Tab 右移、Esc 取消、Shift+Enter 插换行),Ctrl+Z / Ctrl+Y 撤销重做,拖列头/行头边界改宽高,拖浮动图片移动。只读格 / 区域不进编辑。

长文本编辑(WPS 风格, 1.2.1):默认编辑器是 <textarea>, 输入长文本自动换行 + 向下浮起撑高, 编辑期显示完整内容;提交后单元格行高保持原样(跟 WPS 一致, 不持久化撑高;如需永久保持多行, 单元格设 wrapText=true)。自定义编辑器可通过 CellEditorReturn.getDesiredHeight(width) 复用此撑高机制 + ctx.reposition() 主动触发重撑。

命令式编辑 API(模板 ref / 插件 viewer)

| 类别 | 方法 | |---|---| | 值 | editCell(row,col,value) · editRange(range,values[][]) · clearRange(range) | | 粘贴 | pasteText(tsv,at?) · pasteRichHtml(html,at?,behaviorOverride?)(Excel/WPS 复制 → 字体/颜色/填充/边框/对齐/换行/合并/内嵌图 + 单次撤销) · pasteImageBlob(blob,at?)(单图/拖文件落格) | | 粘贴行为 | getPasteBehavior() · setPasteBehavior(cfg)(改 Ctrl+V/右键默认)· openPasteConfigDialog()(弹「⚙ 粘贴配置」面板,框架无关 DOM 三壳共用)。覆盖式 1:1(默认)/ 合并 / 仅值;targetMerges:'clear' 默认清目标旧合并防吞列;colWidth:'firstRowOnly' 仅首行取源宽。右键「选择性粘贴 ▸ 覆盖格式 / 保留原样式(仅值)」逐次选 | | 样式 | setStyle(range, patch)(patch = CellStyleOverride:font/fill/borders/对齐/numFmt) | | 背景/字体色 | getActiveFillColor() · getActiveFontColor()(回显活动格当前色 #RRGGBB) · setSelectionFill(color\|null)(null=清除填充) · setSelectionFontColor(color) | | 自动换行 | getSelectionWrapState()'all'\|'none'\|'mixed'(工具栏 active 用) · toggleWrapTextOnSelection()(WPS 风格 toggle:全开/全关;行高按内容重撑,只扩不缩) | | 列宽行高 | setColumnWidth(col,px) · setRowHeight(row,px) | | 行列结构 | insertRows(at,count?) · deleteRows(at,count?) · insertCols(at,count?) · deleteCols(at,count?) | | 图片 | getImages() · addImage(anchor) · removeImage(i) · moveImage(i,dxPx,dyPx) · resizeImage(i,wPx,hPx) | | WPS 内嵌图 | getCellImages() · setCellImageFit('fill'\|'contain'\|'cover') · convertImageToCellAuto(imgIdx)(就近) · convertAllImagesToCells(col?)(整表/整列) · convertImagesInRangeToCell(range)(选区批量,单次撤销) · convertCellImageToFloat(row,col,size?)(嵌入→浮动) · convertCellImagesInRangeToFloat(range, size?)(选区批量浮动化) · convertImageToCell(imgIdx,row,col)(显式目标) | | 图片放大 | openImageLightbox(src,fileName?)(命令式弹大图+下载) · getCellImageAt(row,col)(某格是否内嵌图→{id,src,mime}) | | 撤销/进编辑 | undo() · redo() · canUndo() · canRedo() · beginEdit(row,col) · cancelEdit() · isEditing() · getEditingCell() | | 公式栏 | getCellEditString()(活动格可编辑字符串:公式→=…,数值→原始数字串) · canEditActiveCell() · commitActiveCellValue(value, move?)(顶部 Fx 公式栏可编辑并与单元格联动,底层即用这套) | | 查询/状态 | getCellSnapshot(row,col) · inspectCell(row,col)(全息体检:snapshot + 合并区 + 浮动图覆盖 + WPS 内嵌图 + 数据验证 + 条件格式命中 + 链接/批注) · isDirty() · resetToOriginal() · isRecalcReady() · getVirtualExtent()(当前虚拟行列范围) | | 导出 | exportXlsx/downloadXlsx · exportJson/downloadJson · exportCsv/downloadCsv(见 导出) |

所有写操作(含拖拽改宽高/移图)统一进撤销栈、发对应事件、翻脏标记;resetToOriginal() 一键放弃全部修改、还原到刚加载的原件。

自定义编辑器 :editor

返回一个工厂(拿到 ctx 里的快照 / 矩形 / commit/cancel),挂任意 DOM 当编辑控件 —— 下拉、日期选择器、图片选择器、带按钮的面板…… 是框架无关的 DOM(Vue/React 共用)。commit 可只给值,也可 { value, style } 同时套样式。

const myEditor: EditorResolver = (cell, pos) => {
  if (pos.col !== 0) return                       // 该列不接管 → 用内置文本编辑器
  return (ctx) => {                               // 工厂:返回 DOM
    const sel = document.createElement('select')
    sel.innerHTML = `<option>A</option><option>B</option>`
    sel.value = String(ctx.snapshot.raw ?? '')
    sel.onchange = () => ctx.commit(sel.value)
    return sel
  }
}

插件也可经 editor 字段贡献(多插件数组序,组件 prop 最后覆盖),与 cellStyle/overlay 同范式。

WPS 单元格内嵌图(DISPIMG)

不少 .xlsx 由 WPS 导出,其"嵌在单元格里的图"用了 WPS 私有存法(xl/cellimages.xml + 单元格 =DISPIMG("id",1) 公式),标准解析读不出来 → 普通工具打开会缺图。本组件:

  • 自动识别并展示:解析 cellimages.xml 登记表,把图画进单元格内(随行高列宽 / 滚动 / 裁剪 / 冻结 / 缩放),非浮动叠加。贴合方式可配置 cellImageFit:contain(默认,等比缩放,与 WPS 渲染一致——WPS 打开导出文件时 DISPIMG 固定按 contain 显示)/ fill(拉伸铺满随格变形)/ cover(等比裁剪铺满)。getCellImages() 读登记表;getCellSnapshot(row,col).cell.dispImgId 看某格是否内嵌图。
  • 一键浮动 ⇄ 嵌入互转(编辑模式):
    • convertImageToCellAuto(imgIdx) —— 就近嵌入:图压在哪个单元格上就嵌进哪格(几何反推,无需手指定目标格)。
    • convertAllImagesToCells(col?) —— 整表/整列批量就近嵌入(col 给定只嵌该列),一次进撤销栈(单次 Ctrl+Z 全撤),返回嵌入张数。
    • convertCellImageToFloat(row,col,size?) —— 内嵌图拎回浮动图。
    • 右键单格菜单:「将此处浮动图嵌入单元格 / 整列浮动图嵌入(N 张)/ 整表浮动图嵌入(N 张)/ 内嵌图转为浮动图」。
    • 全部入撤销栈、发 cell-change/image-change、翻脏标记。(convertImageToCell(imgIdx,row,col) 仍保留,用于显式指定目标格。)
  • 导出往返:downloadXlsx() / exportXlsx() 导出时,在 ExcelJS 写出后于 zip 层回注 WPS 私有件(cellimages.xml + rels + media + [Content_Types].xml/workbook.xml.rels 补丁,从模型重建),原有的 + App 内新转的内嵌图导出后用 WPS 打开都正常显示。rebuild / overlay 两种保真模式均覆盖;无字节的 blob-only 图除外。

不连续多区域选择(1.13.0)

Ctrl/⌘ + 点击 行头 / 列头 / 单元格 → 加选不相邻区域(Shift 仍是连续区间);普通点击 / 键盘导航回单选。多选时复制把各区逐行堆叠成块(TSV + HTML,粘到 Excel/WPS/app 内都成堆叠块),状态栏统计跨所有区聚合。getSelectionRanges() / hasMultiSelection() 读多选状态。纯框架无关 core 交互,三壳一致。

格式刷(1.12.0)

工具栏 format-painter 入口:先选源格点按钮采样其完整样式(字体/填充/边框/对齐/换行/数字格式),再点或拖目标格/区域即把格式刷上(单次撤销);Esc 或再点按钮退出,待刷时光标变 copy。也可 startFormatPainter() / isFormatPainterArmed() / cancelFormatPainter() 直调。纯框架无关 core 交互,三壳一致。

数字格式 / 批注 / 查找替换(1.11.0)

  • 查找替换:Ctrl+F 打开查找栏,编辑模式下多出替换行 —— 替换输入 + 「替换」(替换当前并跳下一个)/「全部替换」(整体单次撤销),支持区分大小写 / 全字匹配,跳过只读格。
  • 数字格式编辑器:工具栏 number-format 入口打开对话框(框架无关 DOM,三壳共用)—— 分类(常规/数值/货币/百分比/日期/时间/文本/自定义)+ 选项(小数位数 / 千分位 / 负数红色 / 货币符号 / 日期时间预设)+ 实时预览 + 可直接编辑格式代码 → 确定即套到选区(单次撤销)。也可 setSelectionNumberFormat(code) / openNumberFormatDialog() 直调。
  • 批注编辑:右键单格「插入/编辑/删除批注」打开对话框(多行文本 + 确定/删除/取消);getCellComment / setCellComment(row,col,text)(空串删除)/ openCommentEditor() 直调,单次撤销;导出 .xlsx 回写批注(rebuild + overlay)。

自动填充柄(拖拽填充序列)

:editable 后,选区右下角出现填充柄小方块(像 Excel/WPS)。拖它向下/上/左/右,松手即把源选区的模式接续填充进新增格(整体单次撤销):

  • 全数值:1 个 → 复制;≥2 个 → 等差外推(1,23,4,5;2,46,8,10,支持递减)
  • 全日期:1 个 → 每格 +1 天;≥2 个 → 按相邻差外推
  • "前缀+末尾整数"文本:Item 1Item 2,Item 3;第1周第2周;A01A02(保留前导零位宽)
  • 星期 / 月份名(中英常见写法):周五周六,周日,周一;NovDec,Jan(循环接续)
  • 其它:循环复制源值

按住 Ctrl/⌘ 拖翻转"复制 ↔ 序列"(对齐 Excel):单个数字普通拖=复制、Ctrl 拖=递增(56,7,8);两个数字序列普通拖=等差、Ctrl 拖=复制;日期/星期月份/文本递增普通=序列、Ctrl=复制。

主轴按鼠标偏移更大的方向决定(上下 vs 左右);列 / 行各自独立成序列;跳过只读格。注:v1 填充,不复制源单元格的格式。纯框架无关 core canvas 交互,三壳一致。

公式重算(可换引擎)

:recalc 后,编辑公式格或被公式引用的格 → 依赖格自动级联重算,每个变动都发 cell-change(source: 'api'|'ui'|'undo'|'redo')。

默认引擎(1.14.0 起)= 内置 MIT 引擎:从零实现的解析 + 求值 + 依赖图 + 拓扑级联 + 循环检测,零依赖、无 GPL,覆盖日常 ~60 个常用函数(SUM/AVERAGE/IF/IFERROR/VLOOKUP/INDEX/MATCH/SUMIF/COUNTIF/ROUND/LEFT/MID/CONCAT/DATE/TODAY… 见 FUNCTION_NAMES 导出)。函数集比 HyperFormula 小,但日常足够。

需要更全覆盖::formula-engine="hyperFormulaEngineFactory" 注入 HyperFormula(可选 peer npm i hyperformula,GPL-3.0/商业双授权,~395 函数),或注入自研引擎(实现 FormulaEngine 接口)。isRecalcReady() 查引擎是否就绪。打开文件的显示值与引擎无关(读原件缓存结果),引擎只在编辑后重算时介入。

公式自动补全(1.14.0):在单元格里输 =SU 时下方弹函数名列表 + 参数提示;↑↓ 选、Enter/Tab 接受(插入 SUM( 并把光标移进括号)、Esc 关、点选即填。列表 = 引擎实际支持的函数(所见即所得),框架无关默认编辑器内置,三壳一致。

事件 = 前后完整快照

每次编辑都以 cell-change 通知:{ before, after, source },before/afterCellSnapshot —— 不只 raw,还含计算值 computed、显示文本 text、整个底层 CellModel + 解析后 style。事件流查询 API(getCellSnapshot)同一份底层结构 → JSON/CSV/XLSX 导出都复用它,无需按格式各写一遍解析。另有 dim-change/image-change/struct-change/dirty-change(见上方事件表)。

v1 已知限制

  • 增删行列会自动重写公式引用(=A5 上方插一行 → =A6,删被引用行 → #REF!,含跨表 Sheet1!A5)。
  • 写回 .xlsx 默认从模型重建,丢 VBA/工作表保护/复杂 DrawingML 等;需要更高保真可用 exportXlsx({ fidelity: 'overlay' }) 重载原件叠加编辑(见 导出保真边界)。

扩展 / 二开

不改源码就能定制外观(:theme)、数据/渲染钩子(transformModel/cellStyle)、自定义编辑器(:editor)、右键菜单 transform、操作工具栏自定义项、分层 UI slots、命令式 API、导出/打印高级选项、以及插件definePlugin 打包多种扩展点、跨框架可用)—— 完整 API 见 EXTENDING.md(二开 / 扩展 API 手册)

想了解内部结构 / 在哪改代码:见 ARCHITECTURE.md

浏览器支持

现代浏览器(Chrome/Edge 80+、Safari 15+、Firefox 114+,需支持 Canvas / ResizeObserver)。

解析线程:发布的组件库在主线程解析(exceljs 1.3.2+ 已内联进 dist chunks,运行时动态加载,不重复进你的产物)。本仓库的 demo/dev 额外启用了 Web Worker 解析(大文件不卡 UI)。如果你的应用要处理很大的文件,可直接用导出的 parseWorkbook 包进你自己的 Worker。

范围边界

  • 编辑已支持(默认只读;开 editable,见 编辑)。下列为暂不覆盖项:
  • 增删行列不自动重写公式引用;写回 .xlsx 丢 VBA/工作表保护/复杂 DrawingML
  • 透视表:已有透视表数据按普通单元格显示并显示只读字段按钮;pivot-table 入口可从当前选区选择生成位置,生成静态透视汇总表到当前表指定单元格或新建工作表,随后打开 WPS 风格右侧“数据透视表”字段面板。创建出的透视表空白起步(对齐 WPS/Excel:不自动猜字段),在右侧面板里选字段填充。面板支持搜索字段;勾选字段列表的复选框 = 加入透视表(数值字段→值,其它→行),取消 = 移出;也可用 筛/列/行/Σ 按钮或拖拽把字段加入“筛选器 / 列 / 行 / 值”四区,并可拖到移除区删除字段。改动后即时重建结果:筛选器支持“全部 / 非空 / 多选(勾选要保留的具体值,WPS 风格)”,列区生成横向分组,行区生成纵向分组,值区可放多个字段且可切换“求和 / 计数 / 平均值 / 最大值 / 最小值”。透视结果是“活”的:① 编辑源数据区任意单元格(含撤销/重做)后,所有透视表自动按源区域重算;② 放两个及以上行字段时,外层分组带小计且可点行首 [−]/[+] 折叠/展开内层明细(单行字段为扁平结果,无折叠)。也可调用 createPivotTable({ sourceRange, sourceSheetIndex, output, layout, showPanel }) 不经过页面直接创建;旧的 createPivotTableFromSelection({ rowFieldIndex, valueFieldIndex, output }) 仍保留。创建出的 PivotTableModel 会保存 sourcelayout 元数据,供运行时重建与导出使用。失败时会提示原因。整个透视表功能由 pivotTable 配置开启(默认关闭),关闭时入口/API/导出回注全部不生效、行为与历史版本一致。导出 .xlsx 时回注真实 OOXML 透视表零件(pivotCacheDefinition / pivotCacheRecords / pivotTableDefinition + 全套 rels,带 refreshOnLoad):Excel/WPS 打开导出件即识别为真透视表并按源区域重算原生布局;静态汇总结果仍在单元格里,不支持透视的查看器也能看。筛选器导出语义对齐 WPS:“= 具体值”写入页字段选中项(打开还原筛选状态),“多选/非空”映射为 multipleItemSelectionAllowed + 未选项隐藏(item@h),“全部”不写选中。原文件已有的透视表在 overlay 导出模式下从原件 zip 原样搬运整套零件(保持“打开→编辑→另存,透视表仍在”);rebuild 模式因结构可能被增删行列改动不搬运(退化为普通单元格)
  • 条件格式编辑:解析时全 6 类规则(突出显示单元格 cellIs / 公式 expression / 色阶 colorScale / 数据条 dataBar / 图标集 iconSet / 项目选取 top10)都渲染;conditionalFormat(还需 editable)后可新建/编辑/删除。工具栏 conditional-format 入口打开条件格式管理对话框(框架无关 DOM,三壳共用一份,UI 天然 1:1):列出当前表所有规则(每条可「编辑」「删除」)+「新建规则」。新建时选规则类型,弹出对应编辑器 —— cellIs:运算符(大于/小于/介于/不介于/等于/不等于/大于等于/小于等于)+ 值(介于类两个值)+ 命中格式(填充色 / 字体色 / 加粗);expression:自定义公式 + 命中格式;colorScale:双色/三色 + 各色标取色;dataBar:条颜色 + 渐变开关;iconSet:7 种图标集(三色交通灯 / 三向箭头 / 三符号 / 三色旗 / 四等评级 / 五等评级 / 五象限)+ 反向;top10:前/后 + 个数 + 百分比开关 + 命中格式。新建规则默认套到当前选区;编辑保留原区域。所有改动整体单次撤销(set-conditional 命令替换整张规则集),改完即时重渲。也可不开对话框直接调 API:getConditionalRules() / addConditionalRule(rule)(返回新 id)/ updateConditionalRule(id, patch) / removeConditionalRule(id) / setConditionalRules(rules) / openConditionalFormatDialog()导出 .xlsx 回写条件格式:rebuildoverlay 都回写(1.9.0 起 rebuild 不再丢条件格式);从文件解析、未在 app 内编辑过的规则按原始 OOXML 原样回写(保留色阶/数据条/图标集的 cfvo 阈值等不全建模字段,零退化),用户新建或编辑过的规则按模型重建(全 6 类)。整个功能由 conditionalFormat 配置开启(默认关闭),关闭时入口/API/导出回写全部不生效、行为与历史一致。当前限制:色阶/数据条/图标集的阈值(cfvo) 暂不在编辑器里微调(未编辑时靠原样回写保住,用户新建用默认阈值);文本包含等 OOXML 子类型用 cellIs/expression 表达。
  • SmartArt;形状仅支持 rect/roundRect/ellipse + 文本(复杂自定义几何按矩形近似)
  • .xls(旧 BIFF 二进制)/ 加密文件(给出友好提示)
  • 图表为 ECharts 近似,非像素级一致
  • 导出为位图(PNG/PDF 内嵌图片),非矢量;PDF 按页宽缩放后竖向分页

开发

npm install
npm run dev            # 本地预览(demo)
node scripts/gen-sample.mjs   # 生成 public/sample.xlsx 示例
npm run test           # 单元测试(node 环境,纯逻辑)
npm run test:e2e       # 真浏览器 e2e(Playwright):canvas 渲染 + jsPDF 导出 + 下载全链路
npm run typecheck      # 类型检查
npm run build          # 构建组件库(dist/)
npm run build:demo     # 构建 demo 站点

e2e 说明:npm run test:e2e 用 Playwright 起 dev 服务 + 无头 Chromium,加载示例 → 渲染 → 导出 PNG/位图PDF/矢量PDF,校验产物(PNG 魔数、%PDF、矢量 PDF 的文字操作符数量多于位图)。覆盖 node 单测做不到的真实 canvas/jsPDF 绘制。首次需 npx playwright install chromium 下载浏览器(本仓库 @playwright/test 固定 1.58.0 对应 chromium-1208)。Vue 3 / React demo 在 5300(//react.html),Vue 2 demo 在独立 5302(plugin-vue2 SFC 隔离);两个 dev server 都由 Playwright 自动拉起。三壳均有 e2e 覆盖。

文档 / 二开

  • EXTENDING.md —— 二开 / 扩展 API 手册(主题 :theme / 数据·渲染钩子 / 自定义编辑器 / 右键菜单 transform / 工具栏自定义项 / 分层 UI slots / 命令式 API / 导出·打印高级选项 / 插件 definePlugin)
  • ARCHITECTURE.md —— 包/入口、core 分层、数据流、ViewerController 桥接、"加功能改哪"
  • CONTRIBUTING.md —— 本地跑通、改动流程、不可破坏的硬约束
  • CHANGELOG.md / RELEASING.md —— 变更记录 / 发布清单
  • docs/编辑权限与只读边界.md —— EditableTarget 白名单 / DimTarget 尺寸多形态 / readOnlyCellStyle 视觉钩子 / permission-denied 事件 体系化说明(1.2.0)
  • docs/Vue2.md —— Vue 2 兼容子入口完整文档(1.3.0;ooxml-excel-editor/vue2 跟 Vue 3 / React 壳 ~100% 功能对齐)

React props/events 与 Vue 对齐(事件用 camelCase 回调:onRendered/onError/onCellClick/onSelectionChange/onSheetChange/onHyperlinkClick),命令式句柄 ExcelViewerHandle 与 Vue 组件 ref 同名方法一致。EXTENDING.md 里的插件 definePlugin 目前服务 Vue 壳;React 壳已可用全部 props/命令式 API/事件,插件 overlay 跨框架化在路线图中。

License

MIT


English

🔗 Live demo: https://ojadeveloper.github.io/ooxml-excel-editor/

A Vue 3 + Vue 2 + React high-fidelity .xlsx preview & edit component with a from-scratch parser and canvas renderer. Renders cells, number formats, merges, conditional formatting, images, charts (via ECharts), sparklines, comments, data validation, frozen panes, and supports selection / copy / hyperlinks. Read-only by default; set editable to enable editing — cell values / styles / column-row sizes / floating images / insert-delete rows-cols, with undo-redo, before/after full-snapshot events, dirty tracking + reset-to-original, swappable formula recalc engine, custom cell editors, and export back to .xlsx / JSON / CSV. Parsing runs in a Web Worker (with main-thread fallback). Only the framework (vue / react) is a peer dependency; exceljs / fflate / jspdf / hyperformula are bundled into dist (no manual install).

npm i ooxml-excel-editor vue
import { ExcelViewer } from 'ooxml-excel-editor'
import 'ooxml-excel-editor/style.css'

Headless / Node (no browser): use ooxml-excel-editor/core to parse .xlsx, extract data, edit and write back — no canvas needed. npm i ooxml-excel-editor only.

import { readFileSync, writeFileSync } from 'node:fs'
import { openWorkbook, sheetToJSON, setCellValue, workbookToXlsxBytes } from 'ooxml-excel-editor/core'

const src = readFileSync('input.xlsx')
const wb = await openWorkbook(src)              // accepts a Node Buffer directly
const rows = sheetToJSON(wb.sheets[0])          // array of row objects (display text)
setCellValue(wb.sheets[0], 1, 2, 123.45)
writeFileSync('out.xlsx', await workbookToXlsxBytes(wb, {
  fidelity: 'overlay',
  sourceBuffer: src.buffer.slice(src.byteOffset, src.byteOffset + src.byteLength),
}))            // workbookToXlsxBytes → Uint8Array (not a Blob)

Not available headless (need a browser canvas/DOM): image/PDF export, print(), the built-in cell editor, and component rendering. See the 中文 Node 用法 section for details.

See the API table above. MIT licensed.