ooxml-excel-editor 1.3.3 → 1.11.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 +283 -0
- package/README.md +52 -11
- package/dist/chunks/index-CeTZbV_m.js +13673 -0
- package/dist/chunks/{index.es-D9BGYyEt.js → index.es-CNm6wZK8.js} +1 -1
- package/dist/chunks/{index.es-n6H_ncuE.js → index.es-DF0q70BO.js} +1 -1
- package/dist/chunks/{jspdf.es.min-B6-ocR7J.js → jspdf.es.min-65KuNx_3.js} +2 -2
- package/dist/chunks/{jspdf.es.min-Dbn0akWf.js → jspdf.es.min-Bo8KrqZO.js} +2 -2
- package/dist/chunks/plugin-overlay-C_fL02Qc.js +12128 -0
- package/dist/chunks/{toolbar-icons-fOm95ASq.js → toolbar-icons-BjwdJDiN.js} +60 -65
- package/dist/components/ExcelViewer.vue.d.ts +46 -1
- package/dist/components/FindBar.vue.d.ts +8 -0
- package/dist/core/edit/autofill.d.ts +10 -0
- package/dist/core/edit/clipboard-html.d.ts +12 -0
- package/dist/core/edit/clipboard-snapshot.d.ts +76 -0
- package/dist/core/edit/commands.d.ts +9 -1
- package/dist/core/edit/context-menu.d.ts +14 -1
- package/dist/core/edit/data-validation.d.ts +15 -0
- package/dist/core/edit/edit-controller.d.ts +33 -2
- package/dist/core/edit/editor-context.d.ts +5 -2
- package/dist/core/edit/paste-behavior.d.ts +33 -0
- package/dist/core/edit/types.d.ts +26 -0
- package/dist/core/export/exporter.d.ts +2 -0
- package/dist/core/export/pivot-tables.d.ts +17 -0
- package/dist/core/export/xlsx-writer.d.ts +6 -0
- package/dist/core/index.d.ts +4 -2
- package/dist/core/model/mutations.d.ts +4 -0
- package/dist/core/model/types.d.ts +113 -2
- package/dist/core/parser/pivot-parser.d.ts +3 -0
- package/dist/core/plugin.d.ts +69 -5
- package/dist/core/render/canvas-renderer.d.ts +28 -0
- package/dist/core/render/pivot-toggle.d.ts +13 -0
- package/dist/core/viewer/comment-dialog-host.d.ts +16 -0
- package/dist/core/viewer/conditional-format-dialog-host.d.ts +30 -0
- package/dist/core/viewer/controller.d.ts +140 -8
- package/dist/core/viewer/number-format-dialog-host.d.ts +25 -0
- package/dist/core/viewer/overlay-manager.d.ts +1 -0
- package/dist/core/viewer/paste-config-host.d.ts +12 -0
- package/dist/core/viewer/pivot-dialog-host.d.ts +48 -0
- package/dist/core/viewer/readonly-prompt-host.d.ts +23 -0
- package/dist/core/viewer/validation-prompt-host.d.ts +25 -0
- package/dist/core.js +67 -64
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1110 -925
- package/dist/react/ExcelViewer.d.ts +41 -4
- package/dist/react.js +835 -628
- package/dist/style.css +1 -1
- package/dist/vue2.css +1 -1
- package/dist/vue2.js +1 -1
- package/package.json +1 -1
- package/dist/chunks/index-6q8kSGQg.js +0 -10575
- package/dist/chunks/plugin-overlay-BUrPrpT2.js +0 -9146
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,289 @@
|
|
|
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.11.0] - 2026-06-15
|
|
6
|
+
|
|
7
|
+
> 三个编辑小件合并:**查找替换补全 + 数字格式编辑器 + 批注编辑**。都复用已有引擎/对话框套路;对话框均为框架无关 DOM(三壳共用一份,UI 天然 1:1)。
|
|
8
|
+
|
|
9
|
+
### 新增 — 查找替换
|
|
10
|
+
|
|
11
|
+
- 此前只有「查找」(高亮定位),现补「替换」:查找栏开 `editable` 时多出替换行(替换输入 + 替换 / 全部替换);控制器 `setFindReplace` / `replaceCurrent`(替换当前并查找下一个,替换后重算命中)/ `replaceAll`(全部替换,**整体单次撤销**)。支持区分大小写 / 全字匹配;跳过只读格。三壳查找栏都加替换行(Vue3 `FindBar.vue` + React/Vue2 内联)。
|
|
12
|
+
|
|
13
|
+
### 新增 — 数字格式编辑器
|
|
14
|
+
|
|
15
|
+
- 框架无关对话框 `viewer/number-format-dialog-host.ts`(工具栏 `number-format` 入口):分类(常规/数值/货币/百分比/日期/时间/文本/自定义)+ 选项(小数位数 / 千分位 / 负数红色 / 货币符号 / 日期时间预设)→ **实时预览**(复用 number-format 引擎)+ 可直接编辑「格式代码」→ 确定即 `setStyle({ numFmt })`(单次撤销)。控制器 `setSelectionNumberFormat(code)` / `openNumberFormatDialog()`。
|
|
16
|
+
|
|
17
|
+
### 新增 — 批注编辑
|
|
18
|
+
|
|
19
|
+
- 批注此前只读显示,现可**新建/编辑/删除**:新 `set-comment` 命令(逆 = restore-cells 精确还原,单次撤销)+ `model/mutations.ts` 的 `setCellComment`(空批注清除、空格挂批注/清空);右键菜单单格加「插入/编辑/删除批注」;框架无关对话框 `viewer/comment-dialog-host.ts`(多行文本 + 确定/删除/取消)。控制器 `getCellComment` / `setCellComment` / `openCommentEditor`。**导出回写**:`xlsx-writer` rebuild + overlay 都把批注写成 ExcelJS note(此前 rebuild 丢批注)。
|
|
20
|
+
|
|
21
|
+
- 三壳句柄 + 插件 `ViewerApi` 都暴露上述 API;三 demo 工具栏加 `number-format` 入口。测试:`find-replace` / `number-format` / `comment` e2e(各 Vue/React/Vue2)+ `autofill`/`comment` 等单测。基线:**389 单测 + 183 e2e**。
|
|
22
|
+
|
|
23
|
+
## [1.10.0] - 2026-06-15
|
|
24
|
+
|
|
25
|
+
> 新增 **自动填充柄**(Excel/WPS 拖拽填充)。纯框架无关 core canvas 交互(渲染层画柄 + 控制器拖拽),三壳零改动自动获得;需 `editable`。
|
|
26
|
+
|
|
27
|
+
### 新增 — 自动填充柄(拖拽填充序列)
|
|
28
|
+
|
|
29
|
+
- **序列引擎**(`edit/autofill.ts`,纯函数可单测):接续源值产出新值 —— 全数值(1 个复制 / ≥2 个等差外推)、全日期(每格 +1 天 / 按相邻差)、"前缀+末尾整数"文本(`Item 1`→`Item 2`,保留前导零位宽)、星期/月份名(中英常见写法循环接续)、其它循环复制。
|
|
30
|
+
- **渲染层**(`canvas-renderer`):选区右下角画**填充柄**小方块(`editable` 才画,随 editable 实时显隐)+ `fillHandleAt` 命中检测 + 拖拽时画目标范围**虚线预览**。
|
|
31
|
+
- **控制器**:`onMouseDown` 命中填充柄 → `fill` 拖拽模式;`onMouseMove` 按鼠标位置算目标范围(主轴 = 偏移更大的方向,支持上/下/左/右)+ 设预览;`onMouseUp` 按源选区逐列/行接续序列,**整体单次撤销**(`set-cells` 命令),填充后选区扩到整片。填充柄上悬停显示十字光标。跳过只读格。
|
|
32
|
+
- **Ctrl/⌘ 修饰键翻转"复制 ↔ 序列"**(对齐 Excel):普通拖 单个数字→复制、≥2 数字→等差;按住 Ctrl 拖 单个数字→递增(+1)、序列→复制;日期/星期月份/文本递增普通→序列、Ctrl→复制。
|
|
33
|
+
- 注:v1 填充**值**(不复制源格式);列/行各自独立成序列。
|
|
34
|
+
- 测试:`edit/__tests__/autofill.test.ts`(13 例:复制/等差/日期/文本递增/星期/月份/循环 + Ctrl 翻转)+ `e2e/autofill.e2e.ts`(拖填充柄出等差序列 + undo + Ctrl 单数字递增,Vue/React/Vue2 三壳)。基线:**386 单测 + 171 e2e**。
|
|
35
|
+
|
|
36
|
+
## [1.9.0] - 2026-06-14
|
|
37
|
+
|
|
38
|
+
> 路线图「保真/编辑完整化」第二阶段:**条件格式从只读渲染 → 可编辑**。整个功能由 `conditionalFormat` prop 开启(三壳同名,默认 false = 关闭、与历史一致;三 demo 已开启)。支持全 6 类规则的新建/编辑/删除 + 导出回写,overlay 模式保留原件未编辑规则原样。
|
|
39
|
+
|
|
40
|
+
### 新增 — 条件格式可编辑(全类型)
|
|
41
|
+
|
|
42
|
+
- **模型 + 解析**:`ConditionalRule` 加 `id` / `origin`('parsed'|'user')/ `dirty` / `raw`(原始 ExcelJS rule,导出原样回写用)+ `top10`(rank/percent/bottom)/ `iconSet.reverse`。`parseConditional` 给每条规则派 id、存 raw、补全 top10/iconSet 字段。
|
|
43
|
+
- **命令栈**:新增 `set-conditional` 命令(整张 `conditional` 数组不可变替换,逆=换回前态)→ 新建/编辑/删除规则**整体单次撤销**。
|
|
44
|
+
- **编程 API**(控制器 + 插件 viewer + 三壳句柄):`getConditionalRules` / `addConditionalRule` / `updateConditionalRule` / `removeConditionalRule` / `setConditionalRules` / `openConditionalFormatDialog`。改完即 live 重渲。需 `conditionalFormat` + `editable`。
|
|
45
|
+
- **管理对话框**(框架无关 DOM `viewer/conditional-format-dialog-host.ts`,三壳共用一份 → UI 天然 1:1):列出当前表所有规则(可删/可编辑)+ 新建。6 类编辑器:突出显示单元格(cellIs:大于/小于/介于/等于… + 填充/字体色/加粗)、公式(expression)、色阶(colorScale 2/3 色)、数据条(dataBar 颜色 + 渐变)、图标集(iconSet 7 种 + 反向)、项目选取(top10 前/后 N + 百分比)。新建默认套到当前选区。
|
|
46
|
+
- **工具栏入口** `conditional-format`(三壳 + 三 demo,`conditionalFormat` 关时不渲染)。
|
|
47
|
+
- **导出回写**(rebuild + overlay 共用 `xlsx-writer` 的 `writeConditionalFormatting`):清空 ExcelJS 现有 CF 后按模型重建 —— **未编辑的 parsed 规则用 `raw` 原样回写**(含 cfvo 阈值,零退化);用户新建/编辑过的按模型 `buildExcelCfRule` 构造(全 6 类)。**1.9.0 起 rebuild 也回写条件格式**(此前 rebuild 丢弃);overlay 满足"原件规则原样留、只增改用户改的"。
|
|
48
|
+
- 测试:`edit/__tests__/conditional-format.test.ts`(解析保真 / 命令撤销 / rebuild + overlay 往返 5 例)+ `e2e/conditional-format.e2e.ts`(API 新增+撤销、对话框新建 cellIs,Vue + React + Vue 2 三壳)。基线:**373 单测 + 165 e2e**。
|
|
49
|
+
|
|
50
|
+
## [1.8.0] - 2026-06-14
|
|
51
|
+
|
|
52
|
+
> 路线图「保真/编辑完整化」第一阶段:① 把 1.7.0 起步的数据验证**做完整**(从"只能选值"到"编辑拦截非法输入 + 输入/出错提示");② 补上 **Vue 2 壳的 e2e 回归网**(此前 Vue 2 零 e2e,改 Vue 2 全靠手测,是 CLAUDE.md 点名的空洞)。
|
|
53
|
+
|
|
54
|
+
### 新增 — 数据验证完整化:编辑时拦截非法输入 + WPS 式提示
|
|
55
|
+
|
|
56
|
+
- **解析全类型规则**(`SheetModel.dataValidationRules`):list / 整数(whole)/ 小数(decimal)/ 日期(date)/ 时间(time)/ 文本长度(textLength)/ 自定义(custom),连同 operator(between/greaterThan/…)、约束操作数、`allowBlank`、出错信息(errorStyle/errorTitle/error)、输入提示(promptTitle/prompt)。1.7.0 的 `dataValidations`(下拉箭头区域)/`dataValidationLists`(选项)从这里派生,**零回归**。
|
|
57
|
+
- **编辑时拦截**(框架无关引擎 `edit/data-validation.ts`,纯函数可单测):内置编辑器提交 / 公式栏提交时校验。`errorStyle='stop'`(默认)→ **硬拒,不写入**,弹模态出错提示,**编辑器保持打开让用户改正**;`warning`/`information` → toast 软提示但放行;`custom` 公式与以 `=` 开头的公式不拦(结果未知);空值放行(允许清空)。
|
|
58
|
+
- **提示 UI**(框架无关 DOM,三壳共用 `viewer/validation-prompt-host.ts`):出错模态 / toast + 选中带"输入提示"的格时格旁弹黄色气泡(随选区/滚动跟手)。
|
|
59
|
+
- **顺手修一个编辑器 UX bug**:校验拒绝后,内置编辑器的"已提交"锁(`done`)曾被卡死 → 用户改正后回车无反应。现 `commit()` 返回成功与否,拒绝时解锁并记住被拒值(避免点弹窗按钮的 blur 二次触发叠弹),改内容即可再次提交;弹窗关闭后焦点还给编辑器。
|
|
60
|
+
- 测试:`edit/__tests__/data-validation.test.ts`(12 例:各类型/operator/空值/坏约束/软提示/自定义信息)+ `date-locale.test.ts` 加全类型规则解析;`data-validation.e2e.ts` 加"整数 1-100 校验:输入 999 → 弹拒、值不变、改 80 → 写入"(Vue + React + **Vue 2** 三壳)。
|
|
61
|
+
|
|
62
|
+
### 新增 — Vue 2 壳 e2e 回归网
|
|
63
|
+
|
|
64
|
+
- 此前 Vue 2 壳**完全没有 e2e**(只有 Vue 3 + React 双覆盖),改 Vue 2 只能手测 → 高风险点(patch 复用 controller 持有的 DOM 致 stale、Vue 2.6 函数 ref 被忽略)迟早回归。现补上:
|
|
65
|
+
- `playwright.config.ts` 加**第二个 dev server**(端口 5302,`npm run dev:vue2`,plugin-vue2 SFC 编译器隔离);`vue2-demo` DEV 下把命令式 API 挂 `window.__excelViewerVue2`(对齐 Vue3 `__excelViewer` / React `__excelViewerReact`)。
|
|
66
|
+
- `e2e/vue2-smoke.e2e.ts`:加载示例→canvas 渲染+模型有 sheet、编辑模式 editCell+undo、rectOf 几何、demo 顶栏按钮 1:1 —— 把"只有真 Vue 2 浏览器才暴露"的 DOM 复用/imperative DOM 回归钉死。
|
|
67
|
+
- `data-validation.e2e.ts` 加 Vue 2 行,数据验证下拉 + 整数拦截在 Vue 2 上同样覆盖。
|
|
68
|
+
- 基线更新:**368 单测 + 159 e2e**(Vue 3 / React / Vue 2 三壳)。
|
|
69
|
+
|
|
70
|
+
## [1.7.0] - 2026-06-12
|
|
71
|
+
|
|
72
|
+
### 新增 — 列表型数据验证:点下拉箭头选值(B4,审计后续专项)
|
|
73
|
+
|
|
74
|
+
- 以前只对"列表型数据验证"的格画下拉箭头**指示**,点了没反应(没解析选项)。现在:**解析选项**(内联 `"a,b,c"` 拆分 + 同表区域引用 `$A$1:$A$5` 读那几格)存进 `SheetModel.dataValidationLists`;编辑模式下点格内下拉箭头 → **弹可选值菜单**(复用右键菜单宿主,框架无关、三壳共用)→ 点选即 `editCell` 填入(走命令栈,**可撤销**)。只读 / 只读格不弹。
|
|
75
|
+
- 解析后续:仅"列表型";其它校验类型(整数/日期/自定义公式范围)仍只解析、不强制(强制是后续编辑增强)。
|
|
76
|
+
- 测试:`date-locale.test.ts` 加"内联选项解析进 dataValidationLists";`data-validation.e2e.ts`(Vue+React)点箭头→弹菜单→点选填值→撤销回退。
|
|
77
|
+
|
|
78
|
+
### 关于审计另一后续专项(矢量 PDF 富文本矢量化)—— 评估后不做
|
|
79
|
+
|
|
80
|
+
- 审计列的"矢量 PDF 富文本走栅格兜底"经核实:矢量 PDF 对**任何非拉丁(中文)文本**(无自定义字体时)本就走栅格(jsPDF 内置字体画不了 CJK)。本组件主力是中文/WPS 文件,文本在矢量 PDF 里**无论如何都栅格**,所以"富文本矢量化"只对"拉丁文 + 自定义字体"的多色格有意义,价值很窄;且栅格兜底**渲染正确**(只是变图片/不可搜索)。故保持现状。
|
|
81
|
+
|
|
82
|
+
## [1.6.2] - 2026-06-12
|
|
83
|
+
|
|
84
|
+
> 一次**深度保真审计**(针对"为什么这么多 bug 等用户拖真实文件才暴露")的产出。审计把同类潜在问题归为两大根因 ——
|
|
85
|
+
> **ExcelJS 有损中间层** + **多条平行渲染路径漂移**,逐条用代码核实。本版修掉其中已确认的几条;另有数条经核实为
|
|
86
|
+
> **非问题 / 已处理 / 故意为之**(见下);两条大件(数据校验下拉编辑器、矢量 PDF 富文本矢量化)作为后续专项。
|
|
87
|
+
|
|
88
|
+
### 修复 — 富文本(多色/多段)往返导出丢每段字体
|
|
89
|
+
|
|
90
|
+
- `xlsx-writer` 导出富文本只写 `{text}`,把每段的颜色/粗斜/下划线/删除线/字号全丢了 → 打开多色文件、编辑、导出后变纯黑文本。现在带回每段字体(`toExcelRichFont`)。往返单测锁定。
|
|
91
|
+
|
|
92
|
+
### 修复 — 富文本渲染只补了换行,缺 indent / 下划线删除线 / shrinkToFit
|
|
93
|
+
|
|
94
|
+
- `drawRichText` 1.6.1 加了 wrap,但仍缺普通文本路径早有的:**缩进**、**逐 run 下划线/删除线**、**shrinkToFit**(超宽统一缩放塞进列宽)。现在补齐,跟普通文本路径同档(溢出仍顶对齐)。
|
|
95
|
+
|
|
96
|
+
### 修复 — 竖排文本忽略垂直对齐;矢量 PDF 文本溢出/双线边框
|
|
97
|
+
|
|
98
|
+
- 竖排文本恒顶对齐 → 现在尊重 vAlign(middle/bottom,超高顶对齐)。
|
|
99
|
+
- 矢量 PDF 导出:wrap 文本折行超过格高时不再被裁掉文头(**溢出顶对齐**,跟屏幕一致);**double 边框**画成两条平行线(原来退化成单线)。
|
|
100
|
+
|
|
101
|
+
### 修复 — 空格上的批注被丢
|
|
102
|
+
|
|
103
|
+
- 解析器对"空且无边框无填充"的格返 null 跳过,连**批注**也一起丢。现在空格带批注也保留入模型。
|
|
104
|
+
|
|
105
|
+
### 审计核实为非问题 / 已处理(本版不改,记录在案)
|
|
106
|
+
|
|
107
|
+
- **隐藏 sheet**:三壳 tab 早已 `filter(state==='visible')`、导出已保留 state —— 已处理。
|
|
108
|
+
- **空格超链接**:ExcelJS 把超链接格给成 object 值(非 null),走非空分支,不会丢 —— 非问题。
|
|
109
|
+
- **内置日期格式 15-21**:14/22(短日期/日期+时间)已重映射;15-17 的英文月名、20/21 的 `h:mm` 本就是正确 OOXML 显示,故意不动。
|
|
110
|
+
- **合并格边框**:合并边框存锚点格、渲染按锚点 box 画整片,正确 —— 加回归单测锁定。
|
|
111
|
+
|
|
112
|
+
### 后续专项(审计已列,本版未做,见路线图)
|
|
113
|
+
|
|
114
|
+
- 数据校验:现仅"列表型"画下拉箭头指示;**全类型解析 + 真下拉选取编辑器**是独立编辑功能。
|
|
115
|
+
- 矢量 PDF 富文本:现走**栅格兜底**(渲染正确,只是变图片、不可搜索),**矢量化富文本**是较大专项。
|
|
116
|
+
|
|
117
|
+
## [1.6.1] - 2026-06-12
|
|
118
|
+
|
|
119
|
+
### 修复 — 打开 .xlsx 边框线不完整(空但带边框的结构格被丢)
|
|
120
|
+
|
|
121
|
+
- **现象**:打开带"树形/网格结构"的 .xlsx(大量**空单元格只承载边框**组成框线),渲染出来的框线残缺、盒子合不拢。
|
|
122
|
+
- **根因**:解析器用 ExcelJS `eachCell({ includeEmpty: false })` 遍历,**跳过 value 为空的格** —— 而结构框线全靠这些"空但有边框"的格,于是它们的边框(及底色)整批丢失。(实测该文件 H3 = 空格 + 上边框,解析后**根本不在模型里**。)
|
|
123
|
+
- **修法**:改 `includeEmpty: true` 遍历;`toCellModel` 对空格**只在有可见样式(边框/填充)时**才入模型,真正空白格(无边框无填充)返 null 跳过 —— 既保住结构格边框/底色,又不把空白格塞进来膨胀。`eachCell` 只扫到该行最右有格的列,大表也不会扫到无限远(大表维度单测仍过)。
|
|
124
|
+
- 测试:`date-locale.test.ts` 加"空但带边框的格入模型(上边框保住)、纯空白格不入模型"。
|
|
125
|
+
|
|
126
|
+
### 修复 — 只读模式右键没有「复制」(复制不改数据,理应可用)
|
|
127
|
+
|
|
128
|
+
- 之前右键内置菜单只在编辑模式给(`editable ? build : []`),只读模式直接空 → 没法右键复制。改为:**复制**(不改数据)任何模式都给;编辑项(粘贴/插入/删除/合并/清除…)仍仅编辑模式。
|
|
129
|
+
- 测试:`edit-contextmenu.e2e.ts` 加"只读模式右键 → 有复制、无编辑项"(Vue+React)。
|
|
130
|
+
|
|
131
|
+
### 修复 — 富文本(多色/多段)单元格不换行,渲染只显示中间一截
|
|
132
|
+
|
|
133
|
+
- **现象**:打开真实 WPS 文件,J 列「货品名称」(多色富文本 + `wrapText`)只显示中间几个字(如"配12件套套"),WPS 是从顶部完整换行显示。多点几次工具栏「自动换行」会显示更多 —— 因为那是另一条 autofit 撑高路径。
|
|
134
|
+
- **根因**:`canvas-renderer.drawRichText` **完全没实现换行** —— 把所有富文本 run 拍在**一整行**上画,水平居中导致起点远在格左外、再裁切到格内,于是只看到水平居中的那一截"中间文字";而普通文本路径早有折行 + 溢出顶对齐。富文本走的是另一条路,漏了。
|
|
135
|
+
- **修法**:重写 `drawRichText` 支持 `wrapText` —— 逐字符按列宽折行(保留各 run 字体/颜色),并套用跟普通文本**完全一致**的垂直对齐 + **溢出顶对齐**(超过行高 → 顶对齐显示文头,WPS 行为)。非 wrapText 仍单行。实测真实文件 J 列现与 WPS 渲染一致(顶部完整换行)。
|
|
136
|
+
|
|
137
|
+
### 修复 — 打开 .xlsx 日期显示成 `04-01-26`(应为 `2026/4/1`)
|
|
138
|
+
|
|
139
|
+
- **根因**:OOXML 内置短日期格式(`numFmtId=14`)的"显示"本应跟随区域设置,但 ExcelJS 把它**硬编码成美式串** `mm-dd-yy` → 我们照渲染成 `04-01-26`;WPS/Excel 中文环境显示 `2026/4/1`。(实测真实 WPS 文件:该格 `numFmtId=14`,wrapText/垂直居中/行高都解析正确,**唯一**出入就是这个内置日期格式。)
|
|
140
|
+
- **修法**:`exceljs-adapter` 把 ExcelJS 的内置日期串重映射成中文 locale —— `mm-dd-yy → yyyy/m/d`、`m/d/yy h:mm → yyyy/m/d h:mm`(纯时间 / 带英文月名的 `d-mmm-yy` 不动)。需自定义可经 `transformModel` 钩子覆盖。
|
|
141
|
+
- 测试:`date-locale.test.ts`(ExcelJS 写 `mm-dd-yy` → 解析回应得 `yyyy/m/d` 渲染 `2026/4/1`;普通货币格式不受影响)。
|
|
142
|
+
|
|
143
|
+
### 修复 — Vue 2 / React demo 拖文件不解析(弹空白标签 `about:blank#blocked`)
|
|
144
|
+
|
|
145
|
+
- 拖文件加载是 demo 层能力,Vue 3 `App.vue` 有 `@drop.prevent`,而 vue2-demo / react-demo 漏了 → 浏览器走默认行为把文件当导航打开。按"三 demo 1:1"补上:两 demo 根容器加 `dragover/dragleave/drop` 的 `preventDefault` + `onDrop` 落 `src` 解析。
|
|
146
|
+
|
|
147
|
+
## [1.6.0] - 2026-06-12
|
|
148
|
+
|
|
149
|
+
### 新增 — 可配置粘贴行为(覆盖 / 合并 / 仅值)+ 右键选择性粘贴 + 工具栏配置面板 + 只读提示
|
|
150
|
+
|
|
151
|
+
**问题**:粘贴(尤其从 WPS 粘真实表格)有两类痛点 —— ① 目标区**原有合并/结构没清掉**,旧合并吞列致数据错位(如示例 A1:E1 旧合并把粘进来的前 5 列吞了);② "贴近源(覆盖)"还是"保留目标格式(仅值)"应由用户选,而非写死。
|
|
152
|
+
|
|
153
|
+
**方案 —— 框架无关的 `PasteBehavior` 配置系统**(core,三壳共用):
|
|
154
|
+
|
|
155
|
+
- **逐项可配**(默认 = 覆盖式 1:1):`cellStyle` / `fill`(覆盖/合并/不粘)· `rowHeight`(搬源/不动)· `colWidth`(仅首行搬源/总搬/不动)· `sourceMerges`(应用/不应用)· **`targetMerges`(清掉/保留 —— 默认清,修数据错位)** · `images`(落格/不粘)。首行 vs 中间唯一差异收敛在 `colWidth: 'firstRowOnly'`(列宽整列共享,仅粘到首行才取源宽,粘到中间不动上方表头)。
|
|
156
|
+
- **两条粘贴路径都按配置走**:`pasteRich`(外部 WPS/Excel)+ `pasteSnapshot`(应用内/跨实例 1:1);样式按「覆盖式以中性默认为基 / 合并式以目标为基 / 仅值留目标」三档算(见 `resolvePastedCellStyle`)。
|
|
157
|
+
- **右键「选择性粘贴」子菜单**(core context-menu 加一级 flyout):`覆盖格式(贴近源)` / `保留原样式(仅值)`,逐次预设,不改默认。
|
|
158
|
+
- **工具栏「⚙ 粘贴配置」面板**(`paste-config-host.ts`,框架无关 DOM,**三壳/三 demo 共用一份**,UI 天然 1:1):列出全部项下拉自由定制 + 两个快捷预设(覆盖式 1:1 / 仅值)+ 恢复默认;应用即 `setPasteBehavior`。
|
|
159
|
+
- **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`。
|
|
160
|
+
- **只读检查 + 提醒 = 核心层统一(不按输入方式重写)**:只读**判定**本就是核心层唯一真相源 `resolveEditable()`(`isCellEditable` 全走它),任何改数据的操作(粘贴/编辑/合并/拆分/图片互转)在 EditController 里逐格 `isEditable` 拦截 + 收集 denied + emit `permission-denied`;**提醒**统一收口在控制器 `emitEditEvent` —— 所有 `permission-denied`(dimension 列宽行高=布局除外)经此**一处**按 `readOnlyPrompt` 配置弹内置提醒。新增输入方式只要照常走 EditController API,**无需各自重写只读检查/提醒**(避免遗漏)。
|
|
161
|
+
- **只读提醒(逐格精确 + 可配 dialog/toast/none)**:撞只读(**编辑模式下也可能有只读格**,如 `readOnlyRanges`)不再静默 —— 收集**所有被跳过的只读格**(不止落点),按 `readOnlyPrompt`:`'dialog'`(默认,弹窗**列出具体哪些格**A1 引用)/ `'toast'`(顶部气泡)/ `'none'`(只发事件)。框架无关 DOM(`readonly-prompt-host.ts`)三壳共用。粘贴落点只读不再整次中止 —— 可编辑的格照常粘、只读格跳过并在提醒里列出。
|
|
162
|
+
- **右键「选择性粘贴」二级菜单可达性修复**:菜单宿主加 flyout 关闭延时 + 紧贴父菜单(消除缝隙),从父项滑到子项不再"还没点到就消失"。
|
|
163
|
+
- **Ctrl+V vs 右键差异(已知,浏览器限制)**:`Ctrl+V` 走 `paste` 事件拿**原始 HTML**(WPS 的 `<style>` 类格式全在);右键「粘贴/选择性粘贴」走 `navigator.clipboard.read()`,**浏览器会净化** HTML(删 `<style>`)→ 从 WPS 粘的类格式不如 Ctrl+V 全(应用内 1:1 复制因带 `data-ooxml-clip` 属性不受影响)。无法在右键路径绕过(无 paste 事件)。
|
|
164
|
+
- 测试:`paste-behavior.test.ts`(9 个,各档样式解析)+ `paste-behavior.e2e.ts`(8 个:仅值保留目标 / 默认覆盖清目标合并修数据错位 / 配置面板弹出+预设+应用 / **粘到只读区弹对话框列出哪些格+只读格不被覆盖**,Vue+React)。
|
|
165
|
+
|
|
166
|
+
### 修复 — 从 WPS/Excel 富粘贴丢"自动换行 + 垂直居中"(连带水平居中也看不出)
|
|
167
|
+
|
|
168
|
+
- **现象**:WPS 里开了自动换行 + 水平/垂直居中的格,粘进来后不换行(长文本溢出/裁切)、垂直贴底,连水平居中也看不出来。
|
|
169
|
+
- **根因(两处解析漏洞,均非回归,一直没实现)**:
|
|
170
|
+
- **`white-space` 没解析** → `wrapText` 永远 false。Excel/WPS 用 `white-space:normal` 标记开了自动换行(全局默认 `td{white-space:nowrap}`,换行格用 `normal` 覆盖)。
|
|
171
|
+
- **裸 `td` 元素默认层没收集** → 垂直居中丢。WPS 把"所有单元格默认"(如 `vertical-align:middle`、`white-space:nowrap`、`font-size:11pt`)放在 `td{...}` 选择器上,各 `.etN` 类只覆盖要改的;而 `parseClassStyles` **只收 `.类名` 规则、不收裸 `td` 规则**,于是没写 `vertical-align` 的格全回落成默认 `bottom`。
|
|
172
|
+
- **渲染器本就支持** `wrapText`/水平居中/垂直居中(canvas-renderer 1122/1152/1140 行),只是解析没喂字段。
|
|
173
|
+
- **修法**:① `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 默认层不暴露,引入默认层后必须修对)。
|
|
174
|
+
- 测试:`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`。
|
|
175
|
+
|
|
176
|
+
> **路径隔离(为什么不会互相串)**:本组件三种"进数据"路径**各走各的解析,零交叉**——① 打开 .xlsx 走 `exceljs-adapter`(OOXML);② 应用内/跨实例复制走 `clipboard-snapshot`(自带 `data-ooxml-clip` 完整快照);③ 外部 WPS/Excel 粘贴才走 `clipboard-html`。本次只改 ③。`pasteRichHtml` 先认快照(② )、不是才退到外部解析(③),所以 ① ② 完全不受影响。每个外部粘贴格的 CSS 也是**独立**算出一份 `CellStyleOverride`(td默认+自己的类+自己的内联),不跨格混用。
|
|
177
|
+
|
|
178
|
+
### 修复 — 外部富粘贴样式是"合并目标"而非"覆盖"(粘到带色表头会漏出表头底色)
|
|
179
|
+
|
|
180
|
+
- **现象**:从首行(带绿底表头)开始粘,源里**没写填充**的格(如 WPS 的 `.et6` 没 `background`)粘完仍保留表头的绿底,不是干净覆盖成源的样子。
|
|
181
|
+
- **根因**:`pasteRich` 用 `applyStyleOverride` 套源样式,而它以**目标格现有样式为基**做浅合并(给工具栏"加粗"那种增量编辑设计的)。源 patch 没写的属性(填充/边框)就保留目标原有的 → 粘到带色区会漏底色。
|
|
182
|
+
- **修法**:`pasteRich` 套样式前先把目标格 styleId 归 0(中性默认),再合并源 patch —— 即**以中性默认为基的覆盖式**,源没写的属性回落默认(无填充/无边框),不再漏目标底色。结果贴近源,同 Excel"粘贴替换格式"、也同应用内 1:1 快照粘贴(后者本就清空目标再落)。
|
|
183
|
+
- **为什么所有落点都这么做、不限首行**:样式是**逐格**的,覆盖只影响被粘的那几格、不波及邻格,所以任何位置都安全;列宽因**整列共享**才需限定首行。纯文本粘贴(源无样式 → 无 patch)不受影响,照常保留目标格式。
|
|
184
|
+
- 测试:`edit-paste-rich.e2e.ts` 加"粘到红底格 → 源白底覆盖成白、源没写填充的格清成无填充(不漏红底)、边框照常"。
|
|
185
|
+
|
|
186
|
+
### 变更 — 粘贴只带行高、列宽默认不改(例外:粘到首行时套用源列宽)
|
|
187
|
+
|
|
188
|
+
- **问题**:粘贴(WPS/Excel 外部富粘贴 + 应用内 1:1 快照粘贴)原会把源**列宽**搬到目标列。但列宽是**整列共享**的,粘到第 18 行却把同列上方的表头(第 1~7 行)宽度一起改了 → 破坏现有表格布局。
|
|
189
|
+
- **改法**:两条粘贴路径(`pasteRich` 外部 / `pasteSnapshot` 应用内)都**只搬行高;列宽默认不搬**(以现有表头为准,同 Excel 默认粘贴)。行高是逐行的,只影响被粘的那几行;内嵌图按**目标格尺寸**填充,不依赖源列宽,图照样填满。
|
|
190
|
+
- **例外 — 粘到首行(`start.row === 0`)套用源列宽**:此时上方没有任何内容可被破坏,粘贴块本身就是新表头/新布局,列宽应以它为准。`row > 0`(粘进已有表格中间)才保持目标列宽不动。
|
|
191
|
+
- 源列宽仍随 `ParsedClipboard.colWidths` / `ClipSnapshot.colWidths` 解析、携带,粘到首行即应用、否则保留。
|
|
192
|
+
- 测试:`edit-paste-rich.e2e.ts`(WPS)断言粘到 row 2 列宽不被源覆盖、粘到 row 0 列宽=源 72/120;`clipboard-snapshot.test.ts`(应用内)断言粘到 row 5 目标列宽不变、粘到 row 0 套用源列宽 120。
|
|
193
|
+
|
|
194
|
+
### 修复 — 空格/新建格/粘贴串入首格底色(根因:解析时 `styles[0]` 不是中性默认,而是第一个被解析到的格样式)
|
|
195
|
+
|
|
196
|
+
- **现象**:加载示例(A1 是绿底表头)后,从 WPS 粘贴一段内容,部分**本应无底色**的格冒出绿底(看起来像"混入了第 1 行样式");散落分布(有的格白、有的格绿)。打开任意"首格带底色"的本地 .xlsx 也会有同类隐患(空格/编辑新建的格染上首格底色)。
|
|
197
|
+
- **根因**:ExcelJS 解析器 `buildSheet` 按**遇到顺序** intern 样式,首个被解析到的单元格(通常是 A1 表头)样式就占据了 `styles[0]`。而全 core 多处把 `styleId 0` / `styles[0]` 当成"中性空白默认基样式"用——空格、`setCellValue` 新建的格、`applyStyleOverride` 对空格的兜底基样式都回落到它。于是 A1 的绿底成了"默认底色":凡是没有显式指定填充的格(如 WPS 类里**没写 `background:`** 的 `.et6`/`.et8`),`mergeStyleOverride` 保留基样式的 `fill` → 冒出绿底;写了 `background:#FFFFFF` 的格才是白 → 散落串色。
|
|
198
|
+
- **修法(深修,非兜底)**:抽出唯一规范工厂 `makeDefaultStyle()`([src/core/model/types.ts](src/core/model/types.ts)),`buildSheet` 解析前**预置 `styles[0] = makeDefaultStyle()`**(无填充/无边框中性默认)并登记进 styleIndex,真实格样式从 index 1 起;首格 A1 的绿底样式仍在(只是换了 index),引用它的格不受影响。loader-json / clipboard-snapshot 原本各有一份重复的默认样式工厂,一并改为复用此唯一来源。
|
|
199
|
+
- **影响面**:不止修了 WPS 粘贴——所有"空格/新建格/兜底基样式"路径现在都正确回落到中性默认,文件打开渲染零变化(带样式的格按各自 styleId 渲染如初),只有"默认格"不再染上首格底色。
|
|
200
|
+
- 测试:`parse.test.ts` 加回归"styles[0] 恒为中性空白默认(首格 A1 有绿底也不占 index 0)";现有 339 单测 + e2e 全绿。
|
|
201
|
+
|
|
202
|
+
### 修复 — Ctrl+V 改走 paste 事件(根因:clipboard.read() 会净化 HTML 删掉 `<style>`)
|
|
203
|
+
|
|
204
|
+
- **真正的根因**:我们 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`(本就有损)且**还被浏览器二次净化**。
|
|
205
|
+
- **修法**:控制器在 `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 不受影响。
|
|
206
|
+
- 右键菜单"粘贴"无 paste 事件,仍走 `pasteFromClipboard()`(`read()`,会净化)→ 从 WPS 粘的格式不如 Ctrl+V 全(已在方法注释/文档说明)。
|
|
207
|
+
- 测试:`edit-paste-rich.e2e.ts` 加"派发 paste 事件(原始 HTML)→ onPaste → 类格式/numFmt/VML 图都还原"用例(Vue+React);自家 1:1 复制 e2e 仍过(快照不被净化)。
|
|
208
|
+
|
|
209
|
+
### 修复 — 从 WPS/Excel 富粘贴丢格式/数字/图片(对照真实 WPS 剪贴板 HTML)
|
|
210
|
+
|
|
211
|
+
- **解析 `<style>` 类样式**:Excel/WPS 复制的剪贴板 HTML 把单元格格式放在 `<style>` 块的 CSS 类里(`<td class=et3>` + `.et3{border:…;background:…}`,整段还包在 `<!-- -->` 里),而旧解析只读每个 `<td>` 的内联 `style=`(`DOMParser` 不会把类规则套到元素上)→ 边框/底色/字体全丢。现在 `parseClipboardHtml` 先收集所有 `<style>` 块的「类名→声明」(剥掉 `<!-- -->` 壳),落格时把命中类的声明合并进 `td.style`(类在前、内联在后,内联优先)再解析 → 还原边框/填充/字体/对齐。
|
|
212
|
+
- **数字格式(日期/货币不再变成裸序列号)**:格式码在 `mso-number-format`(CSSOM 会丢弃这种私有属性),且值是 CSS 转义的(`2`→`"`、`\#`→`#`、`\;`→`;`、`\(`→`\(`)。新增 `parseMsoNumberFormat`/`unescapeMsoNumFmt` 从原始声明串解析并解转义 → `numFmt`(如 `yyyy/m/d`、`"¥"#,##0.00_);[Red]\("¥"#,##0.00\)`),配合 `x:num` 原始序列号 → 日期/货币正确显示(之前 46113 直接显示成裸数字)。
|
|
213
|
+
- **列宽/行高 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>` 完全对应)
|
|
214
|
+
- **图片(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 单元格图)。
|
|
215
|
+
- **不影响应用内 1:1 复制**:本组件自己复制的内容带 `data-ooxml-clip`,粘贴时先走快照路径(`parseSnapshotHtml`/`pasteSnapshot`),根本不进 `parseClipboardHtml`,零影响。
|
|
216
|
+
- 图片方向澄清(文档):WPS/Excel **区域复制**的图片在剪贴板里是 `file:///` 本地路径,浏览器读不到 → 区域里的图必然丢(浏览器限制);单图复制走 `pasteImageBlob` 仍可。
|
|
217
|
+
- 测试:`e2e/edit-paste-rich.e2e.ts` 加 Excel/WPS 类样式(`<style> .xl 类`)用例(Vue + React),断言边框/填充/字体/对齐还原。
|
|
218
|
+
|
|
219
|
+
## [1.5.0] - 2026-06-11
|
|
220
|
+
|
|
221
|
+
### 新增 — 应用内复制粘贴 1:1 保真(走剪贴板嵌入快照)
|
|
222
|
+
|
|
223
|
+
**问题**:复制粘贴走系统剪贴板的 HTML/TSV 交换(为了能贴进 Excel/WPS),而复制端只 emit `<td style=CSS>格式化文本`,导致:① 合并单元格被拍平;② 图片(DISPIMG / 浮动图)整个丢失;③ 数字按格式化文本复制(`¥237` → 文本"¥237",丢原始值);④ 边框/数字格式/行高列宽不带。
|
|
224
|
+
|
|
225
|
+
**方案(全走剪贴板,跨实例 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 也能带上合并和图片)。
|
|
226
|
+
|
|
227
|
+
- 新增 [src/core/edit/clipboard-snapshot.ts](src/core/edit/clipboard-snapshot.ts):`ClipSnapshot` 序列化/反序列化(每格 原始值/类型/公式/超链/批注/富文本/dispImgId + **完整 CellStyle**;合并;浮动图 base64;DISPIMG 字节;**行高/列宽/手动行高标记**),UTF-8 安全 base64。
|
|
228
|
+
- 新增 `EditController.pasteSnapshot`(覆盖式 1:1 落格:intern 样式到目标表、登记 DISPIMG 字节、平移合并/图片/行高列宽;整体单次撤销 + 前后快照 cell-change + 只读 permission-denied),`mutations.setCellModel`。
|
|
229
|
+
- `copySelection` 增强 HTML(合并 colspan/rowspan + 图片 `<img data:>`)+ 嵌入快照;`pasteRichHtml` 先试快照再回退。
|
|
230
|
+
- 不改三壳(纯 core);`Ctrl+C`/`Ctrl+V`/右键菜单复制粘贴全部受益。
|
|
231
|
+
|
|
232
|
+
**大图护栏(三项)**:
|
|
233
|
+
- **不双重 base64**:图片字节不再既进快照又进可见 `<img>`,改为只在可见 `<img data-clip-img="key">` 存一份(`key`=DISPIMG `c:id` / 浮动 `f:序号`),快照只引用;粘贴时 `parseSnapshotHtml` 从 `<img>` 回填字节(`reattachImages`)。剪贴板体积从约 3× 原始图字节降到约 1.4×,有效上限翻倍。(`serializeSnapshot(..., { withImageBytes: false })`)
|
|
234
|
+
- **字节预算 + 优雅降级**:复制区图片总字节超 `CLIP_IMAGE_BUDGET_BYTES`(6 MB)→ 自动**降级为"无图 1:1 复制"**(样式/数字/边框/合并/行高列宽仍 1:1,只跳过图片,DISPIMG 格中性化为空格),避免剪贴板超限导致整次复制静默失败。(`withoutImages`)
|
|
235
|
+
- **降级通知**:降级时经现有 `permission-denied` 通道发事件(`reason: 'copy'` + 中文 message:"复制内容含图过多…已按无图复制"),壳/插件可 toast,不再静默。
|
|
236
|
+
- **贴进 WPS/Excel 图片不再巨大**:可见 `<img>` 改为带 `width`/`height` **属性**(按单元格大小:DISPIMG 用所在格列宽×行高,浮动图用其 EMU 尺寸),WPS/Excel 认属性不认 CSS `max-width` —— 之前只给 CSS 导致按原图像素(常几百上千 px)贴入显得巨大。
|
|
237
|
+
|
|
238
|
+
- 测试:`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,数字仍是数字不退化文本)。
|
|
239
|
+
|
|
240
|
+
## [1.4.0] - 2026-06-11
|
|
241
|
+
|
|
242
|
+
**透视表(Pivot Table)完整闭环** — WPS 式创建入口 → 右侧字段面板 → 编程 API → **导出真实 OOXML 透视表零件**,并支持**活刷新 / 折叠展开 / 多选筛选**。整个功能由 **`pivotTable` 配置开启,默认关闭**(三 demo 已开启)。另含 `scrollToCell` 导航 API 与工具栏 `sort` 内置项。
|
|
243
|
+
|
|
244
|
+
### 新增
|
|
245
|
+
|
|
246
|
+
#### 透视结果"活"化 + 多选筛选(2026-06-11)
|
|
247
|
+
|
|
248
|
+
- **活刷新**:编辑源数据区任意单元格(含撤销/重做,统一走 `onModelChange` chokepoint)后,所有透视表按其 `source` 区域自动重算 —— 从"静态快照"变"活对象"。重算经唯一入口 `recomputePivot`(面板改布局 / 活刷新 / 折叠展开三处共用),直接改模型不入命令栈(派生态),`pivotRefreshing` 防重入,无透视表 / 功能关闭时零开销。
|
|
249
|
+
- **行分组折叠/展开**:放 ≥2 个行字段时 `buildPivotRows` 产出"大纲"——外层分组行带小计、内层缩进明细;分组表头行首画 [−]/[+] 折叠按钮(canvas 绘制 + `CanvasRenderer.pivotToggleAt` 命中,与 autofilter 下拉同款),点击折叠/展开该组。折叠状态存 `PivotTableModel.collapsed`,运行时 `rowGroups` 记录可折叠表头行供渲染/命中。单行字段退化为扁平结果(无折叠),既有行为不变。
|
|
250
|
+
- **空白起步 + 字段勾选联动 + 新建表显示**(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 壳内联重读,本就正常)。
|
|
251
|
+
- **四区大白话 tooltip + 列交叉表确认**:字段面板「筛选器 / 列 / 行 / 值」每个区加 hover 说明(区标题带 ⓘ),筛选器/列空着时方框内还有一句引导;补 e2e 证实「列」字段确实横向展开成二维交叉表。
|
|
252
|
+
- **多选筛选**:`PivotFilterMode` 增 `'include'`(`PivotFilterRule.values` 列出保留值)。字段面板筛选 chip 点开底部明细面板,可选 全部 / 非空 / 多选(勾选具体值,带全选/清空)。导出对齐 WPS:`include`/`non-empty` → `multipleItemSelectionAllowed` + 未选项 `item@h=1`。
|
|
253
|
+
|
|
254
|
+
#### `pivotTable` 功能开关(默认关闭,opt-in)
|
|
255
|
+
|
|
256
|
+
#### `pivotTable` 功能开关(默认关闭,opt-in)
|
|
257
|
+
|
|
258
|
+
- 三壳同名 boolean prop(`:pivot-table` / `pivotTable`),进 `EditConfig.pivotTable`。**默认 `false`:工具栏 `pivot-table` 入口不渲染、`createPivotTable`/`openPivotTableDialog` 等 API 返回 `false` 并提示、导出不回注 pivot 零件 —— 行为与历史版本完全一致(零回归)**。开启后(还需 `editable`)下述全部能力生效。直接用 core `workbookToXlsxBlob` 时对应 `XlsxExportOptions.pivotTables`(经 viewer 导出时自动随开关注入)。
|
|
259
|
+
|
|
260
|
+
#### 透视表创建(WPS 风格,core 落地、三壳共用)
|
|
261
|
+
|
|
262
|
+
- **工具栏 `pivot-table` 入口**(需 `editable`):选中带表头数据区 → 「创建透视表」对话框选择生成位置(现有工作表指定单元格 / 新建工作表)→ 写出静态透视汇总表,入命令栈(undo 整体还原)。
|
|
263
|
+
- **右侧「数据透视表」字段面板**(框架无关 body 级 DOM,[src/core/viewer/pivot-dialog-host.ts](src/core/viewer/pivot-dialog-host.ts)):字段搜索;按钮/拖拽把字段加入 **筛选器 / 列 / 行 / 值** 四区;拖到移除区删除;筛选器支持 全部/非空/具体值;值字段可多个、汇总方式可切 求和/计数/平均值/最大值/最小值;每次变更重建静态结果。
|
|
264
|
+
- **编程 API**:`createPivotTable({ sourceRange, sourceSheetIndex, output, layout, showPanel })` 不经页面直接创建;`createPivotTableFromSelection()` 选区快捷;`openPivotTableDialog()` 打开入口对话框。三壳(Vue3 ref / React handle / Vue2 viewerApi)+ 插件 `viewer` 均已暴露。
|
|
265
|
+
- **模型元数据**:`PivotTableModel` 保存 `source`(源表 + 源区域)与 `layout`(四区布局),`cloneWorkbook` 深克隆,undo 快照不被面板操作污染。
|
|
266
|
+
|
|
267
|
+
#### 导出真实 OOXML 透视表零件 ([src/core/export/pivot-tables.ts](src/core/export/pivot-tables.ts))
|
|
268
|
+
|
|
269
|
+
- ExcelJS 不建模 pivot 零件,写出后在 **zip 层回注**(同 WPS cellimages 模式):`pivotCacheDefinition`(cacheSource + cacheFields/sharedItems)+ `pivotCacheRecords`(源数据行)+ `pivotTableDefinition`(location / pivotFields / row/col/page/dataFields / 样式)+ workbook `<pivotCaches>` + 全套 rels + `[Content_Types].xml`。
|
|
270
|
+
- `refreshOnLoad="1"`:Excel/WPS 打开导出件即识别为**真透视表**并按源区域重算原生布局;静态汇总结果仍写在单元格里,不支持透视的查看器也能看。
|
|
271
|
+
- **筛选器导出语义对齐 WPS**:"= 具体值"写 `pageField@item` 指向选中项(打开还原筛选状态);"非空"映射为多选 + 隐藏空白项(`multipleItemSelectionAllowed` + `item@h`,即 WPS"去掉空白"语义);"全部"不写选中。
|
|
272
|
+
- **overlay 导出保留原文件透视表**(`restoreOriginalPivotPartsIntoZip`):原文件已有的透视表(解析为只读,ExcelJS load→write 会整套丢掉)在 overlay 模式下从原件 zip **原样搬运**整套零件(pivotCache/pivotTables 目录 + workbook 注册 + worksheet 隐式关系按表名重挂 + Content_Types),cacheId 保留、r:id 重新分配;后续 App 新建透视表的零件编号/cacheId 自动避开。"打开 → 编辑 → 另存,透视表仍在"。rebuild 模式因结构可能被增删行列改动,不搬运(退化为普通单元格)。
|
|
273
|
+
- 回注/搬运失败自动降级为纯静态结果,不影响主体导出。
|
|
274
|
+
|
|
275
|
+
#### 其它
|
|
276
|
+
|
|
277
|
+
- `scrollToCell(row, col, { select? })` 命令式导航 API(三壳 + 插件 viewer),超出当前虚拟区自动扩展。
|
|
278
|
+
- 工具栏内置 `sort` 项(按活动单元格所在列升/降序;未开自动筛选先按选区/已用区建立范围),`viewer.sortActiveColumn(dir)` 同步暴露。
|
|
279
|
+
|
|
280
|
+
### 修复
|
|
281
|
+
|
|
282
|
+
- **pivot-parser 支持标准 ECMA-376 隐式关联**:真 Excel 文件的透视表零件靠 worksheet rels 关联(sheet XML 里没有元素),此前只认 worksheet XML 内的 `pivotTableDefinition` 引用 → 标准文件解析不到只读透视表按钮。现在两条路径都认(rels 扫描 + 兼容引用),导出件可往返解析。
|
|
283
|
+
|
|
284
|
+
### 测试
|
|
285
|
+
|
|
286
|
+
- 单测 316 → 330(pivot-parser 解析 + clone 元数据 + 导出回注零件结构/往返 + equals/non-empty/include 筛选语义 + overlay 原件搬运/编号避让等);e2e 118 → 124(`e2e/pivot.e2e.ts`:UI 入口全链路 + 面板切换汇总方式 + undo;API 新建工作表 + 求和;导出 zip 零件断言;活刷新随源编辑/撤销;2 行字段折叠/展开;include 多选筛选;面板筛选复选框)。
|
|
287
|
+
|
|
5
288
|
## [1.3.3] - 2026-06-09
|
|
6
289
|
|
|
7
290
|
**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
|
@@ -41,13 +41,14 @@ const src = ref<File>() // 绑个 <input type="file" @change> 给它即可
|
|
|
41
41
|
- 🔢 **自写数字格式引擎**:千分位/货币/百分比/科学计数/分数、四段格式(正;负;零;文本)、`[Red]` 颜色、`[>=100]` 条件段、中文日期 `yyyy"年"`、`[h]:mm` 经过时间
|
|
42
42
|
- 🗓 **日期序列号**:含 Excel 1900 闰年 bug、1904 系统
|
|
43
43
|
- 🎨 **主题色 + tint**、indexed 调色板、合并单元格、边框(细/粗/虚/双线)、填充(纯色/图案/渐变)
|
|
44
|
-
- 🌈 **条件格式**:色阶 / 数据条 / 图标集 / cellIs / top10
|
|
45
|
-
- 🖼 **图片 + 图表**(DrawingML → ECharts 近似还原)、**形状/文本框**、**迷你图**(sparklines)
|
|
44
|
+
- 🌈 **条件格式**:色阶 / 数据条 / 图标集 / cellIs / top10 渲染;开 `conditionalFormat` 后可**新建/编辑/删除全 6 类规则**(工具栏入口 + API,导出回写 .xlsx)
|
|
45
|
+
- 🖼 **图片 + 图表**(DrawingML → ECharts 近似还原)、**形状/文本框**、**迷你图**(sparklines)、**批注**、**数据验证**(列表型点格内箭头弹选可撤销;整数/小数/日期/文本长度等在编辑模式**拦截非法输入** + WPS 式出错/输入提示)、**自动筛选**样式
|
|
46
46
|
- 📌 **WPS 单元格内嵌图(DISPIMG)**:识别并展示 WPS 私有的"嵌在格里的图"(普通工具会缺图);编辑模式下支持**一键浮动 ⇄ 嵌入互转**。见 [WPS 单元格内嵌图](#wps-单元格内嵌图dispimg)
|
|
47
47
|
- 🔍 **图片点击放大 + 下载原图**:网格里的图(内嵌图/浮动图)点开看大图、下载原件。只读模式单击图放大、编辑模式右键「查看大图」。`imageLightbox` prop 控制(默认开),`openImageLightbox(src)` 命令式打开。
|
|
48
|
-
- 📋 **从 Excel/WPS 富粘贴**:`Ctrl+V` 解析剪贴板 HTML →
|
|
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
|
|
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,8 @@ 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 模式同时保留原文件透视表) |
|
|
297
|
+
| `conditionalFormat` | `boolean` | 条件格式编辑开关(默认 `false` = 关闭、只读渲染)。开启后(还需 `editable`):工具栏 `conditional-format` 入口可见、`openConditionalFormatDialog`/`addConditionalRule`/`updateConditionalRule`/`removeConditionalRule`/`setConditionalRules`/`getConditionalRules` API 生效、导出 .xlsx 回写条件格式(overlay 保留原件未编辑规则原样,只增改用户改的)。支持全 6 类规则(cellIs / 公式 / 色阶 / 数据条 / 图标集 / top10)新建·编辑·删除,整体单次撤销 |
|
|
295
298
|
| `cellReadOnly` | `(cell, pos) => boolean` | 按格只读判定(编辑时) |
|
|
296
299
|
| `readOnlyRanges` | `MergeRange[]` | 只读区域(命中即只读,黑名单) |
|
|
297
300
|
| `editableTargets` | `EditableTarget \| EditableTarget[]` | **可编辑白名单**(2026-06-08)— 设了就是白名单语义:默认只读,**只**命中**任一** target 的格可编辑。4 种 target 形状自动识别:`{row,col}` 单格 / `{row}` 整行 / `{col}` 整列 / `MergeRange` 矩形。单值或数组都行,允许**不相邻**多 target。`undefined` (不传) = 不启用白名单 = 老行为(默认全可编辑);`[]` (显式空) = 全只读。与 `readOnlyRanges` / `cellReadOnly` 叠加 — 白名单命中后仍可被二次"黑"掉。运行时改:命令式 `viewer.setEditableTargets(targets)` |
|
|
@@ -300,6 +303,8 @@ viewer.value.getRangeData(viewer.value.getSelection()) // 取"我选中的"区
|
|
|
300
303
|
| `editor` | `EditorResolver` | 自定义单元格编辑器工厂(返回任意 DOM) |
|
|
301
304
|
| `recalc` | `boolean` | 公式重算(默认 `false`;需 `editable`) |
|
|
302
305
|
| `formulaEngine` | `FormulaEngineFactory` | 自定义/自研公式引擎(默认 HyperFormula) |
|
|
306
|
+
| `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)` / 右键「选择性粘贴」/ 工具栏「⚙ 粘贴配置」面板 |
|
|
307
|
+
| `readOnlyPrompt` | `'dialog' \| 'toast' \| 'none'` | 粘贴撞只读格的内置提醒(默认 `'dialog'`):`dialog` 弹窗**列出具体哪些格**只读 / `toast` 顶部气泡 / `none` 只发 `permission-denied` 事件。逐格精确(编辑模式下也可能有只读格) |
|
|
303
308
|
| `cellImageFit` | `'fill' \| 'contain' \| 'cover'` | WPS 单元格内嵌图贴合方式(默认 `contain` 等比,与 WPS 渲染一致) |
|
|
304
309
|
| `imageLightbox` | `boolean` | 图片点击放大灯箱(默认 `true`;只读单击图放大、编辑右键「查看大图」) |
|
|
305
310
|
|
|
@@ -353,7 +358,8 @@ viewer.value.getRangeData(viewer.value.getSelection()) // 取"我选中的"区
|
|
|
353
358
|
| 类别 | 方法 |
|
|
354
359
|
|---|---|
|
|
355
360
|
| 值 | `editCell(row,col,value)` · `editRange(range,values[][])` · `clearRange(range)` |
|
|
356
|
-
| 粘贴 | `pasteText(tsv,at?)` · `pasteRichHtml(html,at?)`(Excel/WPS 复制 →
|
|
361
|
+
| 粘贴 | `pasteText(tsv,at?)` · `pasteRichHtml(html,at?,behaviorOverride?)`(Excel/WPS 复制 → 字体/颜色/填充/边框/对齐/换行/合并/内嵌图 + 单次撤销) · `pasteImageBlob(blob,at?)`(单图/拖文件落格) |
|
|
362
|
+
| 粘贴行为 | `getPasteBehavior()` · `setPasteBehavior(cfg)`(改 Ctrl+V/右键默认)· `openPasteConfigDialog()`(弹「⚙ 粘贴配置」面板,框架无关 DOM 三壳共用)。覆盖式 1:1(默认)/ 合并 / 仅值;`targetMerges:'clear'` 默认清目标旧合并防吞列;`colWidth:'firstRowOnly'` 仅首行取源宽。右键「选择性粘贴 ▸ 覆盖格式 / 保留原样式(仅值)」逐次选 |
|
|
357
363
|
| 样式 | `setStyle(range, patch)`(`patch` = `CellStyleOverride`:font/fill/borders/对齐/numFmt) |
|
|
358
364
|
| 背景/字体色 | `getActiveFillColor()` · `getActiveFontColor()`(回显活动格当前色 #RRGGBB) · `setSelectionFill(color\|null)`(null=清除填充) · `setSelectionFontColor(color)` |
|
|
359
365
|
| 自动换行 | `getSelectionWrapState()`→`'all'\|'none'\|'mixed'`(工具栏 active 用) · `toggleWrapTextOnSelection()`(WPS 风格 toggle:全开/全关;行高按内容重撑,只扩不缩) |
|
|
@@ -401,6 +407,26 @@ const myEditor: EditorResolver = (cell, pos) => {
|
|
|
401
407
|
- 全部入撤销栈、发 `cell-change`/`image-change`、翻脏标记。(`convertImageToCell(imgIdx,row,col)` 仍保留,用于显式指定目标格。)
|
|
402
408
|
- **导出往返**:`downloadXlsx()` / `exportXlsx()` 导出时,在 ExcelJS 写出后**于 zip 层回注** WPS 私有件(`cellimages.xml` + rels + media + `[Content_Types].xml`/`workbook.xml.rels` 补丁,从模型重建),原有的 + App 内新转的内嵌图导出后用 WPS 打开都正常显示。rebuild / overlay 两种保真模式均覆盖;无字节的 blob-only 图除外。
|
|
403
409
|
|
|
410
|
+
### 数字格式 / 批注 / 查找替换(1.11.0)
|
|
411
|
+
|
|
412
|
+
- **查找替换**:`Ctrl+F` 打开查找栏,编辑模式下多出替换行 —— 替换输入 + 「替换」(替换当前并跳下一个)/「全部替换」(整体单次撤销),支持区分大小写 / 全字匹配,跳过只读格。
|
|
413
|
+
- **数字格式编辑器**:工具栏 `number-format` 入口打开对话框(框架无关 DOM,三壳共用)—— 分类(常规/数值/货币/百分比/日期/时间/文本/自定义)+ 选项(小数位数 / 千分位 / 负数红色 / 货币符号 / 日期时间预设)+ 实时预览 + 可直接编辑格式代码 → 确定即套到选区(单次撤销)。也可 `setSelectionNumberFormat(code)` / `openNumberFormatDialog()` 直调。
|
|
414
|
+
- **批注编辑**:右键单格「插入/编辑/删除批注」打开对话框(多行文本 + 确定/删除/取消);`getCellComment` / `setCellComment(row,col,text)`(空串删除)/ `openCommentEditor()` 直调,单次撤销;**导出 .xlsx 回写批注**(rebuild + overlay)。
|
|
415
|
+
|
|
416
|
+
### 自动填充柄(拖拽填充序列)
|
|
417
|
+
|
|
418
|
+
开 `:editable` 后,选区右下角出现**填充柄**小方块(像 Excel/WPS)。拖它向下/上/左/右,松手即把源选区的模式**接续填充**进新增格(整体单次撤销):
|
|
419
|
+
|
|
420
|
+
- **全数值**:1 个 → 复制;≥2 个 → 等差外推(`1,2` → `3,4,5`;`2,4` → `6,8,10`,支持递减)
|
|
421
|
+
- **全日期**:1 个 → 每格 +1 天;≥2 个 → 按相邻差外推
|
|
422
|
+
- **"前缀+末尾整数"文本**:`Item 1` → `Item 2,Item 3`;`第1周` → `第2周`;`A01` → `A02`(保留前导零位宽)
|
|
423
|
+
- **星期 / 月份名**(中英常见写法):`周五` → `周六,周日,周一`;`Nov` → `Dec,Jan`(循环接续)
|
|
424
|
+
- **其它**:循环复制源值
|
|
425
|
+
|
|
426
|
+
**按住 Ctrl/⌘ 拖**翻转"复制 ↔ 序列"(对齐 Excel):单个数字普通拖=复制、Ctrl 拖=递增(`5`→`6,7,8`);两个数字序列普通拖=等差、Ctrl 拖=复制;日期/星期月份/文本递增普通=序列、Ctrl=复制。
|
|
427
|
+
|
|
428
|
+
主轴按鼠标偏移更大的方向决定(上下 vs 左右);列 / 行各自独立成序列;跳过只读格。注:v1 填充**值**,不复制源单元格的格式。纯框架无关 core canvas 交互,三壳一致。
|
|
429
|
+
|
|
404
430
|
### 公式重算(可换引擎)
|
|
405
431
|
|
|
406
432
|
开 `:recalc` 后,编辑公式格或被公式引用的格 → 依赖格**自动级联重算**,每个变动都发 `cell-change`(`source: 'api'|'ui'|'undo'|'redo'`)。默认引擎 [HyperFormula](https://hyperformula.handsontable.com/)(可选 peer,`npm i hyperformula`;**GPL-3.0/商业双授权**,商业项目用 `:formula-engine` 注入持牌/自研引擎,实现 `FormulaEngine` 接口即可)。`isRecalcReady()` 查引擎是否就绪(异步懒加载)。
|
|
@@ -445,7 +471,21 @@ const myEditor: EditorResolver = (cell, pos) => {
|
|
|
445
471
|
| `rendered` / `error` / `progress` | 见上 |
|
|
446
472
|
|
|
447
473
|
### 命令式 API(模板 ref)
|
|
448
|
-
`load(src)` / `getWorkbook()` / `getActiveSheet()` / `setActiveSheet(i)` / `getSelection()` / `setSelection(range)` / `rectOf(row,col)` / `rectOfRange(range)` / `redraw()`,以及下面的导出方法;**编辑命令式 API**(`editCell`/`setStyle`/`insertRows`/`undo`/`exportXlsx`…)见 [编辑](#编辑可选默认只读)。
|
|
474
|
+
`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`…)见 [编辑](#编辑可选默认只读)。
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
// 需组件开启 :pivot-table="true"(默认关闭)+ :editable="true"
|
|
478
|
+
viewer.value?.createPivotTable({
|
|
479
|
+
sourceRange: { top: 0, left: 0, bottom: 20, right: 4 },
|
|
480
|
+
output: { kind: 'new-sheet' },
|
|
481
|
+
layout: {
|
|
482
|
+
rows: [0],
|
|
483
|
+
columns: [1],
|
|
484
|
+
filters: [{ field: 2, mode: 'equals', value: '华东' }],
|
|
485
|
+
values: [{ field: 3, summary: 'sum' }],
|
|
486
|
+
},
|
|
487
|
+
})
|
|
488
|
+
```
|
|
449
489
|
|
|
450
490
|
### 导出 / 打印
|
|
451
491
|
内置工具栏右侧有「导出 ▾」菜单(PNG / PDF / 打印 / **导出设置…**)。「导出设置…」打开对话框,可选**范围**(当前选区 / 当前表 / 全部表)、清晰度、是否含行列号/网格线、纸张方向。也可命令式调用(模板 ref / 插件 `viewer`):
|
|
@@ -465,7 +505,7 @@ const myEditor: EditorResolver = (cell, pos) => {
|
|
|
465
505
|
|
|
466
506
|
**.xlsx 两种保真模式**:
|
|
467
507
|
|
|
468
|
-
- **`rebuild`(默认)** —— **从编辑后模型完整重建**:遍历 cells/公式/样式(字体/填充/边框/对齐/数字格式)
|
|
508
|
+
- **`rebuild`(默认)** —— **从编辑后模型完整重建**:遍历 cells/公式/样式(字体/填充/边框/对齐/数字格式)/合并/行高列宽/冻结/图片/**条件格式**(1.9.0 起)/**批注**(1.11.0 起) 重组成 ExcelJS 工作簿。干净、所见即所得,但**丢失**原件里我们不建模的部分(数据验证、VBA 宏、工作表保护、复杂 DrawingML/图表 等)。图片导出区分 oneCell/twoCell 锚点 + 子格 EMU 偏移。
|
|
469
509
|
- **`overlay`(`exportXlsx({ fidelity: 'overlay' })`)** —— **重载原始 .xlsx,只把编辑后的 值/样式/合并/行高列宽/冻结 叠加上去**,**保留** ExcelJS 能往返的其余部分(条件格式 / 数据验证 / 打印设置 / 定义名 / 图表 等)。组件加载时自动留存原件字节供其使用;缺原件时自动回退 `rebuild`。注:overlay 不反映**增删行列 / 图片**编辑(那类用 `rebuild`)。
|
|
470
510
|
|
|
471
511
|
公共选项:`target`(`'active'`(默认)/`'all'`/索引/索引数组)、`range`(限定单元格区域)、`scale`(清晰度,默认 2)、`includeHeaders`、`gridlines`、`background`;PDF/打印另有 `format`(a4/a3/letter/`[宽,高]mm`)、`orientation`、`margin`(mm)、`fitToWidth`。
|
|
@@ -588,11 +628,11 @@ viewer.closeContextMenu()
|
|
|
588
628
|
### 操作工具栏(可配置 / 可插件 / 响应式)
|
|
589
629
|
顶栏(文件名/导出/缩放)下方有一行**操作工具栏**,内置 `find`/`filter` 按钮默认显示。用 `:toolbar` 配置:
|
|
590
630
|
```vue
|
|
591
|
-
<ExcelViewer :src="file" /> <!-- 默认: find + filter -->
|
|
631
|
+
<ExcelViewer :src="file" /> <!-- 默认: find + filter + sort -->
|
|
592
632
|
<ExcelViewer :toolbar="['find','filter','separator','zoom','export']" /> <!-- 控制项/顺序/分隔 -->
|
|
593
633
|
<ExcelViewer :toolbar="false" /> <!-- 隐藏整条 -->
|
|
594
634
|
```
|
|
595
|
-
- **内置 id**:`find`(查找)、`filter`(切换自动筛选 —— 文件没设也能点出下拉)、`clear-filter`(清除筛选,无筛选时禁用)、`copy`(复制选区)、`wrap-text`(自动换行 toggle,WPS 风格,需 `editable`)、`image-tools`(图片工具 ▾:选区/整表/整列 浮动 ⇄ 嵌入互转,需 `editable`)、`template`(模板 ▾:仅 JSON / 模型数据源下生效;导入 .xlsx 当样式捐赠者;xlsx 数据源下禁用)、`freeze`(冻结/取消)、`zoom`(缩放下拉)、`export`(导出/打印下拉)、`'separator'`/`'|'`(分隔线)
|
|
635
|
+
- **内置 id**:`find`(查找)、`filter`(切换自动筛选 —— 文件没设也能点出下拉)、`sort`(按活动单元格所在列升序/降序;未开启自动筛选时会先按选区/已用区建立范围)、`clear-filter`(清除筛选,无筛选时禁用)、`copy`(复制选区)、`pivot-table`(透视表入口:选中带表头数据区后选择生成位置,可输出到现有工作表单元格或新建工作表;创建后打开 WPS 风格右侧字段面板,需 `pivotTable` + `editable`,功能未开启时不渲染)、`conditional-format`(条件格式管理入口:列出当前表规则可删/可编辑 + 新建全 6 类规则,需 `conditionalFormat` + `editable`,功能未开启时不渲染)、`number-format`(数字格式编辑入口:分类 + 预览 + 自定义格式代码,需 `editable`)、`wrap-text`(自动换行 toggle,WPS 风格,需 `editable`)、`image-tools`(图片工具 ▾:选区/整表/整列 浮动 ⇄ 嵌入互转,需 `editable`)、`template`(模板 ▾:仅 JSON / 模型数据源下生效;导入 .xlsx 当样式捐赠者;xlsx 数据源下禁用)、`freeze`(冻结/取消)、`zoom`(缩放下拉)、`export`(导出/打印下拉)、`'separator'`/`'|'`(分隔线)。
|
|
596
636
|
- **富项类型**(`ToolbarItem`):`type:'separator'` 分隔线;`items: ToolbarItem[]` 变下拉子菜单;`disabled?(viewer)` 禁用态;`iconSvg`(内联 SVG,优先于 `icon` emoji)/ `icon` / `label` / `title` / `onClick(viewer)` / `active?(viewer)`。
|
|
597
637
|
- **响应式溢出**:宽度不足时,放不下的项自动折叠进「⋯ 更多」下拉。
|
|
598
638
|
- **插件贡献**:`ExcelPlugin.toolbar: ToolbarItem[]`,插件加载即追加(opt-in)。
|
|
@@ -653,7 +693,8 @@ const highlightNegatives = definePlugin({
|
|
|
653
693
|
|
|
654
694
|
- **编辑**已支持(默认只读;开 `editable`,见 [编辑](#编辑可选默认只读))。下列为暂不覆盖项:
|
|
655
695
|
- 增删行列**不自动重写公式引用**;写回 .xlsx 丢 VBA/工作表保护/复杂 DrawingML
|
|
656
|
-
-
|
|
696
|
+
- 透视表:已有透视表**数据按普通单元格显示**并显示只读字段按钮;`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 模式因结构可能被增删行列改动不搬运(退化为普通单元格)
|
|
697
|
+
- 条件格式编辑:解析时全 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 回写条件格式**:`rebuild` 与 `overlay` 都回写(1.9.0 起 rebuild 不再丢条件格式);**从文件解析、未在 app 内编辑过的规则按原始 OOXML 原样回写**(保留色阶/数据条/图标集的 cfvo 阈值等不全建模字段,零退化),用户新建或编辑过的规则按模型重建(全 6 类)。整个功能由 `conditionalFormat` 配置开启(默认关闭),关闭时入口/API/导出回写全部不生效、行为与历史一致。当前限制:色阶/数据条/图标集的**阈值(cfvo)** 暂不在编辑器里微调(未编辑时靠原样回写保住,用户新建用默认阈值);文本包含等 OOXML 子类型用 `cellIs`/`expression` 表达。
|
|
657
698
|
- SmartArt;形状仅支持 rect/roundRect/ellipse + 文本(复杂自定义几何按矩形近似)
|
|
658
699
|
- `.xls`(旧 BIFF 二进制)/ 加密文件(给出友好提示)
|
|
659
700
|
- 图表为 ECharts 近似,非像素级一致
|
|
@@ -672,7 +713,7 @@ npm run build # 构建组件库(dist/)
|
|
|
672
713
|
npm run build:demo # 构建 demo 站点
|
|
673
714
|
```
|
|
674
715
|
|
|
675
|
-
> **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)
|
|
716
|
+
> **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 覆盖。
|
|
676
717
|
|
|
677
718
|
## 文档 / 二开
|
|
678
719
|
|