@wecom/wecom-openclaw-plugin 2026.9.15 → 2026.10.19
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/package.json +2 -2
- package/skills/wecomcli-sheet/SKILL.md +64 -107
- package/skills/wecomcli-sheet/references/sheet-conditional-formatting.md +211 -0
- package/skills/wecomcli-sheet/references/sheet-filter.md +187 -0
- package/skills/wecomcli-sheet/references/sheet-float-image.md +248 -0
- package/skills/wecomcli-sheet/references/sheet-formulas.md +73 -0
- package/skills/wecomcli-sheet/references/sheet-history.md +125 -0
- package/skills/wecomcli-sheet/references/sheet-protection.md +180 -0
- package/skills/wecomcli-sheet/references/sheet-range-operations.md +323 -0
- package/skills/wecomcli-sheet/references/sheet-read-data.md +153 -0
- package/skills/wecomcli-sheet/references/sheet-search-replace.md +111 -0
- package/skills/wecomcli-sheet/references/sheet-structure-operations.md +416 -0
- package/skills/wecomcli-sheet/references/sheet-style.md +246 -0
- package/skills/wecomcli-sheet/references/sheet-validation.md +243 -0
- package/skills/wecomcli-sheet/references/sheet-workbook.md +271 -0
- package/skills/wecomcli-sheet/references/sheet-write-cells.md +332 -0
- package/skills/wecomcli-sheet/references/xlsx-process.md +328 -0
- package/skills/wecomcli-sheet/scripts/collect.py +868 -0
- package/skills/wecomcli-sheet/scripts/recalc.py +403 -0
- package/skills/wecomcli-sheet/references/sheet-contents-update.md +0 -49
- package/skills/wecomcli-sheet/references/sheet-ranges-get.md +0 -47
- package/skills/wecomcli-sheet/references/sheet-rows-append.md +0 -47
- package/skills/wecomcli-sheet/references/sheet-subsheets-add.md +0 -28
- package/skills/wecomcli-sheet/references/sheet-subsheets-delete.md +0 -22
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# 表格数据写操作
|
|
2
|
+
|
|
3
|
+
## 图片写入前置转换
|
|
4
|
+
|
|
5
|
+
单元格的 `image` 字段只接受图片 URL。若源图片是本地文件或 `media_id`,先调用 `wecom-cli sheet images upload` 上传到目标文档空间,获取可用 URL。
|
|
6
|
+
|
|
7
|
+
### 命令
|
|
8
|
+
|
|
9
|
+
使用 wecom-cli 工具按照下面格式调用
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
wecom-cli sheet images upload --json '{"file_path": "/path/to/image.png", "docid": "<目标文档ID>"}'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
wecom-cli sheet images upload --json '{"media_id": "<media_id>", "docid": "<目标文档ID>"}'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`file_path` 与 `media_id` 二选一,不能同时传入。拿到返回的 `url` 后,再把它填入下方任一写图片命令的 `image` 字段;不要把本地路径或 `media_id` 直接填入 `image`。
|
|
20
|
+
|
|
21
|
+
### 单元格图片能力边界
|
|
22
|
+
|
|
23
|
+
- 单元格图片只支持通过 `image` 传入图片 URL,不支持设置 `width`、`height` 或缩放比例。
|
|
24
|
+
- 用户指定图片宽高、像素或显示尺寸时,必须直接读取 `sheet-float-image.md`,使用 `sheet float image set`,并将用户指定的单元格位置作为 `anchor`。
|
|
25
|
+
|
|
26
|
+
## 写入边界与回读校验参数
|
|
27
|
+
|
|
28
|
+
- **`verify`**:写后自动读回本次写入区域,用于检查写入是否生效,并发现公式中的 `#REF!` / `#VALUE!` 等错误。它只校验当前请求,不代表多个批次组成的整表任务已完成,也不提供事务保证。
|
|
29
|
+
- **`allow_overwrite`**:默认 `true`,直接覆盖非空单元格;传 `false` 时目标区域存在非空单元格则报错。它只能防止覆盖非空格,不能识别目标值是否在读取后被其他人修改。
|
|
30
|
+
- **`dry_run`**:仅预览和校验本次请求,不执行写入。它不会锁定表格,也不保证正式执行时内容、范围和权限仍未变化。
|
|
31
|
+
|
|
32
|
+
> 多批写入是逐批提交,可能出现部分批次成功、后续批次失败。当前协议没有声明跨批事务或自动回滚能力。
|
|
33
|
+
|
|
34
|
+
## 新增或追加数据的强制前置流程
|
|
35
|
+
|
|
36
|
+
`cells batchset`、`cells set`、`cells append` 只负责写入数据,不负责保证新增区域继承现有表格样式。凡写入会新增记录、追加行或增加字段,必须先通过 `sheet-structure-operations.md` 的 `sheet dims insert` 插入承载数据的整行和/或整列,再执行写入。
|
|
37
|
+
|
|
38
|
+
适用边界:
|
|
39
|
+
|
|
40
|
+
- **必须插入**:新增记录、表尾追加、在现有记录之间插入新记录、增加新字段、向右或向下扩展数据区。
|
|
41
|
+
- **不得插入**:仅修改、纠正或补全已有单元格内容。此时直接写入原单元格,避免 `dims insert` 导致现有数据、公式和引用整体偏移。
|
|
42
|
+
- **禁止做法**:先直接调用 `batchset` / `set` / `append` 写入新增区域,再手工复制样式;或依赖写接口自动扩展边界、自动继承相邻样式。
|
|
43
|
+
|
|
44
|
+
## 共用数据结构:`Cell`
|
|
45
|
+
|
|
46
|
+
`cells batchset`、`cells set` 和 `cells append` 三个接口共用 `Cell` 数据结构。普通值可直接使用裸值,如 `"名称"`、`120`、`true`;写入时会自动包装为 `{"value": ...}`。需要写入公式、链接、图片、样式或富文本时,使用 `Cell` 对象。
|
|
47
|
+
|
|
48
|
+
| 字段 | 类型 | 说明 |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `value` | any | 单元格值 |
|
|
51
|
+
| `formula` | string | 公式,只需传 `formula` |
|
|
52
|
+
| `link` | string | 超链接;传空字符串 `""` 清除该格所有链接,与 `value` 互斥 |
|
|
53
|
+
| `link_text` | string | 超链接展示文本,与 `link` 搭配使用 |
|
|
54
|
+
| `image` | string | 图片 URL;若源图片是本地路径或 `media_id`,先通过 `images upload` 换取 URL |
|
|
55
|
+
| `style` | object | 单元格样式,字段见下方「`CellStyle` 可设样式」 |
|
|
56
|
+
| `rich_string` | array | 富文本分段数组,一个单元格内混排多段文本、样式和超链接 |
|
|
57
|
+
|
|
58
|
+
### `Cell` 写入规则
|
|
59
|
+
|
|
60
|
+
为避免不同调用方对空值和组合字段产生不同解释,按以下规则调用:
|
|
61
|
+
|
|
62
|
+
1. `value`、`formula`、`link`、`image`、`rich_string` 视为内容字段;一次写入只选择一种主要内容类型,`style` 可以与主要内容一起传入。`link_text` 只能与 `link` 一起使用。
|
|
63
|
+
2. 批量写公式必须传 `Cell` 对象,`"cells": [[{"formula": "=SUM(A1:A10)"}]`,裸字符串会被自动包装为文本,表格显示公式原文、不参与计算、`verify` 的 `csv_sample` 会原样回显公式文本。目前表格公式暂不支持进行跨文档引用和同一文档内跨子表引用(如VLOOKUP等函数)。
|
|
64
|
+
3. 公式写入的验收判定:写入前读 `sheet-formulas.md` 进行试算。写入后用 `sheet cells get` 带 `include:["formula"]` 读回。返回 `{"value": <数字>, "formula": "=..."}` → 公式生效。返回裸字符串,或 `value` 等于 `=...` 原文 → 该格是文本,公式未生效,必须用 `{"formula": ...}` 重写。`verify` 的 `csv_sample` 若原样回显 `=...` 文本,即为写入失败信号,不要当作成功。
|
|
65
|
+
4. 当前协议明确支持的清除语义只有:`link: ""` 清除链接、`number_format: ""` 清除数字格式。清除值、公式、图片或富文本需使用对应的清除命令或等待协议明确,不能自行猜测。
|
|
66
|
+
|
|
67
|
+
### `RichString` 元素结构
|
|
68
|
+
|
|
69
|
+
一个单元格由若干 `RichString` 段按顺序拼接而成,用于同一格内“文本 + 超链接混排”或“不同片段不同字体/颜色”。每段只能是一种内容:`value`(文本)与 `link`(链接)二选一。
|
|
70
|
+
|
|
71
|
+
| 字段 | 类型 | 说明 |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `value` | any | 文本段的文本值 |
|
|
74
|
+
| `link` | string | 链接段的超链接 URL |
|
|
75
|
+
| `link_text` | string | 链接段的显示文字,与 `link` 搭配使用 |
|
|
76
|
+
| `style` | object | 该段样式,字段同下方 `CellStyle` |
|
|
77
|
+
|
|
78
|
+
> 放多个超链接时,拆成多段,每段使用一组 `link` + `link_text`,不要在一段里放数组或嵌套对象。
|
|
79
|
+
|
|
80
|
+
### `CellStyle` 可设样式
|
|
81
|
+
|
|
82
|
+
`Cell.style` 可携带以下字段,未传的字段保持原值:
|
|
83
|
+
|
|
84
|
+
| 字段 | 类型 | 说明 |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `font_family` | string | 字体名称,如 `"Microsoft YaHei"` |
|
|
87
|
+
| `font_size` | int | 字号(pt) |
|
|
88
|
+
| `bold` | bool | 加粗 |
|
|
89
|
+
| `italic` | bool | 斜体 |
|
|
90
|
+
| `underline` | string | 下划线:`none` / `single` / `double` / `single_accounting` / `double_accounting` |
|
|
91
|
+
| `strike` | bool | 删除线 |
|
|
92
|
+
| `font_color` | string | 字体色,ARGB hex |
|
|
93
|
+
| `bg_color` | string | 背景色,ARGB hex |
|
|
94
|
+
| `horizontal_align` | string | 水平对齐:`general` / `left` / `center` / `right` / `fill` / `justify` / `center_continuous` / `distributed` |
|
|
95
|
+
| `vertical_align` | string | 垂直对齐:`top` / `center` / `bottom` / `justify` / `distributed` |
|
|
96
|
+
| `wrap` | bool | 自动换行 |
|
|
97
|
+
| `number_format` | string | 数字格式,Excel 格式代码,见下 |
|
|
98
|
+
|
|
99
|
+
**颜色 ARGB hex**:8 位十六进制,前 2 位为 alpha(不透明度)。如 `"FFFF0000"` = 不透明红、`"FFFFFF00"` = 不透明黄。
|
|
100
|
+
|
|
101
|
+
**`number_format`**:Excel 数字格式代码自由字符串(非枚举),只影响显示,不改变值类型;传空字符串 `""` 表示清除格式并恢复默认。常用取值:
|
|
102
|
+
|
|
103
|
+
| 分类 | 格式代码 | 显示效果 |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| 文本 | `@` | 文本显示格式;需要保留前导零或长编号时,仍应把值作为字符串传入 |
|
|
106
|
+
| 整数 | `#,##0` | `1,234` |
|
|
107
|
+
| 两位小数 | `#,##0.00` | `1,234.00` |
|
|
108
|
+
| 百分比 | `0%` / `0.00%` | `88%` / `87.65%`(值 `0.88`) |
|
|
109
|
+
| 货币 | `¥#,##0.00` / `$#,##0.00` | `¥1,234.00` |
|
|
110
|
+
| 日期 | `yyyy-mm-dd` / `yyyy"年"m"月"d"日` | `2026-08-28` / `2026年8月28日` |
|
|
111
|
+
| 时间 | `hh:mm:ss` | `13:45:30` |
|
|
112
|
+
|
|
113
|
+
分段语法:用 `;` 分隔最多 4 段,依次为正数、负数、零、文本,如 `#,##0.00;[Red]-#,##0.00`。
|
|
114
|
+
|
|
115
|
+
## 使用场景
|
|
116
|
+
|
|
117
|
+
| 场景 | 使用命令 | 原因 |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| 批量写入一块区域(多行多列) | `sheet cells batchset` | 支持 `cells` 二维数组或 `csv_data`;一次写入值、公式、链接、图片或样式 |
|
|
120
|
+
| 只写一个单元格 | `sheet cells set` | 单格写入操作 |
|
|
121
|
+
| 在表格末尾追加一行 | `sheet cells append` | 不需要自行计算末尾行号 |
|
|
122
|
+
|
|
123
|
+
`cells batchset` 的两种数据源二选一:
|
|
124
|
+
|
|
125
|
+
- 数据含公式、链接、图片、样式、富文本,或需要明确控制字符串类型时,使用 `cells`
|
|
126
|
+
- 纯二维值且不含复杂类型时,可以使用 `csv_data`
|
|
127
|
+
|
|
128
|
+
## 单次写入上限与分批原则
|
|
129
|
+
|
|
130
|
+
`cells batchset` 单次最多写入 **400 个单元格**。待写入数据超过 400 格时,必须在调用前拆分为多个批次。
|
|
131
|
+
|
|
132
|
+
### 连续矩形与非连续目标行
|
|
133
|
+
|
|
134
|
+
`cells batchset` 的 `range` 与二维 `cells` / `csv_data` 始终描述一个连续矩形区域。处理非连续目标地址时,按以下优先级选择方案:
|
|
135
|
+
|
|
136
|
+
1. 多个旧值分别统一替换为固定新值:改读 `sheet-search-replace.md`,使用限定子表、限定列范围的精确替换。
|
|
137
|
+
2. 每个目标值不同,但目标行可组成若干连续段:先按实际行号排序并拆成连续段,每个连续段单独调用 `cells batchset`。
|
|
138
|
+
3. 目标地址高度离散且数量很少:逐个调用 `cells set`。
|
|
139
|
+
|
|
140
|
+
### 二维分块
|
|
141
|
+
|
|
142
|
+
1. 优先按完整行分批:`rows_per_batch = floor(400 / total_columns)`。
|
|
143
|
+
2. 若单行超过 400 列,先按列拆分,每个列片不超过 400 列,再在列片内按行拆分。
|
|
144
|
+
3. 每批必须满足:`batch_rows × batch_columns <= 400`。
|
|
145
|
+
4. A1 列号换算必须支持 `Z → AA → AB`,禁止用单字符加减计算列名。
|
|
146
|
+
|
|
147
|
+
### 使用 Python 分批写入
|
|
148
|
+
|
|
149
|
+
Python 分批写入必须按“预览试跑 → 固定批次正式写入 → 失败批次重试 → 整体校验”的顺序执行。
|
|
150
|
+
|
|
151
|
+
写入前必须根据目标起始位置、总行数和总列数计算最终写入范围,并确认该范围未超出当前子表的行列边界。若当前边界不足,必须先按照 `sheet-structure-operations.md` 中的「扩展当前子表边界」扩展所需行或列;扩展完成后重新计算批次坐标,再进行预览试跑和正式写入,不得依赖写入操作自动扩展边界。
|
|
152
|
+
|
|
153
|
+
#### 预览试跑
|
|
154
|
+
|
|
155
|
+
先确认目标起始位置、总行数、总列数和预期写入格数。根据二维分块规则确定每批行数、列数和格数;每批不得超过 400 格。批次大小一旦通过预览试跑,正式运行期间不得根据上一批响应动态修改。
|
|
156
|
+
|
|
157
|
+
正式写入前,先写试运行的Python脚本,用 `"dry_run": true` 运行至少5个批次:
|
|
158
|
+
|
|
159
|
+
预览时使用与正式写入完全相同的 `range`、`cells/csv_data`、`allow_overwrite`,只增加 `"dry_run": true`。
|
|
160
|
+
|
|
161
|
+
- 所有代表批次均未出现参数过长、维度错误或其他参数错误,才认为当前分块大小合理,可以开始正式运行。
|
|
162
|
+
- 如果出现参数过长(Argument Long),缩小单批行数或列数,并重新运行代表批次预览;不能只修改报错批次后继续沿用旧坐标。
|
|
163
|
+
- 如果出现权限、非法公式、数据类型或矩阵维度错误,先修正请求或源数据;这类错误不能靠持续重试解决。
|
|
164
|
+
|
|
165
|
+
#### 失败处理与重试
|
|
166
|
+
|
|
167
|
+
- **明确执行失败**:重试当前批次;成功前不进入下一批。
|
|
168
|
+
- **限流或临时服务错误**:退避间隔重试同一批次。
|
|
169
|
+
- **执行超时**:超时表示结果未知,不等于写入失败。先回读该批固定 range:回读结果不一致或确认未写入,重试同一批次;
|
|
170
|
+
- 对可重试失败持续处理,直到全部批次通过;但权限、参数、非法公式、维度错误等永久错误必须停止并修正,禁止无限重试。 。
|
|
171
|
+
|
|
172
|
+
## 批量写入指定区域
|
|
173
|
+
|
|
174
|
+
### 命令
|
|
175
|
+
|
|
176
|
+
使用 wecom-cli 工具按照下面格式调用
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
wecom-cli sheet cells batchset --json '<JSON 参数>'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 参数
|
|
183
|
+
|
|
184
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
185
|
+
|---|---|---|---|
|
|
186
|
+
| `docid` | string | 是 | 在线表格 ID |
|
|
187
|
+
| `sheet` | string | 是 | 子表 ID 或名称;自动化任务优先传 ID |
|
|
188
|
+
| `range` | string | 是 | 目标 A1 区域;推荐传左上角锚点,由 2D 数据维度推导实际区域 |
|
|
189
|
+
| `cells` | array(2D) | 条件 | 非空矩形二维数组,每元素为 `Cell`,裸值可简写;与 `csv_data` 二选一 |
|
|
190
|
+
| `csv_data` | string | 条件 | 标准 CSV 文本;与 `cells` 二选一,仍受 400 格上限约束 |
|
|
191
|
+
| `allow_overwrite` | bool | 否 | 默认 `true`;`false` 时目标存在非空单元格则报错 |
|
|
192
|
+
| `verify` | bool | 否 | 写后自动读回本次写入区域 |
|
|
193
|
+
| `dry_run` | bool | 否 | 预览本次请求,不执行写入 |
|
|
194
|
+
|
|
195
|
+
> 若传完整 A1 区域而不是左上角锚点,调用前必须保证区域维度与 2D 数据严格一致;当前返回没有定义维度不一致时的具体错误码,因此更推荐只传锚点。
|
|
196
|
+
|
|
197
|
+
### 请求示例(cells 模式 · 复杂类型)
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"docid": "e3_ABC123",
|
|
202
|
+
"sheet": "销售",
|
|
203
|
+
"range": "A1",
|
|
204
|
+
"cells": [
|
|
205
|
+
[{"value": "名称"}, {"link": "https://x.com", "link_text": "看详情", "style": {"font_color": "FF0000FF", "underline": "single"}}],
|
|
206
|
+
[{"value": "苹果"}, {"formula": "=SUM(C2:C10)"}],
|
|
207
|
+
[{"value": "带样式", "style": {"bold": true, "bg_color": "FFFFFF00"}}, {"image": "https://x.com/logo.png"}]
|
|
208
|
+
],
|
|
209
|
+
"verify": true
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 请求示例(rich_string 模式)
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"docid": "e3_ABC123",
|
|
218
|
+
"sheet": "销售",
|
|
219
|
+
"range": "A1",
|
|
220
|
+
"cells": [
|
|
221
|
+
[{"rich_string": [
|
|
222
|
+
{"value": "详情见 "},
|
|
223
|
+
{"link": "https://x.com", "link_text": "官网", "style": {"font_color": "FF0000FF", "underline": "single"}},
|
|
224
|
+
{"value": " 页面"}
|
|
225
|
+
]}]
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 请求示例(裸值简写)
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"docid": "e3_ABC123",
|
|
235
|
+
"sheet": "销售",
|
|
236
|
+
"range": "A1",
|
|
237
|
+
"cells": [["名称", "数量"], ["苹果", 120], ["香蕉", 80]]
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 请求示例(CSV 模式 · 纯数据)
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"docid": "e3_ABC123",
|
|
246
|
+
"sheet": "销售",
|
|
247
|
+
"range": "A1",
|
|
248
|
+
"csv_data": "名称,数量,单价\n苹果,120,3.5\n香蕉,80,2.8\n橙子,95,4.2"
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### 返回
|
|
253
|
+
|
|
254
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
255
|
+
|---|---|---|---|
|
|
256
|
+
| `affected_range` | string | 是 | 实际写入区域 A1 |
|
|
257
|
+
| `written_count` | int32 | 是 | 写入单元格数 |
|
|
258
|
+
| `verify_result` | object | 否 | `verify=true` 时的读回结果;当前协议未定义结构化差异字段 |
|
|
259
|
+
|
|
260
|
+
## 单格写入
|
|
261
|
+
|
|
262
|
+
### 命令
|
|
263
|
+
|
|
264
|
+
使用 wecom-cli 工具按照下面格式调用
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
wecom-cli sheet cells set --json '<JSON 参数>'
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### 参数
|
|
271
|
+
|
|
272
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
273
|
+
|---|---|---|---|
|
|
274
|
+
| `docid` | string | 是 | 工作簿定位 |
|
|
275
|
+
| `sheet` | string | 是 | 子表 ID 或名称 |
|
|
276
|
+
| `range` | string | 是 | 单格 A1,如 `B2` |
|
|
277
|
+
| `cell` | `Cell` | 是 | 写入内容,使用文首 `Cell` 规则 |
|
|
278
|
+
| `verify` | bool | 否 | 写后读回校验 |
|
|
279
|
+
|
|
280
|
+
### 返回
|
|
281
|
+
|
|
282
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
283
|
+
|---|---|---|---|
|
|
284
|
+
| `affected_range` | string | 是 | 实际写入单格 A1 |
|
|
285
|
+
| `cell` | Cell | 否 | `verify=true` 时读回的单元格内容 |
|
|
286
|
+
|
|
287
|
+
### 使用规则
|
|
288
|
+
|
|
289
|
+
- `cell.link` 传空字符串 `""` 即清除该格全部超链接,无需独立清链接命令。
|
|
290
|
+
|
|
291
|
+
## 表格末尾追加一行数据
|
|
292
|
+
|
|
293
|
+
在指定子表末尾追加一行数据,无需自行计算末尾行号。`row` 中各元素按子表列顺序依次写入。
|
|
294
|
+
|
|
295
|
+
### 命令
|
|
296
|
+
|
|
297
|
+
使用 wecom-cli 工具按照下面格式调用
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
wecom-cli sheet cells append --json '<JSON 参数>'
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### 参数
|
|
304
|
+
|
|
305
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
306
|
+
|---|---|---|---|
|
|
307
|
+
| `docid` | string | 是 | 工作簿定位 |
|
|
308
|
+
| `sheet` | string | 是 | 子表 ID 或名称 |
|
|
309
|
+
| `row` | `Cell[]` | 是 | 要追加的一行非空数据,按列顺序排列 |
|
|
310
|
+
|
|
311
|
+
`row` 中的复杂元素使用文首 `Cell` 规则,可使用 `value`、`formula`、`style` 等字段。
|
|
312
|
+
|
|
313
|
+
### 请求示例
|
|
314
|
+
|
|
315
|
+
```json
|
|
316
|
+
{
|
|
317
|
+
"docid": "e3_ABC123",
|
|
318
|
+
"sheet": "销售",
|
|
319
|
+
"row": [
|
|
320
|
+
"苹果",
|
|
321
|
+
120,
|
|
322
|
+
true,
|
|
323
|
+
{
|
|
324
|
+
"formula": "=SUM(A1:A10)",
|
|
325
|
+
"style": {
|
|
326
|
+
"bold": true,
|
|
327
|
+
"number_format": "0.00"
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
```
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# 本地表格创建、编辑与分析
|
|
2
|
+
|
|
3
|
+
用户可能要求创建、编辑或分析 `.xlsx`、`.xlsm`、`.xls`、`.csv`、`.tsv` 文件。本流程依赖 `openpyxl`、`pandas`、`LibreOffice` 等环境组件,使用前须按「环境依赖检查」按需检测是否已安装,未安装时先安装再继续。
|
|
4
|
+
|
|
5
|
+
## 环境依赖检查
|
|
6
|
+
|
|
7
|
+
| 依赖 | 何时需要 |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `openpyxl` | 编辑 xlsx、运行 `recalc.py` |
|
|
10
|
+
| `pandas` | 数据分析 |
|
|
11
|
+
| LibreOffice(`soffice`) | 运行 `recalc.py` 重算公式 |
|
|
12
|
+
|
|
13
|
+
- 按需检测与安装:只在即将用到某个依赖时检测它;纯分析只查 `pandas`,编辑文件查 `openpyxl`,需要重算公式时再查 `soffice`。不用到的依赖不检测、不安装。
|
|
14
|
+
|
|
15
|
+
## 执行前置门禁(处理已有表格时必须完成)
|
|
16
|
+
|
|
17
|
+
本节适用于已经存在的 `.xlsx`、`.xlsm`、`.xls`、`.csv`、`.tsv` 文件。
|
|
18
|
+
|
|
19
|
+
> **硬规则**:必须依次完成“来源规范化 → 结构探查 → 选择处理库”。在结构探查成功前,禁止使用 pandas、openpyxl 或其他方式读取业务数据、分析内容或修改文件。
|
|
20
|
+
|
|
21
|
+
### 得到统一工作文件 `working_path`
|
|
22
|
+
|
|
23
|
+
先按来源完成必要的读取副本或格式转换,并得到一个可供探查脚本读取的绝对路径 `working_path`。
|
|
24
|
+
|
|
25
|
+
| 来源 | 必须执行的操作 |
|
|
26
|
+
|---|---|
|
|
27
|
+
| 本地 `.xlsx` / `.xlsm` | 将原文件绝对路径记为 `working_path`;后续用 openpyxl 编辑 `.xlsm` 时须设置 `keep_vba=True` |
|
|
28
|
+
| 本地 `.xls` | 先用 `wecom-cli parser office convert` 转为 `.xlsx`,将转换结果的绝对路径记为 `working_path` |
|
|
29
|
+
| 本地 `.csv` / `.tsv` | 将原文件绝对路径记为 `working_path`;只读分析可用 pandas,编辑时必须保持用户要求的分隔符、编码和输出格式,不得无提示转为 `.xlsx` |
|
|
30
|
+
|
|
31
|
+
#### `.xls` 格式转换
|
|
32
|
+
|
|
33
|
+
- 判断“源文件是否已是用户要求的格式”时,优先结合用户可见文件名/附件展示名;若已一致,不调用转换工具,只简短回复“你发送的文件已经是 xls 了,无需进行转换。”,不要追加后台预转换、可转其他格式等说明。
|
|
34
|
+
- 若附件展示名/用户引用的文件名是 `.xls`,但本地处理路径是 `.xlsx`,这是后台预转换工作副本,仅用于内部处理;对用户格式判断仍按展示名,禁止说用户上传的是 `.xlsx`,也禁止据此回答 `.xlsx` 转 `.xls` 不支持。
|
|
35
|
+
- 严格按用户明确提出的目标格式处理,不要揣测或改写用户意图;意图不明确时先问清楚。若用户明确要求当前链路不支持的转换,直接说明不支持,不要自动改为 PDF或其他格式。
|
|
36
|
+
|
|
37
|
+
### 命令
|
|
38
|
+
|
|
39
|
+
使用 wecom-cli 工具按照下面格式调用
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
wecom-cli parser office convert --json '<JSON 参数>'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
参数
|
|
46
|
+
|
|
47
|
+
| 字段 | 类型 | 必填 | 语义 |
|
|
48
|
+
|-------------|---|---|----------------------------|
|
|
49
|
+
| `file_path` | string | 是 | 待转换文件的完整路径 |
|
|
50
|
+
| `target_file_type` | string | 是 | 目标文件格式,可选 `xlsx` 和 `xls` |
|
|
51
|
+
|
|
52
|
+
### 强制执行结构探查
|
|
53
|
+
|
|
54
|
+
取得 `working_path` 后,必须立即运行探查脚本:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python3 "{baseDir}/scripts/collect.py" "<working_path>"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- `{baseDir}` 表示当前 Skill 所在目录(`wecomcli-sheet`)。执行时必须替换为实际绝对路径。
|
|
61
|
+
- 脚本已经存在,直接运行;禁止先做脚本存在性检测。
|
|
62
|
+
- 脚本支持 `.xlsx`、`.xlsm`、`.csv`、`.tsv`;`.xls` 必须先转换为 `.xlsx`
|
|
63
|
+
- 此步骤是结构采样,不等于读取全部业务数据。后续正式读取仍须限定在用户要求的子表和范围内。
|
|
64
|
+
|
|
65
|
+
### 根据探查结果选择处理库
|
|
66
|
+
|
|
67
|
+
- 仅做数据分析且不生成交付文件:可以使用 pandas。
|
|
68
|
+
- 编辑数据、公式、格式、合并单元格,或生成需要保留原工作簿对象的交付文件:使用 openpyxl。
|
|
69
|
+
- 任一子表 `image_count > 0`:必须使用 openpyxl,并显式制定浮动图片的保留、删除或移动方案。
|
|
70
|
+
|
|
71
|
+
## 读取与分析数据
|
|
72
|
+
|
|
73
|
+
### 用 pandas 做数据分析
|
|
74
|
+
|
|
75
|
+
数据分析、可视化和基础操作优先用 **pandas**,它提供了完整的数据处理能力:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import pandas as pd
|
|
79
|
+
|
|
80
|
+
# 读取 Excel
|
|
81
|
+
df = pd.read_excel('file.xlsx') # 默认读第一个子表
|
|
82
|
+
all_sheets = pd.read_excel('file.xlsx', sheet_name=None) # 读全部子表,返回 dict
|
|
83
|
+
|
|
84
|
+
# 分析
|
|
85
|
+
df.head() # 预览数据
|
|
86
|
+
df.info() # 列信息
|
|
87
|
+
df.describe() # 描述统计
|
|
88
|
+
|
|
89
|
+
# 写出 Excel
|
|
90
|
+
df.to_excel('output.xlsx', index=False)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Excel 文件工作流
|
|
94
|
+
|
|
95
|
+
### 日期规范化
|
|
96
|
+
|
|
97
|
+
按日期筛选、排序、分组或比较后,交付列必须显示为可读日期,不能保留 Excel 序列号(如 `46327`)
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from datetime import datetime, timedelta
|
|
101
|
+
c.value = datetime(1899, 12, 30) + timedelta(days=c.value)
|
|
102
|
+
c.number_format = "yyyy/mm/dd"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
转换值和设置日期格式缺一不可。
|
|
106
|
+
|
|
107
|
+
### 关键规则:写公式,不写硬编码值
|
|
108
|
+
|
|
109
|
+
**一律用 Excel 公式,不要在 Python 里算好结果再把数字写进单元格。** 只有写公式,表格才是动态可更新的。
|
|
110
|
+
|
|
111
|
+
#### 错误做法 —— 硬编码计算结果
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# 差:在 Python 里算完把结果写死
|
|
115
|
+
total = df['Sales'].sum()
|
|
116
|
+
sheet['B10'] = total # 写死成 5000
|
|
117
|
+
|
|
118
|
+
# 差:在 Python 里算增长率
|
|
119
|
+
growth = (df.iloc[-1]['Revenue'] - df.iloc[0]['Revenue']) / df.iloc[0]['Revenue']
|
|
120
|
+
sheet['C5'] = growth # 写死成 0.15
|
|
121
|
+
|
|
122
|
+
# 差:在 Python 里算平均值
|
|
123
|
+
avg = sum(values) / len(values)
|
|
124
|
+
sheet['D20'] = avg # 写死成 42.5
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### 正确做法 —— 使用 Excel 公式
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# 好:让 Excel 自己求和
|
|
131
|
+
sheet['B10'] = '=SUM(B2:B9)'
|
|
132
|
+
|
|
133
|
+
# 好:增长率写成 Excel 公式
|
|
134
|
+
sheet['C5'] = '=(C4-C2)/C2'
|
|
135
|
+
|
|
136
|
+
# 好:用 Excel 函数求平均
|
|
137
|
+
sheet['D20'] = '=AVERAGE(D2:D19)'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
这条规则适用于**所有**计算 —— 合计、百分比、比率、差值等等。源数据变化时,表格应当能自行重算。
|
|
141
|
+
|
|
142
|
+
### 通用流程
|
|
143
|
+
|
|
144
|
+
1. **选库**:纯数据分析且不生成文件:可使用 pandas。编辑数据、公式/格式,或任一子表 `image_count > 0`:必须使用 openpyxl,并显式处理图片。
|
|
145
|
+
2. **新建 / 加载**:新建工作簿或加载既有文件。
|
|
146
|
+
3. **修改**:写入或编辑数据、公式与格式;涉及增删行列时,先检查是否有浮动图片,含图片则按「处理浮动图片」规则操作。
|
|
147
|
+
4. **保存**:写出文件。
|
|
148
|
+
5. **重算公式(写了公式则必做)**:见下方「重算公式」节,使用当前 Skill 自带的 `scripts/recalc.py`。
|
|
149
|
+
6. **核查并修复错误**:见下方「解读 `recalc()` 的返回」节,按返回 dict 判断成功 / 有错 / 失败。
|
|
150
|
+
|
|
151
|
+
### 新建 Excel 文件
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# 用 openpyxl 处理公式与格式
|
|
155
|
+
from openpyxl import Workbook
|
|
156
|
+
from openpyxl.styles import Font, PatternFill, Alignment
|
|
157
|
+
|
|
158
|
+
wb = Workbook()
|
|
159
|
+
sheet = wb.active
|
|
160
|
+
|
|
161
|
+
# 写入数据
|
|
162
|
+
sheet['A1'] = 'Hello'
|
|
163
|
+
sheet['B1'] = 'World'
|
|
164
|
+
sheet.append(['Row', 'of', 'data'])
|
|
165
|
+
|
|
166
|
+
# 写入公式
|
|
167
|
+
sheet['B2'] = '=SUM(A1:A10)'
|
|
168
|
+
|
|
169
|
+
# 设置格式
|
|
170
|
+
sheet['A1'].font = Font(bold=True, color='FF0000')
|
|
171
|
+
sheet['A1'].fill = PatternFill('solid', start_color='FFFF00')
|
|
172
|
+
sheet['A1'].alignment = Alignment(horizontal='center')
|
|
173
|
+
|
|
174
|
+
# 列宽
|
|
175
|
+
sheet.column_dimensions['A'].width = 20
|
|
176
|
+
|
|
177
|
+
wb.save('output.xlsx')
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 编辑已有 Excel 文件
|
|
181
|
+
|
|
182
|
+
涉及到编辑请求时,要求严格遵守用户的要求,不允许自行扩大或者缩小修改内容和范围。
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
# 用 openpyxl 以保留公式与格式
|
|
186
|
+
from openpyxl import load_workbook
|
|
187
|
+
|
|
188
|
+
# 加载既有文件
|
|
189
|
+
wb = load_workbook('existing.xlsx')
|
|
190
|
+
sheet = wb.active # 指定子表用 wb['SheetName']
|
|
191
|
+
|
|
192
|
+
# 遍历多个子表
|
|
193
|
+
for sheet_name in wb.sheetnames:
|
|
194
|
+
sheet = wb[sheet_name]
|
|
195
|
+
print(f"Sheet: {sheet_name}")
|
|
196
|
+
|
|
197
|
+
# 修改单元格
|
|
198
|
+
sheet['A1'] = 'New Value'
|
|
199
|
+
sheet.insert_rows(2) # 在第 2 行插入一行
|
|
200
|
+
sheet.delete_cols(3) # 删除第 3 列
|
|
201
|
+
|
|
202
|
+
# 新增子表
|
|
203
|
+
new_sheet = wb.create_sheet('NewSheet')
|
|
204
|
+
new_sheet['A1'] = 'Data'
|
|
205
|
+
|
|
206
|
+
wb.save('modified.xlsx')
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## 处理浮动图片
|
|
210
|
+
|
|
211
|
+
- **图片不属于单元格内容**:`ws.delete_rows()` 只删除和移动单元格,不会自动删除浮动图片,也不会更新图片锚点,必须单独处理;
|
|
212
|
+
- **定位图片所在行列**:遍历 `ws._images`,通过 `image.anchor._from.row` / `.col` 获取左上角锚点;锚点使用 **0-based** 编号,换算为 Excel 行列时需要 `+1`;
|
|
213
|
+
- **删除行内图片**:图片锚点行落在待删除区间时,从 `ws._images` 中删除。批量删除应先收集索引,再按索引**倒序删除**,避免列表下标变化;
|
|
214
|
+
- **移动后续图片**:图片位于待删除区间之后时,将 `anchor._from.row` 减去删除行数;如果是 `TwoCellAnchor`,还要同步修改 `anchor.to.row`;
|
|
215
|
+
- **正确操作顺序**:先删除目标行对应的图片并调整后续图片锚点,再调用 `ws.delete_rows()`;
|
|
216
|
+
- **锚点类型注意**:`OneCellAnchor` 只需调整 `_from`;`TwoCellAnchor` 需要同时调整 `_from` 和 `to`,右下角属性是 `anchor.to`,不是 `_to`;`AbsoluteAnchor` 无法按单元格行号可靠移动;
|
|
217
|
+
- **跨簿复制图片保留 ext**:`add_image(img, "F2")` 传字符串锚点会走 anchor setter 丢弃原 `ext` 并按图片像素重算显示尺寸,应手动构造 `OneCellAnchor(_from=AnchorMarker(col=原列, row=目标行-1), ext=原ext)` 后 `add_image(img)` 不传锚点;
|
|
218
|
+
|
|
219
|
+
## 重算公式
|
|
220
|
+
|
|
221
|
+
openpyxl 创建或修改出的 Excel 文件,公式只是字符串,**没有计算值**。用 `recalc.py` 重算(原地覆盖写缓存值):
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
python3 "{baseDir}/scripts/recalc.py" "<xlsx文件绝对路径>"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
- 其中 `{baseDir}` 表示当前 Skill 所在的目录(wecomcli-sheet)。执行命令时,必须将其替换为实际绝对路径。脚本文件已经存在,直接按路径运行即可,禁止对文件做存在性检测。
|
|
228
|
+
- `recalc.py` 会原地覆盖目标工作簿,必须对已保存的待交付副本执行,禁止直接重算唯一原文件;对 `.xlsm` 或包含高级对象的工作簿,重算后还必须复核 VBA 和对象保真。
|
|
229
|
+
|
|
230
|
+
该脚本会:
|
|
231
|
+
|
|
232
|
+
- 重算所有子表的全部公式
|
|
233
|
+
- 扫描**所有**单元格里的 Excel 错误(`#REF!` / `#DIV/0!` 等)
|
|
234
|
+
- 返回 dict,含错误的具体位置与数量
|
|
235
|
+
|
|
236
|
+
## 公式校验清单
|
|
237
|
+
|
|
238
|
+
用于快速确认公式是否正确:
|
|
239
|
+
|
|
240
|
+
### 必查项
|
|
241
|
+
|
|
242
|
+
- [ ] **抽查 2~3 个引用**:铺开整张表之前,先确认这几个引用取到的值正确
|
|
243
|
+
- [ ] **列号映射**:确认 Excel 列号对得上(例如第 64 列是 BL,不是 BK)
|
|
244
|
+
- [ ] **行号偏移**:Excel 行号从 1 开始(DataFrame 第 5 行 = Excel 第 6 行)
|
|
245
|
+
|
|
246
|
+
### 常见坑
|
|
247
|
+
|
|
248
|
+
- [ ] **空值处理**:用 `pd.notna()` 判断 null
|
|
249
|
+
- [ ] **靠右的列**:财年数据常在第 50 列之后
|
|
250
|
+
- [ ] **多处匹配**:要搜出所有出现位置,别只取第一个
|
|
251
|
+
- [ ] **除零**:公式里用 `/` 之前先检查分母(`#DIV/0!`)
|
|
252
|
+
- [ ] **引用错位**:确认每个引用都指向预期单元格(`#REF!`)
|
|
253
|
+
- [ ] **跨子表引用**:跨表链接要用正确写法(`Sheet1!A1`)
|
|
254
|
+
|
|
255
|
+
### 公式测试策略
|
|
256
|
+
|
|
257
|
+
- [ ] **从小做起**:先在 2~3 个单元格上验证公式,再大面积套用
|
|
258
|
+
- [ ] **核对依赖**:确认公式引用到的单元格都存在
|
|
259
|
+
- [ ] **测边界值**:覆盖 0、负数和极大值
|
|
260
|
+
|
|
261
|
+
### 解读 `recalc()` 的返回
|
|
262
|
+
|
|
263
|
+
函数返回带错误明细的 dict:
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
# 成功无错
|
|
267
|
+
{ "status": "success", "total_errors": 0, "total_formulas": 42, "error_summary": {} }
|
|
268
|
+
|
|
269
|
+
# 成功有错
|
|
270
|
+
{
|
|
271
|
+
"status": "errors_found",
|
|
272
|
+
"total_errors": 3,
|
|
273
|
+
"total_formulas": 42,
|
|
274
|
+
"error_summary": { # 仅在发现错误时非空
|
|
275
|
+
"#REF!": { "count": 2, "locations": ["Sheet1!B5", "Sheet1!C10"] }, # locations 每类最多 20
|
|
276
|
+
"#DIV/0!": { "count": 1, "locations": ["Sheet1!D3"] }
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# 失败
|
|
281
|
+
{ "error": "检测到外链(externalLinks);先把外链单元格值拷出再重算" }
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
判断写法:
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
if "error" in result:
|
|
288
|
+
... # 失败,暴露给agent
|
|
289
|
+
elif result["status"] == "success":
|
|
290
|
+
... # 重算成功,无 Excel 错误
|
|
291
|
+
else: # errors_found
|
|
292
|
+
for err_type, info in result["error_summary"].items():
|
|
293
|
+
... # info = {"count": N, "locations": [...]}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## 最佳实践
|
|
297
|
+
|
|
298
|
+
### 选库
|
|
299
|
+
|
|
300
|
+
- **pandas**:适合数据分析、批量操作和简单的数据导出
|
|
301
|
+
- **openpyxl**:适合复杂格式、公式以及 Excel 特有能力
|
|
302
|
+
|
|
303
|
+
### 使用 openpyxl
|
|
304
|
+
|
|
305
|
+
- 单元格下标从 1 开始(`row=1, column=1` 即 A1)
|
|
306
|
+
- 读计算值要用 `data_only=True`:`load_workbook('file.xlsx', data_only=True)`
|
|
307
|
+
- **警告**:以 `data_only=True` 打开后再保存,公式会被值替换且**永久丢失**
|
|
308
|
+
- 大文件:读用 `read_only=True`,写用 `write_only=True`
|
|
309
|
+
- 公式会被保留但不会求值 —— 使用当前 Skill 自带的 `scripts/recalc.py` 重算并检查错误
|
|
310
|
+
|
|
311
|
+
### 使用 pandas
|
|
312
|
+
|
|
313
|
+
- 显式指定数据类型,避免类型推断出错:`pd.read_excel('file.xlsx', dtype={'id': str})`
|
|
314
|
+
- 大文件只读需要的列:`pd.read_excel('file.xlsx', usecols=['A', 'C', 'E'])`
|
|
315
|
+
- 正确处理日期:`pd.read_excel('file.xlsx', parse_dates=['date_column'])`
|
|
316
|
+
|
|
317
|
+
## 代码风格要求
|
|
318
|
+
|
|
319
|
+
**重要**:生成 Excel 相关 Python 代码时:
|
|
320
|
+
|
|
321
|
+
- 代码精简,不写多余注释
|
|
322
|
+
- 不用冗长的变量名,不做重复操作
|
|
323
|
+
- 不加无必要的 print
|
|
324
|
+
|
|
325
|
+
**对 Excel 文件本身**:
|
|
326
|
+
|
|
327
|
+
- 给含复杂公式或重要假设的单元格加批注
|
|
328
|
+
- 硬编码值要标注数据来源
|