ooxml-excel-editor 1.3.3 → 1.7.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/README.md +27 -8
  3. package/dist/chunks/index-BNQIWClg.js +12532 -0
  4. package/dist/chunks/{index.es-D9BGYyEt.js → index.es-BVXJfTmn.js} +1 -1
  5. package/dist/chunks/{index.es-n6H_ncuE.js → index.es-gb2_kTSZ.js} +1 -1
  6. package/dist/chunks/{jspdf.es.min-Dbn0akWf.js → jspdf.es.min-C7JL2eZm.js} +2 -2
  7. package/dist/chunks/{jspdf.es.min-B6-ocR7J.js → jspdf.es.min-CBWDsR7H.js} +2 -2
  8. package/dist/chunks/plugin-overlay-C_qauTcv.js +11057 -0
  9. package/dist/chunks/{toolbar-icons-fOm95ASq.js → toolbar-icons-CD7G5Aof.js} +62 -71
  10. package/dist/components/ExcelViewer.vue.d.ts +29 -1
  11. package/dist/core/edit/clipboard-html.d.ts +12 -0
  12. package/dist/core/edit/clipboard-snapshot.d.ts +76 -0
  13. package/dist/core/edit/context-menu.d.ts +14 -1
  14. package/dist/core/edit/edit-controller.d.ts +22 -1
  15. package/dist/core/edit/paste-behavior.d.ts +33 -0
  16. package/dist/core/edit/types.d.ts +20 -0
  17. package/dist/core/export/exporter.d.ts +2 -0
  18. package/dist/core/export/pivot-tables.d.ts +17 -0
  19. package/dist/core/export/xlsx-writer.d.ts +6 -0
  20. package/dist/core/index.d.ts +4 -2
  21. package/dist/core/model/mutations.d.ts +2 -0
  22. package/dist/core/model/types.d.ts +61 -0
  23. package/dist/core/parser/pivot-parser.d.ts +3 -0
  24. package/dist/core/plugin.d.ts +47 -5
  25. package/dist/core/render/canvas-renderer.d.ts +12 -0
  26. package/dist/core/render/pivot-toggle.d.ts +13 -0
  27. package/dist/core/viewer/controller.d.ts +82 -6
  28. package/dist/core/viewer/overlay-manager.d.ts +1 -0
  29. package/dist/core/viewer/paste-config-host.d.ts +12 -0
  30. package/dist/core/viewer/pivot-dialog-host.d.ts +48 -0
  31. package/dist/core/viewer/readonly-prompt-host.d.ts +23 -0
  32. package/dist/core.js +67 -64
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.js +948 -873
  35. package/dist/react/ExcelViewer.d.ts +25 -4
  36. package/dist/react.js +621 -535
  37. package/dist/style.css +1 -1
  38. package/dist/vue2.css +1 -1
  39. package/dist/vue2.js +1 -1
  40. package/package.json +1 -1
  41. package/dist/chunks/index-6q8kSGQg.js +0 -10575
  42. package/dist/chunks/plugin-overlay-BUrPrpT2.js +0 -9146
