ooxml-excel-editor 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,37 @@
2
2
 
3
3
  本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [1.15.0] - 2026-06-15
6
+
7
+ > 纯 Node(headless)用法闭环 —— 让"解析取数"与"高保真往返编辑"在无浏览器/无 canvas 的 Node 里好用。纯增量,默认行为不变、零回归。
8
+
9
+ ### 新增 — Node 友好的入口 / 出口(框架无关 core)
10
+
11
+ - **`openWorkbook(src)`**:一行门面 = `loadArrayBuffer` 归一化 + `parseWorkbook`,Node 可直接吃 `fs.readFileSync()` 的 **Buffer**(Buffer 是 Uint8Array 子类),浏览器侧仍接受 File/Blob/ArrayBuffer/URL。
12
+ - **`parseWorkbook` 放宽入参** 为 `ArrayBuffer | Uint8Array` 并在内部归一化 —— 老 ArrayBuffer 调用 100% 兼容,Node 传 Buffer/Uint8Array 不再 TS 报错或踩坑。
13
+ - **`workbookToXlsxBytes(wb, opts)`**:返回 `Uint8Array`(不是浏览器 `Blob`),纯 Node `fs.writeFileSync` 直接落盘;`fidelity: 'overlay' + sourceBuffer` 保真往返。`workbookToXlsxBlob` 改为它 + Blob 包装,签名/行为不变。
14
+ - **导出建表 API**:`jsonToWorkbook` / `isWorkbookModel` / `makeDefaultStyle`(及 `JsonInput`/`JsonLoadOptions` 类型)补进 core 出口 —— 可在 Node 从数据(2D 数组 / 对象数组)直建模型再 `workbookToXlsxBytes` 生成 .xlsx。四入口同源,各壳自动可见。
15
+
16
+ ### 文档 / 示例 / 测试
17
+
18
+ - README 新增 **「Node / 服务端 (headless) 用法」** 节(取数 / 高保真往返 / 数据建表三段可跑代码 + headless 不可用清单);英文区加对应段。EXTENDING.md 加 **「Headless / Node 安全 API 面」**(纯 Node 可用 vs 需浏览器的导出清单)。
19
+ - **修正过时安装文档**:`exceljs` / `fflate` / `jspdf` / `hyperformula` 1.3.2+ 已**内联进 dist**,README 各处 `npm i ... exceljs` → 去掉(只需装 framework;纯 Node 仅 `npm i ooxml-excel-editor`)。入口表 peer 列 + "exceljs 为 peer" 旧述一并更正。
20
+ - 新增 [`examples/node-extract.mjs`](./examples/node-extract.mjs) / [`examples/node-roundtrip.mjs`](./examples/node-roundtrip.mjs) 可跑示例;新增 `src/core/__tests__/node-headless.test.ts` 回归网(openWorkbook 吃 Buffer / 取数 / 往返 bytes / 数据建表,5 测)。
21
+
22
+ ## [1.14.1] - 2026-06-15
23
+
24
+ > 文档审计 + 入口出口修正(无运行时行为变更)。一次"保证接入/二开都没问题"的体检。
25
+
26
+ ### 修复 — 四入口出口同源(接入)
27
+
28
+ - **入口出口不一致 bug**:公式引擎工厂(`builtinFormulaEngineFactory` / `hyperFormulaEngineFactory` / `FUNCTION_NAMES` / `FormulaEngine` 类型)此前只在 `/core` 出口,**主入口 / `/react` / `/vue2` 都拿不到** —— 但 `:formula-engine` 是主组件上的 prop,文档让注入 `hyperFormulaEngineFactory`,实际 `import { hyperFormulaEngineFactory } from 'ooxml-excel-editor'` 会失败。`/react` 入口更是只导出组件 + hook,`parseWorkbook`/类型/`definePlugin` 全够不着。
29
+ - **修法**:主 / `/react` / `/vue2` 入口统一 `export * from core`(各自再加自己的组件)→ **四入口同源**,任一入口都拿到完整 core 公共 API(解析/读数据/类型/插件/导出/公式引擎工厂…),不再各维护清单致漂移。`CellStyleCtx` / `DataValidationRule` 补进 core 出口。typecheck + build + 419 单测验证。
30
+
31
+ ### 文档 — 使用 vs 二开 分离 + 准确性
32
+
33
+ - 抽出 **`EXTENDING.md`(二开 / 扩展 API 手册)**:主题 `:theme` / 数据·渲染钩子 / 自定义编辑器 / 右键菜单 transform / 工具栏自定义 / 分层 UI slots / 命令式 API / 导出·打印高级选项 / 插件 `definePlugin`。README 瘦身成**调用方**文档(装/用/props/编辑/导出),顶部留指路;深度二开看 ARCHITECTURE.md。
34
+ - README **具名导出表**重写(原列 5 项、实际 ~40 项 → 按 解析/读数据/格式/插件/公式引擎/导出/类型 分类 + 标注"四入口同源");**props 表**补 `toolbar` / `plugins` / `openLinks` + 数据验证说明;修跨章节断锚点。
35
+
5
36
  ## [1.14.0] - 2026-06-15
6
37
 
7
38
  > 新增**内置公式引擎**(MIT,零依赖)设为 recalc 默认引擎(取代 GPL 的 HyperFormula),+ **公式自动补全**。覆盖日常 ~60 个常用函数;需更全覆盖可注入 HyperFormula 或自研引擎。
