Files
Starlight_Lancher/.claude/skills/lark-sheets/references/lark-sheets-write-cells.md

576 lines
55 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Lark Sheet Write Cells
## 写入边界 + 回读校验(编辑类任务必做)
1. **明确写入边界**:写入前必须能回答"目标 range 的起止行列号是多少?是否落在用户授权范围内?"。除用户明示要修改的区域外,禁止扩张到原数据列以外或新建 Sheet。
2. **完整性断言**:批量写入前先把"预期写入条数"硬编码到代码里(如要填 106 条翻译 → `expected = 106`),写完后回读断言 `actual == expected`。少于预期就继续写,禁止交付半成品。
3. **回读抽样校验**:写完关键值 / 公式后,用 `+csv-get``+cells-get` 重新读取写入区域,至少抽样 3-5 个代表性单元格(首 / 中 / 末),核对值与预期一致(与本地脚本计算的预期值对照)。公式特定的"先验证模板再 --copy-to-range / 修完再读回"细则见下方相关章节。
4. **护原表 · 派生产物落点(写排名 / 标记 / 汇总 / 改写列时易丢数据)**:派生结果一律写到**真实末列 +1 的全新空列**或新建子表,**禁止复用任何已有原数据列**——哪怕该列看起来"空",也要先 `+csv-get` 回读确认整列无原始数据再写。三条准则:① 不把新公式 / 新值写进原数据列(典型反例:把新算的排名公式写进了原本存放另一份原始数据的列,整列原始数据被覆盖丢失);② 不改写、不合并原表头字段名(典型反例:把几个独立表头字段合并成一列,原字段名丢失);③ 慎用 `--allow-overwrite`:它一旦让写入区盖到相邻原始列 / 行就是不可逆数据丢失,加它之前必须用 `+sheet-info` / `+csv-get` 核清目标 range 不含任何原始数据。
## 新增列 / 新增行的样式继承(防止视觉风格不一致)
新增列 / 新增行**必须**先用 `+cells-get` 读相邻原列 / 原行的完整样式作为模板,**禁止**只传 `value` 期望默认样式与原表一致——飞书新单元格默认对齐通常是 `H:right, V:bottom`,与多数原表的 `H:center, V:middle` 不一致。
**完整继承清单**(写新列 / 新行时 cells 数组必须同时携带):
1. `cell_styles.font_family` / `cell_styles.font_size` / `cell_styles.font_weight` / `cell_styles.font_color` / `cell_styles.font_style`(字体名称 / 字号 / 粗细 / 颜色 / 斜体等)
2. `cell_styles.horizontal_alignment` / `cell_styles.vertical_alignment`H-Align / V-Align—— 漏继承会导致新列对齐与原列不一致(常见)
3. `cell_styles.number_format`(小数位 / 千分位 / 百分比 / 日期格式)—— 漏继承会导致同列数值格式混乱
4. `cell_styles.background_color`(背景色)
5. `border_styles`(四边框)
6. **`merged_cells`(合并范围)**——续写场景必查:用 `+sheet-info --include merges` 读原数据区域的合并信息。**原行有跨列合并**(如标题行 `A1:G1` 合并)时,新行**必须**用 `+cells-{merge|unmerge}` 工具复制相同合并模式到新行(如续写第 3 个周报块的标题行 `A23:G23` 必须合并)。仅传 cells 数组的 5 类样式不够——合并范围要单独靠 `+cells-{merge|unmerge}` 工具落地(典型反例:续写多周记录表时,新增周次的标题行未合并,视觉上与原前几周风格不一致)
**采样模板的正确做法**
- 表头新列 → 读相邻表头单元格(如新加 D1 → 读 A1/B1/C1 任一)
- 数据新列 → 读相邻数据行单元格(如新加 M5:M100 → 读 L5 / L6 / L7
- 续写新行 → 读最近一行已有数据(如续写第 20 行 → 读 19 行所有列)
**反模式**(违规):
- 只传 `{"value": "四级菜单"}` 给 D1不传 `cell_styles` → D1 默认非加粗、非居中,与 A1/B1/C1 风格断裂
- 新列 M5 写入 `=SUM(F5:L5)` 时只传 `formula`,不传 `cell_styles.horizontal_alignment / vertical_alignment / number_format` → M 列对齐变 `H:right`,数字格式变默认
## 长数字防科学计数法(数值列写入必查)
写入或计算结果可能产生长数字(≥ 12 位整数 / 高精度小数)的列,**必须**在 `cell_styles.number_format` 显式设置非通用格式,否则飞书会自动用科学计数法显示,用户看到的就是"内容被截断 / 看不清原值"。
| 场景 | 必加的 `number_format` |
|---|---|
| 长整数(订单号 / 身份证 / 单据号) | `"0"``"@"`(强制文本,避免精度丢失) |
| 金额 / 千分位 | `"#,##0.00"` |
| 百分比 | `"0.00%"` |
| 数量 / 计数 | `"0"`(整数) |
| 日期 | `"yyyy-mm-dd"``"yyyy/m/d"` |
**典型反例**:长数字列(如审批单号、流水号)未设 `number_format`,飞书显示为 `1.23E+15`,用户复制出来已经丢失精度。
> **数字还是文本,按"数据本质是量值还是标识符"二选一 —— 不看当下要不要计算**:金额 / 百分比 / 比率 / 计数 / 度量这类**本质是量值**的数据,一律以**数字类型**写入(百分比存小数 `0.54` 配 `number_format:"0%"`**不要**设 `@` 文本格式。**这与"用户当下是否要排序 / 求和"无关**——数据类型由数据本质决定、不由当下用途决定:表格数据几乎总会被后续排序 / 图表 / 二次计算复用,`"54%"` 文本与数值列混排本就破坏一致性,且数字 + `number_format` 显示效果与文本**完全相同**,没有任何理由选文本。**最常见的误判就是"这只是 leaderboard / 报表 / 看板展示,又不用算,写成 `54%` 字符串就行"——这是错的,展示用途不改变"百分比是数值"的事实。**`+table-put` 用 `dtypes` 声明 `int64` / `float64`;版式 `+table-put` 装不下时用 `+cells-set` 传数字 + `number_format`;都别在本地拼成带 `$` / `%` 的字符串走 `+csv-put`。)反过来,编号 `001`、规格 `3-1`、身份证 / 电话 / 单据号等**本质是标识符 / 标签**、要原样保留不被飞书自动解释的内容(否则 `001`→`1`、`3-1`→日期、点分日期 `12.10`→`12.1`(尾零丢失)、长号→科学计数),才以**字符串类型**写入(`dtypes` 设 `object`)并把 `number_format` 设为 `"@"`(文本格式),字面保真。
## 使用场景
写入。向飞书表格的单元格区域写入值、公式、样式、批注、图片或下拉,也可批量写入 CSV / DataFrame。本 reference 覆盖 6 个 shortcut按数据来源 + 内容形态选:
| 场景 | 用这个 shortcut | 原因 |
|------|----------------|------|
| 模型手里已经有 CSV 文本(小规模手动构造、从 `+csv-get` 取到后简单加工) | `+csv-put` | 直接传 CSV 文本 + `--start-cell`,不用自己拼二维 cells 数组;必要时自动扩容行列 |
| 列里有数值语义的数据(数字 / 金额 / 百分比 / 日期 / 计数)→ 飞书要类型保真来源不限DataFrame、Counter、dict、list 都算) | `+table-put` | typed 协议(外层 `{"sheets":[{"name":"…","columns":[...],"data":[[...]],"dtypes":{...},"formats":{...}}]}`**只有这四件套字段**`dtypes` 用 pandas dtype 串声明列类型(`int64` / `float64` / `datetime64[ns]` / `bool` / `object``formats` 给每列展示格式(千分位 / 百分比 / 日期)。**date 落真日期、金额 / 百分比 / 计数等数值列保精度且带 `number_format`(可排序 / 求和 / 入图表)**、string 保前导零,多 sheet 一次写 |
| 写入含样式、批注、图片、数据校验等任意富写入 | `+cells-set` | 唯一支持完整富字段的 shortcut公式 `+csv-put` 也能写) |
| 只改已有 cell 的样式,不动 value/formula | `+cells-set-style` | 拍平 10 个样式字段为独立 flag不触发不必要的值写入 |
| 单 cell 嵌入图片 | `+cells-set-image` | 比 `+cells-set` 参数更简短 |
| 在**已有区域**局部补表头样式/边框 | 先用 `+csv-put` 写值,再用 `+cells-set-style` 补样式 | 分工配合,入参最短 |
| **新建子表 / 整表成套美化**(哪怕全是纯文本) | `+table-put --sheets … --styles …` 一步带值 + 全套样式(区域底色 / 边框 / 列宽 / 行高 / 合并payload 里不存在的 sheet 名自动建子表) | `--styles` 与列是否 typed 无关,纯文本同样适用;比「写值 + 多次刷样式」少好几次调用 |
**选命令按内容形态分流(不设"默认首选"**:① 列有数值语义(金额 / 百分比 / 日期 / 计数)→ `+table-put``dtypes` 声明类型 + `formats` 设展示格式),版式装不下时 → `+cells-set` 传数字 + `number_format`;② 要样式 / 批注 / 图片 / 富文本 → `+cells-set`;③ **仅**全文本、无数值语义的内容平铺 → `+csv-put`(入参最短)。判据详见上方「数字还是文本」。
⚠️ `+csv-put` 可写值或公式:以 `=` 开头的单元格会被当作公式计算(读回时 `formula` 字段保留、`value` 为计算结果)。**公式内部含逗号 / 引号 / 换行时必须按 RFC 4180 转义**——含逗号的字段整格用双引号包裹、字段内部的引号再翻倍:如 `=COUNTIF(D5:D22,"及格")` 必须写成 `"=COUNTIF(D5:D22,""及格"")"`(外层双引号包裹整格,内部 `"及格"` 的引号翻倍成 `""及格""`)。漏转义会被 CSV 解析器按逗号拆列、整块写入区域错位(如本该 `G4:H6` 错成 `G4:K4`),详见下方 `+csv-put` 示例。**因此含逗号 / 引号 / 换行的公式优先改用 `+cells-set`JSON 二维数组)写入——`cells[r][c].formula` 字段直接放公式串,零 CSV 转义负担,从根上避免拆列错位**`+table-put` 的 typed 协议只接受 `columns / data / dtypes / formats` 四件套、没有 `formula` 字段,公式写入只能走 `+cells-set` / `+csv-put`)。此外 `+csv-put` **不会**携带样式/批注/图片,也无法把 `=` 开头的内容当字面量文本写入;需要样式/批注/图片用 `+cells-set`(或"写值 + 补样式"两步法)。
⚠️ **`+csv-put` 会把数值落成文本**:把金额 / 百分比 / 计数等在本地拼成带 `$` / `%` / 千分位的字符串(如 `"$1,234.50"` / `"+30.5%"`)再 `+csv-put` 灌进去,单元格就是**文本**——丢失排序 / 求和 / 图表能力,且与数值列混排无法参与计算。数值该怎么写、何时 `+table-put`、版式装不下时何时退 `+cells-set` 传数字 + `number_format`,判据与分流见上方「数字还是文本」;核心一句:**准备把数字 format 成字符串再写时就是走错了路,数值一律以数字写入 + `number_format` 控制显示。**
⚠️ **`+csv-put` 也会把「看着像数字」的字段静默数值化**与上一条相反的另一半坑CSV 里语义是**日期标签 / 编号 / 标识符**、内容却全是数字字符的列,会被按数值解析——`12.10``12.1`(点分日期尾零丢失)、`3.0``3``001``1`、长号→科学计数。**这类列即使已攒好 CSV 文本也不能裸走 `+csv-put`**:优先 `+table-put` 把该列 `dtypes` 声明为 `object`(无年份的点分标签如 `12.10` / `3-1` 字面保真)或 `datetime64[ns]`(完整真日期),版式装不下再退 `+cells-set` + `number_format:"@"`。此类失真在「抽样首 / 中 / 末」回读时易被掩盖(`12.10` / `12.20` 等尾零行常不落在抽样窗口),日期 / 编号列回读要专挑带尾零 / 前导零的代表值核对。
⚠️ 大数据回写走"`+csv-get``--range` 行窗口分批读到本地 + 本地脚本处理 + `+csv-put` 分批回写"。
## `+cells-set` 写入要点(常用模式 / 公式 / 样式)
> 以下是用 `+cells-set`(及 `+cells-set-style`)做富写入时的常用模式与准则;选哪个 shortcut 见上方「使用场景」。
`+cells-set` 为一块区域设置值 / 公式 / 批注 / 样式,也支持 `rich_text``type: "embed-image"` 嵌入单元格图片。**关键:`cells` 二维数组的行列维度必须与 `range`(闭区间)严格一致,否则触发 `InvalidCellRangeError`**——维度计算示例见文末 `## Schemas``--cells`
> **单元格图片 vs 浮动图片(最易选错)**:图若**属于某条记录、要随那行排序 / 筛选 / 增删**(凭证 / 证件照 / 每行配图,话里带「对应 / 每行 / 这列」等绑定词)→ **单元格图片**(本工具):用 `+cells-set-image`(最短)或 `+cells-set` 的 `rich_text` + `type: "embed-image"`。只是自由摆放的装饰logo / 水印 / 封面)→ 浮动图片,见 lark-sheets-float-image。别因「浮动图更好控制 / 更熟」默认选浮动图——它承载"对应某记录"的图会随增删行 / 排序错位。
常用模式(**必须遵守,禁止逐行写入替代**
- 整列公式:先在 `H2` 写一个公式,再用 `--copy-to-range "H2:H100"``--copy-to-range "H:H"` 向下填充。**禁止对每一行单独调用 `+cells-set` 写入相同结构的公式**
- 整列格式:先在 `J1` 写一个带样式的模板单元格,再用 `--copy-to-range "J:J"`
- 首行样式:先在 `A1` 写一个模板单元格,再用 `--copy-to-range "1:1"`
- 用户说”这列 / 整列 / 这行 / 首行 / 向下复制”时,**必须**使用模板单元格 + `--copy-to-range`
- 多区域写入相同格式/公式结构时,优先写一个模板,再用 `--copy-to-range` 复制到所有目标区域
⚠️ **模板 `--range` 从数据行起算、别把表头圈进去**`--copy-to-range` 会把 `--range` 模板按目标区尺寸周期性平铺,模板里若含了表头行,表头会每隔几行重复铺进数据区。整列填充时模板只取一格数据样式(如 `H2`),不要取成 `H1:H2`
⚠️ **逐行写入公式是常见低效写法**:对每一行单独调用 `+cells-set` 写公式(如 26 次)既慢又易错,且不会自动平移公式引用。正确做法是 1 次模板写入 + 1 次 `--copy-to-range`(公式引用自动平移)。
💡 **写入公式前先按迁移规则改写**:如果公式来自 Excel 或包含数组场景,先读取并遵循 `lark-sheets-formula-translation` 的规则完成改写,再把最终公式写入 `formula` 字段。
💡 **内容与样式分离写入(推荐)**:当需要同时写入内容和样式时,`cells` 中每个单元格都带上 `cell_styles` / `border_styles` 会导致入参非常冗长。由于同一区域的样式通常高度重复(如整列统一背景色、统一边框),推荐拆成两步:
1. **先写内容**`+cells-set` 只传 `value` / `formula`,不带样式,`cells` 入参精简。⚠️ 这里"不带样式"指暂不带 `cell_styles`**不是**降级用 `+csv-put` 铺文本——数值列(百分比 / 金额 / 计数)仍必须以数字写入(百分比传 `0.44`):样式能后补,数据类型不能后补(见上方「数字还是文本」)。
2. **再批量刷样式**:对区域中的一个单元格写入目标样式作为模板,再用 `--copy-to-range` 将样式扩展到整列 / 整行 / 整个区域(`--copy-to-range` 会复制值、公式和样式,所以模板单元格应已包含正确的值)
示例:要对 A2:A100 写入数据并统一设置蓝色背景 + 边框:
```
Step 1: `+cells-set` — range="A2:A100", cells 只含 value无样式入参短
Step 2: `+cells-set` — range="A2", cells 含 value + cell_styles + border_styles单个模板, --copy-to-range="A2:A100"
```
这比在 99 个单元格中都重复写样式 JSON 高效得多。
💡 **样式更新是「部分合并」,不是整体覆盖**`+cells-set-style` / `+cells-batch-set-style`(以及 `+cells-set``cell_styles` / `border_styles`)只改你**显式传入**的样式属性,未传的属性保留原值。两个实用推论:
- **可分层叠加**:对同一区域先刷字体色、再单独刷背景色、再单独刷边框,后一步不会清掉前一步——美化已有区域时无需一次带齐所有字段,可拆成多次窄调用。
- **`border_styles` 按边合并**:只传 `{"top":{...}}` 只更新上边框,`bottom` / `left` / `right` 保留原状;不必为了「只改一条边」而把四边全部重传。(例外见上方「新增行的边框/样式禁止用 `{}` 跳过」:**全新行**底子里没有边框,仍需把要显示的边都显式传出。)
💡 **大批量数据分批写入(推荐)**:当需要写入大量行(如几十行以上)时,不要试图在一次调用中生成全部 `cells` 数据——`cells` 数组过大会让单次生成的内容过长,容易出错或被截断。应将数据拆分为多批,每批 20-50 行,分多次调用 `+cells-set` 逐批生成并写入(如先写 `A2:D21`,再写 `A22:D41`,依此类推)。每次只生成当前批次的数据,控制单次生成量。
注意:
- 不要把 `cells` 写成字符串化 JSON
- `+cells-set` 默认即覆盖非空 cell`--allow-overwrite` 默认 true若要**保护**非空 cell 不被覆盖,显式传 `--allow-overwrite=false`(遇非空 cell 报错)
- 若目标区域涉及合并单元格,不要向合并区域中的非左上角单元格写入数据;如需写入,应改写合并区域左上角单元格,或先调整/取消合并区域
- **构造 `range` 时行号必须基于逻辑行号**:如果之前通过 `+csv-get` 读取了数据CSV 中被双引号包裹的多行字段(如 `"2026年3月2日\n星期一"`)是**一个单元格**,不是两行。写入时的行号必须按逻辑记录计算,不能按物理换行符计数,否则 `range` 会整体偏移导致写入到错误位置
> 用户说"样式和原表一致 / 保持原表格式 / 边框继承"时同理:`cell_styles` 只覆盖字体和对齐、**不含边框**,边框必须用独立 `border_styles` 字段传——完整继承清单见上方「新增列 / 新增行的样式继承」。
⚠️ **公式写入必须自己校验结果(后端不会报语法错)**`+cells-set` 写公式时,即便公式有括号不配对(如 `=IFERROR(VALUE(REGEXEXTRACT(D5, "\d+"))), 0)` 比 IFERROR 多一个 `)`)或用了飞书不支持的函数(如 `GOOGLETRANSLATE` / `CUBEVALUE`**后端工具也会返回 `updated_cells_count=N, rc=0` 的"成功"**——错误会静默写进单元格显示为 `#VALUE!` / `#NAME?` / `#REF!`。因此:
1. **写完立即读回**`+cells-set` 后紧跟 `+csv-get`(或 `+cells-get`)读目标范围前几行,检查是否出现 `#VALUE!` / `#NAME?` / `#REF!` / `#N/A` / `#DIV/0!` / `#NUM!`
2. **看到 `#` 开头的错误值**立即修公式:`#NAME?` 多半是函数名拼错或用了飞书不支持的函数(如 `GOOGLETRANSLATE` / CUBE 系列;注意 `UNIQUE` / `FILTER` / `SPLIT` 飞书是支持的);`#VALUE!` 多半是类型不匹配或括号错位;`#REF!` 是引用错误;`~CIRCULAR~REF~` 是循环引用(公式引用了自身或会闭环)
3. **`--copy-to-range` 扩展前先验证模板**:模板单元格公式自己都算错,`--copy-to-range` 复制到 100 行就是 100 个错误
4. **去重 / 筛选函数**:飞书**支持** `UNIQUE` / `FILTER` / `SPLIT`(原生数组函数,详见 `lark-sheets-formula-translation`),可直接用;`DISTINCT` 不是飞书函数,去重用 `UNIQUE`。大数据量去重 / 分组也可用透视表(`+pivot-{create|update|delete}`,值字段聚合方式选 count
5. **循环引用预检**写聚合公式SUM / AVERAGE / COUNT 等)前必须明确**引用范围不包含目标单元格自身或其传递依赖**。典型反例:在 C3 写 `=SUMIF(B:B,LEFT(B3,9)&"*",C:C)`B 列匹配 B3 前 9 位时 C3 自己也命中,导致 C3 自引用 → `~CIRCULAR~REF~`。修法:用辅助列 / 显式排除自身(`SUMIFS(C:C, B:B, ..., A:A, "<>"&A3)`/ 缩小范围避开自己
6. **REGEX 模式覆盖率验证**:公式里的 `REGEXEXTRACT` / `REGEXMATCH` / `REGEXREPLACE` 等正则模式落地前必须用本地脚本在源列上跑一遍命中率统计(`df[col].str.contains(pattern).mean()`);命中率 < 100% 时必须扩展 pattern 或加多分支IFS / 多个 IFERROR 串联兜底**禁止**只覆盖样本前 N 行就交付典型反例 `REGEXEXTRACT(D5,"长(\d+)")` 只匹配带""前缀的尺寸文本"×"、"×"、"*"等其它分隔符直接漏匹配
7. **公式范围与用户指令字面对齐**用户说" F L 列求和"就必须写 `SUM(F2:L2)` `F2+G2+H2+I2+J2+K2+L2`**不能漏列多列错列**。写完用 `+cells-get` 拿回 `formula` 字符串与用户原话逐字对照参与求和的列名一致 / 起止列号一致 / 运算符一致不一致就是违规
8. **量纲 / 单位换算 / 数量乘项预检(公式不报错但结果整体偏倍数)**从文本提取数字做计算前先核对**单位是否统一是否漏乘数量口径是否一致**——这类错误公式能跑通 `#` 报错回读也看不出"像对的")。必须用本地脚本对 35 个代表行**离线手算一遍预期值**与公式结果逐格比对量级:① 单位不一致先统一再算典型反例尺寸 `320CM*337CM` 直接取数相乘除以 1e6 0.11正确是 CMMM 换算后得 10.78** 100 **);② "单件×数量"的量必须乘数量列典型反例侧面板面积漏乘 F 列数量F=2 的行只算了一半);③ 标准值口径对齐典型反例营养成分 mg/kg g/100g 口径混用整列放大 100 )。**口径 / 单位 / 数量任一项错整列计算结果就是错的这类错误公式不报错回读也不易看出必须靠离线手算对照。**
**公式写入的默认收尾不是停在回读,而是继续跑 `+formula-verify`**`+csv-get` / `+cells-get` 的抽样回读只能帮你快速发现明显错误但它覆盖不到整列中段隐藏行被条件格式遮蔽的错误也看不到 `partial` 截断。**只要这次 `+cells-set` / `--copy-to-range` / `+csv-put` 实际写入了公式收尾默认就是转到 `lark-sheets-formula-verify` `+formula-verify`直到 `status='success'`。** 不要等用户补一句再验证下公式才做
**收到 `formula_errors` 反馈后不要只打补丁**`+cells-set` 返回值里若出现 `formula_errors: [{cell, formula, error_type, detail}]`说明某些 cell 公式编译失败`error_type=compile_failed` 通常是函数语法错如 `SPLIT(x)[1]` 的下标取值飞书不支持SPLIT 本身支持取第 N 项用 `INDEX(SPLIT(...),N)``non_formula` `=` 开头但解析不通过)。此时**禁止只聚焦修报错点的局部语法**如仅把 `[1]` 换成 `INDEX(..,1)`必须
1. **重新审视整条公式的完整性** formula_errors 标出的那一行公式除了下标语法错还可能有其他先天缺陷字符清洗不全IFERROR 兜底漏条件引用列写错修完语法错后立即整体复核
2. **同步对称修复所有相似列**如果同一任务涉及多列相似处理" H 列面积" D 列尺寸、" I 列面积" E 列尺寸**修完一列必须把同样的清洗/兜底逻辑同步到所有相似列**禁止出现 H 列用 `SUBSTITUTE(长)+SUBSTITUTE(高)+SUBSTITUTE(×)` I 列只用 `SUBSTITUTE(×)` 这种不对称处理——会导致一列编译通过有值另一列编译通过但 IFERROR 全返回空用户看到的是"数据为空"而非"公式错"
3. **修完再读回验证**不只看 `formula_errors` 为空这只证明编译通过不证明运行时有值必须 `+csv-get` 读目标列前 3-5 确认**非空源数据对应的目标列有非空计算结果**
4. **核心心智**`formula_errors` "帮你暴露编译错"的工具不是"修掉它就收工"的通行证编译通过 + 运行时 IFERROR 兜底空 = 用户视角的"没算出来"
**新增行的边框/样式禁止用 `{}` 跳过**`cells` 数组里 `{}` 的语义是"**此单元格不做任何修改保留原状态**"。这在写入**已有行**时是安全的原有边框/样式保持不变但在写入**新行**比如表尾追加汇总行扩展行时是灾难新行底子里本来就没边框`{}` 不修改 = 保留无边框状态,导致该 cell 视觉断裂
**"汇总行"识别 `lark-sheets-visual-standards` 拿完整样式规范**下述双重条件**同时满足**才是汇总行禁止仅凭" AVERAGE"就判定
- **语义信号**二选一用户 prompt "合计/汇总/总计/统计/各科平均分/最下面加一行算…/底部总计"等意图词或上下文明确是"表尾追加一行做聚合"
- **结构信号**新行全行都在做聚合 `=SUM/AVERAGE/COUNT/MAX/MIN/SUBTOTAL(...)`支持 IFERROR 包裹**不是**单个 cell 算个参考值或每行都算的派生列
满足上述时**不要在本文里猜样式**直接去读 `lark-sheets-visual-standards` 场景一 1A. 添加汇总行 / 表头行章节按那里的样式要点配齐 `font.bold / horizontal_alignment / background_color / border_styles`
反例**不是**汇总行禁止自动加粗
- 用户说" H5 帮我算个 AVERAGE 参考"→ cell 计算
- 每行都有 `=AVERAGE(本行区间)` 的派生列 属数据列
- 用户明确说"不要加粗/样式和数据行保持一致"→ 遵循用户意图
**正确做法**二选一
- **做法 A推荐**按上方内容与样式分离写入两步法——先用模板单元格 + `--copy-to-range` **完整样式**`cell_styles` + `border_styles` 都要不能只铺 border否则新行字体 / 对齐 / 背景色全裸奔再单独 `+cells-set` value / formula汇总行的 `cell_styles` 要点bold / 背景色 / 上边框 `lark-sheets-visual-standards` 场景一 1A. 添加汇总行 / 表头行」。
- **做法 B**一次写入但每个 cell含空白格都显式带 `cell_styles` + `border_styles`**不能用 `{}`**。
**判断是不是"新行"**写入 range 超出 `+csv-get` 返回的 `current_region` / 下边界 `current_region=A1:H10` `A11:H11`即新行必须按上述做法补边框
## 富文本单元格:超链接 / @人 / @文档(`rich_text`
带显示文本的超链接@人@文档这类富内容**必须** `+cells-set` `rich_text` 字段`cells[].rich_text` 数组每段一个对象 `type`**不能**直接传普通字符串——纯字符串只会被当作纯文本存进单元格完整字段跑 `lark-cli sheets +cells-set --print-schema --flag-name cells`常用段类型
- **超链接带显示文本**`{"type":"link","text":"飞书","link":"https://www.feishu.cn"}` URL 不需要 `rich_text`直接写普通字符串即可
- **@人**`{"type":"mention","mention_token":"<userId>","notify":false}`。**仅支持同租户用户单次写入最多 50 。** `notify` **默认 `true`**会给被 @ 的人发通知不想发务必显式传 `false`
- **@文档**同样 `"type":"mention"``mention_token` 传文档 token `shtXXX`)。
`mention_type`类型编号等可选字段以 `--print-schema` 输出为准
> ⚠️ `rich_text` 一旦设置会**忽略**同一 cell 的 `value`;它与 `formula` / `multiple_values` 三者只能选其一作为内容字段(可叠加 `cell_styles` / `note` 等)。
## Dropdown 选项 + 配色(`+dropdown-set` / `+dropdown-update`
### 选项怎么来:`--options` 与 `--source-range` 二选一
| flag | 选项来源 | 适用场景 |
|---|---|---|
| `--options '["a","b","c"]'` | 写在命令里的固定列表 | 选项集是常量不需要事后维护 |
| `--source-range ''\''Sheet1'\''!T1:T3'` | 已有单元格里的值 | 选项要跟数据动态同步想维护一张枚举值列后多处引用 |
两个 flag **必须传一个、且只能传一个**——同时传或都不传CLI 会立刻报错`--source-range` A1 + sheet 前缀写法 `'Sheet1'!T1:T3`sheet 名按 A1 标准单引号包裹可以指同 sheet 也可以指其它 sheet `'Refs'!A1:A10`)。
### 配色:默认即上色,三种意图三条线
下拉**默认带胶囊高亮**——什么 flag 都不传时所有选项按内置 10 色色板循环上色 UI 手动配下拉的默认行为对齐三种意图
| 想要的效果 | 怎么传 |
|---|---|
| 默认色板循环上色 | 都不传 `--highlight` / `--colors` |
| 按选项指定具体颜色 | 只传 `--colors '["#hex",...]'`不需要再传 `--highlight` |
| 纯白下拉不要高亮 | `--highlight=false`注意 `=false` 不能省单写 `--highlight` cobra 里等价于 true |
`--colors` 长度**可以短于**选项数list 模式短于 `--options` 长度listFromRange 模式短于 `--source-range` 的单元格数未指定的选项按内置色板循环补色**不能长于**——CLI Validate 阶段就会拦截错误形如 `--colors length (4) must not exceed dropdown source size (3)`
`--highlight=false` 显式关闭高亮时`--colors` 即使传了也会被忽略语义自相矛盾但不报错)。
### 最小用例
**`--options` 模式 默认色板最常见**
```
lark-cli sheets +dropdown-set \
--url https://... --sheet-id <id> \
--range A2:A100 \
--options '["待开始","进行中","已完成","已取消"]'
```
**`--options` 模式 指定颜色**4 个选项配 3 个颜色 4 个按色板补
```
lark-cli sheets +dropdown-set \
--url https://... --sheet-id <id> \
--range A2:A100 \
--options '["待开始","进行中","已完成","已取消"]' \
--colors '["#bff7d9","#FFE699","#bacefd"]'
```
**`--source-range` 模式**先在 `'Sheet1'!T1:T3` 维护//保密三行再让 `B2:B21` 引用它
```
lark-cli sheets +dropdown-set \
--url https://... --sheet-id <id> \
--range B2:B21 \
--source-range ''\''Sheet1'\''!T1:T3' \
--colors '["#cce8ff","#ffd6e7","#e6e6e6"]'
```
**纯白下拉**明确告诉用户"不要彩色"时才用
```
lark-cli sheets +dropdown-set \
--url https://... --sheet-id <id> \
--range A2:A100 \
--options '["低","中","高"]' \
--highlight=false
```
> ⚠️ **`--source-range` 必须带 sheet 前缀**(即使跟 `--range` 同 sheet。注意一个坑回读这种 listFromRange 下拉单元格时,`data_validation.range` 看起来不带 sheet 前缀(形如 `$T$1:$T$3`),如果要把读出来的 range 反过来写回 `--source-range`**必须自己重新补上 sheet 前缀**,否则会被拒。
>
> ⚠️ **`--ranges` 类批量 flag 的 sheet 前缀必须「裸写」**——`+cells-batch-set-style` / `+cells-batch-clear` / `+dropdown-update` / `+dropdown-delete` 的 `--ranges` 解析器不接受引号:表名含点或空格(如 `2025.9`、`一月份`)也直接写 `2025.9!A1`,写成 `'2025.9'!A1` 会被当成表名一部分、报 `sheet not found`。**但 `--source-range`、透视表 `--source`、`--range` 走 A1 标准**sheet 名带单引号(如 `'Sheet1'!A1:B2`)是标准写法、裸写也接受,回读统一返回带引号形式——别把 `--ranges` 的裸写要求套到这些 flag 上。
`+dropdown-update` range 批量更新的所有 flag 语义与 `+dropdown-set` 完全一致只是目标 `--ranges` 由单值变成 JSON 数组每项带 sheet 前缀同一份选项 + 配色应用到所有 range
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+cells-set` | write | 单元格 |
| `+cells-set-style` | write | 单元格 |
| `+cells-set-image` | write | 单元格 |
| `+dropdown-set` | write | 对象 |
| `+csv-put` | write | 单元格 |
| `+table-put` | write | 单元格 |
## Flags
### `+cells-set`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 写入区域A1 格式 |
| `--cells` | string + File + Stdin复合 JSON | required | JSON2D 数组 `[[{cell},...],...]`维度与 `--range` 完全一致每个 cell 可含 `value` / `formula` / `cell_styles` / `note` / `rich_text` `type="embed-image"` 单元格嵌图完整字段跑 `--print-schema` |
| `--allow-overwrite` | bool | optional | 允许覆盖非空 cell默认 true设为 false 时遇非空 cell 报错 |
| `--max-cells` | int | optional | 防爆默认 50000隐藏 flag不在 `--help` 列出但可正常传入 |
| `--copy-to-range` | string | optional | 复制范围A1 表示法 --range --cells 写入的内容/公式/样式取决于实际传入字段复制到该区域公式引用自动平移 C2=B2 C3=B3。适合先写一行/一块模板再扩展填充整列/整区域(如 --range A1:G1 写模板--copy-to-range A1:G100 填充 100 )。支持整行 3:6整列 C:E到列尾 D3:D到行尾 D3:3支持英文逗号分隔多个目标区域 C1:D2,E5:F6 |
### `+cells-set-style`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 目标范围A1 格式 `A1:B2` |
| `--background-color` | string | optional | 背景颜色十六进制 `#ffffff` |
| `--font-color` | string | optional | 字体颜色十六进制 `#000000` |
| `--font-family` | string | optional | 字体名称 `Arial``微软雅黑` |
| `--font-size` | float64 | optional | 字体大小px101214 |
| `--font-style` | string | optional | 字体样式可选值`normal` / `italic` |
| `--font-weight` | string | optional | 字重可选值`normal` / `bold` |
| `--font-line` | string | optional | 字体线条样式可选值`none` / `underline` / `line-through` |
| `--horizontal-alignment` | string | optional | 水平对齐可选值`left` / `center` / `right` |
| `--vertical-alignment` | string | optional | 垂直对齐可选值`top` / `middle` / `bottom` |
| `--word-wrap` | string | optional | 换行策略可选值`overflow` / `auto-wrap` / `word-clip` |
| `--number-format` | string | optional | 数字格式文本 `@`数字 `0.00`货币 `$#,##0.00`日期 `mm/dd/yyyy` |
| `--border-styles` | string + File + Stdin复合 JSON | optional | 边框配置 JSON`{ top: {style,color,weight}, bottom: ..., left: ..., right: ... }`4 方向结构相同 |
### `+cells-set-image`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 目标单元格A1 格式必须单 cell `A1`起止 cell 须相同 |
| `--image` | string | required | 本地图片路径支持 PNG / JPEG / JPG / GIF / BMP / JFIF / EXIF / TIFF / BPG / HEIC |
| `--name` | string | optional | 图片文件名含扩展名省略时取 `--image` basename |
### `+dropdown-set`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 目标范围A1 格式 `A2:A100` |
| `--options` | string + File + Stdin复合 JSON | xor | 下拉选项 JSON 数组例如 `["opt1","opt2"]`服务端不限制选项数量也不限制单个选项长度含逗号的选项可以接受写入时会自动转义)。大量选项建议改用 `--source-range` |
| `--colors` | string + File + Stdin简单 JSON | optional | 下拉胶囊背景色RGB hex 数组 `["#1FB6C1","#F006C2"]`)。长度可短不可长——超长 Validate 拦截`--colors length (N) must not exceed dropdown source size (M)`未指定项按内置 10 色色板循环补色。**单独传即生效**`--highlight=false` 时被忽略 |
| `--multiple` | bool | optional | 启用多选默认 `false` |
| `--highlight` | bool | optional | 下拉胶囊背景色高亮开关。**不传 = 开**(按内置 10 色色板循环上色`--highlight=false` 关闭得到纯白下拉配色用 `--colors` 覆盖 |
| `--source-range` | string | xor | listFromRange 模式的下拉源 rangeA1 表示法 + sheet 前缀 `'Sheet1'!T1:T3`)。映射到 server `data_validation.range`搭配 server `data_validation.type='listFromRange'` 自动生效 `--options` 二选一 `--options` inline 列表type=list传本 flag range 引用type=listFromRange`--colors` 长度规则不变(≤ range 单元格数`--highlight` / `--multiple` 行为相同 `--highlight` 开启且 source 覆盖单元格数超过 2000 服务端会将该下拉判为 option-error这是不支持的组合CLI 会向 stderr 输出 warning如需取消 `--highlight=false` |
### `+csv-put`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--start-cell` | string | required | 目标区域起点 A1 `A1``B5`不带 sheet 前缀 `--sheet-id` / `--sheet-name` 指定 sheet必须是单个单元格不接受范围写法终点按 CSV 实际行列数自动推断 |
| `--csv` | string + File + Stdin JSON 文本 | required | RFC 4180 CSV 文本可写值或公式= 开头的单元格按公式计算);不带样式 / 批注 / 图片需要这些用 +cells-set |
| `--allow-overwrite` | bool | optional | 允许覆盖默认 true设为 false 时若目标非空报错 |
| `--range` | string | optional | --start-cell 的别名 +csv-get / +cells-set 一致 --range 定位传区间 A1:H17时自动取其左上角单元格隐藏 flag不在 `--help` 列出但可正常传入 |
### `+table-put`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--sheets` | string + File + Stdin复合 JSON | required | Typed 表格协议pandas-DataFrame-shapedJSON顶层 `{"sheets":[...]}`每个数组项是一张子表 `{name, start_cell?, mode?, header?, allow_overwrite?, columns:["colA","colB",...], data:[[...]], dtypes?:{colA:pandasDtype, ...}, formats?:{colA:numberFormat, ...}}` —— `name` 与外层 `sheets` 数组都不可省Agents `scripts/sheets_df.py` `df_to_sheet(df, name)` 一行把 DataFrame 转成一项多子表就 list 拼起来再包 `{"sheets":[...]}`)。`dtypes` 值是 pandas dtype 字符串`int64``float64``Int64``bool``boolean``datetime64[ns]``object`、...CLI 端映射成内部 string/number/date/bool —— 省略 `dtypes` 时该列按文本写入适合原始 CSV-shaped 数据)。`formats[col]` Excel number_format 字符串 `#,##0.00``0.0%``yyyy-mm`缺省时 date 列用 `yyyy-mm-dd`string 列用文本格式 `@` |
| `--styles` | string + File + Stdin复合 JSON | optional | 类型保真写入后再应用的视觉处理操作 JSON顶层 `{styles:[...]}`每项对应一个被写入的子表 `name`并至少给 `cell_styles` / `row_sizes` / `col_sizes` / `cell_merges` 之一`cell_styles` A1 单元格 range + 扁平样式字段字段同 +cells-set-style number_format / 颜色 / 对齐 / border_stylesrow/col sizes 用行/列范围 + type/sizemerges 用单元格 range + 可选 merge_typestyles 数组的长度/顺序/name 必须与被写入的子表对应 --sheets.sheets 一一对应)。完整 cell_styles 字段结构跑 `+table-put --print-schema --flag-name styles` |
## Schemas
> 复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 `## Examples`,或用 `--print-schema` 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。
### `+cells-set` `--cells`
_【维度】行列数必须与 range 完全一致:'A1:C2'→[[_,_,_],[_,_,_]]2行×3列'B5:B7'→[[_],[_],[_]]3行×1列'A1'→[[_]]1×1_
**二维数组项**类型 object
- `value` (oneOf?) 静态单元格值文本数字布尔
- `formula` (string?) '=' 开头的单元格公式例如'=SUM(A1:A10)'
- `note` (string?) 单元格批注/备注
- `cell_styles` (object?) 单元格样式属性包括字体颜色对齐方式和数字格式 { font_color?: string, font_family?: string, font_size?: number, font_weight?: enum, font_style?: enum, 11 }
- `border_styles` (object?) 单元格边框配置 top/bottom/left/right 四个方向每个方向的结构相同 top { top?: object, bottom?: object, left?: object, right?: object }
- `rich_text` (array<object>?) — 富文本内容 each: { type: enum, text: string, style?: object, link?: string, mention_token?: string, …共 17 项 }
- `multiple_values` (array<object>?) — 多值内容,用于支持多选的列表验证单元格 each: { value: oneOf, format?: string }
- `data_validation` (object?) — 数据验证配置 { type: enum, items?: array<string>, range?: string, operator?: enum, values?: array<oneOf>, …共 9 项 }
### `+cells-set-style` `--border-styles`
_单元格边框配置含 top/bottom/left/right 四个方向,每个方向的结构相同(见 top_
**顶层字段**
- `top` (object?) { style?: enum, weight?: enum, color?: string }
- `bottom` (object?) { style?: enum, weight?: enum, color?: string }
- `left` (object?) { style?: enum, weight?: enum, color?: string }
- `right` (object?) { style?: enum, weight?: enum, color?: string }
### `+dropdown-set` `--options`
_列表选项_
**数组项**(类型 string
- 标量string
### `+table-put` `--sheets`
_一个或多个子表的 typed 数据,每个数组元素写入一张子表;支持多 DataFrame → 多子表一次写入_
**数组项**(类型 object
- `name` (string) — 目标子表名
- `start_cell` (string?) — 写入起点单元格A1 记法,如 "B2"),默认 "A1"
- `mode` (enum?) — overwrite默认从 start_cell 起写「表头 + 数据」块append把数据追加到子表已有数据下方默认不重复表头 [overwrite / append]
- `header` (boolean?) — 是否写一行列名表头
- `allow_overwrite` (boolean?) — 为 false 时,若写入会落在非空单元格则拒写以保护原数据(返回 partial_success
- `columns` (array<string>) — 列名字符串数组,顺序与 `data` 中每行取值一一对应
- `data` (array<array<string|number|boolean|null>>) — 数据行;每行是一个数组,长度必须等于 `columns`
- `dtypes` (object?) — 可选
- `formats` (object?) — 可选
### `+table-put` `--styles`
**数组项**(类型 object
- `cell_merges` (array<object>?) — 单元格合并操作数组range 使用 A1 单元格范围merge_type 默认 all each: { merge_type?: enum, range: string }
- `cell_styles` (array<object>?) — 单元格样式操作数组;每项用 A1 单元格 range 指定范围,字段名与 +cells-set-style 对齐 each: { background_color?: string, border_styles?: object, font_color?: string, font_family?: string, font_line?: enum, …共 13 项 }
- `col_sizes` (array<object>?) — 列宽操作数组range 使用列范围如 A:Ctype 为 pixel/standardpixel 需要 size each: { range: string, size?: number, type: enum }
- `name` (string) — 子表名
- `row_sizes` (array<object>?) — 行高操作数组range 使用行范围如 1:3type 为 pixel/standard/autopixel 需要 size each: { range: string, size?: number, type: enum }
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR
### `+cells-set` 的拆分与转介绍
"工具选择"段已讲清纯值(`+csv-put`vs 富写入(`+cells-set`)。下表补 CLI 侧的 `+cells-set` **兄弟拆分**,以及不属于本 reference 的**跨 reference 转介绍**——避免 agent 用 `+cells-set` 硬扛所有写入场景。
| 写入场景 | 用这个 | 不要用 |
|---------|--------|--------|
| 只改**已有 cell 的样式**,不动 value/formula | `+cells-set-style` | `+cells-set`(会触发不必要的值写入) |
| 把**单张图片嵌入**到某个 cell | `+cells-set-image` | `+cells-set`(参数更繁琐) |
| **插行/列 + 写入** 这种多步组合,且要原子 | `+batch-update`(见 lark-sheets-batch-update | 多次独立 `+cells-set`(非原子;插入会扰动后续 range |
| 在**多个不连续 range** 上应用同一组样式 | `+cells-batch-set-style`(见 lark-sheets-batch-update | 多次 `+cells-set-style`(非原子) |
### `+cells-set`
示例:
```bash
# 纯值(数组形态);默认即覆盖非空 cell无需显式传 --allow-overwrite
lark-cli sheets +cells-set --url "https://example.feishu.cn/sheets/shtXXX" \
--sheet-name "Sheet1" --range "A1:B2" \
--cells '[[{"value":"name"},{"value":"score"}],[{"value":"alice"},{"value":95}]]'
# 富 cell公式 + 样式cells 是二维矩阵每元素一个 cell schema
lark-cli sheets +cells-set --spreadsheet-token shtXXX --sheet-id "$SID" \
--range "C2:C10" --cells @rich-cells.json
```
`--cells` 富格式见 `## Schemas`cells 元素含 value / formula / cell_styles / border_styles / data_validation / multiple_values / note / rich_text值 / 公式 / 样式 / 批注 / 嵌入图片可同一次写入混合提交。
> 中间想跳过的 cell 用空对象 `{}` 占位(底层语义为"保留原值不变"`--cells` 维度仍须与 `--range` 完全一致。例:`--range A1:A5 --cells '[[{"value":1}],[{}],[{}],[{}],[{"value":5}]]'` 只写 A1 和 A5。
>
> 跨多个不连续区域散点写入(如 `D2` + `F7` + `J15`)不属于 `+cells-set` 的能力范围——请用 `+batch-update` 把多次 `+cells-set` 打包成单次原子请求。
### `+cells-set-style`
只改样式,不动 value / formula。10 个 cell_styles 字段拍平为独立 flag边框走 `--border-styles` JSON。
```bash
# 加粗 + 黄底
lark-cli sheets +cells-set-style --url "..." --sheet-name "Sheet1" \
--range "A1:B2" --font-weight bold --background-color "#FFFF00"
# 配套边框
lark-cli sheets +cells-set-style --url "..." --sheet-id "$SID" \
--range "A1:D10" --font-size 12 --horizontal-alignment center \
--border-styles '{"top":{"style":"solid","color":"#000","weight":"thin"},"bottom":{"style":"solid","color":"#000","weight":"thin"}}'
```
### `+cells-set-image`
把单张图片嵌入 cell必须单 cell 范围):
```bash
lark-cli sheets +cells-set-image --url "..." --sheet-name "Sheet1" \
--range "A1" --image ./logo.png
```
### `+csv-put`
示例:
```bash
# 内联 CSV
lark-cli sheets +csv-put --url "https://example.feishu.cn/sheets/shtXXX" \
--sheet-name "Sheet1" --start-cell "A1" \
--csv $'name,score\nalice,95\nbob,87'
# 从文件
lark-cli sheets +csv-put --spreadsheet-token shtXXX --sheet-id "$SID" \
--start-cell "A1" --csv @data.csv
```
> `+csv-put` 比 `+cells-set` 短得多——批量灌值或公式时优先用它。需要样式/批注/图片才换 `+cells-set`。
>
> ✅ `=` 开头的单元格会被当作公式计算(不是字面量文本):
>
> ```bash
> lark-cli sheets +csv-put --url "..." --sheet-name "Sheet1" \
> --start-cell "A1" \
> --csv $'name,score\nalice,=SUM(B2:B10)'
> # ↑ B2 写入公式 =SUM(B2:B10),读回 formula 保留、value 为计算结果。
> # 反过来:无法用 +csv-put 写「= 开头的字面量文本」(会被当公式);样式/批注/图片仍用 +cells-set。
> ```
>
> ⚠️ **公式内部含逗号 / 引号必须 RFC 4180 转义**CSV 用逗号分隔字段,公式里的逗号(如 `COUNTIF(D5:D22,"及格")` 的参数分隔逗号)会被解析器当成字段分隔符,把一格拆成多格、整块二维结构压扁错位。规则:**含逗号的字段整格用双引号包裹,字段内部的引号再翻倍**
>
> ```bash
> # 从 G4 写一个 2 列 3 行的统计块;=COUNTIF 含逗号 + 内部引号,必须转义
> lark-cli sheets +csv-put --url "..." --sheet-name "Sheet1" \
> --start-cell "G4" \
> --csv $'统计项,结果\n成绩总和,=SUM(C5:C22)\n及格人数,"=COUNTIF(D5:D22,""及格"")"'
> # ↑ "=COUNTIF(D5:D22,""及格"")":外层双引号包裹整格,内部 "及格" 的引号翻倍成 ""及格""。
> # 裸写 =COUNTIF(D5:D22,"及格") 会被 CSV 按逗号拆成两格、写入区域从 G4:H6 错位成 G4:K4。
> ```
>
> 💡 **含逗号 / 引号 / 换行的公式优先用 `+cells-set`JSON 二维数组)写入**——`cells[r][c].formula` 字段直接放公式串,没有 CSV 转义负担,从根上杜绝拆列错位。`+table-put` 的 typed 协议只有 `columns / data / dtypes / formats` 四件套、没有 `formula` 字段,公式写入用 `+cells-set` 或 `+csv-put`。准备给 `+csv-put` 的公式加逗号时,先考虑换 `+cells-set`
>
> ```bash
> # 同样的统计块,结构化写入无需任何转义
> lark-cli sheets +cells-set --url "..." --sheet-name "Sheet1" --range "G4:H6" \
> --cells '[[{"value":"统计项"},{"value":"结果"}],[{"value":"成绩总和"},{"formula":"=SUM(C5:C22)"}],[{"value":"及格人数"},{"formula":"=COUNTIF(D5:D22,\"及格\")"}]]'
> ```
> **定位 + 写入边界(关键,避免误覆盖)**
> - 定位用 `--start-cell`(锚点 = 左上角单元格);也接受 `--range` 别名(与 `+csv-get` / `+cells-set` 一致,传区间会自动取左上角)。
> - ⚠️ `--start-cell` / `--range` **只定左上角、不限制写入大小**CSV 从锚点按自身行列数 auto-expand 铺开。给一个"小 range"**不会**截断数据——超出部分照写,且默认覆盖。这与 `+cells-set --range`(精确矩形、`--cells` 必须与 range 同维)语义相反,别把那套心智搬过来。
> - dry-run 与成功响应都回显 `writes_range`(实际落区,如 `B2:D4`**写前先 `--dry-run` 看一眼落区**,确认不会盖到相邻数据。
> - 要保护非空 cell`--allow-overwrite=false`(落区内出现非空 cell 即报错)。
### `+table-put`DataFrame → 飞书,类型保真写入)
把结构化数据DataFrame、list of dict、Counter类型保真写入**已有**表(写入语义同 `+cells-set`)。协议形状**对齐 pandas `to_json(orient="split")`**`columns:[列名]` + `data:[[行...]]`,可选 `dtypes:{列名:pandas_dtype}` 决定每列类型number 保精度、date 落真日期),可选 `formats:{列名:number_format}` 覆盖显示格式(千分位 / 百分比 / 自定义日期。dtypes 缺失时整张表按 string 写入(带 `@` 文本格式,邮编 / 订单号等含前导零的 id 保真)。
只写入**已有**表(`--url` / `--spreadsheet-token` 二选一必填),不新建工作簿——**要新建表格直接用 `+workbook-create --sheets`**(同协议、一步建表 + 类型保真写入,详见 workbook reference。读回用镜像命令 `+table-get`(见 read-data reference输出与 `--sheets` 同构、可 round-trip。
```bash
# sheet 按 name 匹配、缺则新建;多 DataFrame 经 stdin 一次写多 sheet
python export.py | lark-cli sheets +table-put --url "<表URL>" --sheets -
# 某 sheet 带 "mode":"append" 追加到已有数据末尾、默认不重复表头
lark-cli sheets +table-put --spreadsheet-token "<token>" --sheets @payload.json
```
每个 sheet 还可带 `"allow_overwrite": false`(遇非空拒写、保护原数据)、`"header": false`(只写数据不写表头)。完整字段跑 `+table-put --print-schema --flag-name sheets`
#### DataFrame → 协议(用 `df_to_sheet` helper
pandas 的 `df.to_json(orient="split", date_format="iso")` 一步完成所有清洗NaN→null、Timestamp→ISO 字符串、numpy 标量→原生数字),把 dtypes 拼上即可。本 skill 把这段 5 行 helper 打包成可 import 的 [`scripts/sheets_df.py`](../scripts/sheets_df.py)(含 `df_to_sheet``sheet_to_df`,写入 / 读回成对):
```python
from sheets_df import df_to_sheet
# 单 sheet显式 format 覆盖默认显示)
payload = {"sheets": [df_to_sheet(df, "销售", {"营收": "#,##0.00", "毛利率": "0.0%"})]}
# 多 sheet——helper 让每个 sheet 一行,不再重复 boilerplate
payload = {"sheets": [df_to_sheet(df1, "销售"),
df_to_sheet(df2, "成本"),
df_to_sheet(df3, "利润")]}
```
> **CSV-shaped 全文本数据**(不需要类型保真、含前导零的 id 也要保留)省掉 dtypes 即可inline 一行写完,不必走 helper注意保留 `date_format="iso"`,否则 datetime 列会被序列化成 epoch 毫秒数字CLI 拒绝):
> ```python
> payload = {"sheets": [{"name": "原始",
> **json.loads(df.to_json(orient="split", date_format="iso"))}]}
> ```
> **别把 `to_json + json.loads` 换成 `df.to_dict(orient="split")`**:会留 `numpy.int64` 让 `json.dumps` 后续报 "not serializable"——这一步是清洗的关键。
不用 pandas 也行——typed 协议就是纯 JSON。手写场景
```python
# Counter / dict / 手拼数据:直接写 columns + data按需加 dtypes/formats
payload = {"sheets": [{
"name": "渠道",
"columns": ["channel", "count", "rate"],
"data": [["app", 1240, 0.62], ["web", 760, 0.38]],
"dtypes": {"count": "int64", "rate": "float64"},
"formats": {"rate": "0.0%"},
}]}
```
> **dtype 速查**`int64`/`float64`(数值)、`Int64`含空值的整数nullable、`bool`/`boolean`、`datetime64[ns]`date默认 `yyyy-mm-dd`)、`object`string。pandas dtype 字符串原样塞进 dtypes 即可CLI 端按前缀匹配(`int*`/`uint*`/`Int*`/`float*` → number 等)。未识别 dtype 兜底为 string。
#### `--styles`(写入时同时套样式)
`--styles` 在 typed 写入后顺带应用视觉处理,省掉一次 `+cells-set-style` 往返。协议与 `+workbook-create --styles` **完全同构**(详见 workbook reference顶层 `{styles:[...]}`,数组每项对应一个被写入的子表、含 `name`,并按能力拆成四类可选数组——`cell_styles`A1 单元格 range + 扁平样式字段,含 `number_format` / 颜色 / 对齐 / `border_styles`,随内容在同一次写入里一并应用)、`cell_merges``row_sizes``col_sizes`。styles 数组的长度 / 顺序 / name 必须与被写入的子表对应(与 `--sheets.sheets` 一一对应)。
```bash
lark-cli sheets +table-put --url "<表URL>" \
--sheets '{"sheets":[{"name":"明细","columns":["日期","金额"],"dtypes":{"日期":"datetime64[ns]","金额":"float64"},"formats":{"金额":"#,##0.00"},"data":[["2024-01-15",1234.5]]}]}' \
--styles '{"styles":[{"name":"明细",
"cell_styles":[{"range":"A1:B1","font_weight":"bold","background_color":"#f5f5f5","horizontal_alignment":"center"}],
"cell_merges":[{"range":"A1:B1"}],
"col_sizes":[{"range":"A:B","type":"pixel","size":120}]}]}'
```
完整字段跑 `+table-put --print-schema --flag-name styles`
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套;`+cells-set``--cells` 必须能解析为 JSON 二维矩阵且行列数与 `--range` 完全一致;`+cells-set-style` 的样式 flag 至少一个非空(或带 `--border-styles``+cells-set-image``--range` 必须是单 cell起止 cell 相同);`+csv-put``--csv` 必须能按 RFC 4180 解析;`+table-put` 给了 `--styles` 则按子表名 / 顺序 / 数量与 `--sheets.sheets` 对齐校验;防爆参数上限校验。
- `DryRun`:输出目标 range + 推断尺寸 + 是否覆盖非空 cell 警告,零网络副作用。
- `Execute`:写后不自动回读;如需确认,自行调用 `+cells-get --range <写入区域> --include value,formula` 抽样核对。