quickspeadsheet 1.0.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/README.md ADDED
@@ -0,0 +1,1072 @@
1
+ # QuickSpeadsheet
2
+
3
+ 一个基于 Vue 3 的 Excel 在线表格编辑器,支持完整的 OOXML 格式解析与写入,提供 DOM 和 Canvas 两种渲染模式。
4
+
5
+ [![npm version](https://img.shields.io/npm/v/quickspeadsheet.svg)](https://www.npmjs.com/package/quickspeadsheet)
6
+ [![license](https://img.shields.io/npm/l/quickspeadsheet.svg)](https://github.com/your-repo/quickspeadsheet/blob/main/LICENSE)
7
+
8
+ ## 目录
9
+
10
+ - [特性](#特性)
11
+ - [安装](#安装)
12
+ - [产品版本与发版说明](#产品版本与发版说明)
13
+ - [商业授权服务(License Server)](#商业授权服务license-server)
14
+ - [快速开始](#快速开始)
15
+ - [项目结构](#项目结构)
16
+ - [渲染模式](#渲染模式)
17
+ - [图片处理配置](#图片处理配置)
18
+ - [详细用法](#详细用法)
19
+ - [TypeScript 支持](#typescript-支持)
20
+ - [国际化](#国际化)
21
+ - [性能优化](#性能优化)
22
+ - [已知限制](#已知限制)
23
+ - [常见问题](#常见问题)
24
+ - [API 文档](#api-文档)
25
+ - [贡献指南](#贡献指南)
26
+ - [许可证](#许可证)
27
+
28
+ ## 特性
29
+
30
+ ### 核心功能
31
+ - **完整的 Excel 文件支持**:解析和写入 `.xlsx`、`.xlsm` 格式文件
32
+ - **双渲染引擎**:支持 DOM 和 Canvas 两种渲染模式,Canvas 模式支持大文件虚拟滚动
33
+ - **公式引擎**:支持常用 Excel 函数计算
34
+ - **图表支持**:集成 Chart.js,支持柱状图、折线图、饼图等多种图表类型
35
+ - **条件格式**:支持数据条、色阶、图标集等条件格式
36
+ - **数据验证**:支持下拉列表、数字范围、日期等多种验证规则
37
+ - **打印预览**:支持打印预览和 PDF 导出
38
+
39
+ ### 图片格式支持
40
+ - **浏览器原生支持**:PNG、JPEG、GIF、WebP、SVG、BMP
41
+ - **前端解码**:
42
+ - TIFF(utif)
43
+ - HEIC/HEIF(heic2any + libheif wasm)
44
+ - JPEG 2000(jpeg2000)
45
+ - PSD/PSB(@webtoon/psd)
46
+ - PDF(pdfjs-dist)
47
+ - EMF/WMF(emf-converter)
48
+
49
+ ### 高级特性
50
+ - **加密文件支持**:支持加密 Excel 文件的解密和加密
51
+ - **数据透视表**:支持数据透视表的解析和渲染
52
+ - **切片器**:支持数据切片器的交互
53
+ - **批注**:支持单元格批注的显示和编辑
54
+ - **形状与图片**:支持工作表中的形状和图片渲染
55
+
56
+ ## 安装
57
+
58
+ ```bash
59
+ npm install quickspeadsheet
60
+ ```
61
+
62
+ ## 产品版本与发版说明
63
+
64
+ 本包在 **同一 npm 包** 下通过 `package.json` 的 `exports` 提供三个子路径,对应不同 capability 预设:
65
+
66
+ | 子路径 | 典型用途 |
67
+ |--------|----------|
68
+ | `quickspeadsheet/community` | 基础编辑、导入、JSON 导出;无 Excel 导出 / 打印预览 / 协同等 |
69
+ | `quickspeadsheet/pro` | 单机增强(含 Excel 导出、打印、图表设计、设置面板等);仍无协同 |
70
+ | `quickspeadsheet/enterprise` | 完整协同能力 |
71
+
72
+ - **接入步骤与能力对照**:[docs/product-editions-guide.md](./docs/product-editions-guide.md)
73
+ - **三大产品与授权 Playbook**(子路径 + `licenseContext`):[docs/product-license-playbook.md](./docs/product-license-playbook.md)
74
+ - **客户接入与商业交付 SOP**(试用 -> 转付费 -> 续费 -> 回收):[docs/customer-onboarding-sop.md](./docs/customer-onboarding-sop.md)
75
+ - **客户对外接入简版**(可直接给客户):[docs/customer-onboarding-external.md](./docs/customer-onboarding-external.md)
76
+ - **三档版本功能矩阵**(官网/报价单):[docs/version-feature-matrix.md](./docs/version-feature-matrix.md)
77
+ - **客户接入指南 v1**(前端示例 + 后端时序图):[docs/customer-integration-guide-v1.md](./docs/customer-integration-guide-v1.md)
78
+ - **销售报价文案模板**:[docs/sales-quote-template.md](./docs/sales-quote-template.md)
79
+ - **客户邮件模板**(试用/转付费/续费):[docs/customer-email-templates.md](./docs/customer-email-templates.md)
80
+ - **集成场景总览**(Vue / 纯 JS / 授权 / 扩展;协同为可选):[docs/integration-scenarios-overview.md](./docs/integration-scenarios-overview.md)
81
+ - **Runtime 分层架构**(mermaid):[docs/architecture-runtime-edition.md](./docs/architecture-runtime-edition.md)
82
+ - **阶段 E/F 验收台账**(产品包、发版摘要要点、授权预留勾选):[docs/product-release-license-acceptance.md](./docs/product-release-license-acceptance.md)
83
+
84
+ ## 商业授权服务(License Server)
85
+
86
+ 仓库已提供可独立运行的授权后端示例:`license-server`,包含:
87
+
88
+ - PostgreSQL 持久化(license、token、billing event)
89
+ - 授权签发/刷新/吊销/状态查询
90
+ - 服务端验签(含 strict token 校验)与 DB 状态守卫
91
+ - Stripe webhook 签名校验、事件归一化、动作分发
92
+ - 管理接口 Bearer 鉴权、provider 白名单、字段策略校验
93
+
94
+ 快速使用:
95
+
96
+ ```bash
97
+ cd license-server
98
+ npm install
99
+ cp .env.example .env
100
+ npm start
101
+ ```
102
+
103
+ 更多细节见:[`license-server/README.md`](./license-server/README.md)。
104
+
105
+ ## 快速开始
106
+
107
+ ### 基础用法
108
+
109
+ ```vue
110
+ <template>
111
+ <SpreadExcelViewer
112
+ ref="viewerRef"
113
+ :renderer="renderer"
114
+ @workbook-loaded="onWorkbookLoaded"
115
+ />
116
+ </template>
117
+
118
+ <script setup>
119
+ import { ref } from 'vue'
120
+ import { SpreadExcelViewer, useSpread } from 'quickspeadsheet'
121
+
122
+ const viewerRef = ref(null)
123
+ const renderer = ref('canvas') // 'dom' | 'canvas'
124
+
125
+ function onWorkbookLoaded(workbook) {
126
+ console.log('Workbook loaded:', workbook)
127
+ }
128
+ </script>
129
+ ```
130
+
131
+ ### 协同连接真实后端(替换 mock)
132
+
133
+ `SpreadExcelViewer` 支持直接传入 `documentId` 与 `collaborationServices`。
134
+ 未显式开启 `mode: 'mock'` 时,默认按远端模式创建适配器:
135
+
136
+ - REST 默认:`/api/v1`
137
+ - WS 默认:`ws(s)://<当前 host>/realtime/documents/{documentId}`
138
+
139
+ ```vue
140
+ <template>
141
+ <SpreadExcelViewer
142
+ document-id="doc_prod_001"
143
+ :collaboration-services="collabConfig"
144
+ :warnings-clear-merge-window-ms="30000"
145
+ />
146
+ </template>
147
+
148
+ <script setup>
149
+ const collabConfig = {
150
+ mode: 'remote',
151
+ // 生产建议开启:避免误连默认地址
152
+ strictRemote: true,
153
+ apiBaseUrl: 'http://127.0.0.1:3848/api/v1',
154
+ realtimeUrl: 'ws://127.0.0.1:3848/realtime/documents/doc_prod_001',
155
+ // 支持 async,可用于 token 刷新
156
+ getAuthHeader: async () => ({ Authorization: `Bearer ${await getAccessToken()}` }),
157
+ // 可选:WS 额外 query(默认会尝试从 Authorization Bearer 自动提取 access_token)
158
+ getAuthQuery: async () => ({ tenant: await getTenantId() }),
159
+ // 可选:修改 token query 字段名,默认 access_token
160
+ authQueryTokenKey: 'token',
161
+ // 默认 false;仅在后端要求 hello 携带 token 时开启
162
+ exposeAuthInHello: false,
163
+ }
164
+ </script>
165
+ ```
166
+
167
+ `warningsClearMergeWindowMs` 用于控制“清空最近协同吸收”写入审计历史时的合并窗口(毫秒):
168
+
169
+ - 默认 `30000`:同一用户在 30 秒内连续清空会合并为一条历史记录并累计数量。
170
+ - 传 `0`:禁用合并,每次清空都保留独立历史记录。
171
+
172
+ 连接/发送失败时,协同状态中的 `lastError` 会提供分级字段,便于联调定位:
173
+
174
+ - `status`:HTTP 状态码(如 `401` / `403` / `5xx`)
175
+ - `code`:错误分类(如 `AUTH_UNAUTHORIZED`、`AUTH_FORBIDDEN`、`NETWORK_ERROR`)
176
+ - `retryable`:是否建议重试
177
+ - `stage`:失败阶段(如 `connectAuth`、`connectNetwork`、`connectServer`)
178
+
179
+ Viewer 的“重试连接/重发队列”会优先检查 `lastError.retryable`;当为 `false`(如 403/404)时会直接提示并拦截无效重试。
180
+
181
+ ### 使用 Composable
182
+
183
+ ```vue
184
+ <template>
185
+ <div ref="containerRef" class="spread-container"></div>
186
+ <button @click="importFile">导入 Excel</button>
187
+ <button @click="exportFile">导出 Excel</button>
188
+ </template>
189
+
190
+ <script setup>
191
+ import { ref } from 'vue'
192
+ import { useSpread } from 'quickspeadsheet'
193
+
194
+ const containerRef = ref(null)
195
+ const { spread, excelIO, initSpread, importFile, exportFile } = useSpread()
196
+
197
+ // 初始化
198
+ initSpread(containerRef.value)
199
+
200
+ // 导入文件
201
+ async function importFile() {
202
+ const input = document.createElement('input')
203
+ input.type = 'file'
204
+ input.accept = '.xlsx,.xlsm'
205
+ input.onchange = async (e) => {
206
+ const file = e.target.files[0]
207
+ await excelIO.value.open(file, (json) => {
208
+ spread.value.fromJSON(json)
209
+ })
210
+ }
211
+ input.click()
212
+ }
213
+
214
+ // 导出文件
215
+ async function exportFile() {
216
+ const json = spread.value.toJSON()
217
+ const blob = await excelIO.value.save(json, { fileExtension: 'xlsx' })
218
+ // 下载文件...
219
+ }
220
+ </script>
221
+ ```
222
+
223
+ ### 使用核心 API
224
+
225
+ ```javascript
226
+ import { createWorkbook, createExcelIO } from 'quickspeadsheet'
227
+
228
+ // 创建工作簿
229
+ const container = document.getElementById('spread-container')
230
+ const workbook = createWorkbook(container, {
231
+ renderer: 'canvas',
232
+ tabStripRatio: 0.88,
233
+ enableCanvasScrollBlit: true,
234
+ })
235
+
236
+ // 获取活动工作表
237
+ const sheet = workbook.getActiveSheet()
238
+
239
+ // 设置单元格值
240
+ sheet.setValue(0, 0, 'Hello World')
241
+ sheet.setValue(1, 0, 123.45)
242
+
243
+ // 设置公式
244
+ sheet.getCell(2, 0).formula('SUM(A1:A10)')
245
+
246
+ // 设置样式
247
+ sheet.setStyle(0, 0, {
248
+ font: 'bold 14px Arial',
249
+ backColor: '#FFD700',
250
+ foreColor: '#000000',
251
+ hAlign: 'center',
252
+ vAlign: 'center',
253
+ })
254
+
255
+ // 合并单元格
256
+ sheet.addSpan(0, 0, 2, 3) // 合并 2 行 3 列
257
+
258
+ // 添加图表
259
+ sheet.charts.add('Chart1', 'barChart', {
260
+ from: { col: 5, row: 0 },
261
+ to: { col: 12, row: 12 },
262
+ chartData: {
263
+ categories: ['A', 'B', 'C', 'D'],
264
+ series: [
265
+ { name: 'Series 1', values: [10, 20, 30, 40] },
266
+ { name: 'Series 2', values: [15, 25, 35, 45] },
267
+ ],
268
+ },
269
+ })
270
+
271
+ // 导入 Excel 文件
272
+ const excelIO = createExcelIO()
273
+ const file = document.querySelector('input[type="file"]').files[0]
274
+ await excelIO.open(file, (json) => {
275
+ workbook.fromJSON(json)
276
+ })
277
+
278
+ // 导出 Excel 文件
279
+ const json = workbook.toJSON()
280
+ const blob = await excelIO.save(json)
281
+ ```
282
+
283
+ ## 项目结构
284
+
285
+ ```
286
+ src/
287
+ ├── excel/
288
+ │ ├── components/ # Vue 组件
289
+ │ │ ├── SpreadExcelViewer.vue # 主视图组件
290
+ │ │ ├── SpreadToolbar.vue # 工具栏
291
+ │ │ ├── SpreadFormulaBar.vue # 公式栏
292
+ │ │ ├── SpreadSheetTabs.vue # 工作表标签
293
+ │ │ ├── SpreadContainer.vue # 容器组件
294
+ │ │ ├── SpreadPrintPreview.vue # 打印预览
295
+ │ │ ├── SpreadSearchDialog.vue # 搜索对话框
296
+ │ │ └── SpreadInspector/ # 属性检查器
297
+ │ │
298
+ │ ├── composables/ # Vue Composables
299
+ │ │ └── useSpread.js # Spread 逻辑封装
300
+ │ │
301
+ │ ├── core/ # 核心模块
302
+ │ │ ├── workbook.js # 工作簿模型
303
+ │ │ ├── worksheet.js # 工作表模型
304
+ │ │ ├── cell.js # 单元格模型
305
+ │ │ ├── excelio.js # Excel IO(主线程)
306
+ │ │ ├── excelioWorker.js # Excel IO(Worker)
307
+ │ │ ├── formulaEngine.js # 公式引擎
308
+ │ │ ├── styles.js # 样式处理
309
+ │ │ ├── imageDisplay.js # 图片格式处理
310
+ │ │ ├── charts.js # 图表支持
311
+ │ │ ├── print.js # 打印功能
312
+ │ │ │
313
+ │ │ ├── ooxml/ # OOXML 解析/写入
314
+ │ │ │ ├── parseWorkbook.js
315
+ │ │ │ ├── parseWorksheet.js
316
+ │ │ │ ├── parseDrawing.js
317
+ │ │ │ ├── parseChart.js
318
+ │ │ │ ├── writeWorkbook.js
319
+ │ │ │ ├── writeWorksheet.js
320
+ │ │ │ └── ...
321
+ │ │ │
322
+ │ │ └── renderer/ # 渲染器
323
+ │ │ ├── domRenderer.js # DOM 渲染
324
+ │ │ ├── canvasRenderer.js # Canvas 渲染
325
+ │ │ └── canvasTileCache.js # 瓦片缓存
326
+ │ │
327
+ │ ├── styles/ # 样式文件
328
+ │ └── utils/ # 工具函数
329
+
330
+ └── types/ # TypeScript 类型定义
331
+ └── spread.d.ts
332
+ ```
333
+
334
+ ## 渲染模式
335
+
336
+ ### DOM 模式
337
+ - 适用于小型表格
338
+ - 完整的 CSS 样式支持
339
+ - 更好的可访问性
340
+
341
+ ### Canvas 模式
342
+ - 适用于大型表格
343
+ - 虚拟滚动支持
344
+ - 瓦片缓存优化
345
+ - 滚动位图 blit 优化
346
+
347
+ ```javascript
348
+ const workbook = createWorkbook(container, {
349
+ renderer: 'canvas',
350
+ enableCanvasScrollBlit: true, // 启用滚动位图优化
351
+ enableCanvasTileCachePrototype: true, // 启用瓦片缓存
352
+ })
353
+ ```
354
+
355
+ ## 图片处理配置
356
+
357
+ ```javascript
358
+ const workbook = createWorkbook(container, {
359
+ imageDisplay: {
360
+ strategy: 'frontend', // 'frontend' | 'native' | 'custom'
361
+ tryCustomAfterFrontend: true, // 前端解码失败后尝试自定义解析器
362
+ maxDecodePixels: 16_000_000, // 最大解码像素数
363
+ pdfMaxCanvasSide: 2048, // PDF 渲染最大边长
364
+ frontendTiff: true, // 启用 TIFF 解码
365
+ frontendPdf: true, // 启用 PDF 解码
366
+ frontendHeic: true, // 启用 HEIC 解码
367
+ frontendJp2: true, // 启用 JPEG 2000 解码
368
+ frontendPsd: true, // 启用 PSD 解码
369
+ frontendEmf: true, // 启用 EMF/WMF 解码
370
+ },
371
+ })
372
+ ```
373
+
374
+ ## 自定义图片解析器
375
+
376
+ ```javascript
377
+ const workbook = createWorkbook(container, {
378
+ imageDisplay: {
379
+ frontendEmf: false, // 禁用前端 EMF 解码
380
+ resolver: async ({ src, mime, ext }) => {
381
+ if (mime === 'image/x-emf' || mime === 'image/x-wmf') {
382
+ // 调用后端 API 转换
383
+ const response = await fetch('/api/convert-emf', {
384
+ method: 'POST',
385
+ body: JSON.stringify({ image: src }),
386
+ })
387
+ return await response.text()
388
+ }
389
+ return null
390
+ },
391
+ },
392
+ })
393
+ ```
394
+
395
+ ## 事件
396
+
397
+ ```javascript
398
+ // 工作簿事件
399
+ workbook.bind('SheetChanged', (e) => {})
400
+ workbook.bind('SelectionChanged', (e) => {})
401
+ workbook.bind('CellChanged', (e) => {})
402
+
403
+ // 工作表事件
404
+ sheet.bind('ValueChanged', (e) => {})
405
+ sheet.bind('SelectionChanged', (e) => {})
406
+ ```
407
+
408
+ ## 开发
409
+
410
+ ```bash
411
+ # 安装依赖
412
+ npm install
413
+
414
+ # 启动开发服务器
415
+ npm run dev
416
+
417
+ # 运行测试
418
+ npm run test
419
+
420
+ # 构建生产版本
421
+ npm run build
422
+ ```
423
+
424
+ ## 依赖
425
+
426
+ ### 核心依赖
427
+ - `vue` ^3.4.0
428
+ - `jszip` ^3.10.1
429
+
430
+ ### 可选依赖
431
+ - `chart.js` ^4.5.1 - 图表支持
432
+ - `chartjs-chart-financial` - 金融图表
433
+
434
+ ### 图片解码依赖
435
+ - `utif` - TIFF 解码
436
+ - `heic2any` - HEIC/HEIF 解码
437
+ - `jpeg2000` - JPEG 2000 解码
438
+ - `@webtoon/psd` - PSD 解码
439
+ - `pdfjs-dist` - PDF 渲染
440
+ - `emf-converter` - EMF/WMF 解码
441
+
442
+ ## 浏览器支持
443
+
444
+ - Chrome >= 80
445
+ - Firefox >= 75
446
+ - Safari >= 14
447
+ - Edge >= 80
448
+
449
+ ## 详细用法
450
+
451
+ ### 单元格操作
452
+
453
+ ```javascript
454
+ const sheet = workbook.getActiveSheet()
455
+
456
+ // 设置值
457
+ sheet.setValue(0, 0, '文本')
458
+ sheet.setValue(1, 0, 123)
459
+ sheet.setValue(2, 0, new Date())
460
+
461
+ // 获取值
462
+ const value = sheet.getValue(0, 0)
463
+
464
+ // 批量设置
465
+ for (let r = 0; r < 10; r++) {
466
+ for (let c = 0; c < 5; c++) {
467
+ sheet.setValue(r, c, `R${r}C${c}`)
468
+ }
469
+ }
470
+
471
+ // 使用单元格对象
472
+ const cell = sheet.getCell(0, 0)
473
+ cell.setValue('Hello')
474
+ cell.formula('A1+B1')
475
+ cell.setStyle({ font: 'bold 12px Arial' })
476
+ ```
477
+
478
+ ### 样式设置
479
+
480
+ ```javascript
481
+ // 单个单元格样式
482
+ sheet.setStyle(0, 0, {
483
+ font: 'bold 14px Arial',
484
+ fontSize: 14,
485
+ fontFamily: 'Arial',
486
+ fontWeight: 'bold',
487
+ fontStyle: 'italic',
488
+ backColor: '#FFD700',
489
+ foreColor: '#000000',
490
+ hAlign: 'center',
491
+ vAlign: 'center',
492
+ borderLeft: { color: '#000000', style: 'thin' },
493
+ borderRight: { color: '#000000', style: 'thin' },
494
+ borderTop: { color: '#000000', style: 'thin' },
495
+ borderBottom: { color: '#000000', style: 'thin' },
496
+ wordWrap: true,
497
+ })
498
+
499
+ // 范围样式
500
+ const range = sheet.getRange(0, 0, 5, 5)
501
+ range.setStyle({ backColor: '#F0F0F0' })
502
+
503
+ // 条件格式
504
+ sheet.setConditionalFormats([
505
+ {
506
+ sqref: 'A1:A100',
507
+ rules: [{
508
+ type: 'cellIs',
509
+ operator: 'greaterThan',
510
+ value: 100,
511
+ style: { backColor: '#FF6B6B' }
512
+ }]
513
+ }
514
+ ])
515
+ ```
516
+
517
+ ### 合并单元格
518
+
519
+ ```javascript
520
+ // 合并单元格
521
+ sheet.addSpan(0, 0, 2, 3) // 从 A1 开始,合并 2 行 3 列
522
+
523
+ // 取消合并
524
+ sheet.removeSpan(0, 0)
525
+
526
+ // 获取所有合并区域
527
+ const spans = sheet.getSpans()
528
+
529
+ // 检查单元格是否在合并区域内
530
+ const span = sheet.getSpan(0, 0)
531
+ ```
532
+
533
+ ### 行列操作
534
+
535
+ ```javascript
536
+ // 设置行高列宽
537
+ sheet.setRowHeight(0, 30) // 第一行高度 30px
538
+ sheet.setColumnWidth(0, 100) // 第一列宽度 100px
539
+
540
+ // 获取行高列宽
541
+ const rowHeight = sheet.getRowHeight(0)
542
+ const colWidth = sheet.getColumnWidth(0)
543
+
544
+ // 隐藏/显示行列
545
+ sheet.setRowVisible(0, false)
546
+ sheet.setColumnVisible(0, true)
547
+
548
+ // 设置行列数
549
+ sheet.setRowCount(1000)
550
+ sheet.setColumnCount(50)
551
+ ```
552
+
553
+ ### 公式使用
554
+
555
+ ```javascript
556
+ // 基础公式
557
+ sheet.getCell(0, 0).formula('SUM(A1:A10)')
558
+ sheet.getCell(1, 0).formula('AVERAGE(B1:B10)')
559
+ sheet.getCell(2, 0).formula('MAX(C1:C10)')
560
+
561
+ // 条件公式
562
+ sheet.getCell(0, 1).formula('IF(A1>100, "高", "低")')
563
+
564
+ // 跨工作表引用
565
+ sheet.getCell(0, 2).formula('Sheet2!A1 + Sheet3!B2')
566
+
567
+ // 嵌套公式
568
+ sheet.getCell(0, 3).formula('IF(SUM(A1:A10)>100, MAX(B1:B10), MIN(B1:B10))')
569
+
570
+ // 数组公式
571
+ sheet.getCell(0, 4).formula('SUMPRODUCT(A1:A10, B1:B10)')
572
+ ```
573
+
574
+ ### 图表创建
575
+
576
+ ```javascript
577
+ // 柱状图
578
+ sheet.charts.add('SalesChart', 'barChart', {
579
+ from: { col: 5, row: 0 },
580
+ to: { col: 12, row: 15 },
581
+ title: '月度销售额',
582
+ chartData: {
583
+ categories: ['一月', '二月', '三月', '四月', '五月'],
584
+ series: [
585
+ { name: '产品A', values: [120, 150, 180, 200, 170] },
586
+ { name: '产品B', values: [80, 100, 120, 140, 160] }
587
+ ]
588
+ },
589
+ legend: { position: 'bottom' }
590
+ })
591
+
592
+ // 折线图
593
+ sheet.charts.add('TrendChart', 'lineChart', {
594
+ from: { col: 0, row: 20 },
595
+ colCount: 10,
596
+ rowCount: 12,
597
+ chartData: {
598
+ categories: ['Q1', 'Q2', 'Q3', 'Q4'],
599
+ series: [
600
+ { name: '收入', values: [1000, 1200, 1500, 1800] }
601
+ ]
602
+ }
603
+ })
604
+
605
+ // 饼图
606
+ sheet.charts.add('PieChart', 'pieChart', {
607
+ from: { col: 0, row: 0 },
608
+ colCount: 6,
609
+ rowCount: 10,
610
+ chartData: {
611
+ categories: ['技术', '市场', '销售', '运营'],
612
+ series: [{ values: [35, 25, 30, 10] }]
613
+ }
614
+ })
615
+ ```
616
+
617
+ ### 数据验证
618
+
619
+ ```javascript
620
+ // 下拉列表
621
+ sheet.setDataValidator(0, 0, {
622
+ type: 'list',
623
+ formula1: '"选项1,选项2,选项3"',
624
+ showDropDown: true,
625
+ showErrorMessage: true,
626
+ errorTitle: '输入错误',
627
+ errorMessage: '请从下拉列表中选择'
628
+ })
629
+
630
+ // 数字范围验证
631
+ sheet.setDataValidator(1, 0, {
632
+ type: 'number',
633
+ operator: 'between',
634
+ value1: 0,
635
+ value2: 100,
636
+ showErrorMessage: true,
637
+ errorTitle: '数值超出范围',
638
+ errorMessage: '请输入 0-100 之间的数字'
639
+ })
640
+
641
+ // 日期验证
642
+ sheet.setDataValidator(2, 0, {
643
+ type: 'date',
644
+ operator: 'greaterThan',
645
+ value1: '2024-01-01',
646
+ showErrorMessage: true
647
+ })
648
+ ```
649
+
650
+ ### 批注操作
651
+
652
+ ```javascript
653
+ // 添加批注
654
+ sheet.setComment(0, 0, {
655
+ text: '这是一个批注',
656
+ author: '用户名',
657
+ width: 200,
658
+ height: 100
659
+ })
660
+
661
+ // 获取批注
662
+ const comment = sheet.getComment(0, 0)
663
+
664
+ // 删除批注
665
+ sheet.removeComment(0, 0)
666
+ ```
667
+
668
+ ### 打印配置
669
+
670
+ ```javascript
671
+ import { PrintInfo, print } from 'quickspeadsheet'
672
+
673
+ // 配置打印信息
674
+ const printInfo = new PrintInfo()
675
+ printInfo.paperSize = 'a4'
676
+ printInfo.orientation = 'portrait'
677
+ printInfo.fitPagesWide = 1
678
+ printInfo.fitPagesTall = 0
679
+ printInfo.header = '&C&"Arial,Bold"&18销售报表'
680
+ printInfo.footer = '&L&D&C第 &P 页,共 &N 页&R&T'
681
+ printInfo.showGridLine = true
682
+ printInfo.centerHorizontal = true
683
+ printInfo.centerVertical = true
684
+
685
+ // 设置打印区域
686
+ printInfo.printArea = 'A1:F50'
687
+
688
+ // 设置重复标题行
689
+ printInfo.repeatRowStart = 0
690
+ printInfo.repeatRowEnd = 2
691
+
692
+ // 执行打印
693
+ await print(workbook)
694
+ ```
695
+
696
+ ### 撤销/重做
697
+
698
+ ```javascript
699
+ const undoManager = workbook.undoManager()
700
+
701
+ // 撤销
702
+ if (undoManager.canUndo()) {
703
+ undoManager.undo()
704
+ }
705
+
706
+ // 重做
707
+ if (undoManager.canRedo()) {
708
+ undoManager.redo()
709
+ }
710
+
711
+ // 清空历史
712
+ undoManager.clear()
713
+ ```
714
+
715
+ ### 搜索替换
716
+
717
+ ```javascript
718
+ import { findInSheet, replaceInSheet, findAllInSheet } from 'quickspeadsheet'
719
+
720
+ // 查找
721
+ const matches = findInSheet(sheet, '搜索文本', {
722
+ matchCase: false,
723
+ matchWholeWord: false
724
+ })
725
+
726
+ // 查找所有
727
+ const allMatches = findAllInSheet(sheet, '搜索文本')
728
+
729
+ // 替换
730
+ const count = replaceInSheet(sheet, '旧文本', '新文本', {
731
+ matchCase: false,
732
+ replaceAll: true
733
+ })
734
+ ```
735
+
736
+ ## 性能优化
737
+
738
+ ### Canvas 模式优化
739
+
740
+ ```javascript
741
+ const workbook = createWorkbook(container, {
742
+ renderer: 'canvas',
743
+ // 启用滚动位图优化,减少重绘
744
+ enableCanvasScrollBlit: true,
745
+ // 启用瓦片缓存(实验性)
746
+ enableCanvasTileCachePrototype: true,
747
+ })
748
+ ```
749
+
750
+ ### 大文件处理
751
+
752
+ ```javascript
753
+ // 使用 Worker 线程解析大文件
754
+ const excelIO = createExcelIO()
755
+ await excelIO.open(file, (json) => {
756
+ workbook.fromJSON(json)
757
+ }, {
758
+ useWorker: true,
759
+ onProgress: (progress) => {
760
+ console.log(`进度: ${Math.round(progress.progress * 100)}%`)
761
+ }
762
+ })
763
+ ```
764
+
765
+ ### 按需加载
766
+
767
+ ```javascript
768
+ // 设置最大行数限制
769
+ await excelIO.open(file, callback, {
770
+ maxRows: 10000
771
+ })
772
+ ```
773
+
774
+ ### 图片处理优化
775
+
776
+ ```javascript
777
+ const workbook = createWorkbook(container, {
778
+ imageDisplay: {
779
+ // 限制最大解码像素,防止内存溢出
780
+ maxDecodePixels: 16_000_000,
781
+ // PDF 渲染最大边长
782
+ pdfMaxCanvasSide: 2048,
783
+ // 按需启用格式支持
784
+ frontendTiff: true,
785
+ frontendPdf: false, // 不需要时可关闭
786
+ frontendHeic: false,
787
+ }
788
+ })
789
+ ```
790
+
791
+ ## 已知限制
792
+
793
+ ### 功能限制
794
+
795
+ | 功能 | 状态 | 说明 |
796
+ |------|------|------|
797
+ | 公式计算 | 部分支持 | 支持 80+ 常用函数,部分高级函数未实现 |
798
+ | 图表渲染 | 部分支持 | 基于 Chart.js,部分 Excel 图表类型不支持 |
799
+ | 数据透视表 | 只读 | 支持解析和渲染,不支持编辑 |
800
+ | 切片器 | 只读 | 支持解析和交互,不支持创建 |
801
+ | VBA 宏 | 不支持 | `.xlsm` 文件中的宏代码不会执行 |
802
+ | ActiveX | 不支持 | ActiveX 控件无法在浏览器中运行 |
803
+
804
+ ### 格式限制
805
+
806
+ | 格式 | 状态 | 说明 |
807
+ |------|------|------|
808
+ | `.xlsx` | 完全支持 | Office Open XML 格式 |
809
+ | `.xlsm` | 部分支持 | 宏代码不执行 |
810
+ | `.xls` | 不支持 | 旧版二进制(BIFF);**不提供导入**,请另存为 `.xlsx` |
811
+ | `.csv` | 不支持 | 可通过其他库转换 |
812
+ | `.ods` | 不支持 | OpenDocument 格式 |
813
+
814
+ ### 浏览器限制
815
+
816
+ - **IE 浏览器**:不支持
817
+ - **移动端浏览器**:功能受限,建议使用桌面浏览器
818
+ - **Safari**:部分图片格式(如 HEIC)可能需要额外配置
819
+
820
+ ## 常见问题
821
+
822
+ ### 1. 图片不显示
823
+
824
+ 检查图片格式是否支持,EMF/WMF 等格式需要启用前端解码:
825
+
826
+ ```javascript
827
+ const workbook = createWorkbook(container, {
828
+ imageDisplay: {
829
+ frontendEmf: true
830
+ }
831
+ })
832
+ ```
833
+
834
+ ### 2. 大文件加载慢
835
+
836
+ 启用 Worker 线程和进度提示:
837
+
838
+ ```javascript
839
+ await excelIO.open(file, callback, {
840
+ useWorker: true,
841
+ onProgress: (p) => console.log(p)
842
+ })
843
+ ```
844
+
845
+ ### 3. 公式计算错误
846
+
847
+ 检查公式语法和引用范围,部分函数可能未实现。
848
+
849
+ ### 4. 样式丢失
850
+
851
+ 确保导出时包含样式信息:
852
+
853
+ ```javascript
854
+ const json = workbook.toJSON({ includeStyles: true })
855
+ ```
856
+
857
+ ### 5. 能否导入 `.xls` 文件?
858
+
859
+ 不能。`.xls` 为旧版二进制格式,当前版本 **不提供导入**(界面与 `importFile` 均不接受该扩展名)。请使用 Excel、WPS 等 **另存为 `.xlsx`** 后再导入。
860
+
861
+ ## 贡献指南
862
+
863
+ ### 开发环境
864
+
865
+ ```bash
866
+ # 克隆仓库
867
+ git clone https://github.com/your-repo/quickspeadsheet.git
868
+
869
+ # 安装依赖
870
+ npm install
871
+
872
+ # 启动开发服务器
873
+ npm run dev
874
+
875
+ # 运行测试
876
+ npm run test
877
+
878
+ # 构建生产版本
879
+ npm run build
880
+ ```
881
+
882
+ ### 代码规范
883
+
884
+ - 使用 ESLint 进行代码检查
885
+ - 使用 Prettier 进行代码格式化
886
+ - 提交前请确保通过所有测试
887
+
888
+ ### 提交规范
889
+
890
+ ```
891
+ feat: 新功能
892
+ fix: 修复 bug
893
+ docs: 文档更新
894
+ style: 代码格式调整
895
+ refactor: 代码重构
896
+ test: 测试相关
897
+ chore: 构建/工具相关
898
+ ```
899
+
900
+ ## TypeScript 支持
901
+
902
+ 本项目提供完整的 TypeScript 类型定义。
903
+
904
+ ### 类型导入
905
+
906
+ ```typescript
907
+ import type {
908
+ Workbook,
909
+ Worksheet,
910
+ Cell,
911
+ CellStyle,
912
+ Span,
913
+ Selection,
914
+ DataValidator,
915
+ Comment,
916
+ Chart,
917
+ Table,
918
+ Shape,
919
+ Theme,
920
+ PrintInfo,
921
+ WorkbookJSON,
922
+ SheetJSON,
923
+ } from 'quickspeadsheet'
924
+ ```
925
+
926
+ ### 类型定义示例
927
+
928
+ ```typescript
929
+ import { createWorkbook, createExcelIO } from 'quickspeadsheet'
930
+ import type { WorkbookOptions, CellStyle } from 'quickspeadsheet'
931
+
932
+ const options: WorkbookOptions = {
933
+ renderer: 'canvas',
934
+ tabStripRatio: 0.88,
935
+ enableCanvasScrollBlit: true,
936
+ }
937
+
938
+ const workbook = createWorkbook(container, options)
939
+
940
+ const style: CellStyle = {
941
+ font: 'bold 14px Arial',
942
+ backColor: '#FFD700',
943
+ foreColor: '#000000',
944
+ hAlign: 'center',
945
+ vAlign: 'center',
946
+ }
947
+ ```
948
+
949
+ ### 泛型支持
950
+
951
+ ```typescript
952
+ import type { Worksheet } from 'quickspeadsheet'
953
+
954
+ function processSheet<T>(sheet: Worksheet, data: T[]): void {
955
+ data.forEach((item, index) => {
956
+ sheet.setValue(index, 0, item)
957
+ })
958
+ }
959
+ ```
960
+
961
+ ## 国际化
962
+
963
+ ### 内置语言
964
+
965
+ 项目内置中文资源:
966
+
967
+ ```javascript
968
+ import { resourcesZh, getResourceZh } from 'quickspeadsheet'
969
+
970
+ // 获取中文资源
971
+ const resource = getResourceZh('key')
972
+ ```
973
+
974
+ ### 自定义语言
975
+
976
+ ```javascript
977
+ import { cultureManager } from 'quickspeadsheet'
978
+
979
+ // 设置语言
980
+ cultureManager.culture('zh-CN')
981
+
982
+ // 添加自定义资源
983
+ cultureManager.addCulture('ja-JP', {
984
+ // 日文资源
985
+ })
986
+ ```
987
+
988
+ ### 日期格式化
989
+
990
+ ```javascript
991
+ import { cultureManager } from 'quickspeadsheet'
992
+
993
+ // 格式化日期
994
+ const formatted = cultureManager.formatDate(new Date(), 'yyyy-MM-dd')
995
+ ```
996
+
997
+ ### 数字格式化
998
+
999
+ ```javascript
1000
+ import { cultureManager } from 'quickspeadsheet'
1001
+
1002
+ // 格式化数字
1003
+ const formatted = cultureManager.formatNumber(1234567.89, '#,##0.00')
1004
+ ```
1005
+
1006
+ ## API 文档
1007
+
1008
+ 详细的 API 文档请参阅 [API.md](./docs/API.md)。产品与授权验收状态见 [product-release-license-acceptance.md](./docs/product-release-license-acceptance.md)。
1009
+
1010
+ ### 核心模块
1011
+
1012
+ | 模块 | 描述 |
1013
+ |------|------|
1014
+ | [Workbook](./docs/API.md#workbook) | 工作簿类,管理多个工作表 |
1015
+ | [Worksheet](./docs/API.md#worksheet) | 工作表类,管理单元格数据 |
1016
+ | [Cell](./docs/API.md#cell) | 单元格类,管理单个单元格 |
1017
+ | [ExcelIO](./docs/API.md#excelio) | Excel 文件读写类 |
1018
+
1019
+ ### Vue 组件
1020
+
1021
+ | 组件 | 描述 |
1022
+ |------|------|
1023
+ | [SpreadExcelViewer](./docs/API.md#spreadexcelviewer) | 主视图组件 |
1024
+ | [SpreadToolbar](./docs/API.md#spreadtoolbar) | 工具栏组件 |
1025
+ | [SpreadFormulaBar](./docs/API.md#spreadformulabar) | 公式栏组件 |
1026
+
1027
+ ### Composables
1028
+
1029
+ | 函数 | 描述 |
1030
+ |------|------|
1031
+ | [useSpread](./docs/API.md#usespread) | Spread 逻辑封装 |
1032
+
1033
+ ### 高级功能
1034
+
1035
+ | 功能 | 描述 |
1036
+ |------|------|
1037
+ | [条件格式](./docs/API.md#条件格式) | 根据条件自动应用样式 |
1038
+ | [图表](./docs/API.md#图表) | 创建和操作图表 |
1039
+ | [公式引擎](./docs/API.md#公式引擎) | 公式计算支持 |
1040
+ | [打印功能](./docs/API.md#打印功能) | 打印预览和配置 |
1041
+ | [撤销管理](./docs/API.md#撤销管理) | 撤销/重做操作 |
1042
+
1043
+ ## 更新日志
1044
+
1045
+ ### v1.0.0
1046
+
1047
+ - 初始版本发布
1048
+ - 支持基本的 Excel 文件读写
1049
+ - 支持 DOM 和 Canvas 双渲染模式
1050
+ - 支持公式计算
1051
+ - 支持图表渲染
1052
+ - 支持条件格式
1053
+ - 支持数据验证
1054
+ - 支持打印预览
1055
+
1056
+ ## 相关项目
1057
+
1058
+ - [SheetJS](https://github.com/SheetJS/sheetjs) - Excel 文件解析库
1059
+ - [ExcelJS](https://github.com/exceljs/exceljs) - Excel 文件读写库
1060
+ - [Luckysheet](https://github.com/dream-num/Luckysheet) - 在线表格库
1061
+ - [Handsontable](https://github.com/handsontable/handsontable) - 数据表格组件
1062
+
1063
+ ## 许可证
1064
+
1065
+ Proprietary License - 专有许可证
1066
+
1067
+ - ❌ 禁止商业使用
1068
+ - ❌ 禁止分发源代码
1069
+ - ❌ 禁止公开源代码
1070
+ - ✅ 仅限个人非商业用途
1071
+
1072
+ 如需商业授权,请联系版权所有者。