package/README.md CHANGED
@@ -1,8 +1,13 @@
1
1
  # ooxml-excel-editor
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/ooxml-excel-editor.svg)](https://www.npmjs.com/package/ooxml-excel-editor)
4
+ [![npm downloads](https://img.shields.io/npm/dm/ooxml-excel-editor.svg)](https://www.npmjs.com/package/ooxml-excel-editor)
5
+ [![CI](https://github.com/ojaDeveloper/ooxml-excel-editor/actions/workflows/ci.yml/badge.svg)](https://github.com/ojaDeveloper/ooxml-excel-editor/actions/workflows/ci.yml)
6
+ [![license](https://img.shields.io/npm/l/ooxml-excel-editor.svg)](./LICENSE)
7
+
3
8
  > Vue 3 + **Vue 2** + React 高保真 **.xlsx 预览 / 编辑组件** —— Canvas 渲染,**默认只读预览,可选开启编辑**。从零实现解析与渲染,尽量还原微软 Excel 打开工作簿的观感。**三个壳 UI 1:1 对齐**(Vue 3 SFC 是标准,Vue 2 / React 复刻)。
4
9
 
5
- [English](#english) · 中文
10
+ **🔗 在线 demo(直接试用):https://ojadeveloper.github.io/ooxml-excel-editor/** · [English](#english) · 中文
6
11
 
7
12
  ## ⚡ 快速开始
8
13
 
@@ -59,43 +64,96 @@ const src = ref<File>() // 绑个 <input type="file" @change> 给它即可
59
64
 
60
65
  ## 安装
61
66
 
62
- 一个包,**四个子入口** —— 框架无关的 core 引擎被 Vue 3 / React 两个壳共享(`dist/core.js` 只打一份),Vue 2 因 SFC 编译器跟 Vue 3 冲突独立打包(内嵌 core)。按你的框架装对应 peer:
67
+ 一个包,**四个子入口** —— 框架无关的 core 引擎被 Vue 3 / React 两个壳共享(`dist/core.js` 只打一份),Vue 2 因 SFC 编译器跟 Vue 3 冲突独立打包(内嵌 core)。**只需按框架装对应 framework**,`exceljs` / `fflate` / `jspdf` / `hyperformula` 等重依赖 **1.3.2+ 已内联进 dist,无需手动装**:
63
68
 
64
69
  ```bash
65
70
  # Vue 3 项目
66
- npm i ooxml-excel-editor vue exceljs
71
+ npm i ooxml-excel-editor vue
67
72
 
68
73
  # React 项目
69
- npm i ooxml-excel-editor react react-dom exceljs
74
+ npm i ooxml-excel-editor react react-dom
70
75
 
71
76
  # Vue 2.6.x 或 2.7+ 项目 (1.3.0+) — 必装 @vue/composition-api (兼容 2.6 + 2.7)
72
- npm i ooxml-excel-editor vue@2.7 @vue/composition-api exceljs
77
+ npm i ooxml-excel-editor vue@2.7 @vue/composition-api
73
78
  # Vue 2.6.x 还需 main.js: Vue.use(require('@vue/composition-api').default)
74
79
 
75
- # 只解析 / 读数据 / 导出(不渲染 UI)
76
- npm i ooxml-excel-editor exceljs
77
-
78
- # echarts 可选:仅渲染图表时需要;jspdf 可选:仅导出 PDF 时需要
79
- npm i echarts jspdf
80
- # hyperformula 可选:仅开启编辑 + 公式重算(recalc)时需要
81
- npm i hyperformula
80
+ # Node / 只解析读数据 / 导出(不渲染 UI,无框架)
81
+ npm i ooxml-excel-editor
82
82
  ```
83
83
 
84
84
  四个入口:
85
85
 
86
- | import | 内容 | 需要的 peer | 体积 (gzip) |
86
+ | import | 内容 | 需要装的 framework | 体积 (gzip) |
87
87
  |---|---|---|---|
88
- | `ooxml-excel-editor` | **Vue 3** 组件 `<ExcelViewer>` (参考实现 Standard) | `vue@3` + `exceljs` | ~19 KB + 共享 chunks |
89
- | `ooxml-excel-editor/react` | **React** 组件 `<ExcelViewer>` (1:1 复刻 Vue 3) | `react` + `react-dom` + `exceljs` | ~11 KB + 共享 chunks |
90
- | `ooxml-excel-editor/vue2` | **Vue 2.6 / 2.7+** 组件 `<ExcelViewer>` (1:1 复刻 Vue 3) | `vue@2.6+` + `@vue/composition-api` + `exceljs` | ~124 KB (内嵌 core) |
91
- | `ooxml-excel-editor/core` | 框架无关引擎(解析/渲染/控制器/导出/读数据) | `exceljs` | ~1 KB + 共享 chunks |
88
+ | `ooxml-excel-editor` | **Vue 3** 组件 `<ExcelViewer>` (参考实现 Standard) | `vue@3` | ~19 KB + 共享 chunks |
89
+ | `ooxml-excel-editor/react` | **React** 组件 `<ExcelViewer>` (1:1 复刻 Vue 3) | `react` + `react-dom` | ~11 KB + 共享 chunks |
90
+ | `ooxml-excel-editor/vue2` | **Vue 2.6 / 2.7+** 组件 `<ExcelViewer>` (1:1 复刻 Vue 3) | `vue@2.6+` + `@vue/composition-api` | ~124 KB (内嵌 core) |
91
+ | `ooxml-excel-editor/core` | 框架无关引擎(解析/渲染/控制器/导出/读数据) | 无(纯 Node 也可用,见 [Node 用法](#node--服务端-headless-用法)) | ~1 KB + 共享 chunks |
92
92
 
93
- `exceljs` 必需;`vue` / `react` / `vue@2` 按框架三选一(均为可选 peer);`echarts` / `jspdf` / `hyperformula` 为**可选** peer —— 未装分别只影响"图表渲染""PDF 导出""公式重算",其余正常,且**绝不打包进你的产物**(运行时才动态加载)。
93
+ `vue` / `react` / `vue@2` 按框架三选一(均为可选 peer);`exceljs` / `fflate` / `jspdf` / `hyperformula` **已内联进 dist**(无需装、也**绝不重复进你的产物**,运行时才动态从 chunk 加载);`echarts` 是 external 依赖(npm 自动装,仅图表渲染才真正加载,避免主题 dual instance)。
94
94
 
95
95
  > **三壳 UI 1:1**: Vue 3 SFC 是参考实现 (Standard), Vue 2 / React 1:1 复刻视觉与交互 (工具栏 SVG 图标 / 下拉子菜单 / 公式栏 / 状态栏 / dialog / 浮层 / 演示 demo 全部对齐). 详见 [docs/Vue2.md](./docs/Vue2.md) 跟 Vue 3 的差异速查 + [CLAUDE.md](./CLAUDE.md) 第 7 中心原则。
96
96
 
97
97
  > 公式重算的引擎(1.14.0 起):默认是**内置 MIT 引擎**(零依赖,覆盖 ~60 常用函数,无许可证负担)。需要更全函数集时,`formulaEngine` prop 注入 **HyperFormula**(`hyperFormulaEngineFactory`,GPL-3.0/商业双授权,~395 函数)或自研引擎(实现 `FormulaEngine` 接口)。
98
98
 
99
+ ## Node / 服务端 (headless) 用法
100
+
101
+ 不渲染 UI、纯在 **Node**(无浏览器、无 canvas)里处理 .xlsx —— 走 `ooxml-excel-editor/core`(框架无关、ESM)。装包只需 `npm i ooxml-excel-editor`(`exceljs` 等已内联)。可跑示例见 [`examples/`](./examples)。
102
+
103
+ **适合 Node 的两件事**(比裸 `exceljs` 多了"显示文本渲染 / 合并 / 日期 / 富文本"的保真):
104
+
105
+ **① 解析取数** —— 拿"人看到的"数据(显示文本 / JSON / CSV):
106
+
107
+ ```ts
108
+ import { readFileSync } from 'node:fs'
109
+ import { openWorkbook, getSheetData, sheetToJSON, getCellText, toCsv } from 'ooxml-excel-editor/core'
110
+
111
+ // openWorkbook 直接吃 Node Buffer —— 不必手动转 ArrayBuffer
112
+ const wb = await openWorkbook(readFileSync('input.xlsx'))
113
+ const sheet = wb.sheets[0]
114
+
115
+ getCellText(sheet, 0, 0) // 单格显示文本(数字格式/日期已渲染,如 "2021年1月")
116
+ getSheetData(sheet, { format: true }) // 2D 数组(format:false 给原始值)
117
+ sheetToJSON(sheet) // 对象数组(首行当表头)
118
+ toCsv(sheet) // CSV 文本
119
+ ```
120
+
121
+ **② 高保真往返编辑** —— 打开真实 .xlsx → 程序化改值/样式 → 保样式回写(`overlay` 保留原件的样式/条件格式/图片/透视表,裸 exceljs 会丢):
122
+
123
+ ```ts
124
+ import { readFileSync, writeFileSync } from 'node:fs'
125
+ import { openWorkbook, setCellValue, applyStyleOverride, workbookToXlsxBytes } from 'ooxml-excel-editor/core'
126
+
127
+ const src = readFileSync('input.xlsx')
128
+ const wb = await openWorkbook(src)
129
+ const sheet = wb.sheets[0]
130
+
131
+ setCellValue(sheet, 1, 2, 123.45)
132
+ applyStyleOverride(sheet, 1, 2, { font: { bold: true, color: '#FF0000' } })
133
+
134
+ // workbookToXlsxBytes 返回 Uint8Array(不是浏览器 Blob),直接 fs 落盘
135
+ const bytes = await workbookToXlsxBytes(wb, {
136
+ fidelity: 'overlay',
137
+ sourceBuffer: src.buffer.slice(src.byteOffset, src.byteOffset + src.byteLength),
138
+ })
139
+ writeFileSync('output.xlsx', bytes)
140
+ ```
141
+
142
+ **③ 从数据建表**(可选;纯建表裸 `exceljs` 也行)—— `jsonToWorkbook` → `workbookToXlsxBytes`:
143
+
144
+ ```ts
145
+ import { writeFileSync } from 'node:fs'
146
+ import { jsonToWorkbook, workbookToXlsxBytes } from 'ooxml-excel-editor/core'
147
+
148
+ const wb = jsonToWorkbook(
149
+ [{ name: '张三', age: 25 }, { name: '李四', age: 30 }],
150
+ { sheetName: 'People' },
151
+ )
152
+ writeFileSync('new.xlsx', await workbookToXlsxBytes(wb))
153
+ ```
154
+
155
+ **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](./EXTENDING.md#headless--node-安全-api-面)。
156
+
99
157
  ## 使用
100
158
 
101
159
  ### Vue
@@ -307,8 +365,11 @@ viewer.value.getRangeData(viewer.value.getSelection()) // 取"我选中的"区
307
365
  | `readOnlyPrompt` | `'dialog' \| 'toast' \| 'none'` | 粘贴撞只读格的内置提醒(默认 `'dialog'`):`dialog` 弹窗**列出具体哪些格**只读 / `toast` 顶部气泡 / `none` 只发 `permission-denied` 事件。逐格精确(编辑模式下也可能有只读格) |
308
366
  | `cellImageFit` | `'fill' \| 'contain' \| 'cover'` | WPS 单元格内嵌图贴合方式(默认 `contain` 等比,与 WPS 渲染一致) |
309
367
  | `imageLightbox` | `boolean` | 图片点击放大灯箱(默认 `true`;只读单击图放大、编辑右键「查看大图」) |
368
+ | `toolbar` | `false \| Array<string \| ToolbarItem>` | 操作工具栏配置(默认 `['find','filter','sort']`)。内置 id 见 [操作工具栏](EXTENDING.md#操作工具栏可配置--可插件--响应式);`false` 不渲染。可混入自定义项 |
369
+ | `plugins` | `ExcelPlugin[]` | 插件数组(`definePlugin` 打包 theme/cellStyle/transformModel/events/overlay/toolbar/setup);见 [插件](EXTENDING.md#插件-defineplugin) |
370
+ | `openLinks` | `boolean` | 单击超链接是否自动打开(默认 `true`;`false` 只发 `@hyperlink` 事件,自己处理) |
310
371
 
311
- > 编辑相关 props 详见下方 [编辑](#编辑可选默认只读) 章节。
372
+ > 编辑相关 props 详见下方 [编辑](#编辑可选默认只读) 章节;数据验证(开 `editable` 后**编辑时拦截非法输入** + 列表型下拉选值)无需额外 prop,解析到的规则自动生效。
312
373
 
313
374
  | 事件 | 载荷 | 触发时机 |
314
375
  |---|---|---|
@@ -325,13 +386,21 @@ viewer.value.getRangeData(viewer.value.getSelection()) // 取"我选中的"区
325
386
 
326
387
  ### 具名导出
327
388
 
389
+ > **四个入口同源**:`ooxml-excel-editor`(Vue 3)、`/react`、`/vue2`、`/core` 都 **re-export 同一套框架无关 core 公共 API** —— 任一入口都能拿到下表全部(各入口再各自加自己的组件)。所以 React/Vue2 用方不必绕到 `/core`。
390
+
328
391
  | 导出 | 说明 |
329
392
  |---|---|
330
- | `ExcelViewer` | 预览/编辑组件 |
331
- | `parseWorkbook(buffer)` | `ArrayBuffer Promise<WorkbookModel>`(优先 Web Worker) |
332
- | `loadArrayBuffer(src)` | 多种输入归一化为 `ArrayBuffer` |
333
- | `default` | Vue 插件(`app.use`) |
334
- | 类型 | `WorkbookModel` / `SheetModel` / `CellModel` / `CellStyle` / `MergeRange` / `ConditionalRule` / `ChartSpec` / `ImageAnchor` / `CssColor` / `ExcelSource` |
393
+ | `ExcelViewer` | 预览/编辑组件(各框架入口各自的) |
394
+ | `default` | Vue 3 / Vue 2 入口默认导出 = Vue 插件(`app.use`) |
395
+ | **解析/加载** | `openWorkbook(src)`(一行门面 = 归一化 + 解析,Node/浏览器通用)· `parseWorkbook(buffer)`(`ArrayBuffer\|Uint8Array → Promise<WorkbookModel>`,优先 Worker)· `loadArrayBuffer(src)`(多种输入归一)· `jsonToWorkbook(data)`(数据直建模型) |
396
+ | **读数据 API** | `getCellValue` / `getCellText` / `getCellStyle` / `getSheetData` / `getRangeData` / `sheetToJSON` / `getWorkbookJSON` / `cellDisplayText` |
397
+ | **格式/工具** | `formatValue`(数字格式)· `cellKey` · `colIndexToLetters` |
398
+ | **插件 / 主题** | `definePlugin` · `DEFAULT_THEME` / `mergeTheme` |
399
+ | **公式引擎**(1.14.0) | `builtinFormulaEngineFactory`(默认,MIT)· `hyperFormulaEngineFactory`(HyperFormula,GPL/商业)· `FUNCTION_NAMES`(已支持函数名)· `BuiltinFormulaEngine` |
400
+ | **导出工具** | `workbookToXlsxBytes`(→ `Uint8Array`,**纯 Node 落盘用**)· `workbookToXlsxBlob`(→ `Blob`,浏览器下载用)· `toCsv` / `toWorkbookJson` · `canvasToBlob` / `canvasToDataURL` / `downloadBlob`(后三者需浏览器) |
401
+ | **类型** | `WorkbookModel` / `SheetModel` / `CellModel` / `CellStyle` / `CellStyleOverride` / `MergeRange` / `ConditionalRule` / `DataValidationRule` / `ChartSpec` / `ImageAnchor` / `PivotTableModel` / `CssColor` / `ExcelSource` / `ViewerApi` / `ExcelPlugin` / `FormulaEngine` / `FormulaEngineFactory` / `EditConfig` / 导出选项类型(`PdfExportOptions`/`ImageExportOptions`/…)等 |
402
+
403
+ > 想看完整出口清单见 [`src/core/index.ts`](src/core/index.ts);深度二开(直接用 `ViewerController` / `CanvasRenderer` / `EditController` / 模型 mutations 等内部件)也都从这些入口导出,见 [ARCHITECTURE.md](ARCHITECTURE.md)。
335
404
 
336
405
  ## 编辑(可选,默认只读)
337
406
 
@@ -371,7 +440,7 @@ viewer.value.getRangeData(viewer.value.getSelection()) // 取"我选中的"区
371
440
  | 撤销/进编辑 | `undo()` · `redo()` · `canUndo()` · `canRedo()` · `beginEdit(row,col)` · `cancelEdit()` · `isEditing()` · `getEditingCell()` |
372
441
  | 公式栏 | `getCellEditString()`(活动格可编辑字符串:公式→`=…`,数值→原始数字串) · `canEditActiveCell()` · `commitActiveCellValue(value, move?)`(顶部 Fx 公式栏可编辑并与单元格联动,底层即用这套) |
373
442
  | 查询/状态 | `getCellSnapshot(row,col)` · **`inspectCell(row,col)`**(全息体检:snapshot + 合并区 + 浮动图覆盖 + WPS 内嵌图 + 数据验证 + 条件格式命中 + 链接/批注) · `isDirty()` · `resetToOriginal()` · `isRecalcReady()` · `getVirtualExtent()`(当前虚拟行列范围) |
374
- | 导出 | `exportXlsx/downloadXlsx` · `exportJson/downloadJson` · `exportCsv/downloadCsv`(见 [导出](#导出--打印)) |
443
+ | 导出 | `exportXlsx/downloadXlsx` · `exportJson/downloadJson` · `exportCsv/downloadCsv`(见 [导出](EXTENDING.md#导出--打印)) |
375
444
 
376
445
  所有写操作(含拖拽改宽高/移图)**统一进撤销栈**、发对应事件、翻**脏标记**;`resetToOriginal()` 一键放弃全部修改、还原到刚加载的原件。
377
446
 
@@ -452,256 +521,19 @@ const myEditor: EditorResolver = (cell, pos) => {
452
521
  ### v1 已知限制
453
522
 
454
523
  - 增删行列**会自动重写公式引用**(`=A5` 上方插一行 → `=A6`,删被引用行 → `#REF!`,含跨表 `Sheet1!A5`)。
455
- - 写回 .xlsx 默认从模型重建,丢 VBA/工作表保护/复杂 DrawingML 等;需要更高保真可用 `exportXlsx({ fidelity: 'overlay' })` **重载原件叠加编辑**(见 [导出保真边界](#导出--打印))。
456
-
457
- ## 扩展 API(不改源码定制)
458
-
459
- 组件按"分层可扩展"设计 —— 用内置 props/events/slots/命令式 API 即可定制外观、行为、数据,并在网格上叠自己的 UI。
460
-
461
- ### 外观主题 `:theme`
462
- ```vue
463
- <ExcelViewer :src="file" :theme="{ gridLine: '#e8e8e8', selBorder: '#e91e63', selFill: 'rgba(233,30,99,.1)' }" />
464
- ```
465
- 可覆盖:`headerBg / headerText / headerLine / gridLine / selBorder / selFill`(见 `ViewerTheme` / `DEFAULT_THEME` 导出)。
466
-
467
- ### 数据 / 渲染钩子
468
- ```vue
469
- <ExcelViewer
470
- :src="file"
471
- :transform-model="(wb) => { wb.sheets[0].name = '改过的名字'; return wb }"
472
- :cell-style="(cell) => typeof cell.raw === 'number' && cell.raw < 0 ? { font: { color: '#d00' } } : undefined"
473
- />
474
- ```
475
- - `transformModel(wb)`:解析后、渲染前改模型(返回新模型或就地改)。
476
- - `cellStyle(cell, {row,col})`:按条件覆盖单元格样式(`font/fill/borders` 浅合并)。
477
-
478
- ### 事件
479
- | 事件 | 载荷 |
480
- |---|---|
481
- | `cell-click` / `cell-dblclick` | `{ row, col, text }` |
482
- | `selection-change` | `{ range, active }` |
483
- | `sheet-change` | `{ index, name }` |
484
- | `hyperlink-click` | `{ url, cell }`(配 `:open-links="false"` 接管跳转) |
485
- | `rendered` / `error` / `progress` | 见上 |
486
-
487
- ### 命令式 API(模板 ref)
488
- `load(src)` / `getWorkbook()` / `getActiveSheet()` / `setActiveSheet(i)` / `getSelection()` / `setSelection(range)` / `scrollToCell(row,col,{select?})` / `rectOf(row,col)` / `rectOfRange(range)` / `redraw()`,以及下面的导出方法;**编辑命令式 API**(`editCell`/`setStyle`/`createPivotTable`/`openPivotTableDialog`/`createPivotTableFromSelection`/`getConditionalRules`/`addConditionalRule`/`openConditionalFormatDialog`/`setSelectionNumberFormat`/`openNumberFormatDialog`/`getCellComment`/`setCellComment`/`openCommentEditor`/`replaceCurrent`/`replaceAll`/`insertRows`/`undo`/`exportXlsx`…)见 [编辑](#编辑可选默认只读)。
489
-
490
- ```ts
491
- // 需组件开启 :pivot-table="true"(默认关闭)+ :editable="true"
492
- viewer.value?.createPivotTable({
493
- sourceRange: { top: 0, left: 0, bottom: 20, right: 4 },
494
- output: { kind: 'new-sheet' },
495
- layout: {
496
- rows: [0],
497
- columns: [1],
498
- filters: [{ field: 2, mode: 'equals', value: '华东' }],
499
- values: [{ field: 3, summary: 'sum' }],
500
- },
501
- })
502
- ```
503
-
504
- ### 导出 / 打印
505
- 内置工具栏右侧有「导出 ▾」菜单(PNG / PDF / 打印 / **导出设置…**)。「导出设置…」打开对话框,可选**范围**(当前选区 / 当前表 / 全部表)、清晰度、是否含行列号/网格线、纸张方向。也可命令式调用(模板 ref / 插件 `viewer`):
506
-
507
- | 方法 | 说明 |
508
- |---|---|
509
- | `exportImage(opts?)` | → `Promise<Blob>`,当前/指定表渲染为图片(png/jpeg/webp) |
510
- | `downloadImage(opts?)` | 导出图片并触发下载 |
511
- | `exportPdf(opts?)` | → `Promise<Blob>`,分页 PDF(需可选依赖 `jspdf`) |
512
- | `downloadPdf(opts?)` | 导出 PDF 并触发下载 |
513
- | `print(opts?)` | 打开系统打印对话框(可另存为 PDF,零依赖) |
514
- | `exportXlsx(opts?)` / `downloadXlsx(opts?)` | → `Promise<Blob>` / 下载 **.xlsx**(默认从模型重建;`{fidelity:'overlay'}` 重载原件叠加,保真更高;需可选依赖 `exceljs`) |
515
- | `exportJson(opts?)` / `downloadJson(opts?)` | → `string` / 下载 **.json**(各表首行作 key 的对象数组,raw 类型值) |
516
- | `exportCsv(opts?)` / `downloadCsv(opts?)` | → `string` / 下载 **.csv**(格式化显示值,带 UTF-8 BOM;`opts.target` 指定表,默认当前表) |
517
-
518
- **编辑后导出(.xlsx / JSON / CSV)** —— 三种格式都建在**同一份内存数据层**上(`WorkbookModel`:读 `data-access` + 写 `mutations`),无需为每种格式各写一遍解析,故与渲染所见、彼此之间天然一致。JSON 默认输出 raw 类型值(`{format:true}` 可改显示串);CSV 默认输出格式化显示值(WYSIWYG)。
519
-
520
- **.xlsx 两种保真模式**:
521
-
522
- - **`rebuild`(默认)** —— **从编辑后模型完整重建**:遍历 cells/公式/样式(字体/填充/边框/对齐/数字格式)/合并/行高列宽/冻结/图片/**条件格式**(1.9.0 起)/**批注**(1.11.0 起) 重组成 ExcelJS 工作簿。干净、所见即所得,但**丢失**原件里我们不建模的部分(数据验证、VBA 宏、工作表保护、复杂 DrawingML/图表 等)。图片导出区分 oneCell/twoCell 锚点 + 子格 EMU 偏移。
523
- - **`overlay`(`exportXlsx({ fidelity: 'overlay' })`)** —— **重载原始 .xlsx,只把编辑后的 值/样式/合并/行高列宽/冻结 叠加上去**,**保留** ExcelJS 能往返的其余部分(条件格式 / 数据验证 / 打印设置 / 定义名 / 图表 等)。组件加载时自动留存原件字节供其使用;缺原件时自动回退 `rebuild`。注:overlay 不反映**增删行列 / 图片**编辑(那类用 `rebuild`)。
524
-
525
- 公共选项:`target`(`'active'`(默认)/`'all'`/索引/索引数组)、`range`(限定单元格区域)、`scale`(清晰度,默认 2)、`includeHeaders`、`gridlines`、`background`;PDF/打印另有 `format`(a4/a3/letter/`[宽,高]mm`)、`orientation`、`margin`(mm)、`fitToWidth`。
526
-
527
- **长任务进度 + 取消 + 内置遮罩(P1 + P1.5)** —— **两层**叠加,默认开箱即用,可逐层覆盖:
528
-
529
- #### ① Core 层(协议):`onProgress` + `signal`
530
- 所有导出方法(PNG / PDF / XLSX / Print)+ 选区图片批量互转 统一接:
531
- ```ts
532
- const ctrl = new AbortController()
533
- try {
534
- await viewer.value.downloadPdf({
535
- target: 'all',
536
- onProgress: (p) => console.log(p.stage, p.ratio, p.label), // 'render'/'compose'/'paginate'/'write'/'zip'/'convert'
537
- signal: ctrl.signal,
538
- })
539
- } catch (e) {
540
- if ((e as Error).name === 'AbortError') console.log('用户取消')
541
- else throw e
542
- }
543
- ctrl.abort() // 任意时刻取消
544
- ```
545
- 导出全链路在调度点 `await yieldToEvent()` 让出 UI(防假死)+ 调度前 `checkAborted(signal)`(立刻中断)。`ExcelJS.writeBuffer` / `jsPDF` 内部仍是黑盒(`zip`/`write` 阶段),那两段无法细分,但全程都有可视进度。
546
-
547
- #### ② Shell 层(UI):**内置居中模态**(默认开)
548
- **不传任何参数**调 `viewer.downloadPdf()` 等异步方法,壳自动建 `AbortController` + 接 `onProgress` → 显示**居中模态**(stage 标签 + 进度条 + 取消按钮)。用户传入的 `onProgress`/`signal` **仍正常链回调**(并存,不冲突)。
549
-
550
- #### ③ 关闭 / 覆盖
551
- | 需求 | 做法 |
552
- |---|---|
553
- | 完全关掉内置遮罩(纯回调) | `<ExcelViewer :export-progress="false">`(Vue)/ `exportProgress={false}` (React) |
554
- | 自渲染(用 Element Plus / Ant Design 等自家组件) | **Vue**:`<template #export-progress="{ state, busy, cancel }">…</template>` 插槽;**React**:`renderExportProgress={({state,busy,cancel}) => <YourModal …/>}` |
555
- | 既要内置又自动注入跟踪 | 默认行为已是 —— 用户传 `{ onProgress, signal }` 仍被链回调,内置 UI 也照常显示 |
556
-
557
- 覆盖矩阵:**导出**(PDF / PNG / XLSX / Print)、**选区图片批量互转**(P2,壳侧 1.2.0 起返 `Promise<number>` 以接遮罩)。**不包含**:文件解析(parsing 有独立的顶栏进度条,与本遮罩分开)、`copySelection` / `setStyle` 等瞬时操作、模板样式 overlay(P3 重设计后是同步纯函数,耗时可忽略)。
558
-
559
- **默认还原 OOXML 原生页面设置** —— PDF/打印时,未显式指定的 `format`/`orientation`/`margin`/`fitToWidth` 自动取自工作表的 `pageSetup`(纸张、方向、页边距、适应页面/缩放),并应用**打印区域**(默认导出范围)与**打印标题行/列**(每页顶部/左侧重复)。显式传入的选项始终覆盖之。
560
-
561
- **分页** —— `fitToWidth: true`(默认)把整表缩放到页宽、只竖向跨页;`fitToWidth: false`(或工作表未设"适应页面")按自然尺寸×缩放,**宽表横向跨页 + 高表竖向跨页**(像 Excel 的页矩阵,顺序"先下后右"),此时打印标题列在每张横向页左侧重复。
562
-
563
- **`beforeRenderPage` 扩展钩子** —— 每页贴图后调用,拿到 `jsPDF` 实例画页眉/页脚/水印/页码:
564
- ```ts
565
- const viewer = ref() // <ExcelViewer ref="viewer" />
566
- await viewer.value.downloadPdf({
567
- target: 'all',
568
- beforeRenderPage: ({ doc, pageIndex, pageCount, pageWidth, pageHeight, margin, sheetName }) => {
569
- doc.setFontSize(9); doc.setTextColor(120)
570
- doc.text(sheetName, margin.left, pageHeight - 5)
571
- doc.text(`第 ${pageIndex + 1} / ${pageCount} 页`, pageWidth - margin.right, pageHeight - 5, { align: 'right' })
572
- doc.setFontSize(56); doc.setTextColor(230)
573
- doc.text('PREVIEW', pageWidth / 2, pageHeight / 2, { align: 'center', angle: 30 }) // 水印
574
- },
575
- })
576
- ```
577
- 打印另有 `title` / `headerHtml` / `footerHtml`(每页 HTML 片段)。
578
- > 图片/图表/形状是 DOM 叠加层,导出时会自动合成到底图;"导出全部表"中非当前表的图表需 `echarts` 可用才能离屏渲染。
579
-
580
- #### 矢量 PDF(文字可选可搜)
581
-
582
- 两种 PDF 并存,工具栏菜单有「位图 / 矢量」两项,API 用 `vector` 切换:
583
- ```ts
584
- await viewer.value.downloadPdf({ vector: true })
585
- ```
586
- - **位图 PDF**(默认):整表贴图,完整还原观感。
587
- - **矢量 PDF**:逐格用真文字 + 矢量填充/边框绘制 —— 文字**可选中、可搜索、放大清晰、文件更小**。条件格式(背景色/数据条/图标)也走矢量绘制;仅**迷你图、旋转文字、富文本**这几类格会自动从底图**裁小图兜底**(内容不丢)。
588
-
589
- **中文字体** —— jsPDF 内置字体只认拉丁/数字。用 `configureDoc(doc)` 钩子注册中文 TTF 即可全矢量;不注册时,含中文的单元格自动转为该格小图(清晰但不可选):
590
- ```ts
591
- await viewer.value.downloadPdf({
592
- vector: true,
593
- configureDoc: (doc) => {
594
- doc.addFileToVFS('NotoSansSC.ttf', base64Ttf) // 你的中文字体(建议子集化)
595
- doc.addFont('NotoSansSC.ttf', 'NotoSC', 'normal')
596
- doc.setFont('NotoSC') // 设为默认 → 中文也走矢量
597
- },
598
- })
599
- ```
600
- > 提示:中文表格若不注册字体,矢量模式会产生很多小图、文件偏大且较慢 —— 注册一个子集字体即可全矢量。
601
-
602
- ### 右键菜单(Plan C:三层开放)
603
-
604
- 默认 `editable` 时显示内置菜单(复制/粘贴/插入/删除/合并/拆分/自动换行/清除内容 + WPS 图片互转)。三种覆盖方式可同时使用:
605
-
606
- **① 加 / 减 / 重排内置项** —— 用 `:contextMenu` 传 transform:
607
- ```vue
608
- <ExcelViewer
609
- :context-menu="(ctx, items) => [
610
- ...items,
611
- { separator: true },
612
- { label: `导出此格 PDF (${ctx.activeCell.row + 1},${ctx.activeCell.col + 1})`, action: () => viewer.downloadPdf() },
613
- ]"
614
- />
615
- ```
616
- - `ctx`: `{ range, single, activeCell, sheet, workbook, editable }` — 当前选区 + 活动格 + 模型句柄
617
- - `items`: 内置 `MenuItem[]`(`{label, action, disabled, separator}`)—— 加、过滤、重排,返回新数组生效;返 `undefined` / `void` 用原样
618
-
619
- **② 接管渲染**(用自家 UI 框架的菜单,如 Element Plus / Radix / Headless UI):
620
- ```vue
621
- <ExcelViewer
622
- :context-menu="false" <!-- 关闭内置弹层(事件仍触发) -->
623
- @before-context-menu="(p) => p.preventDefault()"
624
- @context-menu="(p) => myMenu.show(p.x, p.y, p.items)"
625
- />
626
- ```
627
- - `@before-context-menu` 在内置弹出前触发;调 `payload.preventDefault()` 取消内置;`:contextMenu="false"` 等价于自动 preventDefault
628
- - `@context-menu` 在内置弹出后(或被 prevent 后)触发,拿到 `{ x, y, ctx, items }` —— **总会触发**,自渲染只需监听这个事件
629
- - React:`onBeforeContextMenu` / `onContextMenuShow` 接同形 payload
630
-
631
- **③ 命令式打开 / 关闭**(键盘 Shift+F10、工具栏触发、跨层调用):
632
- ```ts
633
- viewer.openContextMenu(clickX, clickY) // 按当前选区算内置 items
634
- viewer.openContextMenu(clickX, clickY, customItems) // 直接喂自定义 items
635
- viewer.closeContextMenu()
636
- ```
637
-
638
- **插件贡献**:`definePlugin({ contextMenu: (ctx, items) => [...] })` —— 多插件按数组顺序串行(后者拿前者输出),组件 `:contextMenu` prop 最后覆盖,顺序固定 `内置 → 插件 → prop`。
639
-
640
- `MenuItem` / `ContextMenuCtx` / `ContextMenuTransform` 全部从 `ooxml-excel-editor/core` 导出(TS 类型完整)。
524
+ - 写回 .xlsx 默认从模型重建,丢 VBA/工作表保护/复杂 DrawingML 等;需要更高保真可用 `exportXlsx({ fidelity: 'overlay' })` **重载原件叠加编辑**(见 [导出保真边界](EXTENDING.md#导出--打印))。
641
525
 
642
- ### 操作工具栏(可配置 / 可插件 / 响应式)
643
- 顶栏(文件名/导出/缩放)下方有一行**操作工具栏**,内置 `find`/`filter` 按钮默认显示。用 `:toolbar` 配置:
644
- ```vue
645
- <ExcelViewer :src="file" /> <!-- 默认: find + filter + sort -->
646
- <ExcelViewer :toolbar="['find','filter','separator','zoom','export']" /> <!-- 控制项/顺序/分隔 -->
647
- <ExcelViewer :toolbar="false" /> <!-- 隐藏整条 -->
648
- ```
649
- - **内置 id**:`find`(查找)、`filter`(切换自动筛选 —— 文件没设也能点出下拉)、`sort`(按活动单元格所在列升序/降序;未开启自动筛选时会先按选区/已用区建立范围)、`clear-filter`(清除筛选,无筛选时禁用)、`copy`(复制选区)、`pivot-table`(透视表入口:选中带表头数据区后选择生成位置,可输出到现有工作表单元格或新建工作表;创建后打开 WPS 风格右侧字段面板,需 `pivotTable` + `editable`,功能未开启时不渲染)、`conditional-format`(条件格式管理入口:列出当前表规则可删/可编辑 + 新建全 6 类规则,需 `conditionalFormat` + `editable`,功能未开启时不渲染)、`number-format`(数字格式编辑入口:分类 + 预览 + 自定义格式代码,需 `editable`)、`format-painter`(格式刷:采样源格样式刷到目标,需 `editable`)、`wrap-text`(自动换行 toggle,WPS 风格,需 `editable`)、`image-tools`(图片工具 ▾:选区/整表/整列 浮动 ⇄ 嵌入互转,需 `editable`)、`template`(模板 ▾:仅 JSON / 模型数据源下生效;导入 .xlsx 当样式捐赠者;xlsx 数据源下禁用)、`freeze`(冻结/取消)、`zoom`(缩放下拉)、`export`(导出/打印下拉)、`'separator'`/`'|'`(分隔线)。
650
- - **富项类型**(`ToolbarItem`):`type:'separator'` 分隔线;`items: ToolbarItem[]` 变下拉子菜单;`disabled?(viewer)` 禁用态;`iconSvg`(内联 SVG,优先于 `icon` emoji)/ `icon` / `label` / `title` / `onClick(viewer)` / `active?(viewer)`。
651
- - **响应式溢出**:宽度不足时,放不下的项自动折叠进「⋯ 更多」下拉。
652
- - **插件贡献**:`ExcelPlugin.toolbar: ToolbarItem[]`,插件加载即追加(opt-in)。
653
- - 内置图标用极简线性 **SVG**(跨平台一致);`filter` 按钮让筛选**看得见**,不必依赖文件自带 autofilter。
654
-
655
- ### 分层 UI(slots)
656
- 具名 slot:`header`(顶栏)/ `toolbar`(作用域 `{ items }`,替换整条操作栏)/ `statusbar` / `loading` / `error` / `empty`(缺省用内置)。
657
- **作用域 `overlay` slot** —— 在格子上叠自己的 Vue 组件,随滚动/缩放跟随:
658
- ```vue
659
- <ExcelViewer :src="file">
660
- <template #overlay="{ rectOf, tick }">
661
- <!-- tick 变化触发重算;rectOf(row,col) 给当前屏幕矩形 -->
662
- <button v-if="rectOf(2,1)" :style="posStyle(rectOf(2,1), tick)" @click="...">★</button>
663
- </template>
664
- </ExcelViewer>
665
- ```
666
- 覆盖层容器 `pointer-events:none`(滚动穿透),子元素自动 `pointer-events:auto`(可点)。
526
+ ## 扩展 / 二开
667
527
 
668
- ### 插件 `definePlugin`
669
- 把上面所有扩展点(主题/数据钩子/渲染钩子/事件/overlay/命令式 API)打包成一个插件,`:plugins` 分发;多个插件按数组顺序合并,组件自身 props 最后覆盖。
670
- ```ts
671
- import { definePlugin } from 'ooxml-excel-editor'
672
-
673
- const highlightNegatives = definePlugin({
674
- name: 'highlight-negatives',
675
- theme: { selBorder: '#e91e63' },
676
- cellStyle: (c) => (typeof c.raw === 'number' && c.raw < 0 ? { font: { color: '#d00' } } : undefined),
677
- events: { 'cell-click': (p) => console.log('clicked', p) },
678
- overlay: ({ rectOf }) => {
679
- const r = rectOf(0, 0)
680
- if (!r) return null
681
- const el = document.createElement('div') // 返回 DOM(框架无关,Vue/React 通用)
682
- el.textContent = '⚑'
683
- Object.assign(el.style, { position: 'absolute', left: r.x + 'px', top: r.y + 'px' })
684
- return el
685
- },
686
- setup: ({ viewer, on }) => {
687
- on('selection-change', (s) => console.log(s))
688
- // viewer.setSelection(...) / viewer.getWorkbook() ...
689
- return () => {/* 清理 */}
690
- },
691
- })
692
- ```
693
- ```vue
694
- <ExcelViewer :src="file" :plugins="[highlightNegatives]" />
695
- ```
696
- 插件字段:`theme` / `transformModel` / `cellStyle` / `events`(事件→处理器) / `overlay`(返回 **DOM 节点**,随滚动跟随) / `toolbar`(贡献操作栏按钮 `ToolbarItem[]`) / `setup(ctx)`(拿 `viewer` 命令式 API、`on()` 订阅事件,返回可选清理函数)。
528
+ 不改源码就能定制外观(`:theme`)、数据/渲染钩子(`transformModel`/`cellStyle`)、自定义编辑器(`:editor`)、右键菜单 transform、操作工具栏自定义项、分层 UI slots、命令式 API、导出/打印高级选项、以及**插件**(`definePlugin` 打包多种扩展点、跨框架可用)—— 完整 API 见 **[EXTENDING.md(二开 / 扩展 API 手册)](EXTENDING.md)**。
697
529
 
698
- > **跨框架**:插件全字段框架无关,**同一份 `definePlugin` 在 Vue 和 React 壳通用**(`overlay` 返回 DOM 而非 VNode)。React 用法:`<ExcelViewer plugins={[myPlugin]} />`。
530
+ 想了解内部结构 / 在哪改代码:见 **[ARCHITECTURE.md](ARCHITECTURE.md)**。
699
531
 
700
532
  ## 浏览器支持
701
533
 
702
534
  现代浏览器(Chrome/Edge 80+、Safari 15+、Firefox 114+,需支持 Canvas / ResizeObserver)。
703
535
 
704
- > **解析线程**:发布的组件库在**主线程**解析(`exceljs` peer 依赖,不重复打包)。本仓库的 demo/dev 额外启用了 **Web Worker** 解析(大文件不卡 UI)。如果你的应用要处理很大的文件,可直接用导出的 `parseWorkbook` 包进你自己的 Worker。
536
+ > **解析线程**:发布的组件库在**主线程**解析(`exceljs` 1.3.2+ 已内联进 dist chunks,运行时动态加载,不重复进你的产物)。本仓库的 demo/dev 额外启用了 **Web Worker** 解析(大文件不卡 UI)。如果你的应用要处理很大的文件,可直接用导出的 `parseWorkbook` 包进你自己的 Worker。
705
537
 
706
538
  ## 范围边界
707
539
 
@@ -731,13 +563,14 @@ npm run build:demo # 构建 demo 站点
731
563
 
732
564
  ## 文档 / 二开
733
565
 
566
+ - **[EXTENDING.md](./EXTENDING.md) —— 二开 / 扩展 API 手册**(主题 `:theme` / 数据·渲染钩子 / 自定义编辑器 / 右键菜单 transform / 工具栏自定义项 / 分层 UI slots / 命令式 API / 导出·打印高级选项 / 插件 `definePlugin`)
734
567
  - [ARCHITECTURE.md](./ARCHITECTURE.md) —— 包/入口、core 分层、数据流、`ViewerController` 桥接、"加功能改哪"
735
568
  - [CONTRIBUTING.md](./CONTRIBUTING.md) —— 本地跑通、改动流程、不可破坏的硬约束
736
569
  - [CHANGELOG.md](./CHANGELOG.md) / [RELEASING.md](./RELEASING.md) —— 变更记录 / 发布清单
737
570
  - [docs/编辑权限与只读边界.md](./docs/编辑权限与只读边界.md) —— **EditableTarget 白名单 / DimTarget 尺寸多形态 / readOnlyCellStyle 视觉钩子 / permission-denied 事件** 体系化说明(1.2.0)
738
571
  - [docs/Vue2.md](./docs/Vue2.md) —— **Vue 2 兼容子入口**完整文档(1.3.0;`ooxml-excel-editor/vue2` 跟 Vue 3 / React 壳 ~100% 功能对齐)
739
572
 
740
- > **React props/events** 与 Vue 对齐(事件用 camelCase 回调:`onRendered`/`onError`/`onCellClick`/`onSelectionChange`/`onSheetChange`/`onHyperlinkClick`),命令式句柄 `ExcelViewerHandle` 与 Vue 组件 ref 同名方法一致。上面「扩展 API」中的**插件 `definePlugin`** 目前服务 Vue 壳;React 壳已可用全部 props/命令式 API/事件,插件 overlay 跨框架化在路线图中。
573
+ > **React props/events** 与 Vue 对齐(事件用 camelCase 回调:`onRendered`/`onError`/`onCellClick`/`onSelectionChange`/`onSheetChange`/`onHyperlinkClick`),命令式句柄 `ExcelViewerHandle` 与 Vue 组件 ref 同名方法一致。[EXTENDING.md](./EXTENDING.md) 里的**插件 `definePlugin`** 目前服务 Vue 壳;React 壳已可用全部 props/命令式 API/事件,插件 overlay 跨框架化在路线图中。
741
574
 
742
575
  ## License
743
576
 
@@ -748,10 +581,12 @@ MIT
748
581
  <a name="english"></a>
749
582
  ## English
750
583
 
751
- A **Vue 3 + 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). `vue` / `react` / `exceljs` are peer dependencies; `echarts` / `jspdf` / `hyperformula` are optional peers (charts / PDF / formula recalc).
584
+ **🔗 Live demo: https://ojadeveloper.github.io/ooxml-excel-editor/**
585
+
586
+ 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).
752
587
 
753
588
  ```bash
754
- npm i ooxml-excel-editor vue exceljs
589
+ npm i ooxml-excel-editor vue
755
590
  ```
756
591
 
757
592
  ```ts
@@ -759,4 +594,22 @@ import { ExcelViewer } from 'ooxml-excel-editor'
759
594
  import 'ooxml-excel-editor/style.css'
760
595
  ```
761
596
 
597
+ **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.
598
+
599
+ ```ts
600
+ import { readFileSync, writeFileSync } from 'node:fs'
601
+ import { openWorkbook, sheetToJSON, setCellValue, workbookToXlsxBytes } from 'ooxml-excel-editor/core'
602
+
603
+ const src = readFileSync('input.xlsx')
604
+ const wb = await openWorkbook(src) // accepts a Node Buffer directly
605
+ const rows = sheetToJSON(wb.sheets[0]) // array of row objects (display text)
606
+ setCellValue(wb.sheets[0], 1, 2, 123.45)
607
+ writeFileSync('out.xlsx', await workbookToXlsxBytes(wb, {
608
+ fidelity: 'overlay',
609
+ sourceBuffer: src.buffer.slice(src.byteOffset, src.byteOffset + src.byteLength),
610
+ })) // workbookToXlsxBytes → Uint8Array (not a Blob)
611
+ ```
612
+
613
+ Not available headless (need a browser canvas/DOM): image/PDF export, `print()`, the built-in cell editor, and component rendering. See the 中文 [Node 用法](#node--服务端-headless-用法) section for details.
614
+
762
615
  See the API table above. MIT licensed.