package/CHANGELOG.md CHANGED
@@ -2,6 +2,224 @@
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.7.0] - 2026-06-12
6
+
7
+ ### 新增 — 列表型数据验证:点下拉箭头选值(B4,审计后续专项)
8
+
9
+ - 以前只对"列表型数据验证"的格画下拉箭头**指示**,点了没反应(没解析选项)。现在:**解析选项**(内联 `"a,b,c"` 拆分 + 同表区域引用 `$A$1:$A$5` 读那几格)存进 `SheetModel.dataValidationLists`;编辑模式下点格内下拉箭头 → **弹可选值菜单**(复用右键菜单宿主,框架无关、三壳共用)→ 点选即 `editCell` 填入(走命令栈,**可撤销**)。只读 / 只读格不弹。
10
+ - 解析后续:仅"列表型";其它校验类型(整数/日期/自定义公式范围)仍只解析、不强制(强制是后续编辑增强)。
11
+ - 测试:`date-locale.test.ts` 加"内联选项解析进 dataValidationLists";`data-validation.e2e.ts`(Vue+React)点箭头→弹菜单→点选填值→撤销回退。
12
+
13
+ ### 关于审计另一后续专项(矢量 PDF 富文本矢量化)—— 评估后不做
14
+
15
+ - 审计列的"矢量 PDF 富文本走栅格兜底"经核实:矢量 PDF 对**任何非拉丁(中文)文本**(无自定义字体时)本就走栅格(jsPDF 内置字体画不了 CJK)。本组件主力是中文/WPS 文件,文本在矢量 PDF 里**无论如何都栅格**,所以"富文本矢量化"只对"拉丁文 + 自定义字体"的多色格有意义,价值很窄;且栅格兜底**渲染正确**(只是变图片/不可搜索)。故保持现状。
16
+
17
+ ## [1.6.2] - 2026-06-12
18
+
19
+ > 一次**深度保真审计**(针对"为什么这么多 bug 等用户拖真实文件才暴露")的产出。审计把同类潜在问题归为两大根因 ——
20
+ > **ExcelJS 有损中间层** + **多条平行渲染路径漂移**,逐条用代码核实。本版修掉其中已确认的几条;另有数条经核实为
21
+ > **非问题 / 已处理 / 故意为之**(见下);两条大件(数据校验下拉编辑器、矢量 PDF 富文本矢量化)作为后续专项。
22
+
23
+ ### 修复 — 富文本(多色/多段)往返导出丢每段字体
24
+
25
+ - `xlsx-writer` 导出富文本只写 `{text}`,把每段的颜色/粗斜/下划线/删除线/字号全丢了 → 打开多色文件、编辑、导出后变纯黑文本。现在带回每段字体(`toExcelRichFont`)。往返单测锁定。
26
+
27
+ ### 修复 — 富文本渲染只补了换行,缺 indent / 下划线删除线 / shrinkToFit
28
+
29
+ - `drawRichText` 1.6.1 加了 wrap,但仍缺普通文本路径早有的:**缩进**、**逐 run 下划线/删除线**、**shrinkToFit**(超宽统一缩放塞进列宽)。现在补齐,跟普通文本路径同档(溢出仍顶对齐)。
30
+
31
+ ### 修复 — 竖排文本忽略垂直对齐;矢量 PDF 文本溢出/双线边框
32
+
33
+ - 竖排文本恒顶对齐 → 现在尊重 vAlign(middle/bottom,超高顶对齐)。
34
+ - 矢量 PDF 导出:wrap 文本折行超过格高时不再被裁掉文头(**溢出顶对齐**,跟屏幕一致);**double 边框**画成两条平行线(原来退化成单线)。
35
+
36
+ ### 修复 — 空格上的批注被丢
37
+
38
+ - 解析器对"空且无边框无填充"的格返 null 跳过,连**批注**也一起丢。现在空格带批注也保留入模型。
39
+
40
+ ### 审计核实为非问题 / 已处理(本版不改,记录在案)
41
+
42
+ - **隐藏 sheet**:三壳 tab 早已 `filter(state==='visible')`、导出已保留 state —— 已处理。
43
+ - **空格超链接**:ExcelJS 把超链接格给成 object 值(非 null),走非空分支,不会丢 —— 非问题。
44
+ - **内置日期格式 15-21**:14/22(短日期/日期+时间)已重映射;15-17 的英文月名、20/21 的 `h:mm` 本就是正确 OOXML 显示,故意不动。
45
+ - **合并格边框**:合并边框存锚点格、渲染按锚点 box 画整片,正确 —— 加回归单测锁定。
46
+
47
+ ### 后续专项(审计已列,本版未做,见路线图)
48
+
49
+ - 数据校验:现仅"列表型"画下拉箭头指示;**全类型解析 + 真下拉选取编辑器**是独立编辑功能。
50
+ - 矢量 PDF 富文本:现走**栅格兜底**(渲染正确,只是变图片、不可搜索),**矢量化富文本**是较大专项。
51
+
52
+ ## [1.6.1] - 2026-06-12
53
+
54
+ ### 修复 — 打开 .xlsx 边框线不完整(空但带边框的结构格被丢)
55
+
56
+ - **现象**:打开带"树形/网格结构"的 .xlsx(大量**空单元格只承载边框**组成框线),渲染出来的框线残缺、盒子合不拢。
57
+ - **根因**:解析器用 ExcelJS `eachCell({ includeEmpty: false })` 遍历,**跳过 value 为空的格** —— 而结构框线全靠这些"空但有边框"的格,于是它们的边框(及底色)整批丢失。(实测该文件 H3 = 空格 + 上边框,解析后**根本不在模型里**。)
58
+ - **修法**:改 `includeEmpty: true` 遍历;`toCellModel` 对空格**只在有可见样式(边框/填充)时**才入模型,真正空白格(无边框无填充)返 null 跳过 —— 既保住结构格边框/底色,又不把空白格塞进来膨胀。`eachCell` 只扫到该行最右有格的列,大表也不会扫到无限远(大表维度单测仍过)。
59
+ - 测试:`date-locale.test.ts` 加"空但带边框的格入模型(上边框保住)、纯空白格不入模型"。
60
+
61
+ ### 修复 — 只读模式右键没有「复制」(复制不改数据,理应可用)
62
+
63
+ - 之前右键内置菜单只在编辑模式给(`editable ? build : []`),只读模式直接空 → 没法右键复制。改为:**复制**(不改数据)任何模式都给;编辑项(粘贴/插入/删除/合并/清除…)仍仅编辑模式。
64
+ - 测试:`edit-contextmenu.e2e.ts` 加"只读模式右键 → 有复制、无编辑项"(Vue+React)。
65
+
66
+ ### 修复 — 富文本(多色/多段)单元格不换行,渲染只显示中间一截
67
+
68
+ - **现象**:打开真实 WPS 文件,J 列「货品名称」(多色富文本 + `wrapText`)只显示中间几个字(如"配12件套套"),WPS 是从顶部完整换行显示。多点几次工具栏「自动换行」会显示更多 —— 因为那是另一条 autofit 撑高路径。
69
+ - **根因**:`canvas-renderer.drawRichText` **完全没实现换行** —— 把所有富文本 run 拍在**一整行**上画,水平居中导致起点远在格左外、再裁切到格内,于是只看到水平居中的那一截"中间文字";而普通文本路径早有折行 + 溢出顶对齐。富文本走的是另一条路,漏了。
70
+ - **修法**:重写 `drawRichText` 支持 `wrapText` —— 逐字符按列宽折行(保留各 run 字体/颜色),并套用跟普通文本**完全一致**的垂直对齐 + **溢出顶对齐**(超过行高 → 顶对齐显示文头,WPS 行为)。非 wrapText 仍单行。实测真实文件 J 列现与 WPS 渲染一致(顶部完整换行)。
71
+
72
+ ### 修复 — 打开 .xlsx 日期显示成 `04-01-26`(应为 `2026/4/1`)
73
+
74
+ - **根因**:OOXML 内置短日期格式(`numFmtId=14`)的"显示"本应跟随区域设置,但 ExcelJS 把它**硬编码成美式串** `mm-dd-yy` → 我们照渲染成 `04-01-26`;WPS/Excel 中文环境显示 `2026/4/1`。(实测真实 WPS 文件:该格 `numFmtId=14`,wrapText/垂直居中/行高都解析正确,**唯一**出入就是这个内置日期格式。)
75
+ - **修法**:`exceljs-adapter` 把 ExcelJS 的内置日期串重映射成中文 locale —— `mm-dd-yy → yyyy/m/d`、`m/d/yy h:mm → yyyy/m/d h:mm`(纯时间 / 带英文月名的 `d-mmm-yy` 不动)。需自定义可经 `transformModel` 钩子覆盖。
76
+ - 测试:`date-locale.test.ts`(ExcelJS 写 `mm-dd-yy` → 解析回应得 `yyyy/m/d` 渲染 `2026/4/1`;普通货币格式不受影响)。
77
+
78
+ ### 修复 — Vue 2 / React demo 拖文件不解析(弹空白标签 `about:blank#blocked`)
79
+
80
+ - 拖文件加载是 demo 层能力,Vue 3 `App.vue` 有 `@drop.prevent`,而 vue2-demo / react-demo 漏了 → 浏览器走默认行为把文件当导航打开。按"三 demo 1:1"补上:两 demo 根容器加 `dragover/dragleave/drop` 的 `preventDefault` + `onDrop` 落 `src` 解析。
81
+
82
+ ## [1.6.0] - 2026-06-12
83
+
84
+ ### 新增 — 可配置粘贴行为(覆盖 / 合并 / 仅值)+ 右键选择性粘贴 + 工具栏配置面板 + 只读提示
85
+
86
+ **问题**:粘贴(尤其从 WPS 粘真实表格)有两类痛点 —— ① 目标区**原有合并/结构没清掉**,旧合并吞列致数据错位(如示例 A1:E1 旧合并把粘进来的前 5 列吞了);② "贴近源(覆盖)"还是"保留目标格式(仅值)"应由用户选,而非写死。
87
+
88
+ **方案 —— 框架无关的 `PasteBehavior` 配置系统**(core,三壳共用):
89
+
90
+ - **逐项可配**(默认 = 覆盖式 1:1):`cellStyle` / `fill`(覆盖/合并/不粘)· `rowHeight`(搬源/不动)· `colWidth`(仅首行搬源/总搬/不动)· `sourceMerges`(应用/不应用)· **`targetMerges`(清掉/保留 —— 默认清,修数据错位)** · `images`(落格/不粘)。首行 vs 中间唯一差异收敛在 `colWidth: 'firstRowOnly'`(列宽整列共享,仅粘到首行才取源宽,粘到中间不动上方表头)。
91
+ - **两条粘贴路径都按配置走**:`pasteRich`(外部 WPS/Excel)+ `pasteSnapshot`(应用内/跨实例 1:1);样式按「覆盖式以中性默认为基 / 合并式以目标为基 / 仅值留目标」三档算(见 `resolvePastedCellStyle`)。
92
+ - **右键「选择性粘贴」子菜单**(core context-menu 加一级 flyout):`覆盖格式(贴近源)` / `保留原样式(仅值)`,逐次预设,不改默认。
93
+ - **工具栏「⚙ 粘贴配置」面板**(`paste-config-host.ts`,框架无关 DOM,**三壳/三 demo 共用一份**,UI 天然 1:1):列出全部项下拉自由定制 + 两个快捷预设(覆盖式 1:1 / 仅值)+ 恢复默认;应用即 `setPasteBehavior`。
94
+ - **API / prop**(三壳同名):组件 `:paste-behavior`(`Partial<PasteBehavior>`,缺项回落默认)· `viewer.getPasteBehavior()` / `setPasteBehavior(cfg)` / `openPasteConfigDialog()` · `pasteRichHtml(html, at, behaviorOverride)` 第 3 参逐次预设。导出 `PasteBehavior` / `DEFAULT_PASTE_BEHAVIOR` / `PASTE_PRESET_VALUES_ONLY` / `resolvePasteBehavior`。
95
+ - **只读检查 + 提醒 = 核心层统一(不按输入方式重写)**:只读**判定**本就是核心层唯一真相源 `resolveEditable()`(`isCellEditable` 全走它),任何改数据的操作(粘贴/编辑/合并/拆分/图片互转)在 EditController 里逐格 `isEditable` 拦截 + 收集 denied + emit `permission-denied`;**提醒**统一收口在控制器 `emitEditEvent` —— 所有 `permission-denied`(dimension 列宽行高=布局除外)经此**一处**按 `readOnlyPrompt` 配置弹内置提醒。新增输入方式只要照常走 EditController API,**无需各自重写只读检查/提醒**(避免遗漏)。
96
+ - **只读提醒(逐格精确 + 可配 dialog/toast/none)**:撞只读(**编辑模式下也可能有只读格**,如 `readOnlyRanges`)不再静默 —— 收集**所有被跳过的只读格**(不止落点),按 `readOnlyPrompt`:`'dialog'`(默认,弹窗**列出具体哪些格**A1 引用)/ `'toast'`(顶部气泡)/ `'none'`(只发事件)。框架无关 DOM(`readonly-prompt-host.ts`)三壳共用。粘贴落点只读不再整次中止 —— 可编辑的格照常粘、只读格跳过并在提醒里列出。
97
+ - **右键「选择性粘贴」二级菜单可达性修复**:菜单宿主加 flyout 关闭延时 + 紧贴父菜单(消除缝隙),从父项滑到子项不再"还没点到就消失"。
98
+ - **Ctrl+V vs 右键差异(已知,浏览器限制)**:`Ctrl+V` 走 `paste` 事件拿**原始 HTML**(WPS 的 `<style>` 类格式全在);右键「粘贴/选择性粘贴」走 `navigator.clipboard.read()`,**浏览器会净化** HTML(删 `<style>`)→ 从 WPS 粘的类格式不如 Ctrl+V 全(应用内 1:1 复制因带 `data-ooxml-clip` 属性不受影响)。无法在右键路径绕过(无 paste 事件)。
99
+ - 测试:`paste-behavior.test.ts`(9 个,各档样式解析)+ `paste-behavior.e2e.ts`(8 个:仅值保留目标 / 默认覆盖清目标合并修数据错位 / 配置面板弹出+预设+应用 / **粘到只读区弹对话框列出哪些格+只读格不被覆盖**,Vue+React)。
100
+
101
+ ### 修复 — 从 WPS/Excel 富粘贴丢"自动换行 + 垂直居中"(连带水平居中也看不出)
102
+
103
+ - **现象**:WPS 里开了自动换行 + 水平/垂直居中的格,粘进来后不换行(长文本溢出/裁切)、垂直贴底,连水平居中也看不出来。
104
+ - **根因(两处解析漏洞,均非回归,一直没实现)**:
105
+ - **`white-space` 没解析** → `wrapText` 永远 false。Excel/WPS 用 `white-space:normal` 标记开了自动换行(全局默认 `td{white-space:nowrap}`,换行格用 `normal` 覆盖)。
106
+ - **裸 `td` 元素默认层没收集** → 垂直居中丢。WPS 把"所有单元格默认"(如 `vertical-align:middle`、`white-space:nowrap`、`font-size:11pt`)放在 `td{...}` 选择器上,各 `.etN` 类只覆盖要改的;而 `parseClassStyles` **只收 `.类名` 规则、不收裸 `td` 规则**,于是没写 `vertical-align` 的格全回落成默认 `bottom`。
107
+ - **渲染器本就支持** `wrapText`/水平居中/垂直居中(canvas-renderer 1122/1152/1140 行),只是解析没喂字段。
108
+ - **修法**:① `cssToStyleOverride` 增加 `white-space: normal|pre-wrap|pre-line → wrapText=true`;② `parseClassStyles` 额外收集裸 `td` 默认声明,`rawCssOf` 按 CSS 优先级 **td 默认 < 类 < 内联** 三层合并 → 没写垂直对齐的格拿到 `td` 的 `vertical-align:middle`;③ 字号解析认 `pt` 单位(`font-size:11.0pt` → 11,不再当 px 算成 8 —— 之前没喂 td 默认层不暴露,引入默认层后必须修对)。
109
+ - 测试:`edit-paste-rich.e2e.ts` 的 WPS 夹具加裸 `td{vertical-align:middle;white-space:nowrap;font-size:11pt}` + 类 `white-space:normal`,断言粘贴后 `hAlign=center` + `vAlign=middle` + `wrapText=true` + `font.size=11`。
110
+
111
+ > **路径隔离(为什么不会互相串)**:本组件三种"进数据"路径**各走各的解析,零交叉**——① 打开 .xlsx 走 `exceljs-adapter`(OOXML);② 应用内/跨实例复制走 `clipboard-snapshot`(自带 `data-ooxml-clip` 完整快照);③ 外部 WPS/Excel 粘贴才走 `clipboard-html`。本次只改 ③。`pasteRichHtml` 先认快照(② )、不是才退到外部解析(③),所以 ① ② 完全不受影响。每个外部粘贴格的 CSS 也是**独立**算出一份 `CellStyleOverride`(td默认+自己的类+自己的内联),不跨格混用。
112
+
113
+ ### 修复 — 外部富粘贴样式是"合并目标"而非"覆盖"(粘到带色表头会漏出表头底色)
114
+
115
+ - **现象**:从首行(带绿底表头)开始粘,源里**没写填充**的格(如 WPS 的 `.et6` 没 `background`)粘完仍保留表头的绿底,不是干净覆盖成源的样子。
116
+ - **根因**:`pasteRich` 用 `applyStyleOverride` 套源样式,而它以**目标格现有样式为基**做浅合并(给工具栏"加粗"那种增量编辑设计的)。源 patch 没写的属性(填充/边框)就保留目标原有的 → 粘到带色区会漏底色。
117
+ - **修法**:`pasteRich` 套样式前先把目标格 styleId 归 0(中性默认),再合并源 patch —— 即**以中性默认为基的覆盖式**,源没写的属性回落默认(无填充/无边框),不再漏目标底色。结果贴近源,同 Excel"粘贴替换格式"、也同应用内 1:1 快照粘贴(后者本就清空目标再落)。
118
+ - **为什么所有落点都这么做、不限首行**:样式是**逐格**的,覆盖只影响被粘的那几格、不波及邻格,所以任何位置都安全;列宽因**整列共享**才需限定首行。纯文本粘贴(源无样式 → 无 patch)不受影响,照常保留目标格式。
119
+ - 测试:`edit-paste-rich.e2e.ts` 加"粘到红底格 → 源白底覆盖成白、源没写填充的格清成无填充(不漏红底)、边框照常"。
120
+
121
+ ### 变更 — 粘贴只带行高、列宽默认不改(例外:粘到首行时套用源列宽)
122
+
123
+ - **问题**:粘贴(WPS/Excel 外部富粘贴 + 应用内 1:1 快照粘贴)原会把源**列宽**搬到目标列。但列宽是**整列共享**的,粘到第 18 行却把同列上方的表头(第 1~7 行)宽度一起改了 → 破坏现有表格布局。
124
+ - **改法**:两条粘贴路径(`pasteRich` 外部 / `pasteSnapshot` 应用内)都**只搬行高;列宽默认不搬**(以现有表头为准,同 Excel 默认粘贴)。行高是逐行的,只影响被粘的那几行;内嵌图按**目标格尺寸**填充,不依赖源列宽,图照样填满。
125
+ - **例外 — 粘到首行(`start.row === 0`)套用源列宽**:此时上方没有任何内容可被破坏,粘贴块本身就是新表头/新布局,列宽应以它为准。`row > 0`(粘进已有表格中间)才保持目标列宽不动。
126
+ - 源列宽仍随 `ParsedClipboard.colWidths` / `ClipSnapshot.colWidths` 解析、携带,粘到首行即应用、否则保留。
127
+ - 测试:`edit-paste-rich.e2e.ts`(WPS)断言粘到 row 2 列宽不被源覆盖、粘到 row 0 列宽=源 72/120;`clipboard-snapshot.test.ts`(应用内)断言粘到 row 5 目标列宽不变、粘到 row 0 套用源列宽 120。
128
+
129
+ ### 修复 — 空格/新建格/粘贴串入首格底色(根因:解析时 `styles[0]` 不是中性默认,而是第一个被解析到的格样式)
130
+
131
+ - **现象**:加载示例(A1 是绿底表头)后,从 WPS 粘贴一段内容,部分**本应无底色**的格冒出绿底(看起来像"混入了第 1 行样式");散落分布(有的格白、有的格绿)。打开任意"首格带底色"的本地 .xlsx 也会有同类隐患(空格/编辑新建的格染上首格底色)。
132
+ - **根因**:ExcelJS 解析器 `buildSheet` 按**遇到顺序** intern 样式,首个被解析到的单元格(通常是 A1 表头)样式就占据了 `styles[0]`。而全 core 多处把 `styleId 0` / `styles[0]` 当成"中性空白默认基样式"用——空格、`setCellValue` 新建的格、`applyStyleOverride` 对空格的兜底基样式都回落到它。于是 A1 的绿底成了"默认底色":凡是没有显式指定填充的格(如 WPS 类里**没写 `background:`** 的 `.et6`/`.et8`),`mergeStyleOverride` 保留基样式的 `fill` → 冒出绿底;写了 `background:#FFFFFF` 的格才是白 → 散落串色。
133
+ - **修法(深修,非兜底)**:抽出唯一规范工厂 `makeDefaultStyle()`([src/core/model/types.ts](src/core/model/types.ts)),`buildSheet` 解析前**预置 `styles[0] = makeDefaultStyle()`**(无填充/无边框中性默认)并登记进 styleIndex,真实格样式从 index 1 起;首格 A1 的绿底样式仍在(只是换了 index),引用它的格不受影响。loader-json / clipboard-snapshot 原本各有一份重复的默认样式工厂,一并改为复用此唯一来源。
134
+ - **影响面**:不止修了 WPS 粘贴——所有"空格/新建格/兜底基样式"路径现在都正确回落到中性默认,文件打开渲染零变化(带样式的格按各自 styleId 渲染如初),只有"默认格"不再染上首格底色。
135
+ - 测试:`parse.test.ts` 加回归"styles[0] 恒为中性空白默认(首格 A1 有绿底也不占 index 0)";现有 339 单测 + e2e 全绿。
136
+
137
+ ### 修复 — Ctrl+V 改走 paste 事件(根因:clipboard.read() 会净化 HTML 删掉 `<style>`)
138
+
139
+ - **真正的根因**:我们 Ctrl+V 原先走 `navigator.clipboard.read()`,这个 Async Clipboard API **会净化 HTML** —— 把 `<style>` 块、注释整个删掉。而 WPS/Excel 复制的格式(CSS 类)、数字格式(`mso-number-format`)、内嵌图(VML `o:gfxdata` 注释)**全在这三样里** → 过一遍 `read()` 就没了(实测:写 `<style>` 进剪贴板再 `read()` 读回,`<style>`/类定义/注释全被删)。这就是"直接打开 Excel 文件能完整解析、复制粘贴反而丢格式"的原因:打开文件走 ExcelJS 解 .xlsx(无损 OOXML),粘贴走系统剪贴板的 `text/html`(本就有损)且**还被浏览器二次净化**。
140
+ - **修法**:控制器在 `scroller` 上绑 `paste` 事件,Ctrl+V 改走 `onPaste(e)` —— `e.clipboardData.getData('text/html')` 拿的是**原始未净化** HTML(WPS 的 `<style>`/VML 都在),不再走净化的 `read()`。`onKeyDown` 不再拦截 Ctrl+V(在那 `preventDefault` 反而会阻止 paste 事件)。我们自己复制的 `data-ooxml-clip` 快照也照样原样拿到,1:1 不受影响。
141
+ - 右键菜单"粘贴"无 paste 事件,仍走 `pasteFromClipboard()`(`read()`,会净化)→ 从 WPS 粘的格式不如 Ctrl+V 全(已在方法注释/文档说明)。
142
+ - 测试:`edit-paste-rich.e2e.ts` 加"派发 paste 事件(原始 HTML)→ onPaste → 类格式/numFmt/VML 图都还原"用例(Vue+React);自家 1:1 复制 e2e 仍过(快照不被净化)。
143
+
144
+ ### 修复 — 从 WPS/Excel 富粘贴丢格式/数字/图片(对照真实 WPS 剪贴板 HTML)
145
+
146
+ - **解析 `<style>` 类样式**:Excel/WPS 复制的剪贴板 HTML 把单元格格式放在 `<style>` 块的 CSS 类里(`<td class=et3>` + `.et3{border:…;background:…}`,整段还包在 `<!-- -->` 里),而旧解析只读每个 `<td>` 的内联 `style=`(`DOMParser` 不会把类规则套到元素上)→ 边框/底色/字体全丢。现在 `parseClipboardHtml` 先收集所有 `<style>` 块的「类名→声明」(剥掉 `<!-- -->` 壳),落格时把命中类的声明合并进 `td.style`(类在前、内联在后,内联优先)再解析 → 还原边框/填充/字体/对齐。
147
+ - **数字格式(日期/货币不再变成裸序列号)**:格式码在 `mso-number-format`(CSSOM 会丢弃这种私有属性),且值是 CSS 转义的(`2`→`"`、`\#`→`#`、`\;`→`;`、`\(`→`\(`)。新增 `parseMsoNumberFormat`/`unescapeMsoNumFmt` 从原始声明串解析并解转义 → `numFmt`(如 `yyyy/m/d`、`"¥"#,##0.00_);[Red]\("¥"#,##0.00\)`),配合 `x:num` 原始序列号 → 日期/货币正确显示(之前 46113 直接显示成裸数字)。
148
+ - **列宽/行高 1:1**:剪贴板 HTML 带了 `<col width=N span=M>`(列宽 px)和 `<tr height=N>`(行高 px),`parseClipboardHtml` 现在解析出 `colWidths`/`rowHeights`,`pasteRich` 用 `restoreDimension` 搬到目标列/行 → 列宽行高 1:1。内嵌图填满单元格,**格尺寸对了图也就对了**(之前列宽行高没搬 → 图也显得不对)。(实测真实 WPS:行高 106px、列宽 72/124/197… 与 `<col span>` 完全对应)
149
+ - **图片(WPS 区域复制内嵌图能救回来)**:之前以为是浏览器硬限制——其实 `<img src="file:///…">` 确实读不了,但 WPS 同时把图放在 VML `<v:shape o:gfxdata="base64">`(在 `<!--[if gte vml 1]>…<![endif]-->` 注释里),**那段 base64 是个 zip,内含 `media/imageN.png`**。新增 `extractVmlImageDataUrl`:从 td 的注释节点取 `o:gfxdata` → `unzipSync`(fflate)→ 取图 → data-uri → 走现有图片落格(转 DISPIMG 单元格图)。
150
+ - **不影响应用内 1:1 复制**:本组件自己复制的内容带 `data-ooxml-clip`,粘贴时先走快照路径(`parseSnapshotHtml`/`pasteSnapshot`),根本不进 `parseClipboardHtml`,零影响。
151
+ - 图片方向澄清(文档):WPS/Excel **区域复制**的图片在剪贴板里是 `file:///` 本地路径,浏览器读不到 → 区域里的图必然丢(浏览器限制);单图复制走 `pasteImageBlob` 仍可。
152
+ - 测试:`e2e/edit-paste-rich.e2e.ts` 加 Excel/WPS 类样式(`<style> .xl 类`)用例(Vue + React),断言边框/填充/字体/对齐还原。
153
+
154
+ ## [1.5.0] - 2026-06-11
155
+
156
+ ### 新增 — 应用内复制粘贴 1:1 保真(走剪贴板嵌入快照)
157
+
158
+ **问题**:复制粘贴走系统剪贴板的 HTML/TSV 交换(为了能贴进 Excel/WPS),而复制端只 emit `<td style=CSS>格式化文本`,导致:① 合并单元格被拍平;② 图片(DISPIMG / 浮动图)整个丢失;③ 数字按格式化文本复制(`¥237` → 文本"¥237",丢原始值);④ 边框/数字格式/行高列宽不带。
159
+
160
+ **方案(全走剪贴板,跨实例 1:1)**:复制时把**完整模型快照**序列化(base64 UTF-8 JSON)嵌进剪贴板 HTML 的 `data-ooxml-clip` 属性;粘贴时 `pasteRichHtml` 优先识别该快照走 1:1 还原,否则回退原有外部 HTML 近似解析。因为快照随剪贴板走、不依赖内存,**Vue3 / Vue2 / React 三壳之间、跨标签页互相复制结果都一致**。外部应用(Excel/WPS/Word)忽略该属性,只读可见 `<table>`(同时增强:补 `colspan/rowspan` + `<img data:>`,所以贴进 Excel 也能带上合并和图片)。
161
+
162
+ - 新增 [src/core/edit/clipboard-snapshot.ts](src/core/edit/clipboard-snapshot.ts):`ClipSnapshot` 序列化/反序列化(每格 原始值/类型/公式/超链/批注/富文本/dispImgId + **完整 CellStyle**;合并;浮动图 base64;DISPIMG 字节;**行高/列宽/手动行高标记**),UTF-8 安全 base64。
163
+ - 新增 `EditController.pasteSnapshot`(覆盖式 1:1 落格:intern 样式到目标表、登记 DISPIMG 字节、平移合并/图片/行高列宽;整体单次撤销 + 前后快照 cell-change + 只读 permission-denied),`mutations.setCellModel`。
164
+ - `copySelection` 增强 HTML(合并 colspan/rowspan + 图片 `<img data:>`)+ 嵌入快照;`pasteRichHtml` 先试快照再回退。
165
+ - 不改三壳(纯 core);`Ctrl+C`/`Ctrl+V`/右键菜单复制粘贴全部受益。
166
+
167
+ **大图护栏(三项)**:
168
+ - **不双重 base64**:图片字节不再既进快照又进可见 `<img>`,改为只在可见 `<img data-clip-img="key">` 存一份(`key`=DISPIMG `c:id` / 浮动 `f:序号`),快照只引用;粘贴时 `parseSnapshotHtml` 从 `<img>` 回填字节(`reattachImages`)。剪贴板体积从约 3× 原始图字节降到约 1.4×,有效上限翻倍。(`serializeSnapshot(..., { withImageBytes: false })`)
169
+ - **字节预算 + 优雅降级**:复制区图片总字节超 `CLIP_IMAGE_BUDGET_BYTES`(6 MB)→ 自动**降级为"无图 1:1 复制"**(样式/数字/边框/合并/行高列宽仍 1:1,只跳过图片,DISPIMG 格中性化为空格),避免剪贴板超限导致整次复制静默失败。(`withoutImages`)
170
+ - **降级通知**:降级时经现有 `permission-denied` 通道发事件(`reason: 'copy'` + 中文 message:"复制内容含图过多…已按无图复制"),壳/插件可 toast,不再静默。
171
+ - **贴进 WPS/Excel 图片不再巨大**:可见 `<img>` 改为带 `width`/`height` **属性**(按单元格大小:DISPIMG 用所在格列宽×行高,浮动图用其 EMU 尺寸),WPS/Excel 认属性不认 CSS `max-width` —— 之前只给 CSS 导致按原图像素(常几百上千 px)贴入显得巨大。
172
+
173
+ - 测试:`src/core/edit/__tests__/clipboard-snapshot.test.ts`(6:序列化往返 + `pasteSnapshot` 1:1 + undo + 瘦身回填 1:1 + 降级中性化 + 脏数据回退);`e2e/copy-paste-fidelity.e2e.ts`(真系统剪贴板 Ctrl+C→Ctrl+V,数字仍是数字不退化文本)。
174
+
175
+ ## [1.4.0] - 2026-06-11
176
+
177
+ **透视表(Pivot Table)完整闭环** — WPS 式创建入口 → 右侧字段面板 → 编程 API → **导出真实 OOXML 透视表零件**,并支持**活刷新 / 折叠展开 / 多选筛选**。整个功能由 **`pivotTable` 配置开启,默认关闭**(三 demo 已开启)。另含 `scrollToCell` 导航 API 与工具栏 `sort` 内置项。
178
+
179
+ ### 新增
180
+
181
+ #### 透视结果"活"化 + 多选筛选(2026-06-11)
182
+
183
+ - **活刷新**:编辑源数据区任意单元格(含撤销/重做,统一走 `onModelChange` chokepoint)后,所有透视表按其 `source` 区域自动重算 —— 从"静态快照"变"活对象"。重算经唯一入口 `recomputePivot`(面板改布局 / 活刷新 / 折叠展开三处共用),直接改模型不入命令栈(派生态),`pivotRefreshing` 防重入,无透视表 / 功能关闭时零开销。
184
+ - **行分组折叠/展开**:放 ≥2 个行字段时 `buildPivotRows` 产出"大纲"——外层分组行带小计、内层缩进明细;分组表头行首画 [−]/[+] 折叠按钮(canvas 绘制 + `CanvasRenderer.pivotToggleAt` 命中,与 autofilter 下拉同款),点击折叠/展开该组。折叠状态存 `PivotTableModel.collapsed`,运行时 `rowGroups` 记录可折叠表头行供渲染/命中。单行字段退化为扁平结果(无折叠),既有行为不变。
185
+ - **空白起步 + 字段勾选联动 + 新建表显示**(2026-06-11,修用户反馈):① 对话框创建的透视表不再自动猜字段(此前会把"首个数值列=值、首个其它列=行"硬塞,常把"编号/ID"列当行字段),改为空白占位 + 面板选字段填充,对齐 WPS/Excel;`createPivotTable` 仅在显式传 `layout.rows/values` 时沿用字段(编程 API 不变)。② 字段列表复选框从 `disabled` 改为可点 —— 勾选即加入(数值→值/其它→行),取消即移出,顶部字段列表与底部四区双向联动。③ 修「新建工作表」输出后新表 tab 不显示:Vue 3 壳 `workbook` 是 `shallowRef`,`sheets.push` 不触发 `SheetTabs` 的 `computed` 重算 → 给 SheetTabs 加 `:key` 版本号,`onActiveSheetChange` 时 +1 强制重渲(React/Vue 2 壳内联重读,本就正常)。
186
+ - **四区大白话 tooltip + 列交叉表确认**:字段面板「筛选器 / 列 / 行 / 值」每个区加 hover 说明(区标题带 ⓘ),筛选器/列空着时方框内还有一句引导;补 e2e 证实「列」字段确实横向展开成二维交叉表。
187
+ - **多选筛选**:`PivotFilterMode` 增 `'include'`(`PivotFilterRule.values` 列出保留值)。字段面板筛选 chip 点开底部明细面板,可选 全部 / 非空 / 多选(勾选具体值,带全选/清空)。导出对齐 WPS:`include`/`non-empty` → `multipleItemSelectionAllowed` + 未选项 `item@h=1`。
188
+
189
+ #### `pivotTable` 功能开关(默认关闭,opt-in)
190
+
191
+ #### `pivotTable` 功能开关(默认关闭,opt-in)
192
+
193
+ - 三壳同名 boolean prop(`:pivot-table` / `pivotTable`),进 `EditConfig.pivotTable`。**默认 `false`:工具栏 `pivot-table` 入口不渲染、`createPivotTable`/`openPivotTableDialog` 等 API 返回 `false` 并提示、导出不回注 pivot 零件 —— 行为与历史版本完全一致(零回归)**。开启后(还需 `editable`)下述全部能力生效。直接用 core `workbookToXlsxBlob` 时对应 `XlsxExportOptions.pivotTables`(经 viewer 导出时自动随开关注入)。
194
+
195
+ #### 透视表创建(WPS 风格,core 落地、三壳共用)
196
+
197
+ - **工具栏 `pivot-table` 入口**(需 `editable`):选中带表头数据区 → 「创建透视表」对话框选择生成位置(现有工作表指定单元格 / 新建工作表)→ 写出静态透视汇总表,入命令栈(undo 整体还原)。
198
+ - **右侧「数据透视表」字段面板**(框架无关 body 级 DOM,[src/core/viewer/pivot-dialog-host.ts](src/core/viewer/pivot-dialog-host.ts)):字段搜索;按钮/拖拽把字段加入 **筛选器 / 列 / 行 / 值** 四区;拖到移除区删除;筛选器支持 全部/非空/具体值;值字段可多个、汇总方式可切 求和/计数/平均值/最大值/最小值;每次变更重建静态结果。
199
+ - **编程 API**:`createPivotTable({ sourceRange, sourceSheetIndex, output, layout, showPanel })` 不经页面直接创建;`createPivotTableFromSelection()` 选区快捷;`openPivotTableDialog()` 打开入口对话框。三壳(Vue3 ref / React handle / Vue2 viewerApi)+ 插件 `viewer` 均已暴露。
200
+ - **模型元数据**:`PivotTableModel` 保存 `source`(源表 + 源区域)与 `layout`(四区布局),`cloneWorkbook` 深克隆,undo 快照不被面板操作污染。
201
+
202
+ #### 导出真实 OOXML 透视表零件 ([src/core/export/pivot-tables.ts](src/core/export/pivot-tables.ts))
203
+
204
+ - ExcelJS 不建模 pivot 零件,写出后在 **zip 层回注**(同 WPS cellimages 模式):`pivotCacheDefinition`(cacheSource + cacheFields/sharedItems)+ `pivotCacheRecords`(源数据行)+ `pivotTableDefinition`(location / pivotFields / row/col/page/dataFields / 样式)+ workbook `<pivotCaches>` + 全套 rels + `[Content_Types].xml`。
205
+ - `refreshOnLoad="1"`:Excel/WPS 打开导出件即识别为**真透视表**并按源区域重算原生布局;静态汇总结果仍写在单元格里,不支持透视的查看器也能看。
206
+ - **筛选器导出语义对齐 WPS**:"= 具体值"写 `pageField@item` 指向选中项(打开还原筛选状态);"非空"映射为多选 + 隐藏空白项(`multipleItemSelectionAllowed` + `item@h`,即 WPS"去掉空白"语义);"全部"不写选中。
207
+ - **overlay 导出保留原文件透视表**(`restoreOriginalPivotPartsIntoZip`):原文件已有的透视表(解析为只读,ExcelJS load→write 会整套丢掉)在 overlay 模式下从原件 zip **原样搬运**整套零件(pivotCache/pivotTables 目录 + workbook 注册 + worksheet 隐式关系按表名重挂 + Content_Types),cacheId 保留、r:id 重新分配;后续 App 新建透视表的零件编号/cacheId 自动避开。"打开 → 编辑 → 另存,透视表仍在"。rebuild 模式因结构可能被增删行列改动,不搬运(退化为普通单元格)。
208
+ - 回注/搬运失败自动降级为纯静态结果,不影响主体导出。
209
+
210
+ #### 其它
211
+
212
+ - `scrollToCell(row, col, { select? })` 命令式导航 API(三壳 + 插件 viewer),超出当前虚拟区自动扩展。
213
+ - 工具栏内置 `sort` 项(按活动单元格所在列升/降序;未开自动筛选先按选区/已用区建立范围),`viewer.sortActiveColumn(dir)` 同步暴露。
214
+
215
+ ### 修复
216
+
217
+ - **pivot-parser 支持标准 ECMA-376 隐式关联**:真 Excel 文件的透视表零件靠 worksheet rels 关联(sheet XML 里没有元素),此前只认 worksheet XML 内的 `pivotTableDefinition` 引用 → 标准文件解析不到只读透视表按钮。现在两条路径都认(rels 扫描 + 兼容引用),导出件可往返解析。
218
+
219
+ ### 测试
220
+
221
+ - 单测 316 → 330(pivot-parser 解析 + clone 元数据 + 导出回注零件结构/往返 + equals/non-empty/include 筛选语义 + overlay 原件搬运/编号避让等);e2e 118 → 124(`e2e/pivot.e2e.ts`:UI 入口全链路 + 面板切换汇总方式 + undo;API 新建工作表 + 求和;导出 zip 零件断言;活刷新随源编辑/撤销;2 行字段折叠/展开;include 多选筛选;面板筛选复选框)。
222
+
5
223
  ## [1.3.3] - 2026-06-09
