sheetnext 0.1.9 → 0.2.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/LICENSE +1 -1
- package/README.md +161 -73
- package/README_CN.md +161 -0
- package/dist/sheetnext.css +1 -1
- package/dist/sheetnext.es.js +4 -4
- package/dist/sheetnext.locale.zh-CN.es.js +20 -0
- package/dist/sheetnext.locale.zh-CN.umd.js +20 -0
- package/dist/sheetnext.umd.js +4 -4
- package/docs/docs-detail.md +2998 -0
- package/docs/image_en.png +0 -0
- package/package.json +25 -34
- package/DOCS.md +0 -1703
- package/docs/demo.png +0 -0
- package/types/index.d.ts +0 -170
package/DOCS.md
DELETED
|
@@ -1,1703 +0,0 @@
|
|
|
1
|
-
# SheetNext 简介
|
|
2
|
-
|
|
3
|
-
## ✨ 核心特点
|
|
4
|
-
|
|
5
|
-
- **📊 完整的电子表格功能** - 支持单元格编辑、样式设置、公式引擎、图表绘制、数据排序、筛选等核心功能
|
|
6
|
-
- **🤖 AI 智能工作流** - 内置 AI 全自动操作流程,轻松实现模板生成、数据分析、公式编写、跨表逻辑操作等
|
|
7
|
-
- **📁 原生文件支持** - 原生支持 Excel (.xlsx)、CSV、JSON 文件的导入导出,无需额外插件
|
|
8
|
-
- **🚀 开箱即用** - 零配置开始使用,所有功能内置,无需单独安装依赖库
|
|
9
|
-
- **⚡ 高性能渲染** - 基于 Canvas 的虚拟滚动技术,轻松处理大数据量表格
|
|
10
|
-
- **🔄 快速迭代** - 版本持续更新,积极响应用户反馈和问题
|
|
11
|
-
|
|
12
|
-
## 📦 安装方式
|
|
13
|
-
|
|
14
|
-
SheetNext 提供多种安装方式,满足不同项目需求:
|
|
15
|
-
|
|
16
|
-
- **npm/yarn 安装** - 适用于现代前端项目(React、Vue、Angular 等)
|
|
17
|
-
- **浏览器直接引入** - 通过 CDN 或本地文件直接在 HTML 中使用
|
|
18
|
-
|
|
19
|
-
## 🔗 相关链接
|
|
20
|
-
|
|
21
|
-
- 🏠 [官网](https://www.sheetnext.com)
|
|
22
|
-
- 🎯 [在线体验](https://www.sheetnext.com/editor)
|
|
23
|
-
- 📦 [npm 包](https://www.npmjs.com/package/sheetnext)
|
|
24
|
-
|
|
25
|
-
# 快速开始
|
|
26
|
-
|
|
27
|
-
只需几行代码,即可将 SheetNext 集成到您的项目中。支持 npm 安装和浏览器直接引入两种方式,满足不同场景需求。
|
|
28
|
-
|
|
29
|
-
## 📦 使用 npm 安装
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npm install sheetnext
|
|
33
|
-
```
|
|
34
|
-
```html
|
|
35
|
-
<div id="SNContainer" style="width:100vw;height:100vh;padding:0 7px 7px"></div>
|
|
36
|
-
```
|
|
37
|
-
```javascript
|
|
38
|
-
import SheetNext from 'sheetnext';
|
|
39
|
-
import 'sheetnext/dist/sheetnext.css';
|
|
40
|
-
|
|
41
|
-
// 注意设置容器#SNContainer宽高
|
|
42
|
-
const SN = new SheetNext(document.querySelector('#SNContainer'));
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## 🌐 浏览器直接引入
|
|
46
|
-
|
|
47
|
-
```html
|
|
48
|
-
<!-- 引入样式 -->
|
|
49
|
-
<link rel="stylesheet" href="dist/sheetnext.css">
|
|
50
|
-
|
|
51
|
-
<!-- 编辑器容器 -->
|
|
52
|
-
<div id="SNContainer" style="width: 100vw; height: 100vh;padding:0 7px 7px"></div>
|
|
53
|
-
|
|
54
|
-
<!-- 引入脚本 -->
|
|
55
|
-
<!-- <script src="dist/sheetnext.umd.js"></script> -->
|
|
56
|
-
|
|
57
|
-
<!-- 初始化,注意设置宽高 -->
|
|
58
|
-
<!-- <script>
|
|
59
|
-
const SN = new SheetNext(document.querySelector('#SNContainer'));
|
|
60
|
-
</script> -->
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## ⚙️ 初始化配置
|
|
64
|
-
|
|
65
|
-
SheetNext 支持多种可选配置参数,用于定制编辑器的功能和外观。
|
|
66
|
-
|
|
67
|
-
```javascript
|
|
68
|
-
const SN = new SheetNext(document.querySelector('#container'), {
|
|
69
|
-
AI_URL: "http://localhost:3000/sheetnextAI", // AI 中转地址
|
|
70
|
-
AI_TOKEN: "your-token", // AI 中转 token
|
|
71
|
-
licenseKey: "your-license-key", // 授权密钥
|
|
72
|
-
menuList: (defaultList) => { /* ... */ }, // 自定义菜单栏
|
|
73
|
-
menuRight: '<div>© SheetNext</div>' // 菜单栏右侧自定义内容
|
|
74
|
-
});
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## 配置参数说明
|
|
78
|
-
|
|
79
|
-
| 参数 | 类型 | 说明 |
|
|
80
|
-
|------|------|------|
|
|
81
|
-
| `AI_URL` | `string` | AI 服务中转地址,用于配置 AI 功能的后端接口 |
|
|
82
|
-
| `AI_TOKEN` | `string` | AI 服务访问令牌,用于鉴权认证 |
|
|
83
|
-
| `licenseKey` | `string` | 商业版授权密钥,社区版可不填 |
|
|
84
|
-
| `menuList` | `function` | 自定义顶部菜单栏,接收默认菜单并返回修改后的菜单 |
|
|
85
|
-
| `menuRight` | `string` | 菜单栏右侧区域的自定义 HTML 内容 |
|
|
86
|
-
|
|
87
|
-
## menuList 自定义菜单示例
|
|
88
|
-
|
|
89
|
-
```javascript
|
|
90
|
-
const SN = new SheetNext(document.querySelector('#container'), {
|
|
91
|
-
menuList: (defaultList) => {
|
|
92
|
-
// 在"文件"菜单末尾添加自定义项
|
|
93
|
-
defaultList[0].items.push({
|
|
94
|
-
label: '我的自定义功能',
|
|
95
|
-
handler: () => alert('这是自定义菜单项!')
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// 添加新的顶级菜单
|
|
99
|
-
defaultList.push({
|
|
100
|
-
label: '帮助',
|
|
101
|
-
items: [
|
|
102
|
-
{ label: '使用文档', handler: () => window.open('https://www.sheetnext.com/docs') },
|
|
103
|
-
{ label: '关于', handler: () => alert('SheetNext v1.0') }
|
|
104
|
-
]
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
return defaultList;
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
**MenuList 结构定义:**
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
interface MenuItem {
|
|
116
|
-
label: string; // 菜单项标签
|
|
117
|
-
handler?: () => void; // 点击处理函数
|
|
118
|
-
disabled?: boolean; // 是否禁用
|
|
119
|
-
tip?: string; // 提示信息(右侧显示)
|
|
120
|
-
divider?: boolean; // 是否为分隔线
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
interface Menu {
|
|
124
|
-
label: string; // 菜单标签
|
|
125
|
-
items: MenuItem[]; // 菜单项列表
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
type MenuList = Menu[];
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
**注意事项:**
|
|
132
|
-
- `menuList` 和 `menuRight` 只能在初始化时配置,后续无法修改
|
|
133
|
-
- 如果不传入 `menuList`,将使用默认菜单(包含:文件、插入、公式、数据、视图、更多)
|
|
134
|
-
- AI 功能需要配置 `AI_URL` 才能使用,详见 AI 中转配置
|
|
135
|
-
|
|
136
|
-
# 工作簿级别
|
|
137
|
-
|
|
138
|
-
SheetNext 是主入口类,管理整个电子表格应用。
|
|
139
|
-
|
|
140
|
-
## 核心属性
|
|
141
|
-
|
|
142
|
-
| 属性 | 类型 | 说明 |
|
|
143
|
-
|------|------|------|
|
|
144
|
-
| `workbookName` | `string` | 工作簿名称(可读写,长度1-255字符) |
|
|
145
|
-
| `activeSheet` | `Sheet` | 当前激活的工作表(可读写) |
|
|
146
|
-
| `sheets` | `Sheet[]` | 所有工作表数组 |
|
|
147
|
-
| `sheetNames` | `string[]` | 工作表名称列表(只读) |
|
|
148
|
-
| `containerDom` | `HTMLElement` | 编辑器容器元素 |
|
|
149
|
-
| `namespace` | `string` | 实例的全局命名空间(如 `SN_0`) |
|
|
150
|
-
| `locked` | `boolean` | 是否锁定工作表切换/操作 |
|
|
151
|
-
|
|
152
|
-
## 核心方法
|
|
153
|
-
|
|
154
|
-
### 新建工作表
|
|
155
|
-
`addSheet(name?: string): Sheet`
|
|
156
|
-
|
|
157
|
-
添加新工作表,名称可选(自动生成 Sheet1、Sheet2 等)。
|
|
158
|
-
|
|
159
|
-
```javascript
|
|
160
|
-
const newSheet = SN.addSheet("销售数据");
|
|
161
|
-
const autoSheet = SN.addSheet(); // 自动命名
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
**规则:** 名称不重复、长度1-31字符、不含特殊符号 `: / \ * ? [ ]`
|
|
165
|
-
|
|
166
|
-
### 删除工作表
|
|
167
|
-
`delSheet(name: string): void`
|
|
168
|
-
|
|
169
|
-
删除指定工作表(至少保留一个可见工作表)。
|
|
170
|
-
|
|
171
|
-
```javascript
|
|
172
|
-
SN.delSheet("Sheet2");
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### 根据名称获取工作表
|
|
176
|
-
`getSheetByName(name: string): Sheet | null`
|
|
177
|
-
|
|
178
|
-
根据名称获取工作表。
|
|
179
|
-
|
|
180
|
-
```javascript
|
|
181
|
-
const sheet = SN.getSheetByName("Sheet1");
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### 根据索引获取可见工作表
|
|
185
|
-
`getVisibleSheetByIndex(index: number): Sheet`
|
|
186
|
-
|
|
187
|
-
获取可见工作表(按索引,隐藏工作表不计入)。
|
|
188
|
-
|
|
189
|
-
```javascript
|
|
190
|
-
const firstSheet = SN.getVisibleSheetByIndex(0);
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### 手动重新渲染
|
|
194
|
-
`r(): void`
|
|
195
|
-
|
|
196
|
-
手动触发画布重新渲染(批量修改后使用)。
|
|
197
|
-
|
|
198
|
-
```javascript
|
|
199
|
-
// 批量修改后刷新
|
|
200
|
-
for (let i = 0; i < 100; i++) {
|
|
201
|
-
sheet.getCell(i, 0).editVal = i;
|
|
202
|
-
}
|
|
203
|
-
SN.r();
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### 获取工作簿数据
|
|
207
|
-
`getData(): object`
|
|
208
|
-
|
|
209
|
-
获取完整的工作簿数据(JSON格式),包含所有工作表、单元格数据、样式、公式、图表等。
|
|
210
|
-
|
|
211
|
-
```javascript
|
|
212
|
-
// 获取工作簿数据
|
|
213
|
-
const data = SN.getData();
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
**使用场景:**
|
|
217
|
-
- 数据备份和恢复
|
|
218
|
-
- 数据持久化到数据库或本地存储
|
|
219
|
-
- 数据分析和处理
|
|
220
|
-
- 跨系统数据传输
|
|
221
|
-
|
|
222
|
-
**示例:保存到 localStorage**
|
|
223
|
-
|
|
224
|
-
```javascript
|
|
225
|
-
// 保存数据
|
|
226
|
-
const data = SN.getData();
|
|
227
|
-
localStorage.setItem('sheetData', JSON.stringify(data));
|
|
228
|
-
|
|
229
|
-
// 读取数据
|
|
230
|
-
const savedData = JSON.parse(localStorage.getItem('sheetData'));
|
|
231
|
-
SN.setData(savedData);
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### 加载工作簿数据
|
|
235
|
-
`setData(data: object): boolean`
|
|
236
|
-
|
|
237
|
-
加载完整的工作簿数据,替换当前所有工作表内容。
|
|
238
|
-
|
|
239
|
-
```javascript
|
|
240
|
-
SN.setData(data):boolean;
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
### 导入文件
|
|
244
|
-
`import(file: File): Promise<void>`
|
|
245
|
-
|
|
246
|
-
导入文件,支持 `.xlsx`、`.csv` 和 `.json` 格式。
|
|
247
|
-
|
|
248
|
-
```javascript
|
|
249
|
-
// 通过文件选择器导入
|
|
250
|
-
const fileInput = document.createElement('input');
|
|
251
|
-
fileInput.type = 'file';
|
|
252
|
-
fileInput.accept = '.xlsx,.csv,.json';
|
|
253
|
-
fileInput.onchange = (e) => {
|
|
254
|
-
const file = e.target.files[0];
|
|
255
|
-
SN.import(file);
|
|
256
|
-
};
|
|
257
|
-
fileInput.click();
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
**支持格式:**
|
|
261
|
-
- `.xlsx` - Excel 工作簿(完整支持)
|
|
262
|
-
- `.csv` - 逗号分隔值文件(导入为单个工作表)
|
|
263
|
-
- `.json` - SheetNext JSON 格式(包含样式、公式、图表等)
|
|
264
|
-
|
|
265
|
-
### 从URL导入文件
|
|
266
|
-
`importFromUrl(url: String): Promise<void>`
|
|
267
|
-
|
|
268
|
-
通过在线地址导入 Excel 文件(.xlsx 格式)。
|
|
269
|
-
|
|
270
|
-
```javascript
|
|
271
|
-
await SN.importFromUrl('https://example.com/data.xlsx');
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### 导出文件
|
|
275
|
-
`export(type: string): void`
|
|
276
|
-
|
|
277
|
-
导出电子表格,支持 `"XLSX"`、`"CSV"` 和 `"JSON"` 格式。
|
|
278
|
-
|
|
279
|
-
```javascript
|
|
280
|
-
SN.export('XLSX'); // 导出为 Excel 文件
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
**格式说明:**
|
|
284
|
-
- `XLSX` - Excel 工作簿格式,支持多工作表、样式、公式、图表等
|
|
285
|
-
- `CSV` - 纯文本格式,仅导出当前激活工作表的数据(不含样式)
|
|
286
|
-
- `JSON` - SheetNext 专用格式,完整保存工作簿结构、样式、公式、图表等,适合数据备份和快速加载
|
|
287
|
-
|
|
288
|
-
## 多实例支持
|
|
289
|
-
|
|
290
|
-
SheetNext 支持同一页面创建多个独立实例:
|
|
291
|
-
|
|
292
|
-
```javascript
|
|
293
|
-
const editor1 = new SheetNext(document.querySelector('#container1'));
|
|
294
|
-
const editor2 = new SheetNext(document.querySelector('#container2'));
|
|
295
|
-
|
|
296
|
-
console.log(editor1.namespace); // "SN_0"
|
|
297
|
-
console.log(editor2.namespace); // "SN_1"
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
# 工作表级别
|
|
301
|
-
|
|
302
|
-
Sheet 类代表一个工作表。
|
|
303
|
-
|
|
304
|
-
## 属性
|
|
305
|
-
|
|
306
|
-
| 属性 | 类型 | 说明 |
|
|
307
|
-
|------|------|------|
|
|
308
|
-
| `name` | `string` | 工作表名称 |
|
|
309
|
-
| `hidden` | `boolean` | 是否隐藏 |
|
|
310
|
-
| `merges` | `RangeNum[]` | 合并单元格区域列表 |
|
|
311
|
-
| `defaultColWidth` | `number` | 默认列宽(像素) |
|
|
312
|
-
| `defaultRowHeight` | `number` | 默认行高(像素) |
|
|
313
|
-
| `showGridLines` | `boolean` | 是否显示网格线 |
|
|
314
|
-
| `showRowColHeaders` | `boolean` | 是否显示行列标题 |
|
|
315
|
-
| `activeCell` | `CellNum` | 当前活动单元格 |
|
|
316
|
-
| `activeAreas` | `RangeNum[]` | 当前选中区域 |
|
|
317
|
-
| `rows` | `Row[]` | 所有行 |
|
|
318
|
-
| `cols` | `Col[]` | 所有列 |
|
|
319
|
-
| `rowCount` | `number` | 行数(只读) |
|
|
320
|
-
| `colCount` | `number` | 列数(只读) |
|
|
321
|
-
| `xSplit` | `number` | 冻结列数 |
|
|
322
|
-
| `ySplit` | `number` | 冻结行数 |
|
|
323
|
-
| `drawings` | `Drawing[]` | 图形对象列表(图表、图片等) |
|
|
324
|
-
|
|
325
|
-
## 基础方法
|
|
326
|
-
|
|
327
|
-
### 获取指定行
|
|
328
|
-
`getRow(r: number): Row`
|
|
329
|
-
|
|
330
|
-
获取指定行对象。
|
|
331
|
-
|
|
332
|
-
```javascript
|
|
333
|
-
const row = sheet.getRow(0); // 获取第一行
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### 获取指定列
|
|
337
|
-
`getCol(c: number): Col`
|
|
338
|
-
|
|
339
|
-
获取指定列对象。
|
|
340
|
-
|
|
341
|
-
```javascript
|
|
342
|
-
const col = sheet.getCol(0); // 获取第一列(A列)
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### 获取指定单元格
|
|
346
|
-
`getCell(r: number, c: number): Cell`
|
|
347
|
-
|
|
348
|
-
获取指定单元格。
|
|
349
|
-
|
|
350
|
-
```javascript
|
|
351
|
-
const cell = sheet.getCell(0, 0); // 获取 A1 单元格
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
### 通过字符串获取单元格
|
|
355
|
-
`getCellByStr(cellStr: string): Cell`
|
|
356
|
-
|
|
357
|
-
通过字符串引用获取单元格。
|
|
358
|
-
|
|
359
|
-
```javascript
|
|
360
|
-
const cell = sheet.getCellByStr("A1");
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### 范围字符串转数字
|
|
364
|
-
`rangeStrToNum(rangeStr: string): RangeNum`
|
|
365
|
-
|
|
366
|
-
将字符串范围转换为数字范围对象。
|
|
367
|
-
|
|
368
|
-
```javascript
|
|
369
|
-
const rangeNum = sheet.rangeStrToNum("A1:C3");
|
|
370
|
-
// 返回: {s:{r:0,c:0}, e:{r:2,c:2}}
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
## 区域遍历
|
|
374
|
-
|
|
375
|
-
### 遍历区域
|
|
376
|
-
`eachArea(rangeRef: RangeRef, callback: (r, c, index) => void, reverse?: boolean): void`
|
|
377
|
-
|
|
378
|
-
遍历指定区域的每个单元格。
|
|
379
|
-
|
|
380
|
-
```javascript
|
|
381
|
-
// 正向遍历
|
|
382
|
-
sheet.eachArea("A1:C3", (r, c, index) => {
|
|
383
|
-
const cell = sheet.getCell(r, c);
|
|
384
|
-
console.log(cell.showVal);
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
// 反向遍历(用于删除操作,避免索引混乱)
|
|
388
|
-
sheet.eachArea("A:A", (r, c) => {
|
|
389
|
-
if (sheet.getCell(r, c).showVal === "") {
|
|
390
|
-
sheet.delRows(r, 1);
|
|
391
|
-
}
|
|
392
|
-
}, true);
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
## 行列操作
|
|
396
|
-
|
|
397
|
-
### 显示所有隐藏行
|
|
398
|
-
`showAllHidRows(): void`
|
|
399
|
-
|
|
400
|
-
显示所有隐藏的行。
|
|
401
|
-
|
|
402
|
-
```javascript
|
|
403
|
-
sheet.showAllHidRows();
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### 显示所有隐藏列
|
|
407
|
-
`showAllHidCols(): void`
|
|
408
|
-
|
|
409
|
-
显示所有隐藏的列。
|
|
410
|
-
|
|
411
|
-
```javascript
|
|
412
|
-
sheet.showAllHidCols();
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
### 插入行
|
|
416
|
-
`addRows(startR: number, num: number): void`
|
|
417
|
-
|
|
418
|
-
在指定位置插入行。
|
|
419
|
-
|
|
420
|
-
```javascript
|
|
421
|
-
sheet.addRows(5, 3); // 在第 5 行位置插入 3 行
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
**注意**:多个同时调用时,应反向遍历以避免索引混乱。
|
|
425
|
-
|
|
426
|
-
### 插入列
|
|
427
|
-
`addCols(startC: number, num: number): void`
|
|
428
|
-
|
|
429
|
-
在指定位置插入列。
|
|
430
|
-
|
|
431
|
-
```javascript
|
|
432
|
-
sheet.addCols(2, 2); // 在第 2 列位置插入 2 列
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
### 删除行
|
|
436
|
-
`delRows(startR: number, num: number): void`
|
|
437
|
-
|
|
438
|
-
删除指定行。
|
|
439
|
-
|
|
440
|
-
```javascript
|
|
441
|
-
sheet.delRows(5, 3); // 删除从第 5 行开始的 3 行
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
### 删除列
|
|
445
|
-
`delCols(startC: number, num: number): void`
|
|
446
|
-
|
|
447
|
-
删除指定列。
|
|
448
|
-
|
|
449
|
-
```javascript
|
|
450
|
-
sheet.delCols(2, 2); // 删除从第 2 列开始的 2 列
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
## 合并单元格
|
|
454
|
-
|
|
455
|
-
### 合并单元格
|
|
456
|
-
`mergeCells(rangeRef: RangeRef): void`
|
|
457
|
-
|
|
458
|
-
合并指定区域的单元格。
|
|
459
|
-
|
|
460
|
-
```javascript
|
|
461
|
-
sheet.mergeCells("A1:C3");
|
|
462
|
-
// 或
|
|
463
|
-
sheet.mergeCells({s:{r:0,c:0}, e:{r:2,c:2}});
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
### 取消合并单元格
|
|
467
|
-
`unMergeCells(cellRef: CellRef): void`
|
|
468
|
-
|
|
469
|
-
取消合并(传入区域内任意单元格引用)。
|
|
470
|
-
|
|
471
|
-
```javascript
|
|
472
|
-
sheet.unMergeCells("A1"); // 取消包含 A1 的合并区域
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
## 排序
|
|
476
|
-
|
|
477
|
-
### 区域排序
|
|
478
|
-
`rangeSort(sortItems: SortItem[], range?: RangeRef): void`
|
|
479
|
-
|
|
480
|
-
对指定区域进行排序。
|
|
481
|
-
|
|
482
|
-
**SortItem 接口:**
|
|
483
|
-
|
|
484
|
-
```typescript
|
|
485
|
-
interface SortItem {
|
|
486
|
-
type: "column" | "row" | "custom";
|
|
487
|
-
order?: "asc" | "desc" | "value"; // type="custom" 时省略
|
|
488
|
-
index: string; // 列/行标签,行从 1 开始,列从 A 开始
|
|
489
|
-
sortData?: any[]; // order="value" 时使用,基于此数据排序
|
|
490
|
-
cb?: (rowsArray: Cell[][], sortIndex: number) => Cell[][]; // type="custom" 时使用
|
|
491
|
-
}
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
**示例:按自定义顺序排序**
|
|
495
|
-
|
|
496
|
-
```javascript
|
|
497
|
-
const sheet = SN.activeSheet;
|
|
498
|
-
// 除标题外,按 C 列字母顺序排序:A V U T
|
|
499
|
-
sheet.rangeSort(
|
|
500
|
-
[{
|
|
501
|
-
type: 'column',
|
|
502
|
-
order: 'value',
|
|
503
|
-
index: 'C',
|
|
504
|
-
sortData: ["A", "V", "U", "T"]
|
|
505
|
-
}],
|
|
506
|
-
{s:{c:0,r:1}, e:{c:sheet.colCount, r:sheet.rowCount}}
|
|
507
|
-
);
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
## 批量插入数据
|
|
511
|
-
|
|
512
|
-
### 批量插入数据
|
|
513
|
-
`insertTable(data: (ICellConfig | string | number)[][], startCell: CellRef, globalConfig?: object): RangeNum`
|
|
514
|
-
|
|
515
|
-
在指定位置插入表格数据。
|
|
516
|
-
|
|
517
|
-
**ICellConfig 接口:**
|
|
518
|
-
|
|
519
|
-
```typescript
|
|
520
|
-
interface ICellConfig {
|
|
521
|
-
v?: string; // 单元格值
|
|
522
|
-
w?: number; // 列宽(像素),仅在首行设置
|
|
523
|
-
h?: number; // 行高(像素),仅在首列设置
|
|
524
|
-
b?: boolean; // 是否粗体
|
|
525
|
-
s?: number; // 字体大小
|
|
526
|
-
fg?: string; // 背景色
|
|
527
|
-
a?: 'l' | 'r' | 'c'; // 对齐方式(left/right/center)
|
|
528
|
-
c?: string; // 文本颜色
|
|
529
|
-
mr?: number; // 向右合并单元格数(不包括自身)
|
|
530
|
-
mb?: number; // 向下合并单元格数(不包括自身)
|
|
531
|
-
}
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
**globalConfig 参数:**
|
|
535
|
-
- `a`: 对齐方式
|
|
536
|
-
- `border`: 是否显示边框
|
|
537
|
-
- `w`: 默认列宽
|
|
538
|
-
- `h`: 默认行高
|
|
539
|
-
- `fg`: 背景色
|
|
540
|
-
- `c`: 文本颜色
|
|
541
|
-
|
|
542
|
-
**示例:生成会议记录模板**
|
|
543
|
-
|
|
544
|
-
```javascript
|
|
545
|
-
const t = [
|
|
546
|
-
[
|
|
547
|
-
{ v: "Meeting Minutes", s: 16, mr: 3, fg: "#eee", h: 45, b: true },
|
|
548
|
-
{ w: 160 }, "", { w: 160 }
|
|
549
|
-
],
|
|
550
|
-
["Time", "", "Location", ""],
|
|
551
|
-
["Host", "", "Recorder", ""],
|
|
552
|
-
["Expected", "", "Present", ""],
|
|
553
|
-
["Absent Members", { mr: 2 }, "", ""],
|
|
554
|
-
["Topic", { mr: 2 }, "", ""],
|
|
555
|
-
[{ v: "Content", h: 280 }, { mr: 2 }, "", ""],
|
|
556
|
-
[{ v: "Remarks", h: 80 }, { mr: 2 }, "", ""]
|
|
557
|
-
]; // 必须是矩形矩阵
|
|
558
|
-
|
|
559
|
-
SN.activeSheet.insertTable(t, "A1", {
|
|
560
|
-
border: true,
|
|
561
|
-
a: "c",
|
|
562
|
-
h: 35,
|
|
563
|
-
w: 140
|
|
564
|
-
});
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
**注意**:
|
|
568
|
-
- 对于合并单元格(`mr`/`mb`),需要添加相同数量的空字符串占位符,保持二维数组的矩形结构。
|
|
569
|
-
- 例如:`{ mr: 2 }, "", ""`
|
|
570
|
-
|
|
571
|
-
## 图形对象
|
|
572
|
-
|
|
573
|
-
### 添加图形对象
|
|
574
|
-
`addDrawing(config: object): Drawing`
|
|
575
|
-
|
|
576
|
-
添加图形对象(图表、图片等)。
|
|
577
|
-
|
|
578
|
-
**示例:添加图表**
|
|
579
|
-
|
|
580
|
-
```javascript
|
|
581
|
-
SN.activeSheet.addDrawing({
|
|
582
|
-
type: 'chart',
|
|
583
|
-
startCell: 'B2',
|
|
584
|
-
option: {
|
|
585
|
-
title: { text: '销售趋势图' },
|
|
586
|
-
legend: {
|
|
587
|
-
data: ['销量'] // 或使用引用: `${sheet.name}!B3`
|
|
588
|
-
},
|
|
589
|
-
xAxis: {
|
|
590
|
-
type: 'category',
|
|
591
|
-
data: ['一月', '二月', '三月'] // 或引用: `${sheet.name}!C2:E2`
|
|
592
|
-
},
|
|
593
|
-
yAxis: { type: 'value' },
|
|
594
|
-
series: [
|
|
595
|
-
{
|
|
596
|
-
name: '销量',
|
|
597
|
-
type: 'line',
|
|
598
|
-
data: [820, 932, 901] // 或引用: `${sheet.name}!C3:E3`
|
|
599
|
-
}
|
|
600
|
-
]
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
### 获取单元格的图形对象
|
|
606
|
-
`getDrawingsByCell(cellRef: CellRef): Drawing[]`
|
|
607
|
-
|
|
608
|
-
获取指定单元格位置的所有图形对象。
|
|
609
|
-
|
|
610
|
-
```javascript
|
|
611
|
-
const drawings = sheet.getDrawingsByCell("B2");
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
### 删除图形对象
|
|
615
|
-
`removeDrawing(id: string): void`
|
|
616
|
-
|
|
617
|
-
删除指定 ID 的图形对象。
|
|
618
|
-
|
|
619
|
-
```javascript
|
|
620
|
-
sheet.removeDrawing("drawing-id");
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
---
|
|
624
|
-
|
|
625
|
-
# 单元格级别
|
|
626
|
-
|
|
627
|
-
Cell 类代表单个单元格,提供完整的数据、样式、验证等功能。
|
|
628
|
-
|
|
629
|
-
## 核心属性
|
|
630
|
-
|
|
631
|
-
| 属性 | 类型 | 说明 |
|
|
632
|
-
|------|------|------|
|
|
633
|
-
| `editVal` | `string` | 编辑值或公式(可读写) |
|
|
634
|
-
| `calcVal` | `any` | 计算值(只读) |
|
|
635
|
-
| `showVal` | `string` | 显示值(只读) |
|
|
636
|
-
| `type` | `string` | 单元格类型(只读):`string/number/date/time/dateTime/boolean/error` |
|
|
637
|
-
| `isFormula` | `boolean` | 是否为公式(只读) |
|
|
638
|
-
| `isMerged` | `boolean` | 是否为合并单元格 |
|
|
639
|
-
| `master` | `CellNum \| null` | 如果是合并单元格,指向主单元格 |
|
|
640
|
-
|
|
641
|
-
## 样式属性
|
|
642
|
-
|
|
643
|
-
| 属性 | 类型 | 说明 |
|
|
644
|
-
|------|------|------|
|
|
645
|
-
| `font` | `object` | 字体样式 |
|
|
646
|
-
| `alignment` | `object` | 对齐方式 |
|
|
647
|
-
| `border` | `object` | 边框样式 |
|
|
648
|
-
| `fill` | `object` | 填充样式 |
|
|
649
|
-
| `numFmt` | `string` | 数字格式 |
|
|
650
|
-
|
|
651
|
-
## 功能属性
|
|
652
|
-
|
|
653
|
-
| 属性 | 类型 | 说明 |
|
|
654
|
-
|------|------|------|
|
|
655
|
-
| `hyperlink` | `object` | 超链接配置 |
|
|
656
|
-
| `dataValidation` | `object` | 数据验证规则 |
|
|
657
|
-
| `validData` | `boolean` | 数据验证结果(只读) |
|
|
658
|
-
|
|
659
|
-
## font 对象结构
|
|
660
|
-
|
|
661
|
-
字体样式配置对象。
|
|
662
|
-
|
|
663
|
-
```typescript
|
|
664
|
-
{
|
|
665
|
-
name?: string; // 字体名称,如 'Arial', '微软雅黑'
|
|
666
|
-
size?: number; // 字体大小,默认 11
|
|
667
|
-
bold?: boolean; // 是否粗体
|
|
668
|
-
italic?: boolean; // 是否斜体
|
|
669
|
-
underline?: string; // 下划线:'single' | 'double' | 'none'
|
|
670
|
-
strike?: boolean; // 是否删除线
|
|
671
|
-
color?: string; // 字体颜色,格式:'#RRGGBB'
|
|
672
|
-
vertAlign?: string; // 上下标:'superscript' | 'subscript'
|
|
673
|
-
outline?: boolean; // 是否轮廓(Mac专用)
|
|
674
|
-
charset?: number; // 字符集
|
|
675
|
-
}
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
**示例:**
|
|
679
|
-
|
|
680
|
-
```javascript
|
|
681
|
-
const cell = sheet.getCell(0, 0);
|
|
682
|
-
|
|
683
|
-
// 基础字体设置
|
|
684
|
-
cell.font = {
|
|
685
|
-
name: '微软雅黑',
|
|
686
|
-
size: 14,
|
|
687
|
-
bold: true
|
|
688
|
-
};
|
|
689
|
-
|
|
690
|
-
// 带颜色和下划线
|
|
691
|
-
cell.font = {
|
|
692
|
-
size: 12,
|
|
693
|
-
color: '#FF0000',
|
|
694
|
-
underline: 'single'
|
|
695
|
-
};
|
|
696
|
-
|
|
697
|
-
// 删除线
|
|
698
|
-
cell.font = { strike: true };
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
## alignment 对象结构
|
|
702
|
-
|
|
703
|
-
对齐方式配置对象。
|
|
704
|
-
|
|
705
|
-
```typescript
|
|
706
|
-
{
|
|
707
|
-
horizontal?: string; // 水平对齐:'left' | 'center' | 'right' | 'fill' | 'justify' | 'distributed'
|
|
708
|
-
vertical?: string; // 垂直对齐:'top' | 'middle' | 'bottom' | 'justify' | 'distributed'
|
|
709
|
-
wrapText?: boolean; // 是否自动换行
|
|
710
|
-
indent?: number; // 缩进级别(仅 horizontal='left'/'right' 时有效)
|
|
711
|
-
}
|
|
712
|
-
```
|
|
713
|
-
|
|
714
|
-
**示例:**
|
|
715
|
-
|
|
716
|
-
```javascript
|
|
717
|
-
// 水平居中,垂直居中
|
|
718
|
-
cell.alignment = {
|
|
719
|
-
horizontal: 'center',
|
|
720
|
-
vertical: 'middle'
|
|
721
|
-
};
|
|
722
|
-
|
|
723
|
-
// 自动换行
|
|
724
|
-
cell.alignment = { wrapText: true };
|
|
725
|
-
|
|
726
|
-
// 左对齐并缩进2级
|
|
727
|
-
cell.alignment = {
|
|
728
|
-
horizontal: 'left',
|
|
729
|
-
indent: 2
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
// 两端对齐
|
|
733
|
-
cell.alignment = {
|
|
734
|
-
horizontal: 'justify',
|
|
735
|
-
vertical: 'top'
|
|
736
|
-
};
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
## border 对象结构
|
|
740
|
-
|
|
741
|
-
边框样式配置对象,可单独设置四个方向的边框。
|
|
742
|
-
|
|
743
|
-
```typescript
|
|
744
|
-
{
|
|
745
|
-
top?: {
|
|
746
|
-
style: string; // 边框样式
|
|
747
|
-
color?: string; // 边框颜色,格式:'#RRGGBB',默认 '#000000'
|
|
748
|
-
};
|
|
749
|
-
right?: { style: string; color?: string; };
|
|
750
|
-
bottom?: { style: string; color?: string; };
|
|
751
|
-
left?: { style: string; color?: string; };
|
|
752
|
-
diagonal?: { style: string; color?: string; };
|
|
753
|
-
}
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
**边框样式 (style):**
|
|
757
|
-
- `thin` - 细线(最常用)
|
|
758
|
-
- `medium` - 中等
|
|
759
|
-
- `thick` - 粗线
|
|
760
|
-
- `dotted` - 点线
|
|
761
|
-
- `dashed` - 虚线
|
|
762
|
-
- `double` - 双线
|
|
763
|
-
- `hair` - 极细线
|
|
764
|
-
- `dashDot` - 点划线
|
|
765
|
-
- `dashDotDot` - 双点划线
|
|
766
|
-
- `mediumDashed` - 中等虚线
|
|
767
|
-
- `mediumDashDot` - 中等点划线
|
|
768
|
-
- `mediumDashDotDot` - 中等双点划线
|
|
769
|
-
- `slantDashDot` - 斜点划线
|
|
770
|
-
|
|
771
|
-
**示例:**
|
|
772
|
-
|
|
773
|
-
```javascript
|
|
774
|
-
// 设置所有边框
|
|
775
|
-
cell.border = {
|
|
776
|
-
top: { style: 'thin' },
|
|
777
|
-
right: { style: 'thin' },
|
|
778
|
-
bottom: { style: 'thin' },
|
|
779
|
-
left: { style: 'thin' }
|
|
780
|
-
};
|
|
781
|
-
|
|
782
|
-
// 设置单边框
|
|
783
|
-
cell.border = {
|
|
784
|
-
bottom: { style: 'medium', color: '#FF0000' }
|
|
785
|
-
};
|
|
786
|
-
|
|
787
|
-
// 添加对角线
|
|
788
|
-
cell.border = {
|
|
789
|
-
diagonal: { style: 'thin', color: '#0000FF' }
|
|
790
|
-
};
|
|
791
|
-
|
|
792
|
-
// 清空边框
|
|
793
|
-
cell.border = {};
|
|
794
|
-
```
|
|
795
|
-
|
|
796
|
-
## fill 对象结构
|
|
797
|
-
|
|
798
|
-
填充样式配置对象,支持纯色填充和渐变填充。
|
|
799
|
-
|
|
800
|
-
### 纯色填充
|
|
801
|
-
|
|
802
|
-
```typescript
|
|
803
|
-
{
|
|
804
|
-
type: 'pattern'; // 填充类型
|
|
805
|
-
pattern: string; // 图案类型
|
|
806
|
-
fgColor?: string; // 前景色,格式:'#RRGGBB'
|
|
807
|
-
bgColor?: string; // 背景色,格式:'#RRGGBB'
|
|
808
|
-
}
|
|
809
|
-
```
|
|
810
|
-
|
|
811
|
-
**图案类型 (pattern):**
|
|
812
|
-
- `solid` - 纯色(最常用)
|
|
813
|
-
- `darkGray` - 深灰
|
|
814
|
-
- `mediumGray` - 中灰
|
|
815
|
-
- `lightGray` - 浅灰
|
|
816
|
-
- `gray125` - 12.5% 灰
|
|
817
|
-
- `gray0625` - 6.25% 灰
|
|
818
|
-
- `darkHorizontal` - 深色横线
|
|
819
|
-
- `darkVertical` - 深色竖线
|
|
820
|
-
- `darkDown` - 深色斜线(左上到右下)
|
|
821
|
-
- `darkUp` - 深色斜线(左下到右上)
|
|
822
|
-
- `darkGrid` - 深色网格
|
|
823
|
-
- `darkTrellis` - 深色斜网格
|
|
824
|
-
- `lightHorizontal` - 浅色横线
|
|
825
|
-
- `lightVertical` - 浅色竖线
|
|
826
|
-
- `lightDown` - 浅色斜线(左上到右下)
|
|
827
|
-
- `lightUp` - 浅色斜线(左下到右上)
|
|
828
|
-
- `lightGrid` - 浅色网格
|
|
829
|
-
- `lightTrellis` - 浅色斜网格
|
|
830
|
-
|
|
831
|
-
### 渐变填充
|
|
832
|
-
|
|
833
|
-
```typescript
|
|
834
|
-
{
|
|
835
|
-
type: 'gradient'; // 填充类型
|
|
836
|
-
gradientType?: string; // 渐变类型:'linear' | 'path'
|
|
837
|
-
degree?: number; // 线性渐变角度(0-360)
|
|
838
|
-
left?: number; // 左侧偏移(0-1)
|
|
839
|
-
right?: number; // 右侧偏移(0-1)
|
|
840
|
-
top?: number; // 顶部偏移(0-1)
|
|
841
|
-
bottom?: number; // 底部偏移(0-1)
|
|
842
|
-
stops: Array<{ // 渐变色停止点
|
|
843
|
-
position: number; // 位置(0-1)
|
|
844
|
-
color: string; // 颜色,格式:'#RRGGBB'
|
|
845
|
-
}>;
|
|
846
|
-
}
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
**示例:**
|
|
850
|
-
|
|
851
|
-
```javascript
|
|
852
|
-
// 纯色背景(最常用)
|
|
853
|
-
cell.fill = {
|
|
854
|
-
type: 'pattern',
|
|
855
|
-
pattern: 'solid',
|
|
856
|
-
fgColor: '#FFFF00'
|
|
857
|
-
};
|
|
858
|
-
|
|
859
|
-
// 图案填充
|
|
860
|
-
cell.fill = {
|
|
861
|
-
type: 'pattern',
|
|
862
|
-
pattern: 'lightGrid',
|
|
863
|
-
fgColor: '#FF0000',
|
|
864
|
-
bgColor: '#FFFFFF'
|
|
865
|
-
};
|
|
866
|
-
|
|
867
|
-
// 线性渐变(从红到蓝)
|
|
868
|
-
cell.fill = {
|
|
869
|
-
type: 'gradient',
|
|
870
|
-
gradientType: 'linear',
|
|
871
|
-
degree: 90,
|
|
872
|
-
stops: [
|
|
873
|
-
{ position: 0, color: '#FF0000' },
|
|
874
|
-
{ position: 1, color: '#0000FF' }
|
|
875
|
-
]
|
|
876
|
-
};
|
|
877
|
-
|
|
878
|
-
// 路径渐变
|
|
879
|
-
cell.fill = {
|
|
880
|
-
type: 'gradient',
|
|
881
|
-
gradientType: 'path',
|
|
882
|
-
left: 0.5,
|
|
883
|
-
right: 0.5,
|
|
884
|
-
top: 0.5,
|
|
885
|
-
bottom: 0.5,
|
|
886
|
-
stops: [
|
|
887
|
-
{ position: 0, color: '#FFFFFF' },
|
|
888
|
-
{ position: 1, color: '#000000' }
|
|
889
|
-
]
|
|
890
|
-
};
|
|
891
|
-
|
|
892
|
-
// 清空填充
|
|
893
|
-
cell.fill = {};
|
|
894
|
-
```
|
|
895
|
-
|
|
896
|
-
## numFmt 数字格式
|
|
897
|
-
|
|
898
|
-
数字格式字符串,用于控制数值、日期、时间的显示格式。
|
|
899
|
-
|
|
900
|
-
### 常用格式
|
|
901
|
-
|
|
902
|
-
| 格式字符串 | 说明 | 示例 |
|
|
903
|
-
|-----------|------|------|
|
|
904
|
-
| `0` | 整数 | 1234 |
|
|
905
|
-
| `0.00` | 保留2位小数 | 1234.50 |
|
|
906
|
-
| `#,##0` | 千分位分隔符 | 1,234 |
|
|
907
|
-
| `#,##0.00` | 千分位+2位小数 | 1,234.50 |
|
|
908
|
-
| `0%` | 百分比 | 50% |
|
|
909
|
-
| `0.00%` | 百分比+2位小数 | 50.25% |
|
|
910
|
-
| `0.00E+00` | 科学计数法 | 1.23E+03 |
|
|
911
|
-
| `# ?/?` | 分数 | 1 1/4 |
|
|
912
|
-
| `¥#,##0.00` | 货币格式 | ¥1,234.50 |
|
|
913
|
-
| `yyyy/m/d` | 日期(年/月/日) | 2024/1/15 |
|
|
914
|
-
| `m/d/yyyy` | 日期(月/日/年) | 1/15/2024 |
|
|
915
|
-
| `yyyy-mm-dd` | 日期(年-月-日) | 2024-01-15 |
|
|
916
|
-
| `h:mm` | 时间(时:分) | 14:30 |
|
|
917
|
-
| `h:mm:ss` | 时间(时:分:秒) | 14:30:25 |
|
|
918
|
-
| `yyyy/m/d h:mm:ss` | 日期时间 | 2024/1/15 14:30:25 |
|
|
919
|
-
| `[Red]0.00` | 负数显示红色 | -12.34 |
|
|
920
|
-
| `0.00;[Red]-0.00` | 正数黑色,负数红色 | 12.34 或 -12.34 |
|
|
921
|
-
|
|
922
|
-
### 格式代码说明
|
|
923
|
-
|
|
924
|
-
**数字部分:**
|
|
925
|
-
- `0` - 占位符,不足补0
|
|
926
|
-
- `#` - 占位符,不足不显示
|
|
927
|
-
- `,` - 千分位分隔符
|
|
928
|
-
- `.` - 小数点
|
|
929
|
-
- `%` - 百分比
|
|
930
|
-
- `?` - 空格占位(用于对齐)
|
|
931
|
-
|
|
932
|
-
**日期部分:**
|
|
933
|
-
- `yyyy` - 四位年份(2024)
|
|
934
|
-
- `yy` - 两位年份(24)
|
|
935
|
-
- `m` - 月份(1-12)
|
|
936
|
-
- `mm` - 月份补0(01-12)
|
|
937
|
-
- `mmm` - 月份简写(Jan-Dec)
|
|
938
|
-
- `mmmm` - 月份全称(January-December)
|
|
939
|
-
- `d` - 日期(1-31)
|
|
940
|
-
- `dd` - 日期补0(01-31)
|
|
941
|
-
- `ddd` - 星期简写(Sun-Sat)
|
|
942
|
-
- `dddd` - 星期全称(Sunday-Saturday)
|
|
943
|
-
|
|
944
|
-
**时间部分:**
|
|
945
|
-
- `h` - 小时(0-23)
|
|
946
|
-
- `hh` - 小时补0(00-23)
|
|
947
|
-
- `m` - 分钟(0-59)
|
|
948
|
-
- `mm` - 分钟补0(00-59)
|
|
949
|
-
- `s` - 秒(0-59)
|
|
950
|
-
- `ss` - 秒补0(00-59)
|
|
951
|
-
- `AM/PM` - 12小时制
|
|
952
|
-
|
|
953
|
-
**示例:**
|
|
954
|
-
|
|
955
|
-
```javascript
|
|
956
|
-
const cell = sheet.getCell(0, 0);
|
|
957
|
-
|
|
958
|
-
// 数字格式
|
|
959
|
-
cell.editVal = 1234.5;
|
|
960
|
-
cell.numFmt = '#,##0.00'; // 显示:1,234.50
|
|
961
|
-
|
|
962
|
-
// 百分比
|
|
963
|
-
cell.editVal = 0.258;
|
|
964
|
-
cell.numFmt = '0.00%'; // 显示:25.80%
|
|
965
|
-
|
|
966
|
-
// 货币
|
|
967
|
-
cell.editVal = 1234.5;
|
|
968
|
-
cell.numFmt = '¥#,##0.00'; // 显示:¥1,234.50
|
|
969
|
-
|
|
970
|
-
// 日期
|
|
971
|
-
cell.editVal = '2024/1/15';
|
|
972
|
-
cell.numFmt = 'yyyy-mm-dd'; // 显示:2024-01-15
|
|
973
|
-
|
|
974
|
-
// 时间
|
|
975
|
-
cell.editVal = '14:30:25';
|
|
976
|
-
cell.numFmt = 'h:mm:ss'; // 显示:14:30:25
|
|
977
|
-
|
|
978
|
-
// 自定义格式(正数、负数、零、文本)
|
|
979
|
-
cell.numFmt = '0.00;[Red]-0.00;"零";@';
|
|
980
|
-
|
|
981
|
-
// 清除格式(恢复常规)
|
|
982
|
-
cell.numFmt = null;
|
|
983
|
-
```
|
|
984
|
-
|
|
985
|
-
## hyperlink 对象结构
|
|
986
|
-
|
|
987
|
-
超链接配置对象。
|
|
988
|
-
|
|
989
|
-
```typescript
|
|
990
|
-
{
|
|
991
|
-
target?: string; // 外部链接 URL(如 'https://example.com')
|
|
992
|
-
location?: string; // 内部链接位置(如 'Sheet2!A1')
|
|
993
|
-
tooltip?: string; // 鼠标悬停提示文本
|
|
994
|
-
}
|
|
995
|
-
```
|
|
996
|
-
|
|
997
|
-
**示例:**
|
|
998
|
-
|
|
999
|
-
```javascript
|
|
1000
|
-
// 外部链接
|
|
1001
|
-
cell.editVal = '访问官网';
|
|
1002
|
-
cell.hyperlink = {
|
|
1003
|
-
target: 'https://www.sheetnext.com',
|
|
1004
|
-
tooltip: '点击访问 SheetNext 官网'
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
|
-
// 内部链接(跳转到其他工作表)
|
|
1008
|
-
cell.editVal = '查看数据';
|
|
1009
|
-
cell.hyperlink = {
|
|
1010
|
-
location: 'Sheet2!A1',
|
|
1011
|
-
tooltip: '跳转到 Sheet2 的 A1 单元格'
|
|
1012
|
-
};
|
|
1013
|
-
|
|
1014
|
-
// 移除超链接
|
|
1015
|
-
cell.hyperlink = {};
|
|
1016
|
-
```
|
|
1017
|
-
|
|
1018
|
-
## dataValidation 对象结构
|
|
1019
|
-
|
|
1020
|
-
数据验证规则配置对象,用于限制单元格可输入的内容。
|
|
1021
|
-
|
|
1022
|
-
```typescript
|
|
1023
|
-
{
|
|
1024
|
-
type: string; // 验证类型:'list' | 'whole' | 'decimal' | 'date' | 'time' | 'textLength' | 'custom'
|
|
1025
|
-
operator?: string; // 操作符:'between' | 'notBetween' | 'equal' | 'notEqual' | 'greaterThan' | 'lessThan' | 'greaterThanOrEqual' | 'lessThanOrEqual'
|
|
1026
|
-
allowBlank?: boolean; // 是否允许空白,默认 false
|
|
1027
|
-
formula1: any; // 公式1(type='list' 时为数组)
|
|
1028
|
-
formula2?: any; // 公式2(范围验证时使用)
|
|
1029
|
-
showInputMessage?: boolean; // 是否显示输入提示,默认 true
|
|
1030
|
-
promptTitle?: string; // 输入提示标题
|
|
1031
|
-
prompt?: string; // 输入提示内容
|
|
1032
|
-
showErrorMessage?: boolean; // 是否显示错误提示,默认 true
|
|
1033
|
-
errorTitle?: string; // 错误提示标题
|
|
1034
|
-
error?: string; // 错误提示内容
|
|
1035
|
-
errorStyle?: string; // 错误样式:'stop' | 'warning' | 'information'
|
|
1036
|
-
showDropDown?: boolean; // 是否显示下拉框(type='list' 时),默认 true
|
|
1037
|
-
}
|
|
1038
|
-
```
|
|
1039
|
-
|
|
1040
|
-
**示例:**
|
|
1041
|
-
|
|
1042
|
-
```javascript
|
|
1043
|
-
// 下拉列表
|
|
1044
|
-
cell.dataValidation = {
|
|
1045
|
-
type: 'list',
|
|
1046
|
-
formula1: ['优秀', '良好', '及格', '不及格'],
|
|
1047
|
-
showDropDown: true,
|
|
1048
|
-
promptTitle: '请选择',
|
|
1049
|
-
prompt: '请从列表中选择一个等级'
|
|
1050
|
-
};
|
|
1051
|
-
|
|
1052
|
-
// 整数范围(1-100)
|
|
1053
|
-
cell.dataValidation = {
|
|
1054
|
-
type: 'whole',
|
|
1055
|
-
operator: 'between',
|
|
1056
|
-
formula1: 1,
|
|
1057
|
-
formula2: 100,
|
|
1058
|
-
errorTitle: '输入错误',
|
|
1059
|
-
error: '请输入 1 到 100 之间的整数'
|
|
1060
|
-
};
|
|
1061
|
-
|
|
1062
|
-
// 小数验证(大于0)
|
|
1063
|
-
cell.dataValidation = {
|
|
1064
|
-
type: 'decimal',
|
|
1065
|
-
operator: 'greaterThan',
|
|
1066
|
-
formula1: 0,
|
|
1067
|
-
errorTitle: '输入错误',
|
|
1068
|
-
error: '请输入大于 0 的数字'
|
|
1069
|
-
};
|
|
1070
|
-
|
|
1071
|
-
// 日期范围
|
|
1072
|
-
cell.dataValidation = {
|
|
1073
|
-
type: 'date',
|
|
1074
|
-
operator: 'between',
|
|
1075
|
-
formula1: '2024/1/1',
|
|
1076
|
-
formula2: '2024/12/31',
|
|
1077
|
-
errorTitle: '日期错误',
|
|
1078
|
-
error: '请输入 2024 年的日期'
|
|
1079
|
-
};
|
|
1080
|
-
|
|
1081
|
-
// 时间验证
|
|
1082
|
-
cell.dataValidation = {
|
|
1083
|
-
type: 'time',
|
|
1084
|
-
operator: 'between',
|
|
1085
|
-
formula1: '9:00:00',
|
|
1086
|
-
formula2: '18:00:00',
|
|
1087
|
-
errorTitle: '时间错误',
|
|
1088
|
-
error: '请输入工作时间(9:00-18:00)'
|
|
1089
|
-
};
|
|
1090
|
-
|
|
1091
|
-
// 文本长度限制
|
|
1092
|
-
cell.dataValidation = {
|
|
1093
|
-
type: 'textLength',
|
|
1094
|
-
operator: 'lessThanOrEqual',
|
|
1095
|
-
formula1: 20,
|
|
1096
|
-
errorTitle: '文本过长',
|
|
1097
|
-
error: '最多输入 20 个字符'
|
|
1098
|
-
};
|
|
1099
|
-
|
|
1100
|
-
// 移除验证
|
|
1101
|
-
cell.dataValidation = {};
|
|
1102
|
-
```
|
|
1103
|
-
|
|
1104
|
-
# 行级别
|
|
1105
|
-
|
|
1106
|
-
Row 类代表行,提供行级别的属性和操作。
|
|
1107
|
-
|
|
1108
|
-
## 属性
|
|
1109
|
-
|
|
1110
|
-
| 属性 | 类型 | 说明 |
|
|
1111
|
-
|------|------|------|
|
|
1112
|
-
| `cells` | `Cell[]` | 该行的所有单元格 |
|
|
1113
|
-
| `height` | `number` | 行高(像素) |
|
|
1114
|
-
| `hidden` | `boolean` | 是否隐藏 |
|
|
1115
|
-
| `rIndex` | `number` | 行索引 |
|
|
1116
|
-
| `numFmt` | `string` | 数字格式 |
|
|
1117
|
-
| `font` | `object` | 字体样式 |
|
|
1118
|
-
| `alignment` | `object` | 对齐方式 |
|
|
1119
|
-
| `border` | `object` | 边框样式 |
|
|
1120
|
-
| `fill` | `object` | 填充样式 |
|
|
1121
|
-
|
|
1122
|
-
## 方法
|
|
1123
|
-
|
|
1124
|
-
### 获取该行的单元格
|
|
1125
|
-
`getCell(c: number): Cell`
|
|
1126
|
-
|
|
1127
|
-
获取该行的指定列单元格。
|
|
1128
|
-
|
|
1129
|
-
```javascript
|
|
1130
|
-
const row = sheet.getRow(0); // 获取第一行
|
|
1131
|
-
const cell = row.getCell(0); // 获取该行第一列的单元格
|
|
1132
|
-
```
|
|
1133
|
-
|
|
1134
|
-
## 示例
|
|
1135
|
-
|
|
1136
|
-
```javascript
|
|
1137
|
-
const row = sheet.getRow(5);
|
|
1138
|
-
|
|
1139
|
-
// 设置行高
|
|
1140
|
-
row.height = 30;
|
|
1141
|
-
|
|
1142
|
-
// 隐藏行
|
|
1143
|
-
row.hidden = true;
|
|
1144
|
-
|
|
1145
|
-
// 设置行样式
|
|
1146
|
-
row.fill = { fgColor: '#F0F0F0' };
|
|
1147
|
-
row.font = { bold: true };
|
|
1148
|
-
```
|
|
1149
|
-
|
|
1150
|
-
# 列级别
|
|
1151
|
-
|
|
1152
|
-
Col 类代表列,提供列级别的属性和操作。
|
|
1153
|
-
|
|
1154
|
-
## 属性
|
|
1155
|
-
|
|
1156
|
-
| 属性 | 类型 | 说明 |
|
|
1157
|
-
|------|------|------|
|
|
1158
|
-
| `cells` | `Cell[]` | 该列的所有单元格 |
|
|
1159
|
-
| `hidden` | `boolean` | 是否隐藏 |
|
|
1160
|
-
| `width` | `number` | 列宽(像素) |
|
|
1161
|
-
| `cIndex` | `number` | 列索引 |
|
|
1162
|
-
| `numFmt` | `string` | 数字格式 |
|
|
1163
|
-
| `font` | `object` | 字体样式 |
|
|
1164
|
-
| `alignment` | `object` | 对齐方式 |
|
|
1165
|
-
| `border` | `object` | 边框样式 |
|
|
1166
|
-
| `fill` | `object` | 填充样式 |
|
|
1167
|
-
|
|
1168
|
-
## 方法
|
|
1169
|
-
|
|
1170
|
-
### 获取该列的单元格
|
|
1171
|
-
`getCell(r: number): Cell`
|
|
1172
|
-
|
|
1173
|
-
获取该列的指定行单元格。
|
|
1174
|
-
|
|
1175
|
-
```javascript
|
|
1176
|
-
const col = sheet.getCol(0); // 获取第一列(A列)
|
|
1177
|
-
const cell = col.getCell(0); // 获取该列第一行的单元格
|
|
1178
|
-
```
|
|
1179
|
-
|
|
1180
|
-
## 示例
|
|
1181
|
-
|
|
1182
|
-
```javascript
|
|
1183
|
-
const col = sheet.getCol(2); // 获取 C 列
|
|
1184
|
-
|
|
1185
|
-
// 设置列宽
|
|
1186
|
-
col.width = 120;
|
|
1187
|
-
|
|
1188
|
-
// 隐藏列
|
|
1189
|
-
col.hidden = true;
|
|
1190
|
-
|
|
1191
|
-
// 设置列样式
|
|
1192
|
-
col.fill = { fgColor: '#E0E0E0' };
|
|
1193
|
-
col.alignment = { horizontal: 'center' };
|
|
1194
|
-
```
|
|
1195
|
-
|
|
1196
|
-
# 图形级别
|
|
1197
|
-
|
|
1198
|
-
Drawing 类代表图表、图片或形状(包括连接线)等图形对象。
|
|
1199
|
-
|
|
1200
|
-
## 属性
|
|
1201
|
-
|
|
1202
|
-
| 属性 | 类型 | 说明 |
|
|
1203
|
-
|------|------|------|
|
|
1204
|
-
| `id` | `string` | 唯一标识符(只读) |
|
|
1205
|
-
| `type` | `string` | 类型:`chart`、`image`、`shape` |
|
|
1206
|
-
| `shapeType` | `string` | 形状类型(type=shape 时):`rect`、`line`、`straightConnector1` 等 |
|
|
1207
|
-
| `isConnector` | `boolean` | 是否为连接线(只读) |
|
|
1208
|
-
| `shapeStyle` | `object` | 形状样式(type=shape 时):`{fill, stroke, strokeWidth, startArrow, endArrow}` |
|
|
1209
|
-
| `shapeText` | `string` | 形状内的文本(type=shape 时) |
|
|
1210
|
-
| `startCell` | `CellNum` | 起始单元格位置 |
|
|
1211
|
-
| `offsetX` | `number` | X 轴偏移(默认 5) |
|
|
1212
|
-
| `offsetY` | `number` | Y 轴偏移(默认 5) |
|
|
1213
|
-
| `width` | `number` | 宽度(默认:chart=460, shape=100, image=自动) |
|
|
1214
|
-
| `height` | `number` | 高度(默认:chart=260, shape=100, image=自动) |
|
|
1215
|
-
| `option` | `object` | 图表配置(type=chart 时,与 ECharts 配置相同) |
|
|
1216
|
-
| `imageBase64` | `string` | Base64 图片数据(type=image 时) |
|
|
1217
|
-
| `area` | `object` | 图形对象的实际覆盖区域(只读):`{s:{r,c,offsetX,offsetY}, e:{r,c,offsetX,offsetY}}` |
|
|
1218
|
-
| `anchorType` | `string` | 锚点类型:`twoCell`(随单元格移动+缩放)、`oneCell`(仅随移动)、`absolute`(固定) |
|
|
1219
|
-
| `updRender` | `boolean` | 是否更新渲染 |
|
|
1220
|
-
|
|
1221
|
-
## 方法
|
|
1222
|
-
|
|
1223
|
-
### 更新图层顺序
|
|
1224
|
-
`updIndex(direction: string): void`
|
|
1225
|
-
|
|
1226
|
-
更新图层顺序。
|
|
1227
|
-
|
|
1228
|
-
参数值:`"up"` | `"down"` | `"top"` | `"bottom"`
|
|
1229
|
-
|
|
1230
|
-
```javascript
|
|
1231
|
-
drawing.updIndex("top"); // 移到最上层
|
|
1232
|
-
```
|
|
1233
|
-
|
|
1234
|
-
# 布局管理
|
|
1235
|
-
|
|
1236
|
-
Layout 类管理编辑器的界面布局,包括菜单栏、工具栏、公式栏、Sheet 标签栏和 AI 聊天面板等界面元素的显示与隐藏。
|
|
1237
|
-
|
|
1238
|
-
**说明:** Layout 类由 SheetNext 自动创建,通过 `SN.Layout` 访问。菜单栏相关配置请参见 [快速开始 - 初始化配置](#快速开始)。
|
|
1239
|
-
|
|
1240
|
-
## 属性
|
|
1241
|
-
|
|
1242
|
-
| 属性 | 类型 | 说明 |
|
|
1243
|
-
|------|------|------|
|
|
1244
|
-
| `showMenuBar` | `boolean` | 是否显示菜单栏(可读写) |
|
|
1245
|
-
| `showToolbar` | `boolean` | 是否显示工具栏(可读写) |
|
|
1246
|
-
| `showFormulaBar` | `boolean` | 是否显示公式栏(可读写) |
|
|
1247
|
-
| `showSheetTabBar` | `boolean` | 是否显示 Sheet 标签栏(可读写) |
|
|
1248
|
-
| `showAIChat` | `boolean` | 是否显示 AI 聊天面板(可读写) |
|
|
1249
|
-
| `showAIChatWindow` | `boolean` | 是否显示 AI 聊天小窗口模式(可读写) |
|
|
1250
|
-
| `isSmallWindow` | `boolean` | 当前是否为小窗口模式(宽度 < 900px)(只读) |
|
|
1251
|
-
| `menuConfig` | `object` | 菜单配置对象(只读) |
|
|
1252
|
-
|
|
1253
|
-
# 工具方法
|
|
1254
|
-
|
|
1255
|
-
Utils 类提供坐标转换等实用方法。
|
|
1256
|
-
|
|
1257
|
-
## 方法
|
|
1258
|
-
|
|
1259
|
-
### 数字转字母列标
|
|
1260
|
-
`numToChar(num: number): string`
|
|
1261
|
-
|
|
1262
|
-
数字转字母列标。
|
|
1263
|
-
|
|
1264
|
-
```javascript
|
|
1265
|
-
SN.Utils.numToChar(0); // "A"
|
|
1266
|
-
SN.Utils.numToChar(25); // "Z"
|
|
1267
|
-
SN.Utils.numToChar(26); // "AA"
|
|
1268
|
-
```
|
|
1269
|
-
|
|
1270
|
-
### 字母列标转数字
|
|
1271
|
-
`charToNum(char: string): number`
|
|
1272
|
-
|
|
1273
|
-
字母列标转数字。
|
|
1274
|
-
|
|
1275
|
-
```javascript
|
|
1276
|
-
SN.Utils.charToNum("A"); // 0
|
|
1277
|
-
SN.Utils.charToNum("Z"); // 25
|
|
1278
|
-
SN.Utils.charToNum("AA"); // 26
|
|
1279
|
-
```
|
|
1280
|
-
|
|
1281
|
-
### 范围对象转字符串
|
|
1282
|
-
`rangeNumToStr(rangeNum: RangeNum): string`
|
|
1283
|
-
|
|
1284
|
-
范围对象转字符串。
|
|
1285
|
-
|
|
1286
|
-
```javascript
|
|
1287
|
-
SN.Utils.rangeNumToStr({s:{r:0,c:0}, e:{r:2,c:2}}); // "A1:C3"
|
|
1288
|
-
```
|
|
1289
|
-
|
|
1290
|
-
### 单元格字符串转数字对象
|
|
1291
|
-
`cellStrToNum(cellStr: string): CellNum`
|
|
1292
|
-
|
|
1293
|
-
单元格字符串转数字对象。
|
|
1294
|
-
|
|
1295
|
-
```javascript
|
|
1296
|
-
SN.Utils.cellStrToNum("A1"); // {r:0, c:0}
|
|
1297
|
-
```
|
|
1298
|
-
|
|
1299
|
-
### 单元格数字对象转字符串
|
|
1300
|
-
`cellNumToStr(cellNum: CellNum): string`
|
|
1301
|
-
|
|
1302
|
-
单元格数字对象转字符串。
|
|
1303
|
-
|
|
1304
|
-
```javascript
|
|
1305
|
-
SN.Utils.cellNumToStr({r:0, c:0}); // "A1"
|
|
1306
|
-
```
|
|
1307
|
-
|
|
1308
|
-
### 显示消息提示
|
|
1309
|
-
`msg(message: string): void`
|
|
1310
|
-
|
|
1311
|
-
显示临时消息提示(3秒后自动消失)。
|
|
1312
|
-
|
|
1313
|
-
```javascript
|
|
1314
|
-
SN.Utils.msg("操作成功!");
|
|
1315
|
-
```
|
|
1316
|
-
|
|
1317
|
-
### 显示弹窗
|
|
1318
|
-
`modal(options: object): Promise`
|
|
1319
|
-
|
|
1320
|
-
显示模态弹窗,返回 Promise(确定时 resolve,取消时 reject)。
|
|
1321
|
-
|
|
1322
|
-
```javascript
|
|
1323
|
-
// 基础用法
|
|
1324
|
-
SN.Utils.modal({
|
|
1325
|
-
title: '提示',
|
|
1326
|
-
content: '确定要删除吗?',
|
|
1327
|
-
confirmText: '确定',
|
|
1328
|
-
cancelText: '取消'
|
|
1329
|
-
}).then(() => {
|
|
1330
|
-
console.log('用户点击了确定');
|
|
1331
|
-
}).catch(() => {
|
|
1332
|
-
console.log('用户取消了');
|
|
1333
|
-
});
|
|
1334
|
-
```
|
|
1335
|
-
|
|
1336
|
-
# 历史记录
|
|
1337
|
-
|
|
1338
|
-
管理操作历史,支持撤销和重做功能。
|
|
1339
|
-
|
|
1340
|
-
## 方法
|
|
1341
|
-
|
|
1342
|
-
### 撤销操作
|
|
1343
|
-
`undo(): void`
|
|
1344
|
-
|
|
1345
|
-
撤销上一步操作。
|
|
1346
|
-
|
|
1347
|
-
```javascript
|
|
1348
|
-
SN.UndoRedo.undo();
|
|
1349
|
-
```
|
|
1350
|
-
|
|
1351
|
-
### 重做操作
|
|
1352
|
-
`redo(): void`
|
|
1353
|
-
|
|
1354
|
-
重做上一步操作。
|
|
1355
|
-
|
|
1356
|
-
```javascript
|
|
1357
|
-
SN.UndoRedo.redo();
|
|
1358
|
-
```
|
|
1359
|
-
|
|
1360
|
-
## 示例
|
|
1361
|
-
|
|
1362
|
-
```javascript
|
|
1363
|
-
// 执行一些操作
|
|
1364
|
-
sheet.getCell(0, 0).editVal = "Hello";
|
|
1365
|
-
sheet.mergeCells("A1:B1");
|
|
1366
|
-
|
|
1367
|
-
// 撤销合并操作
|
|
1368
|
-
SN.UndoRedo.undo();
|
|
1369
|
-
|
|
1370
|
-
// 撤销编辑操作
|
|
1371
|
-
SN.UndoRedo.undo();
|
|
1372
|
-
|
|
1373
|
-
// 重做编辑操作
|
|
1374
|
-
SN.UndoRedo.redo();
|
|
1375
|
-
```
|
|
1376
|
-
|
|
1377
|
-
**注意**:
|
|
1378
|
-
- 撤销/重做会自动记录大部分用户操作
|
|
1379
|
-
- 历史记录栈有大小限制,过旧的操作会被清除
|
|
1380
|
-
|
|
1381
|
-
# AI 功能
|
|
1382
|
-
|
|
1383
|
-
通过 `SN.AI` 访问 AI 辅助功能
|
|
1384
|
-
|
|
1385
|
-
## 方法
|
|
1386
|
-
|
|
1387
|
-
### 监听 AI 请求状态
|
|
1388
|
-
`listenRequestStatus(callback: Function): Function`
|
|
1389
|
-
|
|
1390
|
-
监听 AI 请求的 HTTP 状态码(如 200、401、500 等),返回取消监听的函数。
|
|
1391
|
-
|
|
1392
|
-
```javascript
|
|
1393
|
-
// 添加监听器
|
|
1394
|
-
const unsubscribe = SN.AI.listenRequestStatus((httpStatus) => {
|
|
1395
|
-
if (httpStatus === 200) {
|
|
1396
|
-
console.log('AI 请求成功');
|
|
1397
|
-
} else if (httpStatus === 401) {
|
|
1398
|
-
console.log('未授权,请检查 AI_TOKEN');
|
|
1399
|
-
}
|
|
1400
|
-
});
|
|
1401
|
-
|
|
1402
|
-
// 取消监听
|
|
1403
|
-
unsubscribe();
|
|
1404
|
-
```
|
|
1405
|
-
|
|
1406
|
-
# AI 中转配置
|
|
1407
|
-
|
|
1408
|
-
写一个接口将前端传入的message消息分发给你想对接的大模型,然后在前端配置好接口地址即可开始工作!
|
|
1409
|
-
|
|
1410
|
-
## 功能说明
|
|
1411
|
-
|
|
1412
|
-
AI 服务中转层是连接 SheetNext 前端与大模型 API 的桥梁,主要负责以下核心功能:
|
|
1413
|
-
|
|
1414
|
-
**核心功能:**
|
|
1415
|
-
|
|
1416
|
-
1. **消息格式转换** - 将 SheetNext 提供的通用消息结构转换为目标大模型(如 Claude、GPT 等)所需的标准格式
|
|
1417
|
-
2. **流式数据处理** - 实现 AI 响应的流式接收与转发,提升用户交互体验
|
|
1418
|
-
3. **安全隔离** - 在服务端隐藏真实的 API Key,避免密钥泄露风险
|
|
1419
|
-
4. **使用统计** - 企业可在中转层统计 Token 消耗、请求次数等关键数据
|
|
1420
|
-
|
|
1421
|
-
## 核心架构
|
|
1422
|
-
|
|
1423
|
-
```
|
|
1424
|
-
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
|
1425
|
-
│ │ │ │ │ │
|
|
1426
|
-
│ SheetNext │────────▶│ 中转服务器 │──────▶│各种大模型API │
|
|
1427
|
-
│ 前端 │ HTTP │ (您的服务器) │ HTTPS │ (Claude等) │
|
|
1428
|
-
│ │◀────────│ │◀──────│ │
|
|
1429
|
-
└─────────────┘ SSE流 └──────────────┘ Stream └─────────────┘
|
|
1430
|
-
│
|
|
1431
|
-
▼
|
|
1432
|
-
┌──────────────┐
|
|
1433
|
-
│ 使用统计/日志 │
|
|
1434
|
-
└──────────────┘
|
|
1435
|
-
```
|
|
1436
|
-
|
|
1437
|
-
**工作流程:**
|
|
1438
|
-
|
|
1439
|
-
1. **前端请求** - SheetNext 发送包含 `messages` 数组的 POST 请求到中转服务器
|
|
1440
|
-
2. **格式转换** - 中转服务器将通用格式转换为目标大模型的专用格式
|
|
1441
|
-
3. **API 调用** - 使用服务端存储的 API Key 调用大模型 API
|
|
1442
|
-
4. **流式响应** - 接收大模型的流式响应,转换后通过 SSE (Server-Sent Events) 返回前端
|
|
1443
|
-
|
|
1444
|
-
## 完整示例
|
|
1445
|
-
|
|
1446
|
-
通用中转完整实现示例:
|
|
1447
|
-
|
|
1448
|
-
**安装依赖:**
|
|
1449
|
-
|
|
1450
|
-
```bash
|
|
1451
|
-
npm install @anthropic-ai/sdk openai
|
|
1452
|
-
```
|
|
1453
|
-
|
|
1454
|
-
**完整代码:**
|
|
1455
|
-
|
|
1456
|
-
```javascript
|
|
1457
|
-
/**
|
|
1458
|
-
* SheetNext AI & claude/openai 中转服务器示例 Node.js 版本
|
|
1459
|
-
* 2025.10.17 v1.0.0
|
|
1460
|
-
*/
|
|
1461
|
-
|
|
1462
|
-
const http = require('http');
|
|
1463
|
-
const Anthropic = require('@anthropic-ai/sdk');
|
|
1464
|
-
const OpenAI = require('openai');
|
|
1465
|
-
|
|
1466
|
-
// ======= 配置 =======
|
|
1467
|
-
const CONFIG = {
|
|
1468
|
-
model: 'claude-sonnet-4-5-20250929', // 设置模型名称,自动判断使用 claude 还是 openai
|
|
1469
|
-
claude: {
|
|
1470
|
-
apiKey: 'your-apiKey',
|
|
1471
|
-
baseURL: 'https://xx.xx.xx/'
|
|
1472
|
-
},
|
|
1473
|
-
openai: {
|
|
1474
|
-
apiKey: 'your-apiKey',
|
|
1475
|
-
baseURL: 'https://xx.xx.xx/v1'
|
|
1476
|
-
}
|
|
1477
|
-
};
|
|
1478
|
-
|
|
1479
|
-
const anthropic = new Anthropic({ apiKey: CONFIG.claude.apiKey, baseURL: CONFIG.claude.baseURL });
|
|
1480
|
-
const openai = new OpenAI({ apiKey: CONFIG.openai.apiKey, baseURL: CONFIG.openai.baseURL });
|
|
1481
|
-
|
|
1482
|
-
// ======= message默认是openai格式,claude请求时转为它适配格式 =======
|
|
1483
|
-
const convertToClaudeMessages = (messages) => {
|
|
1484
|
-
const system = [];
|
|
1485
|
-
const claudeMessages = [];
|
|
1486
|
-
let isFirstSystem = true;
|
|
1487
|
-
|
|
1488
|
-
// 转换内容部分的辅助函数
|
|
1489
|
-
const convertContent = (content) => {
|
|
1490
|
-
const parts = Array.isArray(content) ? content : [{ type: 'text', text: content }];
|
|
1491
|
-
return parts.map(part => {
|
|
1492
|
-
if (part.type === 'text') {
|
|
1493
|
-
return { type: 'text', text: part.text };
|
|
1494
|
-
}
|
|
1495
|
-
if (part.type === 'image_url') {
|
|
1496
|
-
const [, mediaType, base64Data] = part.image_url.url.match(/data:(.*?);base64,(.*)/) || [];
|
|
1497
|
-
if (base64Data) {
|
|
1498
|
-
return { type: 'image', source: { type: 'base64', media_type: mediaType || 'image/jpeg', data: base64Data } };
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
return null;
|
|
1502
|
-
}).filter(Boolean);
|
|
1503
|
-
};
|
|
1504
|
-
|
|
1505
|
-
for (const msg of messages) {
|
|
1506
|
-
if (msg.role === 'system') {
|
|
1507
|
-
if (isFirstSystem) {
|
|
1508
|
-
// 第一个 system:提取文本作为 system 参数(约定无图片)
|
|
1509
|
-
const text = typeof msg.content === 'string' ? msg.content : msg.content[0]?.text || '';
|
|
1510
|
-
if (text) system.push({ type: 'text', text });
|
|
1511
|
-
isFirstSystem = false;
|
|
1512
|
-
} else {
|
|
1513
|
-
// 其他 system:转为 user
|
|
1514
|
-
claudeMessages.push({ role: 'user', content: convertContent(msg.content) });
|
|
1515
|
-
}
|
|
1516
|
-
} else {
|
|
1517
|
-
// user/assistant 消息
|
|
1518
|
-
claudeMessages.push({ role: msg.role, content: convertContent(msg.content) });
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
return { system, messages: claudeMessages };
|
|
1523
|
-
};
|
|
1524
|
-
|
|
1525
|
-
// ======= Claude SDK =======
|
|
1526
|
-
async function callClaudeSDK(messages, model, onChunk) {
|
|
1527
|
-
const { system, messages: claudeMessages } = convertToClaudeMessages(messages);
|
|
1528
|
-
|
|
1529
|
-
// 打印请求结构(省略 base64 数据)
|
|
1530
|
-
const printableRequest = {
|
|
1531
|
-
system: system.map(s => s.type === 'image'
|
|
1532
|
-
? { type: 'image', source: { ...s.source, data: `[${s.source.data?.length || 0} chars]` } }
|
|
1533
|
-
: s
|
|
1534
|
-
),
|
|
1535
|
-
messages: claudeMessages.map(msg => ({
|
|
1536
|
-
role: msg.role,
|
|
1537
|
-
content: typeof msg.content === 'string' ? msg.content :
|
|
1538
|
-
msg.content.map(c => c.type === 'image'
|
|
1539
|
-
? { type: 'image', source: { ...c.source, data: `[${c.source.data?.length || 0} chars]` } }
|
|
1540
|
-
: c
|
|
1541
|
-
)
|
|
1542
|
-
}))
|
|
1543
|
-
};
|
|
1544
|
-
|
|
1545
|
-
const stream = await anthropic.messages.create({
|
|
1546
|
-
model: model,
|
|
1547
|
-
max_tokens: 8192,
|
|
1548
|
-
system,
|
|
1549
|
-
messages: claudeMessages,
|
|
1550
|
-
stream: true,
|
|
1551
|
-
thinking: { type: "enabled", budget_tokens: 2000 }
|
|
1552
|
-
});
|
|
1553
|
-
|
|
1554
|
-
for await (const event of stream) {
|
|
1555
|
-
if (event.type === 'content_block_delta') {
|
|
1556
|
-
const { delta } = event;
|
|
1557
|
-
if (delta?.type === 'thinking_delta' && delta.thinking) {
|
|
1558
|
-
onChunk({ type: 'think', delta: delta.thinking });
|
|
1559
|
-
} else if (delta?.type === 'text_delta') {
|
|
1560
|
-
onChunk({ type: 'text', delta: delta.text });
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
// ======= OpenAI SDK =======
|
|
1567
|
-
async function callOpenAISDK(messages, model, onChunk) {
|
|
1568
|
-
const stream = await openai.chat.completions.create({
|
|
1569
|
-
model: model,
|
|
1570
|
-
messages: messages, // 直接使用 OpenAI 格式的 messages
|
|
1571
|
-
stream: true
|
|
1572
|
-
});
|
|
1573
|
-
|
|
1574
|
-
for await (const chunk of stream) {
|
|
1575
|
-
const delta = chunk.choices[0]?.delta;
|
|
1576
|
-
if (delta?.content) {
|
|
1577
|
-
onChunk({ type: 'text', delta: delta.content });
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
// ======= HTTP 处理 =======
|
|
1583
|
-
async function handleChat(messages, res) {
|
|
1584
|
-
res.writeHead(200, {
|
|
1585
|
-
'Content-Type': 'text/event-stream',
|
|
1586
|
-
'Cache-Control': 'no-cache',
|
|
1587
|
-
'Connection': 'keep-alive',
|
|
1588
|
-
'Access-Control-Allow-Origin': '*'
|
|
1589
|
-
});
|
|
1590
|
-
|
|
1591
|
-
let ended = false;
|
|
1592
|
-
const write = (data) => !ended && !res.writableEnded && res.write(data);
|
|
1593
|
-
const onChunk = (chunk) => write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
1594
|
-
|
|
1595
|
-
try {
|
|
1596
|
-
// 根据模型名称自动判断使用哪个 provider
|
|
1597
|
-
const provider = CONFIG.model.toLowerCase().includes('claude') ? 'claude' : 'openai';
|
|
1598
|
-
if (provider === 'openai') {
|
|
1599
|
-
await callOpenAISDK(messages, CONFIG.model, onChunk);
|
|
1600
|
-
} else {
|
|
1601
|
-
await callClaudeSDK(messages, CONFIG.model, onChunk);
|
|
1602
|
-
}
|
|
1603
|
-
write(`data: [DONE]\n\n`);
|
|
1604
|
-
} catch (error) {
|
|
1605
|
-
write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
|
|
1606
|
-
} finally {
|
|
1607
|
-
ended = true;
|
|
1608
|
-
res.end();
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
// ======= HTTP 服务器 =======
|
|
1613
|
-
http.createServer(async (req, res) => {
|
|
1614
|
-
const corsHeaders = {
|
|
1615
|
-
'Access-Control-Allow-Origin': '*',
|
|
1616
|
-
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
1617
|
-
'Access-Control-Allow-Headers': 'Content-Type'
|
|
1618
|
-
};
|
|
1619
|
-
|
|
1620
|
-
if (req.method === 'OPTIONS') {
|
|
1621
|
-
res.writeHead(200, corsHeaders);
|
|
1622
|
-
return res.end();
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
if (req.url === '/sheetnextAI' && req.method === 'POST') {
|
|
1626
|
-
let body = '';
|
|
1627
|
-
req.on('data', chunk => body += chunk);
|
|
1628
|
-
req.on('end', async () => {
|
|
1629
|
-
try {
|
|
1630
|
-
const { messages } = JSON.parse(body);
|
|
1631
|
-
if (!Array.isArray(messages)) throw new Error('Invalid messages');
|
|
1632
|
-
await handleChat(messages, res);
|
|
1633
|
-
} catch (error) {
|
|
1634
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1635
|
-
res.end(JSON.stringify({ error: error.message }));
|
|
1636
|
-
}
|
|
1637
|
-
});
|
|
1638
|
-
} else {
|
|
1639
|
-
res.writeHead(404);
|
|
1640
|
-
res.end('Not Found');
|
|
1641
|
-
}
|
|
1642
|
-
}).listen(3000, () => console.log('🚀 Server running on http://localhost:3000'));
|
|
1643
|
-
```
|
|
1644
|
-
|
|
1645
|
-
**配置说明:**
|
|
1646
|
-
|
|
1647
|
-
**判断规则:**
|
|
1648
|
-
- 如果模型名称包含 `claude`(不区分大小写) → 使用 Claude SDK
|
|
1649
|
-
- 其他情况 → 使用 OpenAI SDK
|
|
1650
|
-
|
|
1651
|
-
## 消息格式
|
|
1652
|
-
|
|
1653
|
-
**请求格式:**
|
|
1654
|
-
|
|
1655
|
-
SheetNext 发送的请求体格式:
|
|
1656
|
-
|
|
1657
|
-
```json
|
|
1658
|
-
{
|
|
1659
|
-
"messages": [
|
|
1660
|
-
{
|
|
1661
|
-
"role": "system",
|
|
1662
|
-
"content": "你是一个电子表格助手..."
|
|
1663
|
-
},
|
|
1664
|
-
{
|
|
1665
|
-
"role": "user",
|
|
1666
|
-
"content": "帮我分析销售数据"
|
|
1667
|
-
},
|
|
1668
|
-
{
|
|
1669
|
-
"role": "assistant",
|
|
1670
|
-
"content": "好的,我来帮您分析..."
|
|
1671
|
-
},
|
|
1672
|
-
{
|
|
1673
|
-
"role": "user",
|
|
1674
|
-
"content": [
|
|
1675
|
-
{
|
|
1676
|
-
"type": "text",
|
|
1677
|
-
"text": "某区域图片"
|
|
1678
|
-
},
|
|
1679
|
-
{
|
|
1680
|
-
"type": "image_url",
|
|
1681
|
-
"image_url": {
|
|
1682
|
-
"url": "data:image/png;base64,iVBORw0KGgoAAAANS..."
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
]
|
|
1686
|
-
}
|
|
1687
|
-
]
|
|
1688
|
-
}
|
|
1689
|
-
```
|
|
1690
|
-
|
|
1691
|
-
**响应格式:**
|
|
1692
|
-
|
|
1693
|
-
您的服务器应该返回 SSE 流:
|
|
1694
|
-
|
|
1695
|
-
```
|
|
1696
|
-
data: {"type":"text","delta":"我"}
|
|
1697
|
-
|
|
1698
|
-
data: {"type":"text","delta":"来"}
|
|
1699
|
-
|
|
1700
|
-
data: {"type":"text","delta":"帮"}
|
|
1701
|
-
|
|
1702
|
-
data: [DONE]
|
|
1703
|
-
```
|