6
224
 
7
225
  **Vue 2.6 真实兼容修复合集** — 1.3.2 上线后消费方 Vue 2.6.12 + vue-cli 4 (webpack 4) 项目验证暴露两个 Vue 2.6 特有 bug (函数 ref 不支持 / `ctx.expose` shim 语义不同), 1.3.3 一并修掉. **现在 Vue 2.6 / 2.7 / Vue 3 三个版本都真正可用**.
package/README.md CHANGED
@@ -42,12 +42,13 @@ const src = ref<File>() // 绑个 <input type="file" @change> 给它即可
42
42
  - 🗓 **日期序列号**:含 Excel 1900 闰年 bug、1904 系统
43
43
  - 🎨 **主题色 + tint**、indexed 调色板、合并单元格、边框(细/粗/虚/双线)、填充(纯色/图案/渐变)
44
44
  - 🌈 **条件格式**:色阶 / 数据条 / 图标集 / cellIs / top10
45
- - 🖼 **图片 + 图表**(DrawingML → ECharts 近似还原)、**形状/文本框**、**迷你图**(sparklines)、**批注**、**数据验证**下拉、**自动筛选**样式
45
+ - 🖼 **图片 + 图表**(DrawingML → ECharts 近似还原)、**形状/文本框**、**迷你图**(sparklines)、**批注**、**数据验证下拉**(编辑模式点格内箭头 → 弹可选值菜单选填,可撤销)、**自动筛选**样式
46
46
  - 📌 **WPS 单元格内嵌图(DISPIMG)**:识别并展示 WPS 私有的"嵌在格里的图"(普通工具会缺图);编辑模式下支持**一键浮动 ⇄ 嵌入互转**。见 [WPS 单元格内嵌图](#wps-单元格内嵌图dispimg)
47
47
  - 🔍 **图片点击放大 + 下载原图**:网格里的图(内嵌图/浮动图)点开看大图、下载原件。只读模式单击图放大、编辑模式右键「查看大图」。`imageLightbox` prop 控制(默认开),`openImageLightbox(src)` 命令式打开。
48
- - 📋 **从 Excel/WPS 富粘贴**:`Ctrl+V` 解析剪贴板 HTML → 完美还原字体/颜色/填充/边框/对齐/合并单元格,整块单次撤销。图片走多通道(data-uri / 单图 / 拖文件);**注**:Excel 区域复制的内嵌图进不了浏览器剪贴板(浏览器限制)
48
+ - 📋 **从 Excel/WPS 富粘贴**:`Ctrl+V` 解析剪贴板 HTML → 还原字体/颜色/填充/边框/对齐/合并单元格,整块单次撤销。**Excel/WPS 把格式放在 `<style>` 块的 CSS 类里(`<td class="xl65">`),解析时会把类规则合并进每格** —— 不只读内联 `style=`。**`Ctrl+V` 走 `paste` 事件拿原始 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`,那种区域图仍救不回。
49
+ - 📋 **应用内 1:1 复制粘贴**:本组件自己 `Ctrl+C` 的内容会把**完整模型快照**嵌进剪贴板(`<table data-ooxml-clip>`),`Ctrl+V` 时识别并 1:1 还原 —— 数字不会退化成文本、边框/数字格式/合并/DISPIMG 图片/**行高**全保留;因为快照随剪贴板走,**Vue3/Vue2/React 三壳之间、跨标签页互相复制结果都一致**。粘到外部应用(Excel/WPS/Word)则读可见 `<table>`(近似)。**列宽以目标现有表头为准、不被源覆盖**(列宽整列共享,改了会动表头;同 Excel 默认粘贴)。
49
50
  - 📝 **文本溢出**到相邻空格、**自动行高**
50
- - 🖱 **交互**:单元格选区(合并感知)、拖选、公式栏、状态栏(计数/求和/均值/最值)、超链接可点、裁切文本悬停看全文、Ctrl+C 复制为 TSV、**Ctrl+F 查找**(高亮 + 上/下定位 + 计数 + 区分大小写/全字匹配)、**自动筛选**(点下拉真能筛:去重值多选 + 搜值 + 清除)
51
+ - 🖱 **交互**:单元格选区(合并感知)、拖选、公式栏、状态栏(计数/求和/均值/最值)、超链接可点、裁切文本悬停看全文、Ctrl+C 复制(**同应用内 1:1 保真**:含数字原始值/数字格式/边框/合并/图片/行高,跨 Vue3/Vue2/React 实例互相复制都一致;**列宽以目标表头为准不覆盖**;另带 TSV/HTML 供贴进 Excel/WPS)、**Ctrl+F 查找**(高亮 + 上/下定位 + 计数 + 区分大小写/全字匹配)、**自动筛选**(点下拉真能筛:去重值多选 + 搜值 + 清除)
51
52
  - 🖨 **导出 / 打印**:整表/选区/多表导出 **PNG/JPEG**、**PDF**(位图 + **矢量·文字可选可搜**两种)、**系统打印**(可另存 PDF);默认还原原生 `pageSetup`(纸张/方向/页边距/缩放/打印区域/**打印标题行列每页重复**);宽表**横向跨页**(页矩阵);`beforeRenderPage` 注入页眉/页脚/水印、`configureDoc` 注册字体;内置「导出设置」对话框
52
53
  - ⚡ **按需加载**(无图表文件不下载 echarts、不导出 PDF 不下载 jspdf)、**友好错误兜底**(损坏/加密/旧 .xls)、解析失败自动给出可读提示
53
54
 
@@ -292,6 +293,7 @@ viewer.value.getRangeData(viewer.value.getSelection()) // 取"我选中的"区
292
293
  | `contextMenu` | `false \| ContextMenuTransform` | **右键菜单(Plan C)** — 三层开放:① 默认 = 内置菜单(editable 时弹) ② `false` = 不弹内置(`@before-context-menu` / `@context-menu` 事件仍触发,自渲染) ③ `(ctx, items) => MenuItem[] \| undefined` = transform 回调,在内置 items 上加 / 减 / 重排 |
293
294
  | `fileName` | `string` | 标题栏显示的文件名(可选) |
294
295
  | `editable` | `boolean` | 开启编辑(默认 `false` = 只读,行为与历史一致) |
296
+ | `pivotTable` | `boolean` | 透视表功能开关(默认 `false` = 关闭)。开启后(还需 `editable`):工具栏 `pivot-table` 入口可见、`createPivotTable`/`openPivotTableDialog` 等 API 生效、导出 .xlsx 回注真实 OOXML 透视表零件(overlay 模式同时保留原文件透视表) |
295
297
  | `cellReadOnly` | `(cell, pos) => boolean` | 按格只读判定(编辑时) |
296
298
  | `readOnlyRanges` | `MergeRange[]` | 只读区域(命中即只读,黑名单) |
297
299
  | `editableTargets` | `EditableTarget \| EditableTarget[]` | **可编辑白名单**(2026-06-08)— 设了就是白名单语义:默认只读,**只**命中**任一** target 的格可编辑。4 种 target 形状自动识别:`{row,col}` 单格 / `{row}` 整行 / `{col}` 整列 / `MergeRange` 矩形。单值或数组都行,允许**不相邻**多 target。`undefined` (不传) = 不启用白名单 = 老行为(默认全可编辑);`[]` (显式空) = 全只读。与 `readOnlyRanges` / `cellReadOnly` 叠加 — 白名单命中后仍可被二次"黑"掉。运行时改:命令式 `viewer.setEditableTargets(targets)` |
@@ -300,6 +302,8 @@ viewer.value.getRangeData(viewer.value.getSelection()) // 取"我选中的"区
300
302
  | `editor` | `EditorResolver` | 自定义单元格编辑器工厂(返回任意 DOM) |
301
303
  | `recalc` | `boolean` | 公式重算(默认 `false`;需 `editable`) |
302
304
  | `formulaEngine` | `FormulaEngineFactory` | 自定义/自研公式引擎(默认 HyperFormula) |
305
+ | `pasteBehavior` | `Partial<PasteBehavior>` | 粘贴行为(默认 = 覆盖式 1:1)。逐项可配:`cellStyle`/`fill`(`overwrite`/`merge`/`skip`)·`rowHeight`(`source`/`keep`)·`colWidth`(`firstRowOnly`/`source`/`keep`)·`sourceMerges`(`apply`/`skip`)·`targetMerges`(`clear`/`keep`,默认清=修旧合并吞列)·`images`(`apply`/`skip`)。缺项回落默认。也可运行时 `viewer.setPasteBehavior(cfg)` / 右键「选择性粘贴」/ 工具栏「⚙ 粘贴配置」面板 |
306
+ | `readOnlyPrompt` | `'dialog' \| 'toast' \| 'none'` | 粘贴撞只读格的内置提醒(默认 `'dialog'`):`dialog` 弹窗**列出具体哪些格**只读 / `toast` 顶部气泡 / `none` 只发 `permission-denied` 事件。逐格精确(编辑模式下也可能有只读格) |
303
307
  | `cellImageFit` | `'fill' \| 'contain' \| 'cover'` | WPS 单元格内嵌图贴合方式(默认 `contain` 等比,与 WPS 渲染一致) |
304
308
  | `imageLightbox` | `boolean` | 图片点击放大灯箱(默认 `true`;只读单击图放大、编辑右键「查看大图」) |
305
309
 
@@ -353,7 +357,8 @@ viewer.value.getRangeData(viewer.value.getSelection()) // 取"我选中的"区
353
357
  | 类别 | 方法 |
354
358
  |---|---|
355
359
  | 值 | `editCell(row,col,value)` · `editRange(range,values[][])` · `clearRange(range)` |
356
- | 粘贴 | `pasteText(tsv,at?)` · `pasteRichHtml(html,at?)`(Excel/WPS 复制 → 字体/颜色/填充/边框/对齐/合并 + 单次撤销) · `pasteImageBlob(blob,at?)`(单图/拖文件落格) |
360
+ | 粘贴 | `pasteText(tsv,at?)` · `pasteRichHtml(html,at?,behaviorOverride?)`(Excel/WPS 复制 → 字体/颜色/填充/边框/对齐/换行/合并/内嵌图 + 单次撤销) · `pasteImageBlob(blob,at?)`(单图/拖文件落格) |
361
+ | 粘贴行为 | `getPasteBehavior()` · `setPasteBehavior(cfg)`(改 Ctrl+V/右键默认)· `openPasteConfigDialog()`(弹「⚙ 粘贴配置」面板,框架无关 DOM 三壳共用)。覆盖式 1:1(默认)/ 合并 / 仅值;`targetMerges:'clear'` 默认清目标旧合并防吞列;`colWidth:'firstRowOnly'` 仅首行取源宽。右键「选择性粘贴 ▸ 覆盖格式 / 保留原样式(仅值)」逐次选 |
357
362
  | 样式 | `setStyle(range, patch)`(`patch` = `CellStyleOverride`:font/fill/borders/对齐/numFmt) |
358
363
  | 背景/字体色 | `getActiveFillColor()` · `getActiveFontColor()`(回显活动格当前色 #RRGGBB) · `setSelectionFill(color\|null)`(null=清除填充) · `setSelectionFontColor(color)` |
359
364
  | 自动换行 | `getSelectionWrapState()`→`'all'\|'none'\|'mixed'`(工具栏 active 用) · `toggleWrapTextOnSelection()`(WPS 风格 toggle:全开/全关;行高按内容重撑,只扩不缩) |
@@ -445,7 +450,21 @@ const myEditor: EditorResolver = (cell, pos) => {
445
450
  | `rendered` / `error` / `progress` | 见上 |
446
451
 
447
452
  ### 命令式 API(模板 ref)
448
- `load(src)` / `getWorkbook()` / `getActiveSheet()` / `setActiveSheet(i)` / `getSelection()` / `setSelection(range)` / `rectOf(row,col)` / `rectOfRange(range)` / `redraw()`,以及下面的导出方法;**编辑命令式 API**(`editCell`/`setStyle`/`insertRows`/`undo`/`exportXlsx`…)见 [编辑](#编辑可选默认只读)。
453
+ `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`/`insertRows`/`undo`/`exportXlsx`…)见 [编辑](#编辑可选默认只读)。
454
+
455
+ ```ts
456
+ // 需组件开启 :pivot-table="true"(默认关闭)+ :editable="true"
457
+ viewer.value?.createPivotTable({
458
+ sourceRange: { top: 0, left: 0, bottom: 20, right: 4 },
459
+ output: { kind: 'new-sheet' },
460
+ layout: {
461
+ rows: [0],
462
+ columns: [1],
463
+ filters: [{ field: 2, mode: 'equals', value: '华东' }],
464
+ values: [{ field: 3, summary: 'sum' }],
465
+ },
466
+ })
467
+ ```
449
468
 
450
469
  ### 导出 / 打印
451
470
  内置工具栏右侧有「导出 ▾」菜单(PNG / PDF / 打印 / **导出设置…**)。「导出设置…」打开对话框,可选**范围**(当前选区 / 当前表 / 全部表)、清晰度、是否含行列号/网格线、纸张方向。也可命令式调用(模板 ref / 插件 `viewer`):
@@ -588,11 +607,11 @@ viewer.closeContextMenu()
588
607
  ### 操作工具栏(可配置 / 可插件 / 响应式)
589
608
  顶栏(文件名/导出/缩放)下方有一行**操作工具栏**,内置 `find`/`filter` 按钮默认显示。用 `:toolbar` 配置:
590
609
  ```vue
591
- <ExcelViewer :src="file" /> <!-- 默认: find + filter -->
610
+ <ExcelViewer :src="file" /> <!-- 默认: find + filter + sort -->
592
611
  <ExcelViewer :toolbar="['find','filter','separator','zoom','export']" /> <!-- 控制项/顺序/分隔 -->
593
612
  <ExcelViewer :toolbar="false" /> <!-- 隐藏整条 -->
594
613
  ```
595
- - **内置 id**:`find`(查找)、`filter`(切换自动筛选 —— 文件没设也能点出下拉)、`clear-filter`(清除筛选,无筛选时禁用)、`copy`(复制选区)、`wrap-text`(自动换行 toggle,WPS 风格,需 `editable`)、`image-tools`(图片工具 ▾:选区/整表/整列 浮动 ⇄ 嵌入互转,需 `editable`)、`template`(模板 ▾:仅 JSON / 模型数据源下生效;导入 .xlsx 当样式捐赠者;xlsx 数据源下禁用)、`freeze`(冻结/取消)、`zoom`(缩放下拉)、`export`(导出/打印下拉)、`'separator'`/`'|'`(分隔线);`sort` 规划中。
614
+ - **内置 id**:`find`(查找)、`filter`(切换自动筛选 —— 文件没设也能点出下拉)、`sort`(按活动单元格所在列升序/降序;未开启自动筛选时会先按选区/已用区建立范围)、`clear-filter`(清除筛选,无筛选时禁用)、`copy`(复制选区)、`pivot-table`(透视表入口:选中带表头数据区后选择生成位置,可输出到现有工作表单元格或新建工作表;创建后打开 WPS 风格右侧字段面板,需 `pivotTable` + `editable`,功能未开启时不渲染)、`wrap-text`(自动换行 toggle,WPS 风格,需 `editable`)、`image-tools`(图片工具 ▾:选区/整表/整列 浮动 ⇄ 嵌入互转,需 `editable`)、`template`(模板 ▾:仅 JSON / 模型数据源下生效;导入 .xlsx 当样式捐赠者;xlsx 数据源下禁用)、`freeze`(冻结/取消)、`zoom`(缩放下拉)、`export`(导出/打印下拉)、`'separator'`/`'|'`(分隔线)
596
615
  - **富项类型**(`ToolbarItem`):`type:'separator'` 分隔线;`items: ToolbarItem[]` 变下拉子菜单;`disabled?(viewer)` 禁用态;`iconSvg`(内联 SVG,优先于 `icon` emoji)/ `icon` / `label` / `title` / `onClick(viewer)` / `active?(viewer)`。
597
616
  - **响应式溢出**:宽度不足时,放不下的项自动折叠进「⋯ 更多」下拉。
598
617
  - **插件贡献**:`ExcelPlugin.toolbar: ToolbarItem[]`,插件加载即追加(opt-in)。
@@ -653,7 +672,7 @@ const highlightNegatives = definePlugin({
653
672
 
654
673
  - **编辑**已支持(默认只读;开 `editable`,见 [编辑](#编辑可选默认只读))。下列为暂不覆盖项:
655
674
  - 增删行列**不自动重写公式引用**;写回 .xlsx 丢 VBA/工作表保护/复杂 DrawingML
656
- - 透视表:**数据按普通单元格显示**,但无字段按钮/下拉等透视专属 UI
675
+ - 透视表:已有透视表**数据按普通单元格显示**并显示只读字段按钮;`pivot-table` 入口可从当前选区选择生成位置,生成静态透视汇总表到当前表指定单元格或新建工作表,随后打开 WPS 风格右侧“数据透视表”字段面板。创建出的透视表**空白起步**(对齐 WPS/Excel:不自动猜字段),在右侧面板里选字段填充。面板支持搜索字段;**勾选字段列表的复选框** = 加入透视表(数值字段→值,其它→行),取消 = 移出;也可用 筛/列/行/Σ 按钮或拖拽把字段加入“筛选器 / 列 / 行 / 值”四区,并可拖到移除区删除字段。改动后即时重建结果:筛选器支持“全部 / 非空 / 多选(勾选要保留的具体值,WPS 风格)”,列区生成横向分组,行区生成纵向分组,值区可放多个字段且可切换“求和 / 计数 / 平均值 / 最大值 / 最小值”。**透视结果是“活”的**:① 编辑源数据区任意单元格(含撤销/重做)后,所有透视表自动按源区域重算;② 放两个及以上行字段时,外层分组带小计且可点行首 [−]/[+] 折叠/展开内层明细(单行字段为扁平结果,无折叠)。也可调用 `createPivotTable({ sourceRange, sourceSheetIndex, output, layout, showPanel })` 不经过页面直接创建;旧的 `createPivotTableFromSelection({ rowFieldIndex, valueFieldIndex, output })` 仍保留。创建出的 `PivotTableModel` 会保存 `source` 和 `layout` 元数据,供运行时重建与导出使用。失败时会提示原因。**整个透视表功能由 `pivotTable` 配置开启(默认关闭)**,关闭时入口/API/导出回注全部不生效、行为与历史版本一致。**导出 .xlsx 时回注真实 OOXML 透视表零件**(pivotCacheDefinition / pivotCacheRecords / pivotTableDefinition + 全套 rels,带 `refreshOnLoad`):Excel/WPS 打开导出件即识别为真透视表并按源区域重算原生布局;静态汇总结果仍在单元格里,不支持透视的查看器也能看。筛选器导出语义对齐 WPS:“= 具体值”写入页字段选中项(打开还原筛选状态),“多选/非空”映射为 `multipleItemSelectionAllowed` + 未选项隐藏(`item@h`),“全部”不写选中。原文件已有的透视表在 **overlay 导出模式**下从原件 zip 原样搬运整套零件(保持“打开→编辑→另存,透视表仍在”);rebuild 模式因结构可能被增删行列改动不搬运(退化为普通单元格)
657
676
  - SmartArt;形状仅支持 rect/roundRect/ellipse + 文本(复杂自定义几何按矩形近似)
658
677
  - `.xls`(旧 BIFF 二进制)/ 加密文件(给出友好提示)
659
678
  - 图表为 ECharts 近似,非像素级一致