forked from AxTps/Starlight_Lancher
feat:移除了弹窗,服务器添加sls
This commit is contained in:
159
.claude/skills/lark-base/SKILL.md
Normal file
159
.claude/skills/lark-base/SKILL.md
Normal file
@ -0,0 +1,159 @@
|
||||
---
|
||||
name: lark-base
|
||||
version: 1.2.3
|
||||
description: "飞书多维表格(Base)操作:建表、字段、记录、视图、统计、公式/lookup、表单、仪表盘、workflow、角色权限;遇到 Base/多维表格/bitable 或 /base/ 链接时使用。文件导入转 lark-drive,认证/授权转 lark-shared。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["lark-cli"]
|
||||
cliHelp: "lark-cli base --help"
|
||||
---
|
||||
|
||||
# base
|
||||
|
||||
## 何时使用
|
||||
|
||||
使用本 skill:
|
||||
|
||||
- 用户明确提到 Base / 多维表格 / bitable,或给出 `/base/` 链接。
|
||||
- 用户要在 Base 内建表、改表、管理字段、写记录、查记录、配视图。
|
||||
- 用户要在 Base 内做公式字段、lookup 字段、跨表计算、派生指标、筛选聚合、TopN、统计分析。
|
||||
- 用户要管理 Base 表单、仪表盘、workflow、高级权限或角色。
|
||||
- 用户要把旧 Base 聚合式命令或旧写法迁移到当前 `lark-cli base +...` shortcut。
|
||||
|
||||
不要使用本 skill:
|
||||
|
||||
- 只是认证、初始化配置、切换身份、处理 scope 或权限授权恢复,转 `lark-shared`。
|
||||
- 把本地 Excel / CSV / `.base` 导入成 Base,转 `lark-drive +import --type bitable`。
|
||||
- 泛化数据分析、字段设计、公式讨论,但没有 Base/多维表格上下文。
|
||||
|
||||
## 使用边界
|
||||
|
||||
- Base 业务操作只使用 `lark-cli base +...` shortcut,不使用旧聚合式 `+table / +field / +record / +view / +history / +workspace`。
|
||||
- 执行 update 前必须先查当前 shortcut 的 `--help` 或对应 reference。若命令要求完整配置,首次请求必须基于可信的当前配置执行 read-modify-write:只修改用户明确指定的内容,保留其他仍适用的可写配置,并按命令要求的结构提交。若命令支持局部/delta update,按其契约提交最小合法 payload;不得以不完整请求试错补参。
|
||||
- 用户要把 Excel / CSV / `.base` 导入成 Base 时,先转 `lark-cli drive +import --type bitable`,导入完成后再回到 Base 命令。
|
||||
- 认证、初始化、scope、身份切换、权限不足恢复属于 `lark-shared`;Base 文档只保留会影响 Base 路径选择的权限规则。
|
||||
|
||||
## 先获取 Base Token 和所需 ID
|
||||
|
||||
进入任何需要目标 Base 的 shortcut 前,必须先拿到可用的 `base_token`,以及当前任务需要的 `table_id` / `view_id` / `record_id` / `form_id` / `dashboard_id` / `workflow_id` 等真实 ID;不要把完整 URL、wiki token、workspace token 或孤立 raw token 直接当作 `--base-token`。
|
||||
|
||||
- 用户输入 URL 或分享链接:先运行 `lark-cli base +url-resolve --url "<url>" --as user`,用返回的 `base_token` 和相关 ID 继续后续命令。
|
||||
- 用户输入 Base 标题、关键词或不确定名称:先运行 `lark-cli base +title-resolve --title "<keyword>" --as user`;`--title` 传入标题中的短关键词,不超过 30 个字符;过长标题先取最有区分度的短关键词;多候选时先让用户消歧,不要猜。
|
||||
- 文档嵌入 Base 标签:直接读取 `<bitable>` / `<base_refer>` 的 `token` 作为 `--base-token`,`table-id` 作为 `--table-id`,`view-id` 作为 `--view-id`;孤立 raw token 不走 `+url-resolve`。
|
||||
- 仍无法定位且用户不是要新建 Base 时,先反问用户要操作哪一个 Base;用户要新建时才用 `+base-create`。
|
||||
|
||||
## 快速路由
|
||||
|
||||
| 用户目标 | 优先命令 | 何时读 reference |
|
||||
|---|---|---|
|
||||
| 查 Base 本体 | `+base-get` | 用返回确认 Base 名称、owner、权限和可继续操作的 token |
|
||||
| 创建/复制 Base | `+base-create` / `+base-copy` | 新建时强烈推荐用 `--table-name` + `--fields` 同时配置新 Base 里唯一一个初始数据表的 name 和 schema;写入后报告新 Base 标识和 `permission_grant` |
|
||||
| 查看 Base 内资源目录 | `+base-block-list` | 想先了解一个 Base 里有哪些 table/docx/dashboard/workflow/folder 时优先用它;返回 ID 关系和 fewshot 看 `--help` |
|
||||
| 管理 Base 内资源目录 | `+base-block-create/move/rename/delete` | 创建或整理 Base 直接管理的 folder/table/docx/dashboard/workflow;资源内容继续用对应命令 |
|
||||
| 管理数据表 | `+table-list/get/create/update/delete` | 处理 table 的列出、详情、创建、重命名和删除 |
|
||||
| 列/查/删字段 | `+field-list/get/delete/search-options` | 写入前用 list/get 确认字段类型、选项、ID;删除前确认目标字段 |
|
||||
| 创建/更新字段 | `+field-create` / `+field-update` | 必读 [lark-base-field-json.md](references/lark-base-field-json.md);公式读 [formula-field-guide.md](references/formula-field-guide.md);lookup 读 [lookup-field-guide.md](references/lookup-field-guide.md);命令细节读 [lark-base-field-create.md](references/lark-base-field-create.md) / [lark-base-field-update.md](references/lark-base-field-update.md) |
|
||||
| 读记录明细 | `+record-get` / `+record-list` / `+record-search` | 涉及筛选、排序、Top/Bottom N、聚合、多表关联、全局结论时读 [lark-base-data-analysis-sop.md](references/lark-base-data-analysis-sop.md) |
|
||||
| 写记录 | `+record-upsert` / `+record-batch-create` / `+record-batch-update` | 必读 [lark-base-record-upsert.md](references/lark-base-record-upsert.md) / [lark-base-record-batch-create.md](references/lark-base-record-batch-create.md) / [lark-base-record-batch-update.md](references/lark-base-record-batch-update.md) 和 [lark-base-cell-value.md](references/lark-base-cell-value.md) |
|
||||
| 附件字段 | `+record-upload-attachment` / `+record-download-attachment` / `+record-remove-attachment` | 附件不要伪造成普通 CellValue;上传走本地文件,下载/删除按 file token 或字段定位 |
|
||||
| 删除记录 / 分享记录链接 / 历史 | `+record-delete` / `+record-share-link-create` / `+record-history-list` | 删除前确认 record;分享链接最多 100 条;历史读 [lark-base-record-history-list.md](references/lark-base-record-history-list.md),只查单条记录,不做整表审计 |
|
||||
| 管理视图 | `+view-*` | `+view-set-filter` 读 [lark-base-view-set-filter.md](references/lark-base-view-set-filter.md);其余配置先 get 现状,再按返回结构更新 |
|
||||
| 一次性聚合统计 | `+data-query` | 必读 [lark-base-data-analysis-sop.md](references/lark-base-data-analysis-sop.md) 和入口 [lark-base-data-query-guide.md](references/lark-base-data-query-guide.md);完整 DSL 再读 [lark-base-data-query.md](references/lark-base-data-query.md) |
|
||||
| 公式字段 | `+field-create/update --json '{"type":"formula",...}'` | 必读 [formula-field-guide.md](references/formula-field-guide.md),读后再加隐藏确认 flag `--i-have-read-guide` |
|
||||
| Lookup 字段 | `+field-create/update --json '{"type":"lookup",...}'` | 必读 [lookup-field-guide.md](references/lookup-field-guide.md),读后再加隐藏确认 flag `--i-have-read-guide` |
|
||||
| 表单提交 | `+form-submit` | 先读 [lark-base-form-detail.md](references/lark-base-form-detail.md) 获取题目、filter 和附件所需 `base_token`;提交 JSON 读 [lark-base-form-submit.md](references/lark-base-form-submit.md) |
|
||||
| 表单题目创建/更新 | `+form-questions-create` / `+form-questions-update` | 读 [lark-base-form-questions-create.md](references/lark-base-form-questions-create.md) / [lark-base-form-questions-update.md](references/lark-base-form-questions-update.md) |
|
||||
| 其他表单管理 | `+form-list/get/detail/create/update/delete` / `+form-questions-list/delete` | `+form-detail` 读 [lark-base-form-detail.md](references/lark-base-form-detail.md);删除前确认目标表单 |
|
||||
| 仪表盘与组件 | `+dashboard-*` / `+dashboard-block-*` | 提到图表/看板/block 时先读 [lark-base-dashboard.md](references/lark-base-dashboard.md);组件 `data_config` 读 [dashboard-block-data-config.md](references/dashboard-block-data-config.md);读取图表计算结果用 `+dashboard-block-get-data` |
|
||||
| Workflow | `+workflow-*` | 创建/更新或理解 steps 时读入口 [lark-base-workflow-guide.md](references/lark-base-workflow-guide.md) 和 steps JSON SSOT [lark-base-workflow-schema.md](references/lark-base-workflow-schema.md);list/get/enable/disable 只处理 workflow ID 与启停状态 |
|
||||
| 高级权限与角色 | `+advperm-*` / `+role-*` | 角色操作先读入口 [lark-base-role-guide.md](references/lark-base-role-guide.md);角色 create/update 或解读完整配置再读权限 JSON SSOT [role-config.md](references/role-config.md);系统角色不可删除;关闭高级权限会影响自定义角色 |
|
||||
|
||||
## Base 心智模型
|
||||
|
||||
- Base 曾用名 Bitable;返回字段、错误或旧文档里的 `bitable` 多为历史兼容,不代表应改走裸 API 或另一套命令。
|
||||
- `+base-block-list` 是查看一个 Base 内资源目录的新入口:它列出这个 Base 直接管理的 `folder/table/docx/dashboard/workflow`,适合先判断 Base 里有什么,再决定走 table、dashboard、workflow 或 docx 命令。
|
||||
- `base-block` 只负责资源目录管理,包括创建资源、移动到 folder、重命名和删除;具体资源内容仍走 table/dashboard/workflow 命令。
|
||||
- 新建 Base 时,强烈推荐一次性执行 `lark-cli base +base-create --name "<base>" --table-name "<table>" --fields '<field-json-array>'`,同时配置新 Base 里唯一一个初始数据表的 name 和 schema;使用 `--fields` 前先读 [lark-base-field-json.md](references/lark-base-field-json.md) 或复用 `+field-create` 的字段 JSON 形状,不要猜字段属性。
|
||||
- `+base-create` 不传 `--table-name` 和 `--fields` 时,会创建一个默认 schema 的初始数据表。
|
||||
- 表、字段、视图、workflow、dashboard block 的名称和 ID 必须来自真实返回,不要凭用户口述猜。
|
||||
- 存储字段可写;系统字段、`formula`、`lookup` 只读;附件字段走专用 attachment 命令。
|
||||
- 一次性原始记录查询优先用 `+record-list` / `+record-search` 的 filter/sort;聚合分析优先用 `+data-query`;需要长期显示在表中时,才新增 `formula` / `lookup` 字段。
|
||||
- `formula` 适合常规计算、条件判断、文本/日期处理和长期派生指标;`lookup` 适合明确的跨表查找、筛选后取值或聚合引用。
|
||||
- 写入、分析、公式、lookup、workflow、dashboard 前,先读取真实结构:表、字段、视图、关联表和 dashboard block 名称都以命令返回为准。
|
||||
- 跨表场景必须读取目标表结构;link 单元格中的关联 `record_id` 只是连接键,最终回答要回查并展示用户可读字段。
|
||||
|
||||
## 身份与权限降级
|
||||
|
||||
- 默认显式使用 `--as user` 操作用户资源;只有用户明确要求应用身份时,才直接用 `--as bot`。
|
||||
- user 身份报 scope/授权不足,或错误中包含 `missing_scopes` / `hint`,先转 `lark-shared` 做用户授权恢复,不要直接降级 bot。
|
||||
- user 身份报资源级无访问且无授权恢复提示时,才可用 `--as bot` 重试一次;bot 仍失败就停止重试并按权限错误处理。
|
||||
- `91403` 或明确不可访问错误不要循环换身份重试。
|
||||
- `+base-create` / `+base-copy` 若用 bot 身份执行,关注返回中的 `permission_grant`,并把用户是否可打开新 Base 告知用户。
|
||||
|
||||
## 查询与统计规则
|
||||
|
||||
涉及查询、统计或判断结论时,先阅读 [lark-base-data-analysis-sop.md](references/lark-base-data-analysis-sop.md),并遵守:
|
||||
|
||||
1. `+record-list` 的默认页、固定 `--limit` 和本地 `jq` 只能证明已读取范围内的事实,不能直接支撑全局最值、全量计数、Top/Bottom N、异常识别或分组结论。
|
||||
2. 能由 Base 表达的筛选、排序、投影、聚合、分组和限制,应在 Base 云端查询能力中执行;不要先拉原始记录到本地上下文再手工筛选排序。
|
||||
3. `has_more=true` 或等价分页信号表示当前结果不是全量;除非用户只要样例/前 N 条,不能基于该页回答全局问题。
|
||||
4. 多表查询必须先确认关系字段和连接键;link 单元格里的 `record_id` 是关系键,不是用户可读答案。
|
||||
5. 最终答案必须能追溯到真实表、真实字段、查询范围、筛选/排序/聚合条件和必要的连接键。
|
||||
6. 一次性原始记录查询优先用 `+record-list` / `+record-search` 的 filter/sort;聚合分析优先用 `+data-query`;要把结果长期显示在表里,才考虑新增 `formula` / `lookup` 字段。
|
||||
7. `+data-query` 可返回聚合结果或维度字段行,但维度行按字段组合去重且不返回 `record_id`;需要逐条记录、记录定位或完整行级字段时,再用 `+record-list` / `+record-search` / `+record-get` 回查。
|
||||
|
||||
## 写入前置规则
|
||||
|
||||
- 优先用写入返回确认结果;返回信息不足或任务明确要求核验时,再读回。
|
||||
- 写记录前先读字段结构;只写存储字段。系统字段、附件字段、`formula`、`lookup` 不作为普通记录写入目标。
|
||||
- 附件上传、下载、删除走专用 `+record-*-attachment` 命令。
|
||||
- 写字段前先读 [lark-base-field-json.md](references/lark-base-field-json.md);涉及 `formula` / `lookup` 时必须读 [formula-field-guide.md](references/formula-field-guide.md) / [lookup-field-guide.md](references/lookup-field-guide.md)。
|
||||
- 表名、字段名、视图名、workflow 配置中的名称必须来自真实返回;跨表场景还要读取目标表结构。
|
||||
- 删除、角色更新、字段更新、表单提交(`+form-submit`)等高风险操作遵循 CLI 的 confirmation gate,必须带 `--yes`;目标不明确时先用 get/list 消歧。
|
||||
- 批量写入单批最多 200 条;连续写同一表时串行执行,遇到 `1254291` 按短暂等待后重试处理。
|
||||
- `select` 字段只支持写入字段中已有的选项;构造 CellValue 前先用 `+field-list` 或 `+field-search-options` 确认目标选项存在。
|
||||
|
||||
## 表单与视图细节
|
||||
|
||||
- `+form-submit` 是高风险写操作,必须带 `--yes` 确认;调用前必须先跑 `+form-detail`,读取 `questions[].type`、`required`、`filter` 和附件场景需要的 `base_token`;不要填写被 filter 隐藏的问题。
|
||||
- 表单附件不要写进 `fields`,放在 `--json.attachments`;提交附件时必须同时传表单所属 Base 的 `--base-token`。
|
||||
- `+view-set-filter` 是唯一保留的 view reference;sort/group/card/timebar/visible-fields 这类配置先用对应 get 命令读现状,保留未修改字段,只替换用户要求变更的配置。
|
||||
- 视图适合持久化、共享和 UI 复用;一次性筛选/排序可先用 `+record-list` / `+record-search` 的 filter/sort 验证结果,再按需要沉淀为持久视图。
|
||||
|
||||
## Dashboard / Workflow / Role
|
||||
|
||||
- Dashboard 的复杂点是 block 的 `data_config`,不是 list/get/create/delete 命令参数。创建或更新 block 前先读 [dashboard-block-data-config.md](references/dashboard-block-data-config.md),组件必须串行创建;`+dashboard-arrange` 是服务端智能布局,仅在用户明确要求重排/美化、或对本次会话从零新建的仪表盘做收尾整理时执行。`+dashboard-block-get-data` 读取图表最终计算结果,不返回 block 名称、类型、布局或 `data_config`;需要元数据先用 `+dashboard-block-get`。
|
||||
- Workflow 的复杂点是 `steps` 结构。创建、更新或解释完整 workflow 时读入口 [lark-base-workflow-guide.md](references/lark-base-workflow-guide.md) 和 steps JSON SSOT [lark-base-workflow-schema.md](references/lark-base-workflow-schema.md);enable/disable/list 只需确认 workflow ID、当前启停状态和用户意图。
|
||||
- Role 的复杂点是权限 JSON。角色操作先读入口 [lark-base-role-guide.md](references/lark-base-role-guide.md);`+role-create` 只支持自定义角色;`+role-update` 是 delta merge;角色 create/update 或解读完整配置时读权限 JSON SSOT [role-config.md](references/role-config.md)。`+role-delete` 只适用于自定义角色,系统角色不可删除;删除角色和关闭高级权限前必须确认目标和影响。
|
||||
|
||||
## 常见恢复
|
||||
|
||||
| 错误 / 现象 | 恢复动作 |
|
||||
|---|---|
|
||||
| `param baseToken is invalid` / `base_token invalid` | 检查是否把 wiki token、workspace token 或完整 URL 当成了 `--base-token`;按入口规则重新获取真实 `base_token` |
|
||||
| `not found` 且输入来自 Wiki 链接 | 优先检查是否把 wiki token 当成 base token,不要立刻改走裸 API |
|
||||
| `1254045` 字段名不存在 | 重新 `+field-list`,使用真实字段名或字段 ID;注意空格、大小写和跨表字段 |
|
||||
| `1254015` 字段值类型不匹配 | 先 `+field-list`,再按 [lark-base-cell-value.md](references/lark-base-cell-value.md) 构造 CellValue |
|
||||
| `Invalid discriminator value`(字段写入缺 `type`) | 按完整提交规则读取当前字段,只改目标内容后提交;不要只补 `type` 重试 |
|
||||
| filter 报 `value of type array` / `Only string values` | 用 record/view 的 tuple `--filter-json`(非 `+data-query` 对象型),value 按字段 type 选标量或数组;见 [lark-base-view-set-filter.md](references/lark-base-view-set-filter.md) |
|
||||
| 日期 / 人员 / 超链接字段报格式错误 | 日期用 `YYYY-MM-DD HH:mm:ss`;人员用 `[{ "id": "ou_xxx" }]`;超链接用 URL 或 markdown link 字符串 |
|
||||
| formula / lookup 创建失败 | 先读 [formula-field-guide.md](references/formula-field-guide.md) / [lookup-field-guide.md](references/lookup-field-guide.md),再按 guide 重建请求 |
|
||||
| `ignored_fields` / `READONLY` | 移除只读字段,只写存储字段 |
|
||||
| `1254104` | 批量超过 200,分批调用 |
|
||||
| `1254291` | 并发写冲突,串行写入并在批次间短暂等待 |
|
||||
| `91403` | 无权限访问该 Base,按 `lark-shared` 权限流程处理,不要盲目重试 |
|
||||
|
||||
## 保留 Reference
|
||||
|
||||
- [lark-base-data-analysis-sop.md](references/lark-base-data-analysis-sop.md):查询/统计/全局结论的选路 SOP
|
||||
- [lark-base-data-query-guide.md](references/lark-base-data-query-guide.md) / [lark-base-data-query.md](references/lark-base-data-query.md):聚合查询入口 fewshot 与 DSL SSOT
|
||||
- [lark-base-cell-value.md](references/lark-base-cell-value.md):记录 CellValue 构造
|
||||
- [lark-base-field-json.md](references/lark-base-field-json.md):字段 JSON 构造
|
||||
- [formula-field-guide.md](references/formula-field-guide.md) / [lookup-field-guide.md](references/lookup-field-guide.md):公式与 lookup 字段
|
||||
- [lark-base-field-create.md](references/lark-base-field-create.md) / [lark-base-field-update.md](references/lark-base-field-update.md):字段创建/更新命令级补充
|
||||
- [lark-base-record-upsert.md](references/lark-base-record-upsert.md) / [lark-base-record-batch-create.md](references/lark-base-record-batch-create.md) / [lark-base-record-batch-update.md](references/lark-base-record-batch-update.md) / [lark-base-record-history-list.md](references/lark-base-record-history-list.md):记录写入 JSON 与历史返回解释
|
||||
- [lark-base-view-set-filter.md](references/lark-base-view-set-filter.md):视图筛选 JSON
|
||||
- [lark-base-form-detail.md](references/lark-base-form-detail.md) / [lark-base-form-submit.md](references/lark-base-form-submit.md) / [lark-base-form-questions-create.md](references/lark-base-form-questions-create.md) / [lark-base-form-questions-update.md](references/lark-base-form-questions-update.md):表单详情、提交和复杂 JSON
|
||||
- [lark-base-dashboard.md](references/lark-base-dashboard.md) / [dashboard-block-data-config.md](references/dashboard-block-data-config.md) / [lark-base-dashboard-block-get-data.md](references/lark-base-dashboard-block-get-data.md):仪表盘、组件配置与图表结果协议
|
||||
- [lark-base-workflow-guide.md](references/lark-base-workflow-guide.md) / [lark-base-workflow-schema.md](references/lark-base-workflow-schema.md):workflow 入口与 steps JSON SSOT
|
||||
- [lark-base-role-guide.md](references/lark-base-role-guide.md) / [role-config.md](references/role-config.md):角色入口与权限 JSON SSOT
|
||||
@ -0,0 +1,376 @@
|
||||
# dashboard block data_config SSOT
|
||||
|
||||
Block 的 `data_config` 字段因 `type` 不同而变化。本文档是 dashboard block `data_config` 的单一事实来源(SSOT),包含组件类型、字段结构、筛选格式、约束和可复制模板。
|
||||
|
||||
## 支持的组件类型(`type` 枚举)
|
||||
|
||||
| type 值 | 说明 |
|
||||
|---------|------|
|
||||
| `column` | 柱状图 |
|
||||
| `bar` | 条形图 |
|
||||
| `line` | 折线图 |
|
||||
| `pie` | 饼图 |
|
||||
| `ring` | 环形图 |
|
||||
| `area` | 面积图 |
|
||||
| `combo` | 组合图 |
|
||||
| `scatter` | 散点图 |
|
||||
| `funnel` | 漏斗图 |
|
||||
| `wordCloud` | 词云 |
|
||||
| `radar` | 雷达图 |
|
||||
| `statistics` | 指标卡 |
|
||||
| `text` | 文本(支持 Markdown) |
|
||||
|
||||
## 字段类型与操作符速查(AI 决策用)
|
||||
|
||||
> 先用 `+field-list` / `+field-get` 确认字段 `type`;本节使用当前字段接口里的 canonical 类型名:`number`、`text`、`select`、`datetime`、`checkbox`、`user`。
|
||||
|
||||
```
|
||||
text: is, isNot, contains, doesNotContain, isEmpty, isNotEmpty
|
||||
number: is, isNot, isGreater, isGreaterEqual, isLess, isLessEqual, isEmpty, isNotEmpty
|
||||
select(multiple=false): is, isNot, isEmpty, isNotEmpty
|
||||
select(multiple=true): is, isNot, contains, doesNotContain, isEmpty, isNotEmpty
|
||||
datetime: is, isGreater, isGreaterEqual, isLess, isLessEqual, isEmpty, isNotEmpty
|
||||
checkbox: is (value: true/false)
|
||||
user / created_by / updated_by: is, isNot, isEmpty, isNotEmpty
|
||||
```
|
||||
|
||||
## data_config 通用结构
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `table_name` | string | 关联数据表名称 |
|
||||
| `series` | `[{ "field_name": "xxx", "rollup": "SUM" }]` | 指标/Y 轴(与 `count_all` 二选一)。rollup 支持 `SUM` / `MAX` / `MIN` / `AVERAGE` |
|
||||
| `count_all` | boolean | COUNTA 聚合,统计所有记录数(与 `series` 二选一) |
|
||||
| `group_by` | `[{ "field_name": "xxx", "mode": "integrated", "sort": {...} }]` | X 轴分组维度。`mode` 必填,`sort` 可选,见下方说明 |
|
||||
| `filter` | object | 筛选条件 |
|
||||
| `filter.conjunction` | `"and"` / `"or"` | 筛选逻辑 |
|
||||
| `filter.conditions` | `[{ "field_name", "operator", "value" }]` | 筛选条件数组,value 类型因字段类型而异(见下方 filter 格式规则) |
|
||||
|
||||
### text 类型特殊结构
|
||||
|
||||
`text` 类型组件用于展示富文本内容,**不需要数据源配置**(无 `table_name`、`series`、`group_by`、`filter`)。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `text` | string | **必填**。支持 Markdown 语法,详见下方说明 |
|
||||
|
||||
**支持的 Markdown 语法:**
|
||||
|
||||
| 语法 | 示例 | 效果 |
|
||||
|------|------|------|
|
||||
| 一级标题 | `# 标题` | 大标题 |
|
||||
| 二级标题 | `## 标题` | 中标题 |
|
||||
| 三级标题 | `### 标题` | 小标题 |
|
||||
| 加粗 | `**文字**` | **文字** |
|
||||
| 斜体 | `*文字*` | *文字* |
|
||||
| 删除线 | `~~文字~~` | ~~文字~~ |
|
||||
| 有序列表 | `1. 项目` | 1. 项目 |
|
||||
| 无序列表 | `- 项目` | - 项目 |
|
||||
|
||||
> **注意**:以上未提及的 Markdown 语法(如链接、图片、代码块、表格等)均不支持。
|
||||
|
||||
## group_by 详细说明
|
||||
|
||||
### mode 枚举
|
||||
|
||||
| mode | 含义 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| `integrated` | 聚合分组(默认) | 绝大部分场景,按字段值分组统计 |
|
||||
| `enumerated` | 多值拆分统计 | 多选、人员等多值字段,将每个选项/人员拆开独立统计 |
|
||||
|
||||
> 多选、人员等多值字段默认用 `enumerated`;其他字段默认用 `integrated`。
|
||||
|
||||
### sort 排序
|
||||
|
||||
| sort.type | 含义 | 典型场景 |
|
||||
|-----------|------|----------|
|
||||
| `group` | 按横轴值排序 | 按月份升序、按品类名字母序 |
|
||||
| `value` | 按纵轴值排序 | 按销售额从大到小 |
|
||||
| `view` | 按数据源记录顺序 | 保持原表行序(不常用) |
|
||||
|
||||
`sort.order`:`asc`(升序)/ `desc`(降序)
|
||||
|
||||
只要写 `sort` 对象,就需要明确排序方向。CLI 会把 `sort.type` 为 `group` 或 `view` 且缺少 `order` 的情况规范化为 `order:"asc"`;`sort.type:"value"` 必须显式写 `order:"asc"` 或 `order:"desc"`,因为指标值排序方向会改变业务含义。
|
||||
|
||||
如果表中行序就是业务顺序,首次创建 block 时就一次性设置 `sort:{"type":"view","order":"asc"}` 保留行序,避免创建后再二次更新排序条件。
|
||||
|
||||
示例 — 柱状图按销售额降序:
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "订单表",
|
||||
"series": [{ "field_name": "金额", "rollup": "SUM" }],
|
||||
"group_by": [{ "field_name": "类别", "mode": "integrated", "sort": {"type": "value", "order": "desc"} }]
|
||||
}
|
||||
```
|
||||
|
||||
## filter 格式规则
|
||||
|
||||
**基本结构:**
|
||||
|
||||
```json
|
||||
{
|
||||
"filter": {
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{ "field_name": "字段名", "operator": "操作符", "value": "值" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**多条件示例(and/or):**
|
||||
|
||||
```json
|
||||
{
|
||||
"filter": {
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{ "field_name": "状态", "operator": "is", "value": "已完成" },
|
||||
{ "field_name": "金额", "operator": "isGreater", "value": 1000 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**操作符:**
|
||||
|
||||
| 操作符 | 含义 | 是否需要 value |
|
||||
|--------|------|---------------|
|
||||
| `is` | 等于 | 是 |
|
||||
| `isNot` | 不等于 | 是 |
|
||||
| `contains` | 包含 | 是 |
|
||||
| `doesNotContain` | 不包含 | 是 |
|
||||
| `isEmpty` | 为空 | 否 |
|
||||
| `isNotEmpty` | 不为空 | 否 |
|
||||
| `isGreater` | 大于 | 是 |
|
||||
| `isGreaterEqual` | 大于等于 | 是 |
|
||||
| `isLess` | 小于 | 是 |
|
||||
| `isLessEqual` | 小于等于 | 是 |
|
||||
|
||||
**各字段类型的 value 格式:**
|
||||
|
||||
| 字段类型 | value 类型 | 适用操作符 | 示例 |
|
||||
|----------|-----------|-----------|------|
|
||||
| `text` | string | is, isNot, contains, doesNotContain, isEmpty, isNotEmpty | `{"field_name":"姓名","operator":"contains","value":"张"}` |
|
||||
| `number` | number | is, isNot, isGreater, isGreaterEqual, isLess, isLessEqual, isEmpty, isNotEmpty | `{"field_name":"金额","operator":"isGreater","value":0}` |
|
||||
| `select` (`multiple=false`) | string(选项名) | is, isNot, isEmpty, isNotEmpty | `{"field_name":"状态","operator":"is","value":"已完成"}` |
|
||||
| `select` (`multiple=true`) | string[](选多个)/ string(选单个) | is, isNot, contains, doesNotContain, isEmpty, isNotEmpty | 多选传数组如 `["标签1","标签2"]`;单选传单个字符串 |
|
||||
| `datetime` / `created_at` / `updated_at` | number(Unix 毫秒时间戳,13位) | is, isGreater, isGreaterEqual, isLess, isLessEqual, isEmpty, isNotEmpty | `{"field_name":"创建日期","operator":"isGreater","value":1704038400000}` |
|
||||
| `checkbox` | boolean | is | `{"field_name":"已审核","operator":"is","value":true}` |
|
||||
| `user` / `created_by` / `updated_by` | string 或 string[](用户 ID,格式 `ou_xxx`)。不知道 `open_id` 时先用 `lark-cli contact +search-user --query "<姓名/邮箱/手机号>" --as user` 查 id。 | is, isNot, isEmpty, isNotEmpty | `{"field_name":"负责人","operator":"is","value":"ou_xxxxxxxxxxxxxxxx"}` |
|
||||
| 所有类型(为空/不为空) | 不需要 value | isEmpty, isNotEmpty | `{"field_name":"备注","operator":"isEmpty"}` |
|
||||
|
||||
> `value` 类型为 `string | number | boolean | string[]`,需根据字段类型匹配正确格式
|
||||
|
||||
## 约束与本地校验
|
||||
|
||||
- 必填与互斥
|
||||
- 图表类型必填:`table_name`
|
||||
- text 类型必填:`text`
|
||||
- 互斥:`series` 与 `count_all` 二选一,且至少提供其一(仅图表类型)
|
||||
- text 类型**不支持**:`series`、`count_all`、`group_by`、`filter`
|
||||
- 长度/结构
|
||||
- `group_by` 最多 2 个;每项 `field_name` 必填
|
||||
- `group_by[].sort.type` 取值 `group|value|view`;`order` 取值 `asc|desc`
|
||||
- 规范化(CLI 自动处理;`--no-validate` 时不生效,`data_config` 原样透传给后端)
|
||||
- `series[].rollup` 自动转成大写(如 `sum` → `SUM`)
|
||||
- `group_by[].sort.type/order` 自动转成小写
|
||||
- `group_by[].sort.type` 为 `group` 或 `view` 且缺少 `order` 时,自动补 `order:"asc"`;`value` 排序不会自动补方向
|
||||
- 本地校验(可通过 `--no-validate` 跳过)
|
||||
- `+dashboard-block-create` 默认对 `data_config` 做轻量校验;失败会聚合错误并给出修复建议
|
||||
- `+dashboard-block-update` 不做强类型校验,由后端验证具体字段
|
||||
- 仅需传入合法 JSON;CLI 不会擅自改写你的业务含义
|
||||
|
||||
## 可复制模板
|
||||
|
||||
**按意图选择模板:**
|
||||
- 比较不同类别数值 → 柱状图 / 条形图
|
||||
- 看趋势变化 → 折线图 / 面积图
|
||||
- 看占比分布 → 饼图 / 环形图 / 词云
|
||||
- 多指标对比 → 组合图
|
||||
- 看两变量关系 → 散点图
|
||||
- 看流程转化 → 漏斗图
|
||||
- 看多维度评分 → 雷达图
|
||||
- 显示单个指标 → 指标卡(统计数字或记录数)
|
||||
|
||||
最小柱状图:
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "表名",
|
||||
"series": [{ "field_name": "数值字段", "rollup": "SUM" }],
|
||||
"group_by": [{ "field_name": "分组字段", "mode": "integrated" }]
|
||||
}
|
||||
```
|
||||
|
||||
最小饼图/环形图(按分类字段统计行数占比):
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "表名",
|
||||
"count_all": true,
|
||||
"group_by": [{ "field_name": "分类字段", "mode": "integrated" }]
|
||||
}
|
||||
```
|
||||
|
||||
折线图(按月趋势):
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "表名",
|
||||
"series": [{ "field_name": "金额", "rollup": "SUM" }],
|
||||
"group_by": [{ "field_name": "月份", "mode": "integrated", "sort": {"type":"group","order":"asc"} }]
|
||||
}
|
||||
```
|
||||
|
||||
条形图(横向柱状图):
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "表名",
|
||||
"series": [{ "field_name": "数值字段", "rollup": "SUM" }],
|
||||
"group_by": [{ "field_name": "分组字段", "mode": "integrated" }]
|
||||
}
|
||||
```
|
||||
|
||||
面积图(趋势填充):
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "表名",
|
||||
"series": [{ "field_name": "数值字段", "rollup": "SUM" }],
|
||||
"group_by": [{ "field_name": "时间字段", "mode": "integrated", "sort": {"type":"group","order":"asc"} }]
|
||||
}
|
||||
```
|
||||
|
||||
组合图(柱+线等多指标对比):
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "表名",
|
||||
"series": [
|
||||
{ "field_name": "指标1", "rollup": "SUM" },
|
||||
{ "field_name": "指标2", "rollup": "SUM" }
|
||||
],
|
||||
"group_by": [{ "field_name": "分类字段", "mode": "integrated" }]
|
||||
}
|
||||
```
|
||||
|
||||
散点图(两变量相关性):
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "表名",
|
||||
"series": [{ "field_name": "Y轴字段(数值/指标)", "rollup": "SUM" }],
|
||||
"group_by": [{ "field_name": "X轴字段(分类/维度)", "mode": "integrated" }]
|
||||
}
|
||||
```
|
||||
|
||||
漏斗图(流程转化):
|
||||
|
||||
先判断用户要看的数值语义:
|
||||
|
||||
- **当前数量**:统计每个当前状态/阶段下有多少记录,例如“各环节当前数量”“当前阶段分布”。源表有状态/阶段字段时,直接用 `count_all:true` + `group_by`。
|
||||
- **累计数量**:统计到达该阶段及其后续阶段(后缀和)的累计数量,例如“流程转化”“从 A 到 B 各环节转化”。此口径假设流程单向、无跳阶/回退、记录不删除;不满足时须用状态变更历史,不能对当前快照累加。如果表中已有累计数量字段或阶段汇总表,直接用该字段画漏斗图;否则先计算累计数量,创建并写入 helper 汇总表后再画图。
|
||||
|
||||
当前数量:
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "表名",
|
||||
"count_all": true,
|
||||
"group_by": [{ "field_name": "状态字段", "mode": "integrated" }]
|
||||
}
|
||||
```
|
||||
|
||||
累计数量:
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "流程汇总表名",
|
||||
"series": [{ "field_name": "累计数量", "rollup": "SUM" }],
|
||||
"group_by": [{ "field_name": "阶段字段", "mode": "integrated", "sort": {"type":"view","order":"asc"} }]
|
||||
}
|
||||
```
|
||||
|
||||
如果只有当前状态数据但用户要看流程转化,需要先按业务阶段顺序计算每个阶段的累计数量,再创建 helper 汇总表(如:阶段、累计数量),用 `+record-batch-create` 一次写入后,按“累计数量”模板创建漏斗图。helper 表行序就是业务顺序时,首次创建 block 时一次性设置好 `group_by.sort`。
|
||||
|
||||
> ⚠️ 注意:helper 汇总表仅用于源表无法直接聚合出目标形态的场景(如上面的累计数量漏斗图)。只要能在源表上直接用 `group_by` + `rollup`(含 `AVERAGE`)算出,就不需要新建 helper 表。
|
||||
|
||||
词云(文本频率):
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "表名",
|
||||
"count_all": true,
|
||||
"group_by": [{ "field_name": "文本字段", "mode": "integrated" }]
|
||||
}
|
||||
```
|
||||
|
||||
雷达图(多维度评分):
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "表名",
|
||||
"series": [
|
||||
{ "field_name": "维度1", "rollup": "SUM" },
|
||||
{ "field_name": "维度2", "rollup": "SUM" },
|
||||
{ "field_name": "维度3", "rollup": "SUM" }
|
||||
],
|
||||
"group_by": [{ "field_name": "分类字段", "mode": "integrated" }]
|
||||
}
|
||||
```
|
||||
|
||||
指标卡(统计数字):
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "数据表",
|
||||
"series": [{ "field_name": "数字", "rollup": "SUM" }]
|
||||
}
|
||||
```
|
||||
|
||||
指标卡(统计记录数):
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "数据表",
|
||||
"count_all": true
|
||||
}
|
||||
```
|
||||
|
||||
文本组件(Markdown 富文本):
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "# 🚀 一级标题\n这是一个 **加粗** *斜体* ~~删除线~~ 的示例。\n\n## 📌 二级标题\n1. 有序列表项 1\n2. 有序列表项 2\n\n### 📌 三级标题\n- 无序列表项 1\n- 无序列表项 2"
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:text 类型组件不需要 `table_name`、`series`、`group_by`、`filter` 等数据源相关字段。
|
||||
|
||||
## 常见错误与修复
|
||||
|
||||
- 同时存在 `series` 与 `count_all`
|
||||
- 现象:后端/本地校验报互斥错误
|
||||
- 修复:见「关键约束」章节的二选一规则
|
||||
- 缺少 `table_name`
|
||||
- 现象:本地校验缺少必填字段
|
||||
- 修复:指定数据源表名(使用表名,非表 ID)
|
||||
- `series[].rollup` 大小写/取值不合法
|
||||
- 现象:本地校验提示枚举不支持
|
||||
- 修复:改为 `SUM|MAX|MIN|AVERAGE` 中之一(不区分大小写,CLI 会统一为大写;计数请使用 `count_all:true`)
|
||||
- `group_by` 超出 2 个或字段名为空
|
||||
- 修复:保留前 2 个,或补齐 `field_name`
|
||||
- 排序枚举不合法
|
||||
- 修复:`group_by.sort.type` 仅能为 `group|value|view`;`order` 为 `asc|desc`
|
||||
- filter 写法不规范
|
||||
- 修复:`conjunction` 取 `and|or`;`conditions[].operator` 必须在本页表格列举的范围内;除 `isEmpty/isNotEmpty` 外需提供 `value`
|
||||
|
||||
## 坑点
|
||||
|
||||
- **`count_all` 与 `series` 二选一** — 两者不能同时使用
|
||||
- **filter `value` 类型因字段而异** — 文本/单选为 string,数字为 number,日期为毫秒时间戳,多选/人员可为 string[],复选框为 boolean;`isEmpty`/`isNotEmpty` 不需要 value
|
||||
- **`data_config` 结构随 `type` 变化** — 不同组件类型的字段不同,创建前务必确认类型对应的字段
|
||||
- **表名用 name,不是 ID** — `table_name` 对应的是表名称(如「订单表」),不是 `table_id`
|
||||
737
.claude/skills/lark-base/references/formula-field-guide.md
Normal file
737
.claude/skills/lark-base/references/formula-field-guide.md
Normal file
@ -0,0 +1,737 @@
|
||||
# Base Formula Writing Guide
|
||||
|
||||
## Mandatory Read Acknowledgement
|
||||
|
||||
When creating or updating a formula field with `lark-cli base +field-create/+field-update --json ...` and `type` is `formula`, you should read this guide first and only then add `--i-have-read-guide` to the command.
|
||||
|
||||
Do **not** proactively add `--i-have-read-guide` before reading this guide. Without it, the CLI will fail fast and direct you back to this guide.
|
||||
|
||||
When using `+field-update`, also pass `--yes`: field update is a high-risk `PUT` operation because changing a field definition can affect the whole column.
|
||||
|
||||
## Default strategy
|
||||
|
||||
**All cross-table references, aggregations, and computed fields should use Formula fields by default.** Do NOT use Lookup fields unless the user explicitly requests it. Formula is a strict superset of Lookup — anything Lookup can do, Formula can do with a single expression.
|
||||
|
||||
## Usage
|
||||
|
||||
When creating a formula field, the Agent should:
|
||||
|
||||
1. Get all table names: `lark-cli base +table-list --base-token <base>` — returns `items[].table_name`
|
||||
2. Get table structure: `lark-cli base +table-get --base-token <base> --table-id <table>` — returns `fields[]`
|
||||
3. If the formula references other tables, also get those tables' structures
|
||||
4. Write the formula expression following this guide
|
||||
5. Construct the Formula field JSON and submit it to create or update the field
|
||||
|
||||
**Key constraints**:
|
||||
|
||||
- The JSON must include `"type": "formula"` — this field is required
|
||||
- Table names and field names in the formula must **exactly match** those returned by `+table-list` / `+table-get`
|
||||
- The `expression` value is a string containing the formula expression; double quotes inside the expression must be properly escaped in JSON (e.g. `\"text\"`)
|
||||
|
||||
---
|
||||
|
||||
## Section 1: Core Concepts — Scalar vs List
|
||||
|
||||
This is the foundation of formula logic. You must determine this before writing any formula.
|
||||
|
||||
| Syntax | Meaning | Return type | Example |
|
||||
| --------------------- | -------------------------------------------- | ---------------------- | -------------------------------------------- |
|
||||
| `[Field]` | Value of this field in the current row | Scalar (single value) | `[Name]` → `"Alice"` |
|
||||
| `[TableName].[Field]` | All values of this field in the target table | List (multiple values) | `[Employees].[Name]` → `["Alice","Bob",...]` |
|
||||
| `[TableName]` | The target table (entire table) | Table reference | Used as data range for FILTER/COUNTIF etc. |
|
||||
|
||||
**Rules**:
|
||||
|
||||
- Scalars can be used directly in operations: `[Price] * [Quantity]`
|
||||
- Lists cannot be used as scalars — they must be processed first: use `SUM()` for sum, `ARRAYJOIN(",")` for joining, `FIRST()`/`LAST()`/`NTH()` for single value extraction
|
||||
- Link field access `[LinkField].[TargetField]` returns a list (values of the target field for all linked records)
|
||||
- **LISTCOMBINE flattening rule**: When a FILTER's result column is itself a multi-value field (`select` with `multiple=true`, `link`, etc.), it produces a 2D array and **must** be flattened with `.LISTCOMBINE()`; for single-value fields (`number`, `text`, etc.) it can be omitted, but adding it is never wrong:
|
||||
|
||||
```
|
||||
[Table].FILTER(CurrentValue.[Field] = [Value]).[Tags].LISTCOMBINE() ← required for multi-value columns
|
||||
[Table].FILTER(CurrentValue.[Field] = [Value]).[NumberCol].LISTCOMBINE() ← optional for single-value columns
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 2: Data Types and Type Conversion
|
||||
|
||||
### Field storage types
|
||||
|
||||
| Type | Description | Supported operations |
|
||||
|------|-------------|----------------------|
|
||||
| `number` | Stored as numeric value | Math operations, comparisons, auto-converts to string for concatenation |
|
||||
| `text` | Stored as string | String operations; can participate in math if content is numeric, otherwise errors |
|
||||
| `datetime` | Date object | Date functions, add/subtract with numbers; auto-converts to default format string when using `&` — use TEXT to format first for controlled output |
|
||||
| `select` (`multiple=true`) | Data list | List functions, CONTAIN checks |
|
||||
| `link` | Links to other table records | Chained access `[LinkField].[Field]`, result is a list |
|
||||
| `checkbox` | TRUE/FALSE | Logical operations; auto-converts to number when compared with numbers |
|
||||
|
||||
### Implicit type conversion
|
||||
|
||||
| Scenario | Conversion rule |
|
||||
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| Number + Float | → Float |
|
||||
| Date + Number | → Date (adds/subtracts days). Use `+`/`-` for whole days, use `DURATION()` for hour/minute/second precision |
|
||||
| Date - Date | → Duration |
|
||||
| Boolean compared with Number | Boolean auto-converts to number (TRUE=1, FALSE=0) |
|
||||
| `&` concatenation | Both sides auto-convert to string |
|
||||
|
||||
### Type consistency in comparisons
|
||||
|
||||
When using comparison operators (`>`, `>=`, `<`, `<=`, `=`, `!=`), **both sides should be the same type** to avoid semantic errors or unexpected results.
|
||||
|
||||
**Principle**: When types differ, explicitly convert one side rather than relying on implicit conversion:
|
||||
|
||||
- `number` vs `text` → use `VALUE()` to convert text to number
|
||||
- `datetime` vs `text` → use `TEXT()` to convert date to text
|
||||
- `datetime` vs `datetime` equality → dates include time components, so direct `=` comparison may fail due to different hours/minutes/seconds. For day-level equality, convert to text first: `TEXT([DateA], "YYYY/MM/DD") = TEXT([DateB], "YYYY/MM/DD")`
|
||||
- `select` and `user` fields can be compared with both same-type values and text
|
||||
- `text` fields in numeric aggregation (SUM/AVERAGE/MIN/MAX etc.) → convert to number with `VALUE()` first. For FILTER results, use `.MAP(VALUE(CurrentValue)).SUM()`
|
||||
|
||||
---
|
||||
|
||||
## Section 3: CurrentValue
|
||||
|
||||
**CurrentValue is the iteration variable in FILTER/MAP/COUNTIF/SUMIF functions, representing the "current item" being processed in the data range.**
|
||||
|
||||
### CurrentValue meaning in different contexts
|
||||
|
||||
| Data range type | CurrentValue represents | Access pattern | Example |
|
||||
| ---------------------------- | ----------------------- | --------------------------- | --------------------------------------------------------- |
|
||||
| Entire table `[TableName]` | A row in the table | `CurrentValue.[FieldName]` | `[Orders].FILTER(CurrentValue.[Amount] > 100).[Customer]` |
|
||||
| Column `[TableName].[Field]` | A single field value | Use `CurrentValue` directly | `[Orders].[Amount].FILTER(CurrentValue > 100)` |
|
||||
| `select` (`multiple=true`) field `[Tags]` | One option | Use `CurrentValue` directly | `[Tags].FILTER(CurrentValue = "Important")` |
|
||||
| LIST-generated list | One element | Use `CurrentValue` directly | `LIST(1,2,3).MAP(CurrentValue * 2)` |
|
||||
|
||||
### Key rules
|
||||
|
||||
1. **When data range is a table**, use `CurrentValue.[FieldName]` to access row fields
|
||||
2. **When data range is a column/list**, use `CurrentValue` directly for the element value — **cannot** use `CurrentValue.[FieldName]`
|
||||
3. CurrentValue can **only** appear inside the condition/mapping parameters of FILTER/MAP/COUNTIF/SUMIF functions
|
||||
4. To reference the current table's field value in a condition, write `[FieldName]` directly — it refers to the formula row's value, not a property of CurrentValue
|
||||
|
||||
### Anti-patterns
|
||||
|
||||
| Wrong | Reason | Correct |
|
||||
| ---------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
||||
| `[Table].[Col].FILTER(CurrentValue.[Col] > 0)` | Data range is a column; CurrentValue is a scalar, cannot use `.` to access fields | `[Table].[Col].FILTER(CurrentValue > 0)` |
|
||||
| `[Table].FILTER(CurrentValue > 100)` | Data range is a table; CurrentValue is a row, cannot compare directly | `[Table].FILTER(CurrentValue.[Amount] > 100).[Amount]` |
|
||||
| `CurrentValue + 1` (at top level) | CurrentValue can only be used inside iteration functions | Use inside MAP/FILTER etc. |
|
||||
|
||||
---
|
||||
|
||||
## Section 4: Operators
|
||||
|
||||
Base formulas **only allow** the following operators. `like`, `in`, `<>`, `**`, `^` etc. are prohibited.
|
||||
|
||||
| Category | Operators | Description |
|
||||
| ------------- | -------------------------- | -------------------------------------------------------------------------- |
|
||||
| Arithmetic | `+` `-` `*` `/` `%` | Add, subtract, multiply, divide, modulo (`%` is equivalent to `MOD()`) |
|
||||
| Comparison | `>` `>=` `<` `<=` `=` `!=` | Greater than, greater or equal, less than, less or equal, equal, not equal |
|
||||
| Logical | `&&` `\|\|` | AND, OR |
|
||||
| Concatenation | `&` | Text concatenation; non-text values auto-convert to string |
|
||||
|
||||
**Important**:
|
||||
|
||||
- Equality uses `=` (single equals), not `==`
|
||||
- Not-equal uses `!=`, not `<>`
|
||||
- String concatenation uses `&`, not `+`
|
||||
- Both `&&`/`||` and AND()/OR() functions are supported
|
||||
|
||||
---
|
||||
|
||||
## Section 5: Link Fields and Cross-Table References
|
||||
|
||||
### Link field description
|
||||
|
||||
When a field type is described as `FieldName: Link [target table: X, foreign key: Y]`, it links to target table X using field Y as the join key.
|
||||
|
||||
### Chained cross-table access
|
||||
|
||||
```
|
||||
[LinkField].[TargetField]
|
||||
```
|
||||
|
||||
Retrieves the target field values for all linked records as a list. Supports continued chaining: `[LinkA].[LinkB].[Field]`.
|
||||
|
||||
### Equivalent expanded form
|
||||
|
||||
- Multi-value link: `[TargetTableX].FILTER([LinkField].CONTAIN(CurrentValue.[Y])).[TargetField].LISTCOMBINE()`
|
||||
- Single-value link: `[TargetTableX].FILTER(CurrentValue.[Y] = [LinkField]).[TargetField].LISTCOMBINE()`
|
||||
|
||||
(`.LISTCOMBINE()` is required when `[TargetField]` is a multi-value field; optional for single-value fields)
|
||||
|
||||
### Notes
|
||||
|
||||
- Link fields typically return **lists** (possibly empty)
|
||||
- To output a single value, use aggregation (SUM/MAX), joining (ARRAYJOIN), or extraction (FIRST/LAST/NTH)
|
||||
- Do not nest FILTER inside FILTER for cross-table queries — prefer link field chained access
|
||||
|
||||
---
|
||||
|
||||
## Section 6: Function Call Conventions
|
||||
|
||||
### Two calling styles
|
||||
|
||||
| Style | Format | Description |
|
||||
| ---------- | ------------------ | ----------------------------------- |
|
||||
| Functional | `FUNC(arg1, arg2)` | Works for all functions |
|
||||
| Chained | `arg1.FUNC(arg2)` | Moves the first argument before `.` |
|
||||
|
||||
**Rules**:
|
||||
|
||||
- Zero-argument functions cannot be chained: `NOW()`, `TODAY()`, `PI()`, `TRUE()`, `FALSE()`
|
||||
- SORTBY can **only** be chained: `[Table].SORTBY([Table].[SortCol]).[OutputCol]`. The sort column always uses the original table's column name (`[TableName].[Field]` format); the engine aligns rows internally, even when the data range is a FILTER result
|
||||
- FILTER is recommended to be chained: `[Table].FILTER(condition).[OutputCol]`
|
||||
|
||||
### FILTER / SORTBY result column rules
|
||||
|
||||
- **When data range is a table** `[TableName]`, FILTER / SORTBY returns a table reference. The chain **must** end with `.[Field]` to specify the result column, otherwise the formula fails:
|
||||
|
||||
```
|
||||
Correct: [Sales].FILTER(CurrentValue.[Amount] > 100).[Customer]
|
||||
Correct: [Sales].FILTER(condition).SORTBY([Sales].[SortCol]).[Customer] ← result column at end of chain
|
||||
Wrong: [Sales].FILTER(CurrentValue.[Amount] > 100) ← missing result column
|
||||
```
|
||||
|
||||
- **When data range is a column** `[TableName].[Field]` or a list, FILTER returns the filtered list directly — **no** result column needed:
|
||||
|
||||
```
|
||||
Correct: [Sales].[Amount].FILTER(CurrentValue > 100)
|
||||
```
|
||||
|
||||
After the result column, it's recommended to flatten with `.LISTCOMBINE()` first (especially when the result column is a multi-value field), then chain aggregation functions:
|
||||
|
||||
```
|
||||
[Sales].FILTER(CurrentValue.[Amount] > 100).[Amount].LISTCOMBINE().SUM()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 7: Hard Constraints
|
||||
|
||||
1. **Nesting prohibition**: FILTER / SUMIF / COUNTIF / MAP **must not be nested** inside each other's condition/mapping expressions. None of these functions can appear inside the condition or mapping parameter of another.
|
||||
- Prohibited: `[Table1].FILTER(CurrentValue.[Col] = [Table2].FILTER(...).[Col])` ← FILTER inside FILTER condition
|
||||
- Prohibited: `[Table].MAP([Table2].MAP(...))` ← MAP inside MAP mapping
|
||||
- **Allowed**: `[Table].FILTER(cond1).[Col].FILTER(cond2)` ← chained call; the first FILTER's output is the second's data range, not nesting
|
||||
|
||||
2. **Function whitelist**: Only use functions listed in Section 8. No unlisted functions.
|
||||
|
||||
3. **Exact name matching**: Table names and field names in formulas must **exactly match** those returned by `+table-get` — no renaming or adding spaces.
|
||||
|
||||
4. **Operator whitelist**: Only use operators listed in Section 4.
|
||||
|
||||
5. **Strings use double quotes**: Strings must be wrapped in double quotes `"`, single quotes are not supported.
|
||||
|
||||
6. **Do not use LOOKUP**: FILTER is a superset of LOOKUP. All LOOKUP formulas can be rewritten with FILTER. Use FILTER exclusively to reduce complexity.
|
||||
|
||||
---
|
||||
|
||||
## Section 8: Complete Function Reference
|
||||
|
||||
### 8.1 Logic functions
|
||||
|
||||
| Function | Signature | Return type | Description |
|
||||
| ------------- | ------------------------------------------------------------------ | -------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| IF | `IF(condition, true_val, [false_val])` | Matches branch type | Returns true_val when TRUE, false_val otherwise; omitting false_val returns false (not null) |
|
||||
| IFS | `IFS(cond1, val1, cond2, val2, ...)` | Matches branch type | Multi-condition branching; returns value for the first TRUE condition |
|
||||
| SWITCH | `SWITCH(expr, match1, result1, [match2, result2, ...], [default])` | Matches branch type | Matches expression value and returns corresponding result |
|
||||
| IFERROR | `IFERROR(expr, fallback)` | Matches branch type | Returns fallback when expression errors |
|
||||
| IFBLANK | `IFBLANK(expr, fallback)` | Matches branch type | Returns fallback when expression is blank (blank = NULL/empty string/empty list) |
|
||||
| AND | `AND(cond1, cond2, ...)` | Boolean | TRUE when all conditions are TRUE |
|
||||
| OR | `OR(cond1, cond2, ...)` | Boolean | TRUE when any condition is TRUE |
|
||||
| NOT | `NOT(condition)` | Boolean | Logical negation |
|
||||
| ISBLANK | `ISBLANK(value)` | Boolean | Tests if blank (NULL/empty string/empty list are blank; 0 and FALSE are not) |
|
||||
| ISNULL | `ISNULL(value)` | Boolean | Tests if NULL (only NULL is true; empty string is not) |
|
||||
| ISERROR | `ISERROR(expr)` | Boolean | Tests if expression errors |
|
||||
| ISNUMBER | `ISNUMBER(value)` | Boolean | Tests if value is a number |
|
||||
| CONTAIN | `CONTAIN(search_range, value, ...)` | Boolean | Tests if a list or `select` (`multiple=true`) contains the value; **does NOT do text substring matching** |
|
||||
| CONTAINSALL | `CONTAINSALL(search_range, value, ...)` | Boolean | Tests if a list or `select` (`multiple=true`) contains all specified values |
|
||||
| CONTAINSONLY | `CONTAINSONLY(search_range, value, ...)` | Boolean | Tests if a list or `select` (`multiple=true`) contains only the specified values |
|
||||
| TRUE | `TRUE()` | Boolean | Returns TRUE |
|
||||
| FALSE | `FALSE()` | Boolean | Returns FALSE |
|
||||
| RECORD_ID | `RECORD_ID()` | Text | Returns the current row's record ID |
|
||||
| RANDOMBETWEEN | `RANDOMBETWEEN(min_int, max_int, [keep_updating])` | Number | Random integer in the specified range |
|
||||
| RANDOMITEM | `RANDOMITEM(list, [keep_updating])` | Matches element type | Randomly picks one element from a list |
|
||||
|
||||
### 8.2 Numeric functions
|
||||
|
||||
| Function | Signature | Return type | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| SUM | `SUM(val1, val2, ...)` | Number | Sum; accepts multiple values or a list |
|
||||
| AVERAGE | `AVERAGE(val1, val2, ...)` | Number | Average |
|
||||
| MAX | `MAX(val1, val2, ...)` | Number | Maximum |
|
||||
| MIN | `MIN(val1, val2, ...)` | Number | Minimum |
|
||||
| MEDIAN | `MEDIAN(val1, val2, ...)` | Number | Median |
|
||||
| COUNTA | `COUNTA(val1, val2, ...)` | Number | Count of non-blank values |
|
||||
| COUNTIF | `COUNTIF(data_range, condition)` | Number | Count matching items. Data range can be a **table** (CurrentValue is a row, use `CurrentValue.[Field]`) or a **column** (CurrentValue is a scalar value) |
|
||||
| SUMIF | `SUMIF(data_range, condition)` | Number | Sum matching values. Data range **must be a numeric column** (e.g. `[Table].[NumField]`); CurrentValue is each value in that column (scalar), cannot use `CurrentValue.[Field]` to access other fields. For cross-field conditions, use FILTER+SUM instead |
|
||||
| ROUND | `ROUND(number, digits)` | Number | Round. digits: 1=one decimal, 0=integer, -1=tens place |
|
||||
| ROUNDUP | `ROUNDUP(number, digits)` | Number | Round away from zero. Same digits semantics as ROUND |
|
||||
| ROUNDDOWN | `ROUNDDOWN(number, digits)` | Number | Round toward zero. Same digits semantics as ROUND |
|
||||
| FLOOR | `FLOOR(number, [base])` | Number | Round down to nearest multiple of base (default 1) |
|
||||
| CEILING | `CEILING(number, [base])` | Number | Round up to nearest multiple of base (default 1) |
|
||||
| ABS | `ABS(number)` | Number | Absolute value |
|
||||
| INT | `INT(number)` | Integer | Truncate to integer |
|
||||
| MOD | `MOD(dividend, divisor)` | Number | Modulo |
|
||||
| POWER | `POWER(base, exponent)` | Number | Exponentiation |
|
||||
| QUOTIENT | `QUOTIENT(dividend, divisor)` | Number | Integer division |
|
||||
| VALUE | `VALUE(text)` | Number | Convert text to number |
|
||||
| ISODD | `ISODD(number)` | Boolean | Tests if number is odd |
|
||||
| RANK | `RANK(value, search_range, [ascending])` | Number | Rank of value in range; default descending |
|
||||
| SEQUENCE | `SEQUENCE(start, end, [step])` | List | Generate number sequence |
|
||||
| PI | `PI()` | Number | Pi constant |
|
||||
| SIN/COS/TAN/ASIN/ACOS/ATAN/ATAN2/SINH/COSH/TANH/ASINH/ACOSH/ATANH | `func(radians_or_value)` | Number | Trigonometric and hyperbolic functions; arguments in radians |
|
||||
|
||||
### 8.3 Text functions
|
||||
|
||||
| Function | Signature | Return type | Description |
|
||||
| --------------- | ---------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| CONCATENATE | `CONCATENATE(text1, text2, ...)` | Text | Concatenate multiple texts; supports lists as input |
|
||||
| LEN | `LEN(text)` | Number | Character count |
|
||||
| LEFT | `LEFT(text, [count])` | Text | Extract from left; default 1 |
|
||||
| RIGHT | `RIGHT(text, [count])` | Text | Extract from right; default 1 |
|
||||
| MID | `MID(text, start, count)` | Text | Extract from middle |
|
||||
| FIND | `FIND(search_val, search_range, [start])` | Number | Find substring position (case-sensitive); returns -1 if not found |
|
||||
| REPLACE | `REPLACE(text, start, count, new_text)` | Text | Replace by position |
|
||||
| SUBSTITUTE | `SUBSTITUTE(text, old_text, new_text, [occurrence])` | Text | Replace by content; can specify which occurrence |
|
||||
| UPPER | `UPPER(text)` | Text | Convert to uppercase |
|
||||
| LOWER | `LOWER(text)` | Text | Convert to lowercase |
|
||||
| TRIM | `TRIM(text)` | Text | Remove leading/trailing spaces |
|
||||
| TEXT | `TEXT(value, format)` | Text | Format output. Date formats: `"YYYY-MM-DD"`, `"YYYY/MM/DD hh:mm:ss"`; number formats: `"00"`, `"000.00"` |
|
||||
| CONTAINTEXT | `CONTAINTEXT(text, search_text)` | Boolean | Tests if text contains substring (text substring matching) |
|
||||
| SPLIT | `SPLIT(text, delimiter)` | List | Split text by delimiter |
|
||||
| TODATE | `TODATE(value)` | Date | Convert date string to date type |
|
||||
| CHAR | `CHAR(number)` | Text | ASCII code to character |
|
||||
| FORMAT | `FORMAT(template, [val1, val2, ...])` | Text | Template string formatting; use `{1}`, `{2}` as placeholders |
|
||||
| HYPERLINK | `HYPERLINK(url, [display_text])` | Hyperlink | Create a hyperlink |
|
||||
| ENCODEURL | `ENCODEURL(text)` | Text | URL encode |
|
||||
| REGEXMATCH | `REGEXMATCH(text, regex)` | Boolean | Regex match test |
|
||||
| REGEXEXTRACT | `REGEXEXTRACT(text, regex)` | List | Extract first match's capture groups |
|
||||
| REGEXEXTRACTALL | `REGEXEXTRACTALL(text, regex)` | 2D List | Extract all matches |
|
||||
| REGEXREPLACE | `REGEXREPLACE(text, regex, replacement)` | Text | Regex replace |
|
||||
|
||||
### 8.4 Date functions
|
||||
|
||||
| Function | Signature | Return type | Description |
|
||||
| ----------- | ----------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| NOW | `NOW()` | Date | Current date and time |
|
||||
| TODAY | `TODAY()` | Date | Current date (midnight) |
|
||||
| DATE | `DATE(year, month, day)` | Date | Construct a date |
|
||||
| YEAR | `YEAR(date)` | Number | Extract year |
|
||||
| MONTH | `MONTH(date)` | Number | Extract month |
|
||||
| DAY | `DAY(date)` | Number | Extract day |
|
||||
| HOUR | `HOUR(date)` | Number | Extract hour |
|
||||
| MINUTE | `MINUTE(date)` | Number | Extract minute |
|
||||
| SECOND | `SECOND(date)` | Number | Extract second |
|
||||
| WEEKDAY | `WEEKDAY(date, [type])` | Number | Day of week |
|
||||
| WEEKNUM | `WEEKNUM(date, [type])` | Number | Week number |
|
||||
| DAYS | `DAYS(end_date, start_date)` | Number | Days between two dates (end - start), includes decimals. **Note parameter order: end date comes first** |
|
||||
| DATEDIF | `DATEDIF(start_date, end_date, [unit])` | Number | Whole days/months/years between dates. Unit: `"D"`(default)/`"M"`/`"Y"`. **Start must be before end** |
|
||||
| DURATION | `DURATION(days, [hours], [minutes], [seconds])` | Duration | Create a duration for date arithmetic |
|
||||
| EDATE | `EDATE(date, months)` | Date | Date N months later |
|
||||
| EOMONTH | `EOMONTH(date, [months])` | Date | End of month N months later; months default 0 |
|
||||
| WORKDAY | `WORKDAY(start_date, days, [holidays])` | Date | Date N workdays later (skips weekends and holidays) |
|
||||
| NETWORKDAYS | `NETWORKDAYS(start_date, end_date, [holidays])` | Number | Workdays between dates (inclusive) |
|
||||
|
||||
### 8.5 List functions
|
||||
|
||||
| Function | Signature | Return type | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| LIST | `LIST(val1, val2, ...)` | List | Create a list |
|
||||
| FIRST | `FIRST(list)` | Scalar | First element |
|
||||
| LAST | `LAST(list)` | Scalar | Last element |
|
||||
| NTH | `NTH(list, index)` | Scalar | Nth element (1-based) |
|
||||
| FILTER | `[Table].FILTER(condition).[ResultCol]` or `[Table].[Col].FILTER(condition)` | List | Filter by condition. When data range is a table, result column is **required**; when it's a column/list, it's not needed. Use CurrentValue in conditions. Add `.LISTCOMBINE()` when result column is multi-value |
|
||||
| MAP | `data_range.MAP(mapping_expr)` | List | Apply mapping to each element. Use CurrentValue in mapping |
|
||||
| SORT | `SORT(list, [ascending])` | List | Sort; default ascending (TRUE) |
|
||||
| SORTBY | `[Table].SORTBY([Table].[SortCol], [ascending]).[OutputCol]` | List | Sort by column then extract output column. **Chain-only, must include output column** |
|
||||
| UNIQUE | `UNIQUE(list)` | List | Deduplicate |
|
||||
| ARRAYJOIN | `ARRAYJOIN(list, [delimiter])` | Text | Join list elements as text; default comma-separated |
|
||||
| LISTCOMBINE | `LISTCOMBINE(val1, [val2, ...])` or `list.LISTCOMBINE()` | List | Two uses: (1) merge values/lists into one list; (2) chained call to flatten 2D array (commonly used when FILTER result column is a multi-value field) |
|
||||
| DISTANCE | `DISTANCE(location1, location2)` | Number | Distance between two geographic locations (km) |
|
||||
|
||||
---
|
||||
|
||||
## Section 9: Commonly Confused Functions
|
||||
|
||||
### CONTAIN vs CONTAINTEXT
|
||||
|
||||
| | CONTAIN | CONTAINTEXT |
|
||||
| ----------- | -------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| Purpose | Tests if a **list / `select` (`multiple=true`)** contains a value | Tests if **text** contains a substring |
|
||||
| Example | `[Tags].CONTAIN("Urgent")` | `[Notes].CONTAINTEXT("completed")` |
|
||||
| Wrong usage | `CONTAIN([Notes], "completed")` — cannot do substring matching | `CONTAINTEXT([Tags], "Urgent")` — Tags is a list, not text |
|
||||
|
||||
### ISBLANK vs ISNULL
|
||||
|
||||
| | ISBLANK | ISNULL |
|
||||
| ----------------- | ------- | ------ |
|
||||
| NULL | TRUE | TRUE |
|
||||
| `""` empty string | TRUE | FALSE |
|
||||
| Empty list `[]` | TRUE | FALSE |
|
||||
| `0` | FALSE | FALSE |
|
||||
| `FALSE` | FALSE | FALSE |
|
||||
|
||||
### DAYS vs DATEDIF
|
||||
|
||||
| | DAYS | DATEDIF |
|
||||
| --------------- | ------------------------------------------------------------ | ----------------------------------------- |
|
||||
| Parameter order | `DAYS(end, start)` — end first | `DATEDIF(start, end, unit)` — start first |
|
||||
| Precision | Includes decimals (hours/minutes/seconds as fractional days) | Integer only (whole days/months/years) |
|
||||
| Negative values | Returns negative when start is after end | **Errors** when start is after end |
|
||||
|
||||
### SUM vs SUMIF
|
||||
|
||||
| | SUM | SUMIF |
|
||||
| --------- | ---------------------------------------------- | -------------------------------------------------------------- |
|
||||
| Purpose | Sum all values | Sum values **matching a condition** |
|
||||
| Arguments | `SUM(val1, val2, ...)` or `SUM([Table].[Col])` | `SUMIF(data_range, condition)` with CurrentValue in condition |
|
||||
| Example | `SUM([Orders].[Amount])` — sum all | `SUMIF([Orders].[Amount], CurrentValue > 100)` — sum only >100 |
|
||||
|
||||
### FILTER+aggregation vs COUNTIF/SUMIF
|
||||
|
||||
| | FILTER+aggregation | COUNTIF/SUMIF |
|
||||
| ----------- | ----------------------------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| Nature | Filter then aggregate (two steps) | One-step (syntactic sugar) |
|
||||
| Equivalence | `[Table].FILTER(cond).[Col].LISTCOMBINE().SUM()` | `SUMIF([Table].[Col], cond)` (only when condition involves only column values) |
|
||||
| When to use | Conditions span multiple fields, or multi-step needed | Conditions only involve column values (e.g. `CurrentValue > 100`) |
|
||||
|
||||
---
|
||||
|
||||
## Section 10: Decision Trees
|
||||
|
||||
### Cross-table queries: which approach?
|
||||
|
||||
```
|
||||
Need data from another table?
|
||||
├─ Current table has a link field to the target table?
|
||||
│ ├─ Yes → Use chained access: [LinkField].[TargetField]
|
||||
│ │ Need aggregation? → .SUM() / .ARRAYJOIN(",") / .FIRST()
|
||||
│ └─ No → Need to match by field value?
|
||||
│ ├─ Field matching or complex filtering → [TargetTable].FILTER(CurrentValue.[MatchField] = [Value]).[OutputCol]
|
||||
│ └─ Only counting or summing → COUNTIF([TargetTable], condition) / FILTER+SUM
|
||||
```
|
||||
|
||||
### Conditional logic: IF vs IFS vs SWITCH?
|
||||
|
||||
```
|
||||
Need conditional logic?
|
||||
├─ Single condition → IF(condition, true_val, false_val)
|
||||
├─ Multiple mutually exclusive conditions (if-elseif-else) → IFS(cond1, val1, cond2, val2, ...)
|
||||
├─ Matching a value against fixed options → SWITCH(expr, option1, result1, option2, result2, ..., default)
|
||||
└─ Need error handling?
|
||||
├─ Catch errors → IFERROR(expr, fallback)
|
||||
└─ Catch blanks → IFBLANK(expr, fallback)
|
||||
```
|
||||
|
||||
### Aggregation: which function?
|
||||
|
||||
```
|
||||
Need to aggregate data?
|
||||
├─ Sum/average/max/min for entire column → SUM/AVERAGE/MAX/MIN([Table].[Col])
|
||||
├─ Count non-blank → COUNTA([Table].[Col])
|
||||
├─ Conditional count → COUNTIF([Table], CurrentValue.[Field] = [Value])
|
||||
├─ Conditional sum (column-only condition) → SUMIF([Table].[Col], CurrentValue > threshold)
|
||||
├─ Conditional sum (cross-field condition) → [Table].FILTER(CurrentValue.[Field]=value).[NumCol].LISTCOMBINE().SUM()
|
||||
├─ Count unique → [Table].[Col].UNIQUE().COUNTA()
|
||||
└─ Ranking → RANK([Value], [Table].[Col])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 11: Common Formula Patterns
|
||||
|
||||
### Pattern 1: Cross-table conditional count
|
||||
|
||||
Count rows in target table matching a condition:
|
||||
|
||||
```
|
||||
[TargetTable].COUNTIF(CurrentValue.[MatchField] = [CurrentTableField])
|
||||
```
|
||||
|
||||
### Pattern 2: Cross-table conditional sum
|
||||
|
||||
Filter target table by current row's value, then sum:
|
||||
|
||||
```
|
||||
[TargetTable].FILTER(CurrentValue.[MatchField] = [CurrentTableField]).[NumCol].LISTCOMBINE().SUM()
|
||||
```
|
||||
|
||||
SUMIF works when data range is a column and conditions only involve column values:
|
||||
|
||||
```
|
||||
SUMIF([TargetTable].[NumCol], CurrentValue > 100)
|
||||
```
|
||||
|
||||
Note: COUNTIF can use a table as data range (only counting, no specific column needed), but SUMIF's data range **must be a numeric column** (needs values to sum), so `CurrentValue` is each value in that column (scalar) — cannot use `CurrentValue.[OtherField]` to access other fields. For cross-field conditions, use FILTER with a table as data range.
|
||||
|
||||
### Pattern 3: Cross-table lookup
|
||||
|
||||
```
|
||||
[TargetTable].FILTER(CurrentValue.[MatchCol] = [CurrentTableField]).[ReturnCol]
|
||||
```
|
||||
|
||||
### Pattern 4: Link field values + aggregation
|
||||
|
||||
```
|
||||
SUM([LinkField].[NumField])
|
||||
[LinkField].[TextField].UNIQUE().ARRAYJOIN(",")
|
||||
```
|
||||
|
||||
### Pattern 5: Conditional text concatenation
|
||||
|
||||
```
|
||||
IF([Condition], "prefix" & [Field] & "suffix", "default text")
|
||||
```
|
||||
|
||||
### Pattern 6: Date difference
|
||||
|
||||
```
|
||||
DATEDIF([StartDate], [EndDate], "D") & " days"
|
||||
DAYS([EndDate], [StartDate])
|
||||
```
|
||||
|
||||
### Pattern 7: List element mapping
|
||||
|
||||
```
|
||||
[SelectField(which multiple=true)].MAP(CurrentValue & " tag")
|
||||
SPLIT([TextField], ",").MAP(TRIM(CurrentValue))
|
||||
```
|
||||
|
||||
### Pattern 8: Cross-table with sorting
|
||||
|
||||
```
|
||||
[TargetTable].SORTBY([TargetTable].[SortCol], FALSE).[OutputCol]
|
||||
[TargetTable].FILTER(CurrentValue.[Field] = [Value]).SORTBY([TargetTable].[SortCol]).[OutputCol]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 12: Anti-Pattern Collection
|
||||
|
||||
### Mistake 1: Extra argument in MAP
|
||||
|
||||
```
|
||||
Wrong: [Table].[Col].MAP([Table2].[Col], CurrentValue + 1)
|
||||
Correct: [Table].[Col].MAP(CurrentValue + 1)
|
||||
```
|
||||
|
||||
Reason: MAP takes only two arguments (data range + mapping expression), no "lookup range".
|
||||
|
||||
### Mistake 2: Inverted FILTER syntax
|
||||
|
||||
```
|
||||
Wrong: condition.[Table].FILTER()
|
||||
Correct: [Table].FILTER(condition).[ResultCol] (result column required when data range is a table)
|
||||
```
|
||||
|
||||
Reason: FILTER's data range comes first, condition is passed as the argument.
|
||||
|
||||
### Mistake 3: Using CurrentValue.[Field] on a column range
|
||||
|
||||
```
|
||||
Wrong: SUMIF([Sales].[Revenue], CurrentValue.[Salesperson] = [Name])
|
||||
Correct: [Sales].FILTER(CurrentValue.[Salesperson] = [Name]).[Revenue].LISTCOMBINE().SUM()
|
||||
```
|
||||
|
||||
Reason: `SUMIF([Sales].[Revenue], ...)` uses "Revenue" column as data range. CurrentValue is each revenue value (scalar), not a row — cannot use `.` to access other fields. Use FILTER with the table as data range for cross-field conditions.
|
||||
|
||||
### Mistake 4: Missing result column after FILTER
|
||||
|
||||
```
|
||||
Wrong: [Sales].FILTER(CurrentValue.[Amount] > 100)
|
||||
Correct: [Sales].FILTER(CurrentValue.[Amount] > 100).[Customer]
|
||||
```
|
||||
|
||||
Reason: FILTER on a table returns a table reference; must specify result column with `.[Field]` at the end.
|
||||
|
||||
### Mistake 5: Nested FILTER
|
||||
|
||||
```
|
||||
Wrong: [Table1].FILTER(CurrentValue.[ID] = [Table2].FILTER(CurrentValue.[Status]="Done").[ID])
|
||||
Correct: [Table1].FILTER(CurrentValue.[ID] = [CurrentRowField]).[OutputCol]
|
||||
```
|
||||
|
||||
Reason: FILTER/MAP/SUMIF/COUNTIF cannot be nested inside each other's conditions. Split into multiple steps or use link fields.
|
||||
|
||||
### Mistake 6: SORTBY without output column
|
||||
|
||||
```
|
||||
Wrong: [Table].SORTBY([Table].[Col])
|
||||
Correct: [Table].SORTBY([Table].[Col]).[OutputCol]
|
||||
```
|
||||
|
||||
Reason: SORTBY must have an output column at the end; otherwise the result cannot be represented as an array.
|
||||
|
||||
### Mistake 7: SORTBY sort column without table name
|
||||
|
||||
```
|
||||
Wrong: [Table].SORTBY([Col]).[OutputCol]
|
||||
Correct: [Table].SORTBY([Table].[Col]).[OutputCol]
|
||||
```
|
||||
|
||||
Reason: SORTBY's sort column must use `[TableName].[FieldName]` format.
|
||||
|
||||
### Mistake 8: Using CONTAIN for text substring matching
|
||||
|
||||
```
|
||||
Wrong: CONTAIN([Notes], "urgent")
|
||||
Correct: CONTAINTEXT([Notes], "urgent")
|
||||
```
|
||||
|
||||
Reason: CONTAIN checks if a list or `select` (`multiple=true`) contains a whole value, not substring matching. Use CONTAINTEXT for text substrings.
|
||||
|
||||
### Mistake 9: Date concatenation without formatting
|
||||
|
||||
```
|
||||
Not recommended: "Deadline: " & [DateField] ← output format is uncontrolled
|
||||
Recommended: "Deadline: " & TEXT([DateField], "YYYY-MM-DD")
|
||||
```
|
||||
|
||||
Reason: Concatenating a date with `&` won't error, but uses the default format. Use TEXT to specify the format explicitly.
|
||||
|
||||
### Mistake 10: Reversed DAYS parameter order
|
||||
|
||||
```
|
||||
Wrong: DAYS([StartDate], [EndDate]) → returns negative
|
||||
Correct: DAYS([EndDate], [StartDate]) → returns positive
|
||||
```
|
||||
|
||||
Reason: DAYS parameter order is end date first, start date second.
|
||||
|
||||
### Mistake 11: Chaining zero-argument functions
|
||||
|
||||
```
|
||||
Wrong: TODAY.DAYS([Date])
|
||||
Correct: TODAY().DAYS([Date])
|
||||
```
|
||||
|
||||
Reason: NOW, TODAY, PI and other zero-argument functions must include parentheses.
|
||||
|
||||
---
|
||||
|
||||
## Section 13: Complete Examples
|
||||
|
||||
### Example 1: Employee sales summary
|
||||
|
||||
**Table structure** (from `+table-get`):
|
||||
|
||||
- Employees: EmployeeID (Text), Name (Text), Department (Text)
|
||||
- Sales: ContractID (Number), SalespersonID (Text), Quantity (Number), Total (Number)
|
||||
|
||||
**Current table**: Employees
|
||||
|
||||
**Requirement**: For each employee, output "Sold XX orders" if they have sales records, otherwise "No sales records".
|
||||
|
||||
**Formula**:
|
||||
|
||||
```
|
||||
IF(
|
||||
[Sales].COUNTIF(CurrentValue.[SalespersonID] = [EmployeeID]) >= 1,
|
||||
"Sold " & [Sales].COUNTIF(CurrentValue.[SalespersonID] = [EmployeeID]) & " orders",
|
||||
"No sales records"
|
||||
)
|
||||
```
|
||||
|
||||
**Field JSON**:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "formula",
|
||||
"name": "Sales Summary",
|
||||
"expression": "IF([Sales].COUNTIF(CurrentValue.[SalespersonID] = [EmployeeID]) >= 1, \"Sold \" & [Sales].COUNTIF(CurrentValue.[SalespersonID] = [EmployeeID]) & \" orders\", \"No sales records\")"
|
||||
}
|
||||
```
|
||||
|
||||
**Explanation**: `[Sales].COUNTIF(...)` uses the entire Sales table as data range. CurrentValue represents each row in Sales, accessing `CurrentValue.[SalespersonID]` for that row's salesperson. `[EmployeeID]` refers to the current row in the Employees table (where the formula lives).
|
||||
|
||||
### Example 2: Chained cross-table access via link fields
|
||||
|
||||
**Table structure**:
|
||||
|
||||
- Orders: ID (`auto_number`), OrderItems (`link` [target: OrderItems, foreign key: ID])
|
||||
- OrderItems: ID (`auto_number`), Product (`link` [target: Products, foreign key: ID])
|
||||
- Products: ID (`auto_number`), ProductName (`text`)
|
||||
|
||||
**Current table**: Orders
|
||||
|
||||
**Requirement**: Deduplicate and comma-join all product names from linked order items.
|
||||
|
||||
**Formula**:
|
||||
|
||||
```
|
||||
[OrderItems].[Product].[ProductName].UNIQUE().ARRAYJOIN(",")
|
||||
```
|
||||
|
||||
**Field JSON**:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "formula",
|
||||
"name": "Product List",
|
||||
"expression": "[OrderItems].[Product].[ProductName].UNIQUE().ARRAYJOIN(\",\")"
|
||||
}
|
||||
```
|
||||
|
||||
**Explanation**: `[OrderItems]` gets linked order item records, `.[Product]` expands to each item's linked product, `.[ProductName]` gets all product names, `.UNIQUE()` deduplicates, `.ARRAYJOIN(",")` joins with commas.
|
||||
|
||||
### Example 3: Cross-table filter + sort
|
||||
|
||||
**Table structure**:
|
||||
|
||||
- Projects: ProjectName (Text), Status (Text), Owner (Text)
|
||||
- Tasks: TaskName (Text), Project (Text), Priority (Number), DueDate (Date)
|
||||
|
||||
**Current table**: Projects
|
||||
|
||||
**Requirement**: Find the highest-priority (lowest number) task name for the current project.
|
||||
|
||||
**Formula**:
|
||||
|
||||
```
|
||||
FIRST(
|
||||
[Tasks].FILTER(CurrentValue.[Project] = [ProjectName]).SORTBY([Tasks].[Priority], TRUE).[TaskName]
|
||||
)
|
||||
```
|
||||
|
||||
**Field JSON**:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "formula",
|
||||
"name": "Top Priority Task",
|
||||
"expression": "FIRST([Tasks].FILTER(CurrentValue.[Project] = [ProjectName]).SORTBY([Tasks].[Priority], TRUE).[TaskName])"
|
||||
}
|
||||
```
|
||||
|
||||
**Explanation**: `[Tasks].FILTER(CurrentValue.[Project] = [ProjectName])` filters tasks belonging to the current project. `.SORTBY([Tasks].[Priority], TRUE)` sorts by priority ascending. `.[TaskName]` extracts task names. `FIRST(...)` gets the first one (highest priority).
|
||||
|
||||
---
|
||||
|
||||
## Section 14: Translating User Requirements to Formulas
|
||||
|
||||
When the user describes their formula need in natural language, follow these rules to convert it into a precise expression:
|
||||
|
||||
1. **Numbers must use precise values**: "less than 80%" → field value less than `0.8`. "above 1000" → `>= 1000`.
|
||||
2. **Interval boundaries**: "above/below/within" = closed (inclusive); "less than/more than/outside" = open (exclusive).
|
||||
3. **Branching logic** must be organized as an ordered list with a fallback branch. Each branch has a condition and output.
|
||||
- Example: "return risk level for 1-3" → `IFS([Value] = 1, "low", [Value] = 2, "medium", [Value] = 3, "high")` with an `IFERROR` or trailing empty-string fallback.
|
||||
4. **Multi-level branches must be flattened** to a single level. Nested if-else chains → flat IFS.
|
||||
5. **Branch conditions must be mutually exclusive**. If the user's conditions overlap, rewrite to eliminate ambiguity.
|
||||
6. **Reorder branches by logical priority** if the user's order is illogical (e.g., check specific conditions before catch-all).
|
||||
|
||||
---
|
||||
|
||||
## Section 15: Constraint Summary
|
||||
|
||||
- Request body must include `"type": "formula"` — this field is required
|
||||
- Only use functions and operators listed in this document
|
||||
- FILTER/SUMIF/COUNTIF/MAP must not be nested inside each other's conditions (chained calls are not nesting)
|
||||
- Do not use LOOKUP — use FILTER exclusively
|
||||
- Table and field names must exactly match `+table-get` output
|
||||
- Strings must use double quotes `"`
|
||||
- Format dates with TEXT before concatenating, to control output format
|
||||
- SORTBY can only be chained and must include an output column
|
||||
- Link fields return lists — aggregate or extract single values before output
|
||||
158
.claude/skills/lark-base/references/lark-base-cell-value.md
Normal file
158
.claude/skills/lark-base/references/lark-base-cell-value.md
Normal file
@ -0,0 +1,158 @@
|
||||
# base CellValue 规范(lark-base-cell-value)
|
||||
|
||||
> 适用命令:`lark-cli base +record-upsert`、`lark-cli base +record-batch-create`、`lark-cli base +record-batch-update`
|
||||
|
||||
本文件定义 **shortcut 写记录** 时 `CellValue` 的推荐格式,目标是让 AI 一次写对。不同命令的外层 JSON 形状不同,但每个 cell 都以本文为 source of truth。
|
||||
|
||||
## 1. 顶层规则(必须遵守)
|
||||
|
||||
- `--json` 必须是 JSON 对象。
|
||||
- `+record-upsert`:顶层直接传字段映射:`{"字段名或字段ID": CellValue}`。
|
||||
- `+record-batch-create`:使用 `create_records`,其每个元素都是 `Map<FieldNameOrID, CellValue>`。
|
||||
- `+record-batch-update`:使用 `update_records`,其每个 value 都是 `Map<FieldNameOrID, CellValue>`。
|
||||
- 一次 payload 里同一字段只用一种 key(字段名或字段 ID),不要重复。
|
||||
- 写入前先 `+field-list` 获取字段 `type/style/multiple`,再构造值。
|
||||
- 需要清空字段时优先传 `null`(字段允许清空时)。
|
||||
|
||||
## 2. 各类型 CellValue
|
||||
|
||||
### 2.1 text
|
||||
|
||||
text 字段的 `style.type` 影响单元格检查逻辑:
|
||||
`type=plain` 传 Markdown 格式的字符串。
|
||||
`type=url` 传一个带 title 的 Markdown 格式链接,或单独传一个链接。
|
||||
`type=phone` 传合法电话号码。
|
||||
`type=email` 传合法邮箱字符串。
|
||||
|
||||
```json
|
||||
{
|
||||
"标题": "Hello, [lark-cli](https://github.com/larksuite/cli)",
|
||||
"官网": "[官网](https://example.com)",
|
||||
"联系电话": "1380000000000",
|
||||
"邮箱": "owner@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 number
|
||||
|
||||
用 JSON number,不要用带单位或千分位的字符串。货币、百分比、进度、评分等数字类字段也按数字写入,展示格式由字段配置决定。
|
||||
|
||||
```json
|
||||
{
|
||||
"工时": 12.5,
|
||||
"预算": 3000,
|
||||
"完成度": 0.65,
|
||||
"评分": 4
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 select(单选/多选)
|
||||
|
||||
`select` 字段用 `multiple` 区分单选和多选:`multiple=false` 时传选项名字符串,`multiple=true` 时传选项名数组。只支持写入字段中已有的选项;构造 CellValue 前先用 `+field-list` 或 `+field-search-options` 确认目标选项存在。
|
||||
|
||||
```json
|
||||
{
|
||||
"单选": "Todo",
|
||||
"多选": ["后端", "高优"]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 datetime
|
||||
|
||||
优先用 `YYYY-MM-DD HH:mm:ss` 字符串,这是最稳妥的写法,也和常见 API 输出更容易对齐。不要写相对时间(如“明天上午”)。
|
||||
|
||||
```json
|
||||
{
|
||||
"截止时间": "2026-03-24 10:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 checkbox
|
||||
|
||||
用 JSON boolean:`true` 或 `false`,不要用 `"true"`、`"是"`、`1`。
|
||||
|
||||
```json
|
||||
{
|
||||
"已完成": true
|
||||
}
|
||||
```
|
||||
|
||||
### 2.6 user / group_chat
|
||||
|
||||
用对象数组,元素至少包含 `id`。人员字段传用户 ID(如 `ou_xxx`),群字段传群 ID(如 `oc_xxx`);单值/多值都统一使用数组。
|
||||
|
||||
> **人员字段:不要猜 ID。** 不知道 `open_id` 时,先用 `lark-contact` 查 id:`lark-cli contact +search-user --query "<姓名/邮箱/手机号>" --as user`。
|
||||
|
||||
> **群组字段:不要猜 ID。** 不知道 `chat_id` 时,先用 `lark-im` 搜群:`lark-cli im +chat-search --query "<群名关键词>" --as user`;取结果里的 `oc_xxx`。
|
||||
|
||||
```json
|
||||
{
|
||||
"负责人": [
|
||||
{ "id": "ou_xxx" },
|
||||
{ "id": "ou_xxx2" }
|
||||
],
|
||||
"协作群": [
|
||||
{ "id": "oc_xxx" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.7 link
|
||||
|
||||
用对象数组,元素包含 `id`,值为目标记录的 `record_id`。不要传记录标题;先用 `+record-list` / `+record-search` 找到目标记录 ID。
|
||||
|
||||
```json
|
||||
{
|
||||
"关联任务": [
|
||||
{ "id": "<record_id>" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.8 location
|
||||
|
||||
写入对象必须使用 `{lng, lat}`,两者都是数字;`lng` 是经度,`lat` 是纬度。不需要手动传 `full_address`,平台会根据坐标解析地址。
|
||||
|
||||
```json
|
||||
{
|
||||
"坐标": {
|
||||
"lng": 116.397428,
|
||||
"lat": 39.90923
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
读取、筛选、转文本等场景使用 `full_address` 字符串;只有公式能访问坐标。如果用户只给地址文本,先获取或确认坐标后再写入;不要把仅有地址文本直接当作 location CellValue。
|
||||
|
||||
### 2.9 attachment(不作为普通 CellValue 写入)
|
||||
|
||||
- 追加附件:使用 `lark-cli base +record-upload-attachment --record-id <record_id> --field-id <field_id> --file <path>`;可重复 `--file` 一次追加多个附件,不能用普通记录操作接口写附件值。
|
||||
- 删除附件:使用 `lark-cli base +record-remove-attachment --record-id <record_id> --field-id <field_id> --file-token <file_token> --yes`;可重复 `--file-token` 一次删除同一单元格里的多个附件。
|
||||
- 下载附件:使用 `lark-cli base +record-download-attachment --record-id <record_id> --file-token <file_token> --output <dir>`;不传 `--file-token` 时下载整行所有附件,也可重复 `--file-token` 只下载指定附件。Base 附件必须用这个命令下载,用其他下载入口可能失败。
|
||||
|
||||
## 3. 只读字段(不要写)
|
||||
|
||||
以下字段在写记录时应视为只读:
|
||||
- `auto_number`
|
||||
- `lookup`
|
||||
- `formula`
|
||||
- `created_at` / `updated_at`
|
||||
- `created_by` / `updated_by`
|
||||
|
||||
写入只读字段通常不会更新数据;返回里可能出现 `ignored_fields`,reason 会说明 `READONLY`。看到这种返回时,不要重试同一 payload,应移除只读字段,只写存储字段。
|
||||
|
||||
## 4. 完整示例
|
||||
|
||||
```json
|
||||
{
|
||||
"标题": "Created from shortcut",
|
||||
"状态": "Todo",
|
||||
"标签": ["高优", "外部依赖"],
|
||||
"工时": 8,
|
||||
"截止时间": "2026-03-24 10:00:00",
|
||||
"已完成": false,
|
||||
"负责人": [{ "id": "ou_123" }],
|
||||
"关联任务": [{ "id": "rec_456" }],
|
||||
"坐标": { "lng": 116.397428, "lat": 39.90923 }
|
||||
}
|
||||
```
|
||||
@ -0,0 +1,717 @@
|
||||
# base +dashboard-block-get-data
|
||||
|
||||
> **前置条件:** 先阅读 [lark-base-dashboard.md](lark-base-dashboard.md) 了解 dashboard 整体工作流。
|
||||
|
||||
获取仪表盘图表组件(block)的**最终计算结果**,返回一份适合 AI 直接消费的图表协议 JSON。
|
||||
|
||||
这个命令适合以下场景:
|
||||
|
||||
1. 读取柱状图 / 条形图 / 折线图 / 饼图 / 环形图 / 面积图 / 组合图 / 散点图 / 漏斗图 / 雷达图 / 词云 / 指标卡的**实际计算结果**;
|
||||
2. 把图表结果交给 AI 做后续总结、趋势解释、同比/环比说明、异常点提取;
|
||||
3. 在**不读取原始记录**的前提下,直接消费图表层已经聚合好的结果;
|
||||
4. 验证某个图表当前展示的数据是否符合预期。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> - 本命令返回的是**图表结果协议**,不是 block 元数据;
|
||||
> - 如果你需要 `name`、`type`、`layout`、`data_config` 等配置,请先用 `+dashboard-block-get`;
|
||||
> - 文本组件(`text`)不涉及计算,不适用本命令;
|
||||
|
||||
## 一句话理解
|
||||
|
||||
`+dashboard-block-get-data` = **拿图表“算出来的结果”**,而不是拿图表“怎么配置的”。
|
||||
|
||||
---
|
||||
|
||||
## 支持的图表类型
|
||||
|
||||
当前支持以下图表类型的数据计算与返回:
|
||||
|
||||
### 二维图表(10 种)
|
||||
|
||||
- 柱状图
|
||||
- 条形图
|
||||
- 折线图
|
||||
- 饼图
|
||||
- 环形图
|
||||
- 面积图
|
||||
- 组合图
|
||||
- 散点图
|
||||
- 漏斗图
|
||||
- 雷达图
|
||||
|
||||
### 特殊类型(2 种)
|
||||
|
||||
- 词云
|
||||
- 指标卡(statistics)
|
||||
|
||||
> [!CAUTION]
|
||||
> 文本组件虽然也属于 dashboard block,但它不产生可计算数据,因此不会返回本协议。
|
||||
|
||||
---
|
||||
|
||||
## 推荐命令
|
||||
|
||||
```bash
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token bascn***************CtadY \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
如果你还不知道目标 block 的 ID,典型顺序是:
|
||||
|
||||
```bash
|
||||
# 先看仪表盘里有哪些组件
|
||||
lark-cli base +dashboard-block-list \
|
||||
--base-token bascn***************CtadY \
|
||||
--dashboard-id blkxxxxxxxx
|
||||
|
||||
# 再读取某个组件的最终计算结果
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token bascn***************CtadY \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
如果你需要先确认组件类型、名称或 `data_config`,请先执行:
|
||||
|
||||
```bash
|
||||
lark-cli base +dashboard-block-get \
|
||||
--base-token bascn***************CtadY \
|
||||
--dashboard-id blkxxxxxxxx \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--base-token <token>` | 是 | Base Token,标识目标多维表格 |
|
||||
| `--block-id <id>` | 是 | 图表 Block ID,即目标组件的唯一标识 |
|
||||
| `--format <fmt>` | 否 | 输出格式,遵循 CLI 全局输出格式规则 |
|
||||
| `--dry-run` | 否 | 只预览 API 调用,不真正执行 |
|
||||
|
||||
> [!TIP]
|
||||
> 这个命令**不需要** `--dashboard-id`。只要 `base_token + block_id` 即可定位并读取图表结果。
|
||||
|
||||
---
|
||||
|
||||
## 返回结构总览
|
||||
|
||||
CLI 成功输出使用标准 `{ok, identity, data}` 信封:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"identity": "user",
|
||||
"data": {
|
||||
"dimensions": [],
|
||||
"measures": [],
|
||||
"main_data": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中 `identity` 是本次调用实际使用的身份,`data` 是 CLI 图表协议本体。不同图表类型的 `data` 结构略有不同:
|
||||
|
||||
| 图表类型 | 一定有 | 可能有 |
|
||||
|----------|--------|--------|
|
||||
| 二维图表 | `dimensions` / `measures` / `main_data` | 无 |
|
||||
| 词云 | `dimensions` / `measures` / `main_data` | 无 |
|
||||
| 指标卡 | `dimensions` / `measures` / `main_data` | `comparison_data` / `trend_data` |
|
||||
|
||||
---
|
||||
|
||||
## 协议字段说明
|
||||
|
||||
### 1) `dimensions`
|
||||
|
||||
维度定义数组,告诉你主结果里每个 `dim_*` key 代表什么字段。
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"field_name": "文本",
|
||||
"alias": "dim_5bKp"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
字段含义:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `field_name` | 维度字段显示名称 |
|
||||
| `alias` | 维度别名,在 `main_data` / `trend_data` 中作为 key 使用 |
|
||||
|
||||
### 2) `measures`
|
||||
|
||||
指标定义数组,告诉你每个 `me_*` key 代表什么聚合指标。
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"field_name": "Count",
|
||||
"aggregation": "count_all",
|
||||
"alias": "me_Y291bnRfYWxsX0NvdW50"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
字段含义:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `field_name` | 统计该指标时所使用的字段名称;当 `aggregation = count_all` 时固定为 `Count`,表示统计记录总数 |
|
||||
| `aggregation` | 聚合方式,常见值:`count_all` / `count` / `sum` / `avg` / `min` / `max` |
|
||||
| `alias` | 指标别名,在 `main_data` / `comparison_data` / `trend_data` 中作为 key 使用 |
|
||||
|
||||
例如:
|
||||
|
||||
- 如果统计“销售额”的求和,则 `field_name = 销售额`、`aggregation = sum`
|
||||
- 如果统计记录总数,则 `field_name = Count`、`aggregation = count_all`
|
||||
|
||||
### 3) `main_data`
|
||||
|
||||
主结果集。每一行都是一个对象,key 不是字段名本身,而是 `dimensions` / `measures` 中声明过的 `alias`。
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"me_Y291bnRfYWxsX0NvdW50": {"value": 3}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 4) `comparison_data`
|
||||
|
||||
仅指标卡可能返回。表示同/环比的两个值,顺序固定为:
|
||||
|
||||
1. 当前周期值
|
||||
2. 对比周期值
|
||||
|
||||
> [!NOTE]
|
||||
> 原始协议里通常**不直接展示周期名称**,只提供对应的值。因此解释“同比”还是“环比”、以及比较窗口具体是什么,通常要结合组件配置或 UI 上下文理解。
|
||||
|
||||
### 5) `trend_data`
|
||||
|
||||
仅指标卡可能返回。表示时间序列趋势,每一行通常包含一个时间维度和一个指标值。
|
||||
|
||||
---
|
||||
|
||||
## alias 规则与读取方式
|
||||
|
||||
你不应该把 alias 当成人类可读字段名,而应把它视为**结果表里的列 ID**。
|
||||
|
||||
常见生成规则:
|
||||
|
||||
- 维度 alias:`dim_` + `base64(field_name)`
|
||||
- 指标 alias:`me_` + `base64(aggregation + "_" + field_name)`
|
||||
|
||||
> [!NOTE]
|
||||
> 为了便于阅读,本文档中的部分示例会使用**简化后的 alias**(例如 `dim_xxx`、`me_xxx` 或较短的示例值),不保证和真实返回值逐字符一致。
|
||||
> 在实际读取结果时,应始终以 `dimensions` / `measures` 中声明的 alias 为准,而不要假设所有示例都严格展开成完整编码值。
|
||||
|
||||
例如:
|
||||
|
||||
```json
|
||||
{
|
||||
"dimensions": [
|
||||
{"field_name": "文本", "alias": "dim_5bKp"}
|
||||
],
|
||||
"measures": [
|
||||
{"field_name": "Count", "aggregation": "count_all", "alias": "me_xxx"}
|
||||
],
|
||||
"main_data": [
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"me_xxx": {"value": 3}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
应解读为:
|
||||
|
||||
- `dim_5bKp` 对应字段“文本”,取值是 `A`
|
||||
- `me_xxx` 对应指标 `count_all(Count)`,取值是 `3`
|
||||
|
||||
> [!TIP]
|
||||
> 读取结果时,**先看 `dimensions` / `measures`,再解 `main_data`**。不要仅凭 alias 名字猜含义。
|
||||
|
||||
---
|
||||
|
||||
## 各图表类型的协议细节
|
||||
|
||||
### 一、二维图表
|
||||
|
||||
适用于:柱状图、条形图、折线图、饼图、环形图、面积图、组合图、散点图、漏斗图、雷达图。
|
||||
|
||||
#### 结构特征
|
||||
|
||||
- `dimensions`:通常有 `1~2` 个维度
|
||||
- 不分组聚合时:通常 1 个维度
|
||||
- 开启分组聚合时:通常 2 个维度
|
||||
- `measures`:指标定义数组
|
||||
- `main_data`:按“维度组合”展开后的行数据
|
||||
|
||||
#### 这类数据代表什么
|
||||
|
||||
二维图表返回的本质上是一张**聚合结果表**:
|
||||
|
||||
- 每一行代表一个维度值,或一组维度组合;
|
||||
- 每一个 measure 值代表该维度下算出来的指标结果;
|
||||
- 如果图表开启了分组聚合,那么每一行表示“主维度 + 分组维度”的一个组合结果;
|
||||
- 如果图表是折线图、面积图这类带时间轴的图,通常可以把第一维理解为横轴、把 measure 理解为纵轴数值;
|
||||
- 如果图表是饼图、环形图这类占比图,通常可以把每一行理解为一个扇区对应的分类及其数值。
|
||||
|
||||
换句话说,AI 在读取这类结果时,可以把它当作“按某些维度聚合后的统计明细表”,适合进一步做排序、Top N、占比解释、分组对比和趋势总结。
|
||||
|
||||
#### 示例 1:普通二维图表(无分组聚合)
|
||||
|
||||
```json
|
||||
{
|
||||
"dimensions": [
|
||||
{
|
||||
"field_name": "文本",
|
||||
"alias": "dim_5bKp"
|
||||
}
|
||||
],
|
||||
"measures": [
|
||||
{
|
||||
"aggregation": "count_all",
|
||||
"field_name": "Count",
|
||||
"alias": "me_Y291bnRfYWxsX0NvdW50"
|
||||
}
|
||||
],
|
||||
"main_data": [
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"me_Y291bnRfYWxsX0NvdW50": {"value": 3}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "B"},
|
||||
"me_Y291bnRfYWxsX0NvdW50": {"value": 2}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "C"},
|
||||
"me_Y291bnRfYWxsX0NvdW50": {"value": 2}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
可解读为:
|
||||
|
||||
- 维度字段是“文本”
|
||||
- 指标是“按记录总数统计”
|
||||
- 当“文本”字段为 `A` 时,对应的 `Count` 指标值是 `3`
|
||||
- 当“文本”字段为 `B` 时,对应的 `Count` 指标值是 `2`
|
||||
- 当“文本”字段为 `C` 时,对应的 `Count` 指标值是 `2`
|
||||
|
||||
#### 示例 2:二维图表(开启分组聚合)
|
||||
|
||||
```json
|
||||
{
|
||||
"dimensions": [
|
||||
{
|
||||
"field_name": "文本",
|
||||
"alias": "dim_5bKp"
|
||||
},
|
||||
{
|
||||
"field_name": "单选",
|
||||
"alias": "dim_5aSl"
|
||||
}
|
||||
],
|
||||
"measures": [
|
||||
{
|
||||
"aggregation": "count_all",
|
||||
"field_name": "Count",
|
||||
"alias": "me_YW91bnR"
|
||||
}
|
||||
],
|
||||
"main_data": [
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"dim_5aSl": {"value": "a-1"},
|
||||
"me_YW91bnR": {"value": 2}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"dim_5aSl": {"value": "a-2"},
|
||||
"me_YW91bnR": {"value": 1}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "B"},
|
||||
"dim_5aSl": {"value": "b-1"},
|
||||
"me_YW91bnR": {"value": 1}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "C"},
|
||||
"dim_5aSl": {"value": "c-1"},
|
||||
"me_YW91bnR": {"value": 2}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
可解读为:
|
||||
|
||||
- 第一维是“文本”,第二维是“单选”,指标是“按记录总数统计”
|
||||
- 当“文本”字段为 `A`、且“单选”字段为 `a-1` 时,对应的指标值是 `2`
|
||||
- 当“文本”字段为 `A`、且“单选”字段为 `a-2` 时,对应的指标值是 `1`
|
||||
- 当“文本”字段为 `B`、且“单选”字段为 `b-1` 时,对应的指标值是 `1`
|
||||
- 当“文本”字段为 `C`、且“单选”字段为 `c-1` 时,对应的指标值是 `2`
|
||||
- 如果按“文本”字段汇总,那么“文本”字段为 `A` 时总指标值是 `3`;为 `B` 时总指标值是 `1`;为 `C` 时总指标值是 `2`
|
||||
|
||||
---
|
||||
|
||||
### 二、词云
|
||||
|
||||
#### 结构特征
|
||||
|
||||
词云协议仍然沿用 `dimensions + measures + main_data` 的结构,但语义稍有不同:
|
||||
|
||||
- `dimensions` 对应被分词的字段;
|
||||
- `main_data` 每一行代表一个词;
|
||||
- `measure` 的 value 表示按该词分组后计算出来的统计值。
|
||||
|
||||
#### 这类数据代表什么
|
||||
|
||||
词云返回的不是“原文列表”,而是**按词分组后的聚合统计结果**:
|
||||
|
||||
- `dimensions` 定义的是被分词的来源字段;
|
||||
- `measure` 对应的是该词在当前图表统计范围内对应的统计值,具体含义取决于聚合方式和指标字段;
|
||||
- `main_data` 的每一行都可以理解成“某个词 + 该词对应的统计结果”,其中该维度的具体 value 就是拆分出来的词;
|
||||
- 返回结果通常已经结合图表当前过滤条件、时间范围、数据权限等上下文计算完成。
|
||||
|
||||
因此,AI 读取词云数据时,更适合做“关键词排序”“热点词解释”“按词聚合结果分析”“主题归纳”,而不是把它当成逐条文本记录去理解。
|
||||
|
||||
#### 示例
|
||||
|
||||
```json
|
||||
{
|
||||
"dimensions": [
|
||||
{
|
||||
"field_name": "文本",
|
||||
"alias": "dim_5bKp"
|
||||
}
|
||||
],
|
||||
"measures": [
|
||||
{
|
||||
"aggregation": "count_all",
|
||||
"field_name": "Count",
|
||||
"alias": "me_YW91bnR"
|
||||
}
|
||||
],
|
||||
"main_data": [
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"me_YW91bnR": {"value": 3}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "B"},
|
||||
"me_YW91bnR": {"value": 2}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "C"},
|
||||
"me_YW91bnR": {"value": 2}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
可解读为:
|
||||
|
||||
- 被统计的分词字段是“文本”
|
||||
- 当前示例里的 measure 是 `count_all(Count)`,所以这里的统计值可以理解为“按词分组后的记录总数”
|
||||
- 当分词结果为 `A` 时,对应的统计值是 `3`
|
||||
- 当分词结果为 `B` 时,对应的统计值是 `2`
|
||||
- 当分词结果为 `C` 时,对应的统计值是 `2`
|
||||
- 按统计值排序,分词结果 `A` 对应的值最高
|
||||
- 分词结果 `B` 和 `C` 的统计值相同,说明它们处于同一梯队
|
||||
|
||||
---
|
||||
|
||||
### 三、指标卡(statistics)
|
||||
|
||||
指标卡除了主值外,还可能包含同/环比与趋势结果,是本命令里结构最特殊的一类。
|
||||
|
||||
#### 结构特征
|
||||
|
||||
- `measures`:**有且仅有一个指标**
|
||||
- `main_data`:通常只有一行,表示总指标值
|
||||
- `comparison_data`:可选,表示当前周期值与对比周期值
|
||||
- `trend_data`:可选,表示趋势序列
|
||||
- `dimensions`:可能包含同/环比日期字段、趋势日期字段
|
||||
|
||||
#### 这类数据代表什么
|
||||
|
||||
指标卡返回的核心是一个**主指标摘要**,外加可选的比较信息和趋势信息:
|
||||
|
||||
- `main_data` 表示当前卡片最核心、最醒目的那个主值;它通常是某个表的记录总数,或某个字段的聚合值,本身**不带时间周期概念**;
|
||||
- `comparison_data` 表示用于同/环比展示的两个数值,通常是“当前周期值”和“对比周期值”;它们表示某个时间周期下的记录总数,或某个字段的聚合值;
|
||||
- `trend_data` 表示这个指标在一段时间内的变化轨迹,用来支持走势判断;
|
||||
- `dimensions` 在指标卡里通常不是拿来做主分组展示,而是给 `trend_data` 或同/环比相关日期字段提供语义说明。
|
||||
|
||||
例如:
|
||||
|
||||
- `main_data = 7` 可以理解为当前卡片展示的主数据,比如某张表当前总记录数是 `7`;
|
||||
- `comparison_data[0] = 6` 则表示某个比较周期下的当前值,比如“本月记录总数 = 6”;
|
||||
- 因此,`main_data` 与 `comparison_data[0]` **不一定相等**,因为两者表达的口径并不完全相同。
|
||||
|
||||
因此,AI 在解读指标卡时,应该优先回答这几个问题:
|
||||
|
||||
1. 当前主值是多少;
|
||||
2. 和对比周期相比是上升、下降还是持平;
|
||||
3. 趋势整体是增长、波动还是下滑;
|
||||
4. 是否存在明显的异常峰值或低谷。
|
||||
|
||||
> [!NOTE]
|
||||
> 当指标卡**同时指定同/环比和趋势**时,`dimensions` 中日期维度的顺序是固定的:
|
||||
> 1. 第一个元素是**趋势**对应的日期维度;
|
||||
> 2. 第二个元素是**同/环比**对应的日期维度。
|
||||
>
|
||||
> 另外要注意:`comparison_data` 自身通常**不直接携带日期字段**,它只给出“当前周期值 / 对比周期值”。
|
||||
> `dimensions` 中的第一个日期维度会直接出现在 `trend_data` 中,作为趋势序列的时间列;
|
||||
> 第二个日期维度则主要用于补充“该卡片配置了哪类比较相关日期字段”的语义。
|
||||
|
||||
#### 示例
|
||||
|
||||
```json
|
||||
{
|
||||
"dimensions": [
|
||||
{
|
||||
"field_name": "日期",
|
||||
"alias": "dim_ZGF0ZQ"
|
||||
},
|
||||
{
|
||||
"field_name": "日期2",
|
||||
"alias": "dim_ZGF0ZTI"
|
||||
}
|
||||
],
|
||||
"measures": [
|
||||
{
|
||||
"aggregation": "count_all",
|
||||
"field_name": "Count",
|
||||
"alias": "me_YW91b"
|
||||
}
|
||||
],
|
||||
"main_data": [
|
||||
{
|
||||
"me_YW91b": {"value": 7}
|
||||
}
|
||||
],
|
||||
"comparison_data": [
|
||||
{
|
||||
"me_YW91b": {"value": 6}
|
||||
},
|
||||
{
|
||||
"me_YW91b": {"value": 0}
|
||||
}
|
||||
],
|
||||
"trend_data": [
|
||||
{
|
||||
"dim_ZGF0ZQ": {"value": "2026-01-15"},
|
||||
"me_YW91b": {"value": 1}
|
||||
},
|
||||
{
|
||||
"dim_ZGF0ZQ": {"value": "2026-01-17"},
|
||||
"me_YW91b": {"value": 1}
|
||||
},
|
||||
{
|
||||
"dim_ZGF0ZQ": {"value": "2026-03-22"},
|
||||
"me_YW91b": {"value": 1}
|
||||
},
|
||||
{
|
||||
"dim_ZGF0ZQ": {"value": "2026-04-24"},
|
||||
"me_YW91b": {"value": 2}
|
||||
},
|
||||
{
|
||||
"dim_ZGF0ZQ": {"value": "2026-05-01"},
|
||||
"me_YW91b": {"value": 1}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
可解读为:
|
||||
|
||||
- 当前主指标值 = `7`
|
||||
- 当前主指标值不带时间周期概念,可理解为当前卡片主数据
|
||||
- comparison_data[0] = 当前周期值 `6`,例如某个时间周期(如本月)下的统计值
|
||||
- comparison_data[1] = 对比周期值 `0`
|
||||
- `dimensions[0]` 对应趋势日期维度,因此实际出现在 `trend_data` 里
|
||||
- `dimensions[1]` 对应同/环比相关的日期维度,用来补充比较语义
|
||||
- trend_data 展示该指标随时间的变化序列
|
||||
- 从 comparison_data 看,当前周期相较对比周期是上升的,并且对比周期值为 0
|
||||
- 从 trend_data 看,这个指标并不是每天都有值,而是在若干离散日期出现
|
||||
- 趋势序列里的最高点出现在 `2026-04-24`,值为 `2`
|
||||
- 其余出现的日期大多为 `1`,说明整体上有波动,但暂时没有持续快速增长的趋势
|
||||
|
||||
> [!NOTE]
|
||||
> `comparison_data` 只告诉你“当前值 / 对比值”,**不额外标出日期区间文本**。如果用户需要完整说明“和上周比”还是“和上月比”,通常要结合组件配置或界面上下文进一步判断。
|
||||
|
||||
---
|
||||
|
||||
## 如何正确解读返回值
|
||||
|
||||
建议按下面顺序阅读:
|
||||
|
||||
1. **先看 `dimensions`**:确认每个 `dim_*` alias 对应哪个字段;
|
||||
2. **再看 `measures`**:确认每个 `me_*` alias 是什么聚合方式;
|
||||
3. **最后读 `main_data` / `comparison_data` / `trend_data`**:把 alias 还原成“字段名 + 指标名”再做解释。
|
||||
|
||||
### 推荐解释模板
|
||||
|
||||
如果要把结果转成自然语言,建议不要只“复述数值”,而应尽量覆盖下面几个层次:
|
||||
|
||||
1. **先解释指标含义**:说明 measure 代表“记录总数”“某字段求和”“平均值”等;
|
||||
2. **再给出核心结果**:明确当前主值、主要分类、主要组合或主要词项;
|
||||
3. **做排序或 Top N 提炼**:指出最高、最低、前几名、同一梯队;
|
||||
4. **补充分组/对比关系**:如果有第二维或 comparison_data,就说明比较对象和差异;
|
||||
5. **分析趋势或异常点**:如果有时间序列,指出上升、下降、波动、峰值、低谷;
|
||||
6. **最后给一句结论**:总结最值得关注的信息。
|
||||
|
||||
可参考下面模板:
|
||||
|
||||
- 二维图表:
|
||||
- 基础模板:`按 <维度字段> 统计,当前指标 <指标含义>;其中 <维度值1>=<指标值1>,<维度值2>=<指标值2> ...`
|
||||
- 增强模板:`按 <维度字段> 统计,当前指标表示 <指标含义>。从结果看,<Top1维度值> 的值最高,为 <Top1值>;<Top2维度值> 和 <Top3维度值> 紧随其后。若按 Top N 看,前 <N> 项合计贡献了 ...;若看低值项,<低值维度值> 最低,为 <低值>。整体上,<一句总结>`
|
||||
|
||||
- 分组聚合图表:
|
||||
- 基础模板:`按 <维度1> 统计,并以 <维度2> 分组,得到 <组合1>=<值1>,<组合2>=<值2> ...`
|
||||
- 增强模板:`当前指标表示 <指标含义>。按 <维度1> 拆分后,不同 <维度2> 组之间存在明显差异:例如 <组合1> = <值1>,<组合2> = <值2>。如果按 <维度1> 汇总,<Top1维度1值> 总值最高,为 <汇总值>;如果看组内对比,<某组> 在 <某维度1值> 下表现最强 / 最弱。整体说明 <一句总结>`
|
||||
|
||||
- 词云:
|
||||
- 基础模板:`按分词结果统计,当前指标表示 <指标含义>;其中 <词1>=<统计值1>,<词2>=<统计值2> ...`
|
||||
- 增强模板:`当前词云反映的是“按词分组后的 <指标含义>”。从结果看,<Top1词> 的值最高,为 <值1>,说明它是当前最突出的关键词;<Top2词>、<Top3词> 处于第二梯队。如果按 Top N 看,主要关注词集中在 <主题A>、<主题B>;如果有多个词数值接近,可归为同一热点层级。整体上,这组词更适合用来总结 <主题/热点/关注点>`
|
||||
|
||||
- 指标卡:
|
||||
- 基础模板:`当前主指标值为 <main_data>;当前周期值为 <comparison_data[0]>;对比周期值为 <comparison_data[1]>;趋势上 ...`
|
||||
- 增强模板:`当前主指标表示 <指标含义>,主值为 <main_data>。若看周期比较,当前周期值为 <comparison_data[0]>,对比周期值为 <comparison_data[1]>,因此整体表现为 <上升/下降/持平>。若看趋势序列,最高点出现在 <日期>,值为 <峰值>;最低点出现在 <日期>,值为 <低值>;整体走势表现为 <持续增长/阶段波动/明显回落>。如果需要给出结论,可总结为:<一句总结>`
|
||||
|
||||
> [!TIP]
|
||||
> 当用户明确要求“帮我分析”“帮我总结”“帮我找异常 / Top N / 趋势”时,优先采用增强模板,而不是只逐条复述原始数值。
|
||||
|
||||
---
|
||||
|
||||
## 常见工作流
|
||||
|
||||
### 场景 1:用户要“拿这个图表当前展示的数据”
|
||||
|
||||
```bash
|
||||
# 如果已知 block_id,直接读结果
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token xxx \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
### 场景 2:用户说“帮我分析这个图表”,但你还不知道它是什么组件
|
||||
|
||||
```bash
|
||||
# 先看组件配置,确认它是不是支持计算的图表类型
|
||||
lark-cli base +dashboard-block-get \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx \
|
||||
--block-id chtxxxxxxxx
|
||||
|
||||
# 再读最终计算结果
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token xxx \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
### 场景 3:用户要找“仪表盘里哪个图的结果异常”
|
||||
|
||||
```bash
|
||||
# 先列组件
|
||||
lark-cli base +dashboard-block-list \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx
|
||||
|
||||
# 再针对可疑 block 逐个取结果
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token xxx \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 何时优先用这个命令
|
||||
|
||||
- 用户说“帮我拿这个图表算出来的数据 / 结果 / 指标”
|
||||
- 用户已经知道 `block_id`,目标是**读取结果**而不是看配置
|
||||
- 用户后续还要让 AI 对图表结果做解释、归纳、比较、总结
|
||||
- 你只关心图表层的聚合产出,不需要回到底表逐条读记录
|
||||
|
||||
## 何时不要误用
|
||||
|
||||
- 想看 block 的 `data_config`、名称、类型、布局 → 用 `+dashboard-block-get`
|
||||
- 想列出仪表盘里有哪些组件 → 用 `+dashboard-block-list`
|
||||
- 想修改或新建组件 → 用 `+dashboard-block-update` / `+dashboard-block-create`
|
||||
- 想看原始记录明细,而不是图表聚合结果 → 回到 `record-*`
|
||||
- 目标是文本组件 → 本命令不适用
|
||||
|
||||
---
|
||||
|
||||
## 常见误区
|
||||
|
||||
### 误区 1:把这个命令当成“获取 block 详情”
|
||||
|
||||
不是。这个命令不返回:
|
||||
|
||||
- block 名称
|
||||
- block 类型
|
||||
- layout
|
||||
- `data_config`
|
||||
- 所属 dashboard 信息
|
||||
|
||||
这些都应该通过 `+dashboard-block-get` 获取。
|
||||
|
||||
### 误区 2:以为它返回的是原始记录
|
||||
|
||||
不是。它返回的是**图表聚合后的最终结果**。如果图表本身做了过滤、分组、聚合、时间窗口限制,返回值反映的是图表视角,不是原始表全量明细。
|
||||
|
||||
### 误区 3:直接把 alias 当真实字段名读
|
||||
|
||||
不应该。alias 只是协议里的键,必须结合 `dimensions` / `measures` 还原语义。
|
||||
|
||||
### 误区 4:看到指标卡的 `comparison_data` 就以为已经知道“同比/环比周期文本”
|
||||
|
||||
不一定。它只给出比较值,不一定给出周期标签。若要精确解释比较窗口,通常还需要组件配置或 UI 上下文。
|
||||
|
||||
---
|
||||
|
||||
## dry-run 用途
|
||||
|
||||
可用来确认最终会调用的接口路径:
|
||||
|
||||
```bash
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token bascn_example_token \
|
||||
--block-id chtxxxxxxxx \
|
||||
--dry-run \
|
||||
--format pretty
|
||||
```
|
||||
|
||||
你应能看到类似:
|
||||
|
||||
```text
|
||||
GET /open-apis/base/v3/bases/bascn_example_token/dashboards/blocks/chtxxxxxxxx/data
|
||||
```
|
||||
|
||||
适合在以下场景使用:
|
||||
|
||||
- 校验 `base_token` / `block_id` 是否传对;
|
||||
- 调试 agent 生成的命令;
|
||||
- 编写自动化测试时确认请求结构。
|
||||
|
||||
---
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base-dashboard.md](lark-base-dashboard.md) — dashboard 模块总指引
|
||||
- `+dashboard-block-get` — 获取 block 元数据
|
||||
- [dashboard-block-data-config.md](dashboard-block-data-config.md) — data_config 结构和组件类型说明
|
||||
247
.claude/skills/lark-base/references/lark-base-dashboard.md
Normal file
247
.claude/skills/lark-base/references/lark-base-dashboard.md
Normal file
@ -0,0 +1,247 @@
|
||||
# Dashboard(仪表盘/数据看板)模块指引
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
Dashboard 是 Base 中的数据可视化看板,可以把表格数据变成**组件**(图表、指标卡等)进行展示。
|
||||
|
||||
## 核心概念
|
||||
|
||||
- **Dashboard(仪表盘)**:容器,包含多个组件
|
||||
- **Block(组件)**:仪表盘中的单个可视化元素(柱状图、折线图、饼图、指标卡等)
|
||||
- **data_config**:组件的数据源配置(表名、字段、分组等)
|
||||
|
||||
## 能力速览
|
||||
|
||||
| 你想做什么 | 用这些命令 | 关键文档 |
|
||||
|------|-----------|---------|
|
||||
| 创建/删除/改名称 | `+dashboard-create/delete/update` | 本页下方「仪表盘管理」 |
|
||||
| 在仪表盘里添加组件 | `+dashboard-block-create` | 先定位 dashboard、表和字段,再读 [dashboard-block-data-config.md](dashboard-block-data-config.md) 构造 `data_config` |
|
||||
| 修改组件 | `+dashboard-block-update` | 先读 block 现状,再读 [dashboard-block-data-config.md](dashboard-block-data-config.md) 决定替换哪些顶层 key |
|
||||
| 查看仪表盘有哪些组件 | `+dashboard-get` 或 `+dashboard-block-list` | 本页下方「查看仪表盘」 |
|
||||
| 读取图表计算结果 | `+dashboard-block-get-data` | 返回图表最终数据协议;需要 block 元数据先用 `+dashboard-block-get` |
|
||||
| 智能重排组件布局 | `+dashboard-arrange` | 用户明确要求重排,或本次会话新建仪表盘的收尾整理;无法指定精确位置 |
|
||||
|
||||
## 典型场景工作流
|
||||
|
||||
### 场景 1:从 0 到 1 创建仪表盘
|
||||
|
||||
从 0 到 1 创建仪表盘时,按用户需求规划组件的类型和数量,并注意以下要点:
|
||||
|
||||
- 聚合方式:创建指标卡或分布图时优先把聚合写进 `data_config`,只有 Top N、字段取值探索、复杂筛选校验或 helper 汇总表场景才先用 `+data-query`。
|
||||
- Dry-run 边界:已按模板构造的简单指标卡、分布图、趋势图不需要逐个 `--dry-run` 后再真实创建;只有在调试 JSON、检查请求体、复杂自造 `data_config` 或处理 API validation 错误时才 dry-run。
|
||||
- 验证方式:通过创建接口返回值确认创建成功与否,只在结果不确定时用 `+dashboard-get` 或 `+dashboard-block-list` 确认仪表盘和组件存在,或调用 `+dashboard-block-get-data`读取计算结果验证。
|
||||
- 布局方式:`+dashboard-arrange` 仅两种情况使用:① 用户明确要求美化/重排;② 本次会话中从零新建的仪表盘,建完组件后做一次性布局整理。不是创建成功的必要步骤。
|
||||
|
||||
示例:搭建一个销售数据分析仪表盘
|
||||
|
||||
```bash
|
||||
# 第 1 步:创建空白仪表盘
|
||||
lark-cli base +dashboard-create --base-token xxx --name "销售数据分析"
|
||||
# 记录返回的 dashboard_id
|
||||
|
||||
# 第 2 步:获取数据源信息
|
||||
lark-cli base +table-list --base-token xxx
|
||||
lark-cli base +field-list --base-token xxx --table-id <table_id>
|
||||
|
||||
# 第 3 步:规划应该创建哪些组件(根据用户需求确定组件类型和数量)
|
||||
# 例如:总销售额(指标卡)、月度趋势(折线图)、品类占比(饼图)
|
||||
|
||||
# 第 4 步:顺序创建每个组件(必须串行执行,不能并发)
|
||||
# 重要:创建组件前,先确定 dashboard_id、组件 name/type 和真实表字段
|
||||
# 再阅读 dashboard-block-data-config.md 了解 data_config 结构、组件类型和 filter 规则
|
||||
|
||||
# 第 1 个组件
|
||||
lark-cli base +dashboard-block-create \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx \
|
||||
--name "总销售额" \
|
||||
--type statistics \
|
||||
--data-config '{"table_name":"订单表","series":[{"field_name":"金额","rollup":"SUM"}]}'
|
||||
|
||||
# 第 2 个组件(等上一个完成后再执行)
|
||||
lark-cli base +dashboard-block-create \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx \
|
||||
--name "月度趋势" \
|
||||
--type line \
|
||||
--data-config '{"table_name":"订单表","series":[{"field_name":"金额","rollup":"SUM"}],"group_by":[{"field_name":"月份","mode":"integrated"}]}'
|
||||
|
||||
# 继续创建其他组件...
|
||||
|
||||
# 第 5 步:组件创建完成后,使用 arrange 命令智能重排布局(可选但推荐)
|
||||
# 默认布局可能不够美观,arrange 会根据组件数量和类型自动优化布局
|
||||
# 若用户没有要求美化/重排,可先跳过此步骤;这不影响仪表盘和组件是否已创建成功
|
||||
lark-cli base +dashboard-arrange \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx
|
||||
```
|
||||
|
||||
### 场景 2:在已有仪表盘上添加新组件
|
||||
|
||||
```bash
|
||||
# 第 1 步:列出仪表盘,定位到当前仪表盘
|
||||
lark-cli base +dashboard-list --base-token xxx
|
||||
# 获取目标 dashboard_id
|
||||
|
||||
# 第 2 步:根据用户诉求规划组件类型和数据源
|
||||
# 建议先查看当前仪表盘已有组件,避免重复创建,或作为参考
|
||||
lark-cli base +dashboard-get --base-token xxx --dashboard-id blk_xxx
|
||||
|
||||
# 第 3 步:获取数据源信息
|
||||
lark-cli base +table-list --base-token xxx
|
||||
lark-cli base +field-list --base-token xxx --table-id <table_id>
|
||||
|
||||
# 第 4 步:顺序创建每个新组件(必须串行执行,不能并发)
|
||||
# 重要:先确定 dashboard_id、组件 name/type 和真实表字段
|
||||
# 再阅读 dashboard-block-data-config.md 了解 data_config 结构
|
||||
lark-cli base +dashboard-block-create \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx \
|
||||
--name "新组件名" \
|
||||
--type column \
|
||||
--data-config '{...}'
|
||||
```
|
||||
|
||||
### 场景 3:编辑已有组件
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `+dashboard-block-update` **不能修改组件的 `type`**(图表类型),只能更新 `name` 和 `data_config`。
|
||||
> 如需更换组件类型,必须先删除再重新创建。
|
||||
|
||||
```bash
|
||||
# 第 1 步:列出仪表盘,定位到当前仪表盘
|
||||
lark-cli base +dashboard-list --base-token xxx
|
||||
|
||||
# 第 2 步:列出组件,获取到目标组件
|
||||
lark-cli base +dashboard-block-list --base-token xxx --dashboard-id blk_xxx
|
||||
# 获取目标 block_id
|
||||
# 提示:查看已有组件可作为参考,或检查是否重复创建相似组件
|
||||
|
||||
# 第 3 步:获取组件当前详情
|
||||
lark-cli base +dashboard-block-get --base-token xxx --dashboard-id blk_xxx --block-id chtxxxxxxxx
|
||||
|
||||
# 第 4 步:根据用户编辑诉求准备更新
|
||||
# 如果编辑诉求涉及数据源变更,需要先获取数据源信息
|
||||
lark-cli base +table-list --base-token xxx
|
||||
lark-cli base +field-list --base-token xxx --table-id <table_id>
|
||||
|
||||
# 第 5 步:执行更新
|
||||
# 重要:先读取当前 block 的 name/type/data_config
|
||||
# 再阅读 dashboard-block-data-config.md 了解 data_config 更新规则
|
||||
lark-cli base +dashboard-block-update \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx \
|
||||
--block-id chtxxxxxxxx \
|
||||
--data-config '{...}'
|
||||
|
||||
```
|
||||
|
||||
### 场景 4:重排仪表盘布局
|
||||
|
||||
当用户明确要求对已有仪表盘进行布局重排或美化时使用(对本次会话从零新建的仪表盘,可在建完组件后直接做一次性整理,见场景 1)。
|
||||
|
||||
> [!CAUTION]
|
||||
> - 排列结果是**服务端智能推荐**,不一定完全符合用户预期
|
||||
> - 无法指定具体位置(如"第一排放 A,第二排放 B"),排列逻辑是**自适应**的
|
||||
> - **不建议**在已有仪表盘上自动调用,除非用户明确要求
|
||||
|
||||
```bash
|
||||
# 第 1 步:列出仪表盘,定位到目标仪表盘
|
||||
lark-cli base +dashboard-list --base-token xxx
|
||||
|
||||
# 第 2 步:执行智能重排
|
||||
lark-cli base +dashboard-arrange \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx
|
||||
```
|
||||
|
||||
### 场景 5:读取仪表盘或组件现状
|
||||
|
||||
**选择查询方式:**
|
||||
- 想看仪表盘整体结构(含主题、所有组件名称和类型)→ 用 **方式 A**
|
||||
- 只想快速查看有哪些组件 → 用 **方式 B**
|
||||
- 想看某个组件的详细 data_config 配置 → 用 **方式 C**
|
||||
- 想看某个图表/指标卡实际算出来的数据 → 用 **方式 D**
|
||||
|
||||
```bash
|
||||
# 第 1 步:列出仪表盘,定位到当前仪表盘
|
||||
lark-cli base +dashboard-list --base-token xxx
|
||||
|
||||
# 第 2 步:根据用户诉求查看详情
|
||||
|
||||
# 方式 A:查看仪表盘整体情况(包含所有组件列表)
|
||||
lark-cli base +dashboard-get --base-token xxx --dashboard-id blk_xxx
|
||||
|
||||
# 方式 B:列出所有组件
|
||||
lark-cli base +dashboard-block-list --base-token xxx --dashboard-id blk_xxx
|
||||
|
||||
# 方式 C:查看某个组件的详细配置
|
||||
lark-cli base +dashboard-block-get --base-token xxx --dashboard-id blk_xxx --block-id chtxxxxxxxx
|
||||
|
||||
# 方式 D:查看某个图表组件的计算结果(AI 友好的 chart protocol)
|
||||
lark-cli base +dashboard-block-get-data --base-token xxx --block-id chtxxxxxxxx
|
||||
|
||||
# 最后:把获取到的现状信息整理好告诉用户
|
||||
```
|
||||
|
||||
## 组件类型选择
|
||||
|
||||
组件 `type` 决定展示形式:
|
||||
|
||||
| 用户想看什么 | 选什么 type | 说明 |
|
||||
|-------------|------------|------|
|
||||
| 数据趋势(时间变化) | line | 折线图组件 |
|
||||
| 类别比较(谁高谁低) | column | 柱状图组件 |
|
||||
| 占比分布(各部分比例) | pie | 饼图组件 |
|
||||
| 单个关键指标 | statistics | 指标卡组件 |
|
||||
| 富文本说明/标题/注释 | text | 文本组件(支持 Markdown) |
|
||||
|
||||
详细组件类型和 data_config 完整规则:[dashboard-block-data-config.md](dashboard-block-data-config.md)
|
||||
|
||||
## 常见问题
|
||||
|
||||
**Q: 创建组件的命令和 data_config 怎么写?**
|
||||
A:
|
||||
1. 先确定 `dashboard_id`、组件 `name`、组件 `type` 和真实表字段
|
||||
2. 再读 [dashboard-block-data-config.md](dashboard-block-data-config.md) 了解:
|
||||
- 全部组件类型的可复制模板
|
||||
- filter 筛选条件格式
|
||||
- 字段类型与操作符对应表
|
||||
|
||||
**Q: 为什么组件创建失败了?**
|
||||
A: 常见原因:
|
||||
- `table_name` 用了 table_id 而不是表名(必须用表名称,如「订单表」)
|
||||
- `series` 和 `count_all` 同时存在(必须二选一,互斥)
|
||||
- 字段名拼写错误(必须用 `+field-list` 获取的真实字段名,禁止猜测)
|
||||
- 组件创建并发执行(必须串行,等上一个完成再执行下一个)
|
||||
|
||||
**Q: 可以一次创建多个组件吗?**
|
||||
A: 不可以,必须串行执行。等上一个 `+dashboard-block-create` 完成后再执行下一个。
|
||||
|
||||
**Q: 组件的 `type` 创建后能改吗?**
|
||||
A: 不能。`+dashboard-block-update` 只能修改 `name` 和 `data_config`,不能修改 `type`。
|
||||
|
||||
**Q: 更新组件的命令和 data_config 怎么写?**
|
||||
A:
|
||||
1. 先读取当前 block,确认 `block_id`、当前 `type` 和已有 `data_config`
|
||||
2. 再读 [dashboard-block-data-config.md](dashboard-block-data-config.md) 了解 data_config 结构
|
||||
|
||||
**data_config 更新策略(顶层 key merge)**:
|
||||
- 只传入需要修改的顶层字段(如 `series`、`filter`)
|
||||
- 未传的顶层字段(如 `group_by`)自动保留原值
|
||||
- 但每个传入的字段内部是**全量替换**(如传新 `filter` 会完整覆盖旧 `filter`)
|
||||
|
||||
**Q: 查看已有组件有什么用?**
|
||||
A: 在「添加新组件」或「编辑组件」前查看已有组件可以:
|
||||
- 了解当前仪表盘已有哪些可视化
|
||||
- 避免重复创建相似的组件
|
||||
- 参考已有组件的 data_config 结构作为模板
|
||||
|
||||
**Q: 我想直接拿图表算好的结果给 AI 分析,应该用什么?**
|
||||
A: 用 `+dashboard-block-get-data`。它返回图表协议 JSON(常见字段包括 `dimensions`、`measures`、`main_data`,指标卡可能还有 `comparison_data`、`trend_data`),不返回 block 名称、类型、布局或 `data_config`;需要这些元数据时先用 `+dashboard-block-get`。
|
||||
|
||||
## 写入前检查
|
||||
|
||||
- 创建 block 前必须知道 `base_token`、`dashboard_id`、组件 `name/type` 和 `data_config`。
|
||||
- 更新 block 前必须知道 `base_token`、`dashboard_id`、`block_id`,并读过当前 block。
|
||||
- `data_config` 中使用表名和字段名,不使用 table_id / field_id;名称必须来自 `+table-list` / `+field-list` 的真实返回。
|
||||
@ -0,0 +1,210 @@
|
||||
# Base data analysis SOP
|
||||
|
||||
Base 数据查询与分析任务的执行契约。覆盖记录读取、筛选、排序、Top/Bottom N、聚合统计、分组聚合、多表关联、临时分析和查询后写入前的目标定位。
|
||||
|
||||
本文只管查询选路和正确性边界;具体操作前先读真实结构和现状,复杂 JSON 再跳到 reference:
|
||||
|
||||
- `+data-query`: entry guide [lark-base-data-query-guide.md](lark-base-data-query-guide.md), full DSL SSOT [lark-base-data-query.md](lark-base-data-query.md)
|
||||
- 视图筛选: [lark-base-view-set-filter.md](lark-base-view-set-filter.md)
|
||||
- 记录读取: `+record-list` / `+record-search` / `+record-get`,先确认字段 ID、字段名、分页和投影范围
|
||||
|
||||
## 0. Hard Rules
|
||||
|
||||
- 全局问题不能用默认 `+record-list --limit N` 片面地回答。
|
||||
- `jq` / shell / 本地代码是在个人电脑或当前运行环境中处理已返回数据,只适合小范围结果;超过 200 行默认不推荐本地统计、排序或求极值,应改用 Base 云端查询服务的 filter/sort/aggregate。
|
||||
- “最高、最低、最新、最早、Top、Bottom、总数、全部、异常、最大、最小、最多、最少、优先级最高”等全局语义,必须在 Base 云端查询服务中完成筛选、排序或聚合。
|
||||
- 一次性原始记录查询优先用 `+record-list` / `+record-search` 的 filter/sort;聚合分析优先用 `+data-query`。
|
||||
- `+record-search` 用于关键词检索字段的展示文本;金额、状态、日期、空值、关联等结构化条件继续用 `--filter-json` 表达。
|
||||
- 不要依赖已有视图,除非用户明确指定该视图,或你已读取并验证其 filter/sort/projection 符合当前问题。
|
||||
- 交付输出必须使用用户可读的真实字段值;内部 ID、`record_id`、关联记录 ID、open_id、编码字段只可作为连接键或定位键,不能替代最终输出,除非用户明确要求输出这些键值。
|
||||
- 每次读取必须做最小投影,并包含后续解释、回查或写入需要的业务 key。
|
||||
|
||||
## 1. Intent -> Tool Path
|
||||
|
||||
| 用户意图 | 首选路径 | 关键规则 |
|
||||
| --- | --- | --- |
|
||||
| 看几条、预览、示例 | `+record-list --limit N --field-id ...` | 保持局部语义;不要推广为全局结论 |
|
||||
| 已知 `record_id` | `+record-get` | 直接读取;不要 search/list 反查 |
|
||||
| 明确关键词 | `+record-search --keyword ... --search-field ... --field-id ...` | 必须显式指定 `--search-field`;可叠加 `--filter-json` |
|
||||
| 按条件找原始记录 | `+record-list --filter-json ...` | `filter-json` 与视图筛选结构一致,支持文本、数字、日期、选项、人员、群组、关联等值 |
|
||||
| 排序 / TopN 原始记录 | `+record-list --filter-json ... --sort-json ... --limit N` | 最高/最新用 `desc:true`,最低/最早用 `desc:false`;数组顺序表达优先级;最多 10 个排序条件 |
|
||||
| 聚合 / 分组 / 分组排序 | `+data-query` | 使用 filters/dimensions/measures/sort/limit |
|
||||
| 聚合后输出逐条记录 | `+data-query` 得到业务 key 或候选字段组合 -> `+record-list --filter-json` / `+record-get` 回查 | `+data-query` 维度行按字段组合去重且不返回 `record_id` |
|
||||
| 多表 / 多跳关联 | 以候选数最小的事实表为驱动表,沿业务 key 或 link `record_id` 逐跳回查 | 读出 link 单元格里的关联 `record_id` 后,到被关联表批量 `+record-get` 展示字段 |
|
||||
| 查询后写入 / 视图化 | 先用本 SOP 得到可复核的目标记录 id 集合 | 再进入记录写入或视图配置;高价值可复用查询可沉淀为持久视图 |
|
||||
|
||||
## 2. Execution Patterns
|
||||
|
||||
### 2.1 结构化原始记录与 TopN
|
||||
|
||||
使用 `+record-list` 的 filter/sort 路径:
|
||||
|
||||
1. `+field-list` 确认筛选字段、排序字段、展示字段、业务 key。
|
||||
2. 筛选只用 `--filter-json` 或 `--filter-json @file`。
|
||||
3. 排序用 `--sort-json`。
|
||||
4. `--field-id` 做最小投影,`--limit` 控制返回数量。
|
||||
|
||||
Example: string/number 条件 + TopN:
|
||||
|
||||
```bash
|
||||
lark-cli base +record-list \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--filter-json '{"logic":"and","conditions":[["Title","==","Launch plan"],["Score",">=",80]]}' \
|
||||
--sort-json '[{"field":"Updated","desc":true}]' \
|
||||
--field-id Name \
|
||||
--field-id Title \
|
||||
--field-id Score \
|
||||
--limit 20
|
||||
```
|
||||
|
||||
Example: 复杂筛选从文件读取:
|
||||
|
||||
```bash
|
||||
lark-cli base +record-list \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--filter-json @filter.json \
|
||||
--sort-json '[{"field":"Priority","desc":true}]' \
|
||||
--field-id Name \
|
||||
--field-id Tags \
|
||||
--limit 50
|
||||
```
|
||||
|
||||
`filter-json` 与视图筛选结构一致。下面只列常用 fewshot;字段类型、operator、value 形状拿不准,或需要人员、群组、关联、空值、地理位置、formula / lookup 等完整筛选时,先读 [lark-base-view-set-filter.md](lark-base-view-set-filter.md),再把同样的 filter JSON 传给 `--filter-json`。
|
||||
|
||||
文本 `==`:字段值等于目标文本。
|
||||
```json
|
||||
{"logic":"and","conditions":[["Title","==","Launch plan"]]}
|
||||
```
|
||||
|
||||
文本包含 / like:文本字段包含目标片段;operator 写 `intersects`。
|
||||
```json
|
||||
{"logic":"and","conditions":[["Title","intersects","urgent"]]}
|
||||
```
|
||||
|
||||
数字 `==`:字段值等于目标数字。
|
||||
```json
|
||||
{"logic":"and","conditions":[["Score","==",95]]}
|
||||
```
|
||||
|
||||
日期 `==`:字段值等于目标日期;datetime / created_at / updated_at 用 `ExactDate(...)`。
|
||||
```json
|
||||
{"logic":"and","conditions":[["Due Date","==","ExactDate(2026-06-02)"]]}
|
||||
```
|
||||
|
||||
选项 `==`:字段值匹配单个选项;选项值使用选项名数组,单个选项也写数组。
|
||||
```json
|
||||
{"logic":"and","conditions":[["Priority","==",["P0"]]]}
|
||||
```
|
||||
|
||||
选项 `intersects`:字段值与给定选项集合有交集,常用于多选或“命中任一选项”。
|
||||
```json
|
||||
{"logic":"and","conditions":[["Tags","intersects",["P0","Blocked"]]]}
|
||||
```
|
||||
|
||||
`--sort-json` 传排序数组,数组顺序就是优先级,`desc:true` 为降序,`desc:false` 为升序,最多 10 个排序条件。
|
||||
|
||||
### 2.2 关键词检索后叠加结构化条件
|
||||
|
||||
使用 `+record-search` 做关键词命中,结构化条件仍用 `--filter-json` 下推:
|
||||
|
||||
```bash
|
||||
lark-cli base +record-search \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--keyword Alice \
|
||||
--search-field Name \
|
||||
--filter-json '{"logic":"and","conditions":[["Status","!=","Done"]]}' \
|
||||
--sort-json '[{"field":"Updated","desc":true}]' \
|
||||
--field-id Name \
|
||||
--field-id Status \
|
||||
--limit 20
|
||||
```
|
||||
|
||||
不要把 `+record-search` 当成金额、状态、日期、空值、关联字段的结构化筛选入口;这些条件继续写成 `--filter-json`。
|
||||
|
||||
### 2.3 聚合分析与 TopN
|
||||
|
||||
使用 `+data-query`:
|
||||
|
||||
- 让 Base 云端查询服务完成 filters、dimensions、measures、sort、pagination.limit。
|
||||
- `pagination.limit` 是 Base 云端查询服务中的结果限制,不是本地分页扫描。
|
||||
- 常用聚合 fewshot 先读 [lark-base-data-query-guide.md](lark-base-data-query-guide.md);字段类型、日期 value、DSL shape 以 [lark-base-data-query.md](lark-base-data-query.md) 为准。
|
||||
- `+data-query` 可返回聚合结果或维度字段行;维度字段行按字段组合去重且不返回 `record_id`,不能当逐条原始记录结果使用。
|
||||
- 需要输出逐条记录、记录定位或完整行级字段时,先用 `+data-query` 得到业务 key、分组值或候选字段组合,再用 `+record-list --filter-json` / `+record-get` 回查。
|
||||
|
||||
Example: 分组计数:
|
||||
|
||||
```bash
|
||||
lark-cli base +data-query \
|
||||
--base-token <base_token> \
|
||||
--dsl '{"datasource":{"type":"table","table":{"tableId":"<table_id>"}},"dimensions":[{"field_name":"Status","alias":"status"}],"measures":[{"field_name":"Status","aggregation":"count","alias":"count"}],"shaper":{"format":"flat"}}'
|
||||
```
|
||||
|
||||
Example: 过滤后汇总并取 TopN:
|
||||
|
||||
```bash
|
||||
lark-cli base +data-query \
|
||||
--base-token <base_token> \
|
||||
--dsl '{"datasource":{"type":"table","table":{"tableId":"<table_id>"}},"dimensions":[{"field_name":"Owner","alias":"owner"}],"measures":[{"field_name":"Amount","aggregation":"sum","alias":"total_amount"}],"filters":{"type":1,"conjunction":"and","conditions":[{"field_name":"Status","operator":"is","value":["Done"]}]},"sort":[{"field_name":"total_amount","order":"desc"}],"pagination":{"limit":10},"shaper":{"format":"flat"}}'
|
||||
```
|
||||
|
||||
### 2.4 视图化与复用
|
||||
|
||||
一次性查询先用 `+record-list` / `+record-search` 的 filter/sort 验证。需要用户长期打开、共享或复用时,再把同一套 filter/sort 沉淀为视图。
|
||||
|
||||
Example: 将已验证的筛选排序写入视图:
|
||||
|
||||
```bash
|
||||
lark-cli base +view-set-filter \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--view-id <view_id> \
|
||||
--json @filter.json
|
||||
|
||||
lark-cli base +view-set-sort \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--view-id <view_id> \
|
||||
--json '{"sort_config":[{"field":"Priority","desc":true}]}'
|
||||
```
|
||||
|
||||
手动配置和视图配置的优先级:
|
||||
|
||||
1. `--filter-json` 覆盖 `--view-id` 保存的 view filter JSON。
|
||||
2. `--sort-json` 覆盖 `--view-id` 保存的 view sort config。
|
||||
3. 没有手动 filter/sort 时,`--view-id` 使用视图自身保存的 filter/sort。
|
||||
|
||||
### 2.5 关系查询与回查
|
||||
|
||||
- link 单元格通常是关联表 `record_id` 数组,不是用户可读内容,只是连接键。
|
||||
- 先用 `+field-list` 确认 link 字段的 `link_table`、业务唯一键和展示字段。
|
||||
- 从驱动表拿到候选记录后,用关联 `record_id` 到关联表 `+record-get` 批量读取记录内容。
|
||||
- 多跳关系逐跳建立 `record_id/key -> 用户可读字段` 映射;最终用户可读的信息。
|
||||
|
||||
禁止:
|
||||
|
||||
- 把 link `record_id` 当最终输出。
|
||||
- 用 `+record-search` 搜 link `record_id`。
|
||||
- 基于 ID、自增编号、link 值做语义猜测;禁止依赖字段先验、样本记忆补全交付输出。
|
||||
|
||||
## 3. Range & Pagination Contract
|
||||
|
||||
- `+record-list` 默认页、固定 `--limit`、本地 `jq`、shell 管道、手工浏览输出,都只覆盖已读取范围;超过 200 行不要把本地处理当作推荐路径。
|
||||
- `has_more=true`、存在下一页 offset/page token、或返回行数等于 page size,都表示可能还有未读取数据。
|
||||
- 对全局问题,只有 Base 云端查询服务已经通过 filter/sort/aggregate 收敛目标范围,或 `+data-query` 已在云端完成聚合、排序和限制时,才可以用有限返回形成结论。
|
||||
- 必须全量导出时,按 `+record-list` 分页语义串行翻页;不要并发调用 `+record-list`。
|
||||
|
||||
## 4. Final Answer Check
|
||||
|
||||
形成交付输出前必须能确认:
|
||||
|
||||
- 问题范围是局部样例、单点定位、全局原始记录、聚合分析、多表关联,还是查询后写入。
|
||||
- 筛选、排序、聚合是否发生在 Base 云端查询服务中,而不是本地 `jq` / shell 中。
|
||||
- 如果使用 `jq` / shell,本地输入是否是 200 行以内的小范围结果;超过 200 行是否已改用 Base 云端查询服务查询。
|
||||
- 如果使用 `+record-list` / `+record-search`,是否处理了 `has_more`,且投影包含业务 key 和解释字段。
|
||||
- 如果涉及关系查询,是否按 `record_id` 或业务 key 精确回查,交付输出是否来自关联表真实字段。
|
||||
- 交付输出能追溯到表、字段、筛选条件、排序/聚合条件和连接键。
|
||||
|
||||
任一项无法确认时,继续查询或明确说明只能得到局部结论。
|
||||
@ -0,0 +1,61 @@
|
||||
# Base data-query guide
|
||||
|
||||
This guide is the entry point for `+data-query`. Use it for common aggregation fewshots and command selection. For the complete DSL fields, operators, limits, and response details, use [lark-base-data-query.md](lark-base-data-query.md) as the DSL SSOT.
|
||||
|
||||
Before using `+data-query`, also follow [lark-base-data-analysis-sop.md](lark-base-data-analysis-sop.md) to confirm that the task really needs aggregation instead of record listing or a temporary view.
|
||||
|
||||
## When to use
|
||||
|
||||
Use `+data-query` when the user asks for server-side:
|
||||
|
||||
- group by / aggregation
|
||||
- sum, average, min, max, count, distinct count
|
||||
- filtered aggregation
|
||||
- sorted Top N or Bottom N
|
||||
- global statistical conclusions
|
||||
|
||||
`+data-query` can return dimension field rows, but those rows are grouped by dimension values and do not include `record_id`. Use `+record-list`, `+record-search`, or `+record-get` for row-level output, record identity, or full raw record details.
|
||||
|
||||
## Common Fewshots
|
||||
|
||||
Count records by a category field:
|
||||
|
||||
```bash
|
||||
lark-cli base +data-query \
|
||||
--base-token <base_token> \
|
||||
--dsl '{"datasource":{"type":"table","table":{"tableId":"<table_id>"}},"dimensions":[{"field_name":"Status","alias":"status"}],"measures":[{"field_name":"Status","aggregation":"count","alias":"count"}],"shaper":{"format":"flat"}}'
|
||||
```
|
||||
|
||||
Sum a number field by category and return Top 10:
|
||||
|
||||
```bash
|
||||
lark-cli base +data-query \
|
||||
--base-token <base_token> \
|
||||
--dsl '{"datasource":{"type":"table","table":{"tableId":"<table_id>"}},"dimensions":[{"field_name":"Region","alias":"region"}],"measures":[{"field_name":"Amount","aggregation":"sum","alias":"total_amount"}],"sort":[{"field_name":"total_amount","order":"desc"}],"pagination":{"limit":10},"shaper":{"format":"flat"}}'
|
||||
```
|
||||
|
||||
Aggregate only records matching a filter:
|
||||
|
||||
```bash
|
||||
lark-cli base +data-query \
|
||||
--base-token <base_token> \
|
||||
--dsl '{"datasource":{"type":"table","table":{"tableId":"<table_id>"}},"dimensions":[{"field_name":"Owner","alias":"owner"}],"measures":[{"field_name":"Amount","aggregation":"sum","alias":"total_amount"}],"filters":{"type":1,"conjunction":"and","conditions":[{"field_name":"Status","operator":"is","value":["Done"]}]},"shaper":{"format":"flat"}}'
|
||||
```
|
||||
|
||||
Use `tableName` when the table ID is unavailable but the table name is known:
|
||||
|
||||
```bash
|
||||
lark-cli base +data-query \
|
||||
--base-token <base_token> \
|
||||
--dsl '{"datasource":{"type":"table","table":{"tableName":"Orders"}},"measures":[{"field_name":"Amount","aggregation":"sum","alias":"total_amount"}],"shaper":{"format":"flat"}}'
|
||||
```
|
||||
|
||||
## Routing to the DSL SSOT
|
||||
|
||||
Read [lark-base-data-query.md](lark-base-data-query.md) when you need:
|
||||
|
||||
- the full DSL field reference
|
||||
- supported aggregations and field types
|
||||
- filter operator details
|
||||
- pagination and result limits
|
||||
- response shape and error recovery
|
||||
454
.claude/skills/lark-base/references/lark-base-data-query.md
Normal file
454
.claude/skills/lark-base/references/lark-base-data-query.md
Normal file
@ -0,0 +1,454 @@
|
||||
|
||||
# Base data-query DSL SSOT
|
||||
|
||||
> **入口指南**: [lark-base-data-query-guide.md](lark-base-data-query-guide.md) | **前置条件**: 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
本文档是 `+data-query` JSON DSL 的单一事实来源(SSOT),用于说明完整字段、操作符、限制、返回和错误恢复。常用 fewshot 与命令选择先读 [lark-base-data-query-guide.md](lark-base-data-query-guide.md)。
|
||||
|
||||
查询类任务还必须先遵守 [`lark-base-data-analysis-sop.md`](lark-base-data-analysis-sop.md)。`+data-query` 适合让筛选、分组、聚合、排序和 TopN 在 Base 云端查询服务中执行;不要用默认分页的 `+record-list` 或本地 `jq` 替代聚合查询。
|
||||
|
||||
## 限制
|
||||
|
||||
- **权限要求**(按文档类型分流):
|
||||
- **普通多维表格**:调用者拥有文档的**阅读权限**即可
|
||||
- **高级权限多维表格**:调用者必须是文档管理员,拥有 **FA(Full Access / 完全访问权限)**
|
||||
|
||||
权限不足时返回权限错误。
|
||||
|
||||
## 推荐命令
|
||||
|
||||
```bash
|
||||
# 按字段分组计数
|
||||
lark-cli base +data-query \
|
||||
--base-token MAGObxxxxx \
|
||||
--dsl '{
|
||||
"datasource": {"type": "table", "table": {"tableId": "tblxxxxxxxx"}},
|
||||
"dimensions": [{"field_name": "城市", "alias": "dim_city"}],
|
||||
"measures": [{"field_name": "城市", "aggregation": "count", "alias": "count"}],
|
||||
"shaper": {"format": "flat"}
|
||||
}'
|
||||
|
||||
# 带过滤条件 + 排序 + 限制条数
|
||||
lark-cli base +data-query \
|
||||
--base-token MAGObxxxxx \
|
||||
--dsl '{
|
||||
"datasource": {"type": "table", "table": {"tableId": "tblxxxxxxxx"}},
|
||||
"dimensions": [{"field_name": "城市", "alias": "dim_city"}],
|
||||
"measures": [{"field_name": "金额", "aggregation": "sum", "alias": "total_amount"}],
|
||||
"filters": {
|
||||
"type": 1,
|
||||
"conjunction": "and",
|
||||
"conditions": [{"field_name": "城市", "operator": "isNot", "value": [""]}]
|
||||
},
|
||||
"sort": [{"field_name": "total_amount", "order": "desc"}],
|
||||
"pagination": {"limit": 100},
|
||||
"shaper": {"format": "flat"}
|
||||
}'
|
||||
|
||||
# 使用 tableName(表名)代替 tableId
|
||||
lark-cli base +data-query \
|
||||
--base-token MAGObxxxxx \
|
||||
--dsl '{
|
||||
"datasource": {"type": "table", "table": {"tableName": "销售数据"}},
|
||||
"measures": [{"field_name": "金额", "aggregation": "sum", "alias": "total"}],
|
||||
"shaper": {"format": "flat"}
|
||||
}'
|
||||
|
||||
# 聚合或维度查询后如需读取逐条记录,先让 data-query 返回可回查的业务 key
|
||||
lark-cli base +data-query \
|
||||
--base-token MAGObxxxxx \
|
||||
--dsl '{
|
||||
"datasource": {"type": "table", "table": {"tableId": "tblxxxxxxxx"}},
|
||||
"dimensions": [{"field_name": "业务编号", "alias": "biz_key"}],
|
||||
"measures": [{"field_name": "指标值", "aggregation": "max", "alias": "max_value"}],
|
||||
"filters": {
|
||||
"type": 1,
|
||||
"conjunction": "and",
|
||||
"conditions": [{"field_name": "状态", "operator": "is", "value": ["有效"]}]
|
||||
},
|
||||
"sort": [{"field_name": "max_value", "order": "desc"}],
|
||||
"pagination": {"limit": 10},
|
||||
"shaper": {"format": "flat"}
|
||||
}'
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------------------------|------|------|
|
||||
| `--base-token <token>` | 是 | Base Token(base_token) |
|
||||
| `--dsl <json>` | 是 | LiteQuery Protocol JSON DSL 查询语句 |
|
||||
|
||||
## 如何从链接中提取参数
|
||||
|
||||
用户通常会提供如下 URL:
|
||||
|
||||
```
|
||||
https://example.feishu.cn/base/<base_token>?table=<table_id>
|
||||
```
|
||||
|
||||
- `--base-token`:取 `/base/` 后面的字符串
|
||||
- DSL 中的 `tableId`:取 `table=` 后面的值
|
||||
|
||||
## API 入参详情
|
||||
|
||||
**HTTP 方法和路径:**
|
||||
|
||||
```
|
||||
POST /open-apis/base/v3/bases/:base_token/data/query
|
||||
```
|
||||
|
||||
**Path 参数:**
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `base_token` | 是 | Base Token |
|
||||
|
||||
**Request Body — DSL 结构:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `datasource` | object | 是 | 数据源,包含 `type`(固定 `"table"`)和 `table` 对象 |
|
||||
| `datasource.table.tableId` | string | 二选一 | 目标数据表 ID |
|
||||
| `datasource.table.tableName` | string | 二选一 | 目标数据表名称 |
|
||||
| `dimensions` | Dimension[] | 否* | 分组维度字段(GROUP BY) |
|
||||
| `measures` | Measure[] | 否* | 聚合度量字段 |
|
||||
| `filters` | FilterGroup | 否 | 过滤条件(WHERE) |
|
||||
| `sort` | Sort[] | 否 | 排序规则 |
|
||||
| `pagination` | object | 否 | 限制返回行数,`{limit: N}`,最大 5000 |
|
||||
| `shaper` | object | 否 | 结果格式,固定 `{format: "flat"}` |
|
||||
|
||||
> \* `dimensions` 和 `measures` 至少填写一个。
|
||||
|
||||
**Dimension 字段:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `field_name` | string | 是 | 字段名称 |
|
||||
| `alias` | string | 否 | 输出列别名,需全局唯一 |
|
||||
|
||||
**Measure 字段:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `field_name` | string | 是 | 字段名称 |
|
||||
| `aggregation` | string | 是 | 聚合函数:`sum`、`avg`、`min`、`max`、`count`、`count_all`、`distinct_count` |
|
||||
| `alias` | string | 否 | 输出列别名,需全局唯一 |
|
||||
|
||||
**聚合函数适用字段类型:**
|
||||
|
||||
| 聚合函数 | 适用字段类型 |
|
||||
|----------|-------------|
|
||||
| `sum` / `avg` | `number` |
|
||||
| `min` / `max` | `number`、`datetime` |
|
||||
| `count` | 全字段适用,计数非空值 |
|
||||
| `count_all` | 全字段适用,计数所有行 |
|
||||
| `distinct_count` | 全字段适用 |
|
||||
|
||||
> `number` 包含 `style.type` 为 `progress` / `currency` / `rating` 等所有子类型。
|
||||
|
||||
**FilterGroup:**
|
||||
|
||||
```json
|
||||
{
|
||||
"filters": {
|
||||
"type": 1,
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{"field_name": "城市", "operator": "is", "value": ["北京"]}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `type` | int | 是 | 固定填 `1` |
|
||||
| `conjunction` | string | 否 | 条件组合逻辑:`"and"` 或 `"or"`,默认 `"and"` |
|
||||
| `conditions` | Condition[] | 否 | 条件列表 |
|
||||
|
||||
**Condition:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `field_name` | string | 是 | 字段名称(必须与表中字段名精确匹配) |
|
||||
| `operator` | string | 是 | 运算符(见下方运算符表) |
|
||||
| `value` | string[] | 是 | 条件值数组;`isEmpty`/`isNotEmpty` 时**必须**传空数组 `[]` |
|
||||
|
||||
**运算符:**
|
||||
|
||||
| 运算符 | 说明 |
|
||||
|--------|------|
|
||||
| `is` | 等于 |
|
||||
| `isNot` | 不等于 |
|
||||
| `contains` | 包含 |
|
||||
| `doesNotContain` | 不包含 |
|
||||
| `isEmpty` | 为空 |
|
||||
| `isNotEmpty` | 不为空 |
|
||||
| `isGreater` | 大于 |
|
||||
| `isGreaterEqual` | 大于等于 |
|
||||
| `isLess` | 小于 |
|
||||
| `isLessEqual` | 小于等于 |
|
||||
|
||||
> 各运算符的适用字段类型见下方「按各字段类型筛选时 value 格式详解」。
|
||||
|
||||
**按各字段类型筛选时 value 格式详解:**
|
||||
|
||||
*`text`*
|
||||
|
||||
| 运算符 | value 格式 | 元素个数 | 示例 |
|
||||
|--------|-----------|---------|------|
|
||||
| `is` / `isNot` / `contains` / `doesNotContain` | `["文本内容"]` | 仅 1 个 | `["Hello"]` |
|
||||
| `isEmpty` / `isNotEmpty` | `[]` | 0 个 | `[]` |
|
||||
|
||||
> **不支持** `isGreater` / `isGreaterEqual` / `isLess` / `isLessEqual`:文本无自然顺序,比较运算无意义。
|
||||
> `text` 也覆盖电话、超链接、邮箱、条码字段;通过 `style.type` 区分(`plain`(默认)/ `phone` / `url` / `email` / `barcode`),运算符集合一致。
|
||||
> 当 `style.type=url` 时,value 筛选的是链接显示名称,而不是 URL 本身。
|
||||
|
||||
*`number`*
|
||||
|
||||
| 运算符 | value 格式 | 元素个数 | 示例 |
|
||||
|--------|-----------|---------|------|
|
||||
| `is` / `isNot` / `isGreater` / `isGreaterEqual` / `isLess` / `isLessEqual` | `["数字字符串"]` | 仅 1 个 | `["23.4"]`、`["-100"]` |
|
||||
| `isEmpty` / `isNotEmpty` | `[]` | 0 个 | `[]` |
|
||||
|
||||
> value 必须为合法数字的字符串形式。
|
||||
> `number` 也覆盖货币、进度、评分字段;通过 `style.type` 区分(`plain`(默认)/ `currency` / `progress` / `rating`),运算符集合一致,仅 value 解释不同:
|
||||
> - 当 `style.type=progress` 时,34% 对应 0.34 而不是 34。
|
||||
> - 当 `style.type=rating` 时,必须输入整数,代表评分。
|
||||
|
||||
*`auto_number`*
|
||||
|
||||
| 运算符 | value 格式 | 元素个数 | 示例 |
|
||||
|--------|-----------|---------|------|
|
||||
| `is` / `isNot` / `contains` / `doesNotContain` | `["编号字符串"]` | 仅 1 个 | `["00001"]` |
|
||||
| `isGreater` / `isGreaterEqual` / `isLess` / `isLessEqual` | `["编号字符串"]` | 仅 1 个 | `["00010"]` |
|
||||
| `isEmpty` / `isNotEmpty` | `[]` | 0 个 | `[]` |
|
||||
|
||||
*`select`*
|
||||
|
||||
| 运算符 | value 格式 | 元素个数 | 示例 |
|
||||
|--------|-----------|---------|------|
|
||||
| `is` / `isNot` | `["选项名"]` | **仅 1 个** | `["选项A"]` |
|
||||
| `contains` / `doesNotContain` | `["选项A", "选项B"]` | 可多个 | `["选项A", "选项B"]` |
|
||||
| `isEmpty` / `isNotEmpty` | `[]` | 0 个 | `[]` |
|
||||
|
||||
> **不支持** `isGreater` / `isGreaterEqual` / `isLess` / `isLessEqual`:选项为枚举值,无自然顺序。
|
||||
> 通过 `multiple` 区分单选(`multiple=false`,默认)/ 多选(`multiple=true`)。
|
||||
|
||||
*`user` / `created_by` / `updated_by`*
|
||||
|
||||
| 运算符 | value 格式 | 元素个数 | 示例 |
|
||||
|--------|-----------|---------|------------------------|
|
||||
| `is` / `isNot` | `["用户ID1", "用户ID2"]` | **可多个** | `["ou_aaa", "ou_bbb"]` |
|
||||
| `contains` / `doesNotContain` | `["用户ID1", "用户ID2"]` | 可多个 | `["ou_aaa", "ou_bbb"]` |
|
||||
| `isEmpty` / `isNotEmpty` | `[]` | 0 个 | `[]` |
|
||||
|
||||
> **不支持** `isGreater` / `isGreaterEqual` / `isLess` / `isLessEqual`:人员无法比大小。
|
||||
> 用户 ID 使用 `open_id`(`ou_` 前缀),接口层会自动做 ID 转换。
|
||||
|
||||
*`group_chat`*
|
||||
|
||||
| 运算符 | value 格式 | 元素个数 | 示例 |
|
||||
|--------|-----------|---------|------|
|
||||
| `is` / `isNot` | `["群组ID1", "群组ID2"]` | 可多个 | `["oc_aaa", "oc_bbb"]` |
|
||||
| `contains` / `doesNotContain` | `["群组ID1", "群组ID2"]` | 可多个 | `["oc_aaa", "oc_bbb"]` |
|
||||
| `isEmpty` / `isNotEmpty` | `[]` | 0 个 | `[]` |
|
||||
|
||||
> **不支持** `isGreater` / `isGreaterEqual` / `isLess` / `isLessEqual`:群组无法比大小。
|
||||
|
||||
*`link`*
|
||||
|
||||
| 运算符 | value 格式 | 元素个数 | 示例 |
|
||||
|--------|-----------|---------|------|
|
||||
| `is` / `isNot` | `["recId1", "recId2"]` | 可多个 | `["recAAA", "recBBB"]` |
|
||||
| `contains` / `doesNotContain` | `["recId1", "recId2"]` | 可多个 | `["recAAA", "recBBB"]` |
|
||||
| `isEmpty` / `isNotEmpty` | `[]` | 0 个 | `[]` |
|
||||
|
||||
> **不支持** `isGreater` / `isGreaterEqual` / `isLess` / `isLessEqual`:关联记录无法比大小。
|
||||
> value 传关联表记录的 `record_id`。
|
||||
> 双向关联(创建时设 `bidirectional=true`)也属于 `link` 类型,运算符与单向关联一致。
|
||||
|
||||
*`location`*
|
||||
|
||||
| 运算符 | value 格式 | 元素个数 | 示例 |
|
||||
|--------|-----------|---------|------|
|
||||
| `is` / `isNot` / `contains` / `doesNotContain` | `["地址文本"]` | 仅 1 个 | `["北京市朝阳区..."]` |
|
||||
| `isEmpty` / `isNotEmpty` | `[]` | 0 个 | `[]` |
|
||||
|
||||
> **不支持** `isGreater` / `isGreaterEqual` / `isLess` / `isLessEqual`:地理位置无自然顺序。
|
||||
> location 按 `full_address` 字符串筛选,不支持经纬度空间筛选;查城市/片区时优先用 `contains`,避免用 `is` 匹配短地址词。
|
||||
|
||||
*`checkbox`*
|
||||
|
||||
| 运算符 | value 格式 | 元素个数 | 示例 |
|
||||
|--------|-----------|---------|------|
|
||||
| `is` | `["true"]` 或 `["false"]` | 仅 1 个 | `["true"]` |
|
||||
|
||||
> 仅支持 `is` 运算符,不支持其他运算符。
|
||||
|
||||
*`datetime` / `created_at` / `updated_at`*
|
||||
|
||||
日期字段仅支持 `is`、`isEmpty`、`isNotEmpty`、`isGreater`、`isLess` 五种运算符。
|
||||
|
||||
value 使用预定义关键字机制,第一个元素为字符串常量名称:
|
||||
|
||||
| 关键字 | 说明 | value 格式 | 支持的运算符 |
|
||||
|--------|------|-----------|-------------|
|
||||
| `ExactDate` | 精确日期 | `["ExactDate", "1773187200000"]`(毫秒时间戳) | `is`、`isGreater`、`isLess` |
|
||||
| `Today` | 今天 | `["Today"]` | `is`、`isGreater`、`isLess` |
|
||||
| `Tomorrow` | 明天 | `["Tomorrow"]` | `is`、`isGreater`、`isLess` |
|
||||
| `Yesterday` | 昨天 | `["Yesterday"]` | `is`、`isGreater`、`isLess` |
|
||||
| `CurrentWeek` | 本周 | `["CurrentWeek"]` | 仅 `is` |
|
||||
| `LastWeek` | 上周 | `["LastWeek"]` | 仅 `is` |
|
||||
| `CurrentMonth` | 本月 | `["CurrentMonth"]` | 仅 `is` |
|
||||
| `LastMonth` | 上月 | `["LastMonth"]` | 仅 `is` |
|
||||
| `TheLastWeek` | 过去七天 | `["TheLastWeek"]` | 仅 `is` |
|
||||
| `TheNextWeek` | 未来七天 | `["TheNextWeek"]` | 仅 `is` |
|
||||
| `TheLastMonth` | 过去三十天 | `["TheLastMonth"]` | 仅 `is` |
|
||||
| `TheNextMonth` | 未来三十天 | `["TheNextMonth"]` | 仅 `is` |
|
||||
|
||||
> - **ExactDate 时区行为**:毫秒时间戳在实际筛选时会被转为**文档时区当天零点**,跨时区场景需注意日期可能偏移一天。
|
||||
> - **范围型关键字**(`CurrentWeek`、`LastWeek`、`CurrentMonth`、`LastMonth`、`TheLastWeek`、`TheNextWeek`、`TheLastMonth`、`TheNextMonth`)仅支持 `is` 运算符。
|
||||
> - **关键字大小写敏感**:`ExactDate`、`Today`、`CurrentWeek` 等首字母大写,写错大小写会导致校验失败。
|
||||
|
||||
*`attachment`*
|
||||
|
||||
| 运算符 | value 格式 | 元素个数 | 示例 |
|
||||
|--------|-----------|---------|------|
|
||||
| `isEmpty` / `isNotEmpty` | `[]` | 0 个 | `[]` |
|
||||
|
||||
> 附件字段仅支持 `isEmpty` 和 `isNotEmpty`,不支持其他运算符。
|
||||
|
||||
*`formula` / `lookup`*
|
||||
|
||||
公式和查找引用字段的运算符和 value 格式 **取决于其结果数据类型**,按结果类型参照上方对应字段类型的规则。例如:
|
||||
|
||||
- 公式结果为数字 → 按 `number` 规则
|
||||
- 公式结果为日期 → 按 `datetime` 规则
|
||||
- 公式结果为单选 → 按 `select` 规则
|
||||
|
||||
**Sort 字段:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `field_name` | string | 是 | 字段名称或 alias |
|
||||
| `order` | string | 否 | `"asc"`(默认)或 `"desc"` |
|
||||
|
||||
**Pagination 字段:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `limit` | int | 否 | 返回记录数上限,必须为正整数,最大 5000;不填时使用系统默认值。不支持 offset |
|
||||
|
||||
**Shaper 字段:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `format` | string | 是 | 固定为 `"flat"`,表示返回扁平化的对象数组 |
|
||||
|
||||
## CLI 出参详情
|
||||
|
||||
CLI 输出标准信封 `{ok, identity, data}`(失败时为 `{ok:false, identity, error}`)。
|
||||
|
||||
**成功时:**
|
||||
|
||||
```json
|
||||
{"ok": true, "identity": "user", "data": {"main_data": [{"dim_city": {"value": "北京"}, "total_amount": {"value": 12345.00}}, ...]}}
|
||||
```
|
||||
|
||||
**失败时:**
|
||||
|
||||
```json
|
||||
{"ok": false, "identity": "user", "error": {"type": "api", "subtype": "unknown", "code": 800004006, "message": "...does not exist in table schema", "hint": "...", "log_id": "..."}}
|
||||
```
|
||||
|
||||
**Response 字段:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `ok` | bool | 是否成功 |
|
||||
| `identity` | string | 执行身份:`user` / `bot` |
|
||||
| `data.main_data` | []object | 查询结果数组,每个元素为一行数据(成功时) |
|
||||
| `error` | object | 失败时的 typed 错误,含 `type` / `subtype` / `code` / `message` / `hint` / `log_id` |
|
||||
|
||||
每行数据的字段值封装在 CellValue 中:
|
||||
|
||||
```json
|
||||
{
|
||||
"dim_city": {
|
||||
"value": "北京"
|
||||
},
|
||||
"total_amount": {
|
||||
"value": 12345.00
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `value`:展示值(人员名称、选项名称、格式化日期等)
|
||||
|
||||
## 返回值
|
||||
|
||||
命令成功后输出 `data` 字段的内容:
|
||||
|
||||
```json
|
||||
{
|
||||
"main_data": [
|
||||
{
|
||||
"dim_city": {"value": "直营"},
|
||||
"measure_count": {"value": 1}
|
||||
},
|
||||
{
|
||||
"dim_city": {"value": "加盟"},
|
||||
"measure_count": {"value": 2}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 工作流
|
||||
|
||||
1. 确认 base-token 和 table-id
|
||||
2. **先查表结构**:执行 `lark-cli base +field-list --base-token <base_token> --table-id <table_id>`
|
||||
3. 从返回的字段列表中获取 field_name(DSL 中使用的字段名称)
|
||||
4. 根据字段信息构造 DSL JSON
|
||||
5. 执行 +data-query
|
||||
6. 解读返回结果:
|
||||
- 结果在 `data.main_data` 数组中,每个元素代表一行
|
||||
- 每行对象的 key 为 DSL 中指定的 `alias`;未指定 alias 时,key 为自动生成的列名
|
||||
- 每个 value 是 CellValue 对象,实际值在 `value` 字段中,如 `{"value": "北京"}` 或 `{"value": 12345.00}`
|
||||
- 失败时结果在 `data.error` 中,包含具体错误码和信息
|
||||
|
||||
## 与记录读取组合
|
||||
|
||||
`+data-query` 可返回聚合结果,也可在只传 `dimensions` 时返回维度字段行;这些维度行按字段组合去重,不包含 `record_id`,不能等同于逐条原始记录。需要输出聚合结果对应的原始记录字段、展示值、记录定位信息或关联表字段时,按以下方式组合:
|
||||
|
||||
1. 用 `+data-query` 在 Base 云端查询服务中完成全局筛选、分组、聚合、排序和 TopN,得到业务 key、分组值或候选字段组合。
|
||||
2. 如果已经拿到候选记录的 `record_id`,用 `+record-get` 读取逐条记录字段。
|
||||
3. 如果拿到的是结构化业务 key(例如编号、状态、日期、金额等),用 `+record-list --filter-json` 做精确过滤后读取;不要用 `+record-search` 代替结构化条件。
|
||||
4. 只有候选条件本身是文本展示值关键词时,才使用 `+record-search`,并用 `search_fields` 限定范围、`select_fields` 做投影。
|
||||
5. 若候选记录包含 link 字段,提取关联 `record_id` 后到关联表用 `+record-get` 批量读取展示字段。
|
||||
6. 最终回答业务字段,不要把内部 `record_id` 当作用户可读答案。
|
||||
|
||||
不要把 `data-query pagination.limit` 理解为分页扫描;它只限制 Base 云端查询服务返回的聚合结果行数,不支持 offset。需要全量原始记录导出时回到 data analysis SOP 的 `+record-list` 分页规则。
|
||||
|
||||
## 坑点
|
||||
|
||||
- ⚠️ **必须先查表结构**:DSL 的 `field_name` 必须与表中字段名称精确匹配(区分大小写),不能凭猜测构造。先用 `lark-cli base +field-list --base-token <base_token> --table-id <table_id>` 获取真实字段名
|
||||
- ⚠️ **权限要求按文档类型分流**:普通多维表格只需文档**阅读权限**;高级权限多维表格必须是文档管理员(**FA / Full Access**),否则返回权限错误
|
||||
- ⚠️ **alias 不支持中文**:dimensions 和 measures 的 alias 必须使用英文(如 `dim_city`、`total_amount`),中文 alias 会导致错误
|
||||
- ⚠️ **API 路径是 `base/v3`**:本接口路径为 `/open-apis/base/v3/bases/:base_token/data/query`,不是 `bitable/v1`。两者完全不同,用错版本号会返回 `[2200] Internal Error`
|
||||
- ⚠️ **`dimensions` 和 `measures` 至少填一个**:两个都不填会返回 DSL 校验错误
|
||||
- ⚠️ **`shaper` 必须为 `{"format": "flat"}`**:不填或填其他值会导致结果格式不可预期,建议始终显式指定
|
||||
- ⚠️ **数据表标识 `tableId` vs `tableName`**:datasource 中可以用 `tableId`(如 `tblXXX`)或 `tableName`(数据表的用户自定义显示名称),二选一,不要混用
|
||||
- ⚠️ **`pagination.limit` 最大 5000**:超过会报错,且不支持 offset,只支持 limit
|
||||
- ⚠️ **所有 alias 必须全局唯一**:dimensions 和 measures 之间的 alias 也不能重名
|
||||
- ⚠️ **不要用本地分页结果替代 data-query**:凡是全局计数、分组、聚合、排序 TopN,优先让 `+data-query` 在 Base 云端查询服务中执行;默认页 `+record-list` 后本地统计只能得到已读取范围内的结果
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base](../SKILL.md) — 多维表格全部命令
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
- [lark-base-data-analysis-sop.md](lark-base-data-analysis-sop.md) — 查询范围、选路、下推、分页、`+record-list` / `+record-search` 回查和关系查询 SOP
|
||||
- [lark-base-cell-value.md](lark-base-cell-value.md) — CellValue 格式规范
|
||||
- [lark-base-field-json.md](lark-base-field-json.md) — 字段类型与 JSON 结构
|
||||
109
.claude/skills/lark-base/references/lark-base-field-create.md
Normal file
109
.claude/skills/lark-base/references/lark-base-field-create.md
Normal file
@ -0,0 +1,109 @@
|
||||
# base +field-create
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
创建一个字段。
|
||||
|
||||
## Agent 最小工作流
|
||||
|
||||
1. 先判断是不是 `formula` / `lookup`。
|
||||
2. 如果是:先读对应 guide。
|
||||
3. 没读 guide 前,不要直接创建 formula / lookup 字段。
|
||||
4. 读完 guide 后,再构造 `--json` 并创建字段。
|
||||
5. 如果是跨表 formula / lookup,再补查**目标表**的结构。
|
||||
|
||||
## 推荐命令
|
||||
|
||||
```bash
|
||||
lark-cli base +field-create \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--json '{"name":"预算","type":"number","style":{"type":"plain","precision":2}}'
|
||||
|
||||
lark-cli base +field-create \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--json '{"name":"状态","type":"select","multiple":false,"default_value":["Todo"],"options":[{"name":"Todo","hue":"Blue","lightness":"Lighter"},{"name":"Done","hue":"Green","lightness":"Light"}]}'
|
||||
|
||||
lark-cli base +field-create \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--json '{"name":"负责人","type":"user","multiple":false,"default_value":[{"$slot":"current_user"}],"description":"用于标记记录的直接负责人;协作约定可参考[团队字段约定](https://example.com/field-spec)"}'
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--base-token <token>` | 是 | Base Token |
|
||||
| `--table-id <id_or_name>` | 是 | 表 ID 或表名 |
|
||||
| `--json <body>` | 是 | 字段属性 JSON 对象 |
|
||||
## API 入参详情
|
||||
|
||||
**HTTP 方法和路径:**
|
||||
|
||||
```
|
||||
POST /open-apis/base/v3/bases/:base_token/tables/:table_id/fields
|
||||
```
|
||||
|
||||
## JSON 值规范
|
||||
|
||||
- `--json` 必须是 **JSON 对象**,顶层直接传字段定义,不要再套一层。
|
||||
- 顶层最少包含:`name`、`type`。
|
||||
- 所有字段类型都支持可选 `description`;支持纯文本,也支持 Markdown 链接,如 `协作约定可参考[团队字段约定](https://example.com/field-spec)`。
|
||||
- 需要字段默认值时传 `default_value`,直接使用字段对应 CellValue;`datetime` / `user` 的动态填充用 `$slot`。完整规则见 [lark-base-field-json.md](lark-base-field-json.md)。
|
||||
- `type` 不同,必填子字段不同:
|
||||
- `select`:`multiple` 控制是否多选,`options` 定义静态选项,`dynamic_options_source` 定义动态选项来源。静态与动态选项配置二选一,不能同时传。
|
||||
- `link`:必须有 `link_table`,可选 `bidirectional`、`bidirectional_link_field_name`。
|
||||
- `formula`:必须有 `expression`;先读 formula guide,再创建。
|
||||
- `lookup`:必须有 `from`、`select`、`where`;先读 lookup guide,再创建。
|
||||
|
||||
**正确(base +field-create)**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "状态",
|
||||
"type": "select",
|
||||
"multiple": false,
|
||||
"default_value": ["Todo"],
|
||||
"options": [
|
||||
{ "name": "Todo", "hue": "Blue", "lightness": "Lighter" },
|
||||
{ "name": "Done", "hue": "Green", "lightness": "Light" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "负责人",
|
||||
"type": "user",
|
||||
"multiple": false,
|
||||
"description": "用于标记记录的直接负责人;协作约定可参考[团队字段约定](https://example.com/field-spec)"
|
||||
}
|
||||
```
|
||||
|
||||
## 返回重点
|
||||
|
||||
- 返回 `field` 和 `created: true`。
|
||||
- 如果返回 `field_get_recommended:false` 且 `next_step:"done"`,表示本次是简单字段创建,通常不需要立刻执行 `+field-get`。
|
||||
- 如果返回 `field_get_recommended:true` 或 `next_step:"field_get"`,按 `verification_hint` 读回字段;`formula`、`lookup`、`link`、`auto_number` 等计算、关联或生成型字段更适合读回确认服务端最终结构。
|
||||
|
||||
## 工作流
|
||||
|
||||
|
||||
1. formula / lookup 字段必须先阅读对应指南;没读之前不要直接创建。
|
||||
2. 创建简单字段时,优先相信命令返回;只有用户要求精确核对额外属性,或返回建议读回时,才继续执行 `+field-get`。
|
||||
|
||||
## 坑点
|
||||
|
||||
- ⚠️ 这是写入操作,执行前必须确认。
|
||||
- ⚠️ 当 `type` 是 `formula` 或 `lookup` 时,先读对应 guide,再创建。
|
||||
- ⚠️ 不要把“每次创建后都 `+field-get`”当作固定流程;按返回里的 `field_get_recommended` 和 `next_step` 决定是否读回。
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base-field-json.md](lark-base-field-json.md) — 字段 JSON 规范(推荐)
|
||||
- [formula-field-guide.md](formula-field-guide.md) — formula 指南(创建公式必读)
|
||||
- [lookup-field-guide.md](lookup-field-guide.md) — lookup 指南(创建查找引用必读)
|
||||
527
.claude/skills/lark-base/references/lark-base-field-json.md
Normal file
527
.claude/skills/lark-base/references/lark-base-field-json.md
Normal file
@ -0,0 +1,527 @@
|
||||
# Base field JSON SSOT
|
||||
|
||||
> 适用命令:`lark-cli base +field-create`、`lark-cli base +field-update`
|
||||
|
||||
本文档定义 `+field-create` / `+field-update` 写字段时 `--json` 的推荐格式,是字段类型与字段 JSON 结构的 source of truth。目标不是复刻完整 schema,而是让 agent 稳定产出正确 payload。
|
||||
|
||||
## 1. 顶层规则(必须遵守)
|
||||
|
||||
- `--json` 必须是 JSON 对象。
|
||||
- 顶层统一使用:`type` + `name` + 类型特有字段。
|
||||
- 所有字段类型都支持可选 `description`;支持纯文本,也支持 Markdown 链接。
|
||||
- 字段默认值使用 `default_value`,直接传对应 CellValue;支持范围只有 `text`、`number`、静态 `select`、`datetime`、`user`。清空默认值传 `null`;省略表示创建时不设置、更新时不修改。
|
||||
- 不要使用旧结构:`field_name`、`property`、`ui_type`、数字枚举 `type`。
|
||||
- `+field-update` 使用同样的字段 JSON 结构,但语义是 `PUT`;这是高风险写入操作,建议先 `+field-get` 再按目标状态全量提交,并带 `--yes`。
|
||||
- `type=formula` 或 `type=lookup` 创建/更新前,必须先读对应 guide。
|
||||
|
||||
推荐示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"name": "需求背景",
|
||||
"description": "记录需求背景与已知约束"
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 字段速查
|
||||
|
||||
| 类型 | 最小必填字段 | 常见补充字段 |
|
||||
|------|--------------|-------------|
|
||||
| `text` | `type` `name` | `style.type` `default_value` |
|
||||
| `number` | `type` `name` | `style` `default_value` |
|
||||
| `select` | `type` `name` | `multiple` + `options` + 静态 `default_value`,或 `multiple` + `dynamic_options_source` |
|
||||
| `datetime` | `type` `name` | `style.format` `default_value` |
|
||||
| `created_at` / `updated_at` | `type` `name` | `style.format` |
|
||||
| `user` / `group_chat` | `type` `name` | `multiple`;仅 `user` 支持 `default_value` |
|
||||
| `created_by` / `updated_by` | `type` `name` | 无 |
|
||||
| `link` | `type` `name` `link_table` | `bidirectional` `bidirectional_link_field_name` |
|
||||
| `formula` | `type` `name` `expression` | 无 |
|
||||
| `lookup` | `type` `name` `from` `select` `where` | `aggregate` |
|
||||
| `auto_number` | `type` `name` | `style.rules` |
|
||||
| `attachment` / `location` / `checkbox` | `type` `name` | 无 |
|
||||
|
||||
所有类型都可额外传 `description`;上表的“常见补充字段”只列类型特有配置。
|
||||
|
||||
## 3. 各类型写法
|
||||
|
||||
### 3.1 text
|
||||
|
||||
文本字段;电话、超链接、邮箱、条码也都属于 `text`,通过 `style.type` 区分。
|
||||
支持 `default_value`:静态 Markdown 文本字符串;`phone` style 必须是合法电话号码;`url` style 传一个 Markdown 链接或裸 URL;`email` style 必须是合法邮箱字符串,不要传 Markdown 链接或 `mailto:`。
|
||||
|
||||
最小写法(默认 `style.type` 为 `plain`):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"name": "标题",
|
||||
"default_value": "默认标题"
|
||||
}
|
||||
```
|
||||
|
||||
常用写法:
|
||||
|
||||
默认值可以是 Markdown 文本
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"name": "标题",
|
||||
"description": "主标题字段",
|
||||
"default_value": "未命名"
|
||||
}
|
||||
```
|
||||
|
||||
`style.type=phone` 时默认值是合法电话号码字符串。
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"name": "联系电话",
|
||||
"style": { "type": "phone" },
|
||||
"default_value": "+8613800000000"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"name": "官网",
|
||||
"style": { "type": "url" },
|
||||
"default_value": "[官网](https://example.com)"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"name": "邮箱",
|
||||
"style": { "type": "email" },
|
||||
"default_value": "owner@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
常用 `style.type`:`plain`(默认)、`phone`、`url`、`email`、`barcode`。
|
||||
|
||||
### 3.2 number
|
||||
|
||||
数字字段;货币、进度、评分都属于 `number`,通过 `style.type` 区分。
|
||||
支持 `default_value`:静态 JSON number;所有 number style 都按这个规则写。
|
||||
|
||||
最小写法(默认 `style.type` 为 `plain`):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "number",
|
||||
"name": "工时",
|
||||
"default_value": 8
|
||||
}
|
||||
```
|
||||
|
||||
`style` 是按 `type` 区分的对象;不同 `style.type` 的内部字段不一样,不要混传。
|
||||
|
||||
#### `plain`
|
||||
|
||||
支持字段:`precision`、`percentage`、`thousands_separator`
|
||||
|
||||
默认值 / 约束:
|
||||
- `precision` 取值 `0..4`,默认 `2`
|
||||
- `percentage` 默认 `false`
|
||||
- `thousands_separator` 默认 `false`
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "number",
|
||||
"name": "工时",
|
||||
"style": {
|
||||
"type": "plain",
|
||||
"precision": 2,
|
||||
"percentage": false,
|
||||
"thousands_separator": true
|
||||
},
|
||||
"default_value": 8
|
||||
}
|
||||
```
|
||||
|
||||
#### `currency`
|
||||
|
||||
支持字段:`precision`、`currency_code`
|
||||
|
||||
默认值 / 约束:
|
||||
- `precision` 取值 `0..4`,默认 `2`
|
||||
- `currency_code` 必填,如 `CNY`、`USD`、`EUR`
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "number",
|
||||
"name": "预算",
|
||||
"style": { "type": "currency", "precision": 2, "currency_code": "CNY" }
|
||||
}
|
||||
```
|
||||
|
||||
#### `progress`
|
||||
|
||||
支持字段:`percentage`、`color`
|
||||
|
||||
默认值 / 约束:
|
||||
- `percentage` 默认 `true`
|
||||
- `color` 必填
|
||||
- `color` 可用:`Blue`、`Purple`、`DarkGreen`、`Green`、`Cyan`、`Orange`、`Red`、`Gray`、`WhiteToBlueGradient`、`WhiteToPurpleGradient`、`WhiteToOrangeGradient`、`GreenToRedGradient`、`RedToGreenGradient`、`BlueToPinkGradient`、`PinkToBlueGradient`、`SpectralGradient`
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "number",
|
||||
"name": "完成度",
|
||||
"style": { "type": "progress", "percentage": true, "color": "Blue" },
|
||||
"default_value": 0.65
|
||||
}
|
||||
```
|
||||
|
||||
#### `rating`
|
||||
|
||||
支持字段:`icon`、`min`、`max`
|
||||
|
||||
默认值 / 已知平台范围:
|
||||
- `icon` 默认 `star`
|
||||
- `icon` 可用:`star`、`heart`、`thumbsup`、`fire`、`smile`、`lightning`、`flower`、`number`
|
||||
- `min` 取值 `0..1`,默认 `1`
|
||||
- `max` 默认 `5`;常见或已文档化的范围为 `1..10`,但 CLI 不强制上限为 `10`。如果用户明确需要更大评分范围,优先确认平台能力或用 `+field-create/update --dry-run` 检查请求形状;平台拒绝后再建议改用普通数字或进度字段。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "number",
|
||||
"name": "评分",
|
||||
"style": { "type": "rating", "icon": "star", "min": 1, "max": 5 }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 select
|
||||
|
||||
单选和多选都使用 `select`;用 `multiple` 区分。`multiple` 默认 `false`。静态选项用 `options`,动态选项用 `dynamic_options_source`;两者不要同时传。
|
||||
|
||||
#### 静态选项
|
||||
|
||||
支持字段:`multiple`、`options`
|
||||
支持 `default_value`:静态选项名数组;即使 `multiple=false` 也写数组,如 `["Todo"]`。
|
||||
|
||||
默认值 / 约束:
|
||||
- `multiple` 默认 `false`
|
||||
- `options` 最多 `10000` 项
|
||||
- `options[]` 结构是 `{name, hue?, lightness?}`
|
||||
- `options[].name` 必填
|
||||
- `options[].hue` 可用:`Red`、`Orange`、`Yellow`、`Lime`、`Green`、`Turquoise`、`Wathet`、`Blue`、`Carmine`、`Purple`、`Gray` 缺省值为 `Blue`
|
||||
- `options[].lightness` 可用:`Lighter`、`Light`、`Standard`、`Dark`、`Darker` 缺省值为 `Lighter`
|
||||
- 选项里没有 `id`,只有 `name`。
|
||||
- 支持 `default_value` 配置:填选项名数组。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "select",
|
||||
"name": "状态",
|
||||
"multiple": false,
|
||||
"default_value": ["Todo"],
|
||||
"options": [
|
||||
{ "name": "Todo", "hue": "Blue", "lightness": "Lighter" },
|
||||
{ "name": "Done", "hue": "Green", "lightness": "Light" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 动态选项
|
||||
|
||||
支持字段:`multiple`、`dynamic_options_source`
|
||||
动态选项不支持 `default_value`。
|
||||
|
||||
默认值 / 约束:
|
||||
- `multiple` 默认 `false`
|
||||
- `dynamic_options_source` 结构是 `{table_id, field_id}`
|
||||
- `dynamic_options_source.table_id` 填来源表 id 或表名
|
||||
- `dynamic_options_source.field_id` 填来源字段 id 或字段名
|
||||
- `dynamic_options_source` 仅创建支持;更新已有字段时不要传
|
||||
- 引用选项条件 / 级联筛选条件:这个功能在 Base 前端支持,属于 UI-only 属性,OpenAPI 里不支持,CLI 不能读取、创建或更新;不要根据接口返回缺失判断未配置
|
||||
- 动态选项不支持配置 `default_value`。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "select",
|
||||
"name": "动态状态",
|
||||
"multiple": false,
|
||||
"dynamic_options_source": {
|
||||
"table_id": "选项表",
|
||||
"field_id": "候选状态"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 datetime
|
||||
|
||||
手动填写的日期/时间字段。系统时间用 `created_at` / `updated_at`。
|
||||
支持 `default_value`:静态时间字符串,或 `{ "$slot": "record_created_time" }`。`datetime + record_created_time` 是自动填充可编辑单元格;`created_at` 是只读创建时间元信息。
|
||||
|
||||
最小写法:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "datetime",
|
||||
"name": "截止时间",
|
||||
"default_value": "2026-03-24 10:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
支持字段:`style.format`
|
||||
|
||||
默认值 / 约束:
|
||||
- `style.format` 默认 `yyyy/MM/dd` 可用格式:`yyyy/MM/dd`、`yyyy/MM/dd HH:mm`、`yyyy/MM/dd HH:mm Z`、`yyyy-MM-dd`、`yyyy-MM-dd HH:mm`、`yyyy-MM-dd HH:mm Z`、`MM-dd`、`MM/dd/yyyy`、`dd/MM/yyyy`
|
||||
- `style.format` 只控制前端显示格式;当前可配置格式最多显示到分钟,底层时间值仍可保留秒级精度。
|
||||
|
||||
常用写法:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "datetime",
|
||||
"name": "截止时间",
|
||||
"style": { "format": "yyyy-MM-dd HH:mm" },
|
||||
"default_value": { "$slot": "record_created_time" }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 created_at / updated_at
|
||||
|
||||
系统创建时间 / 系统更新时间字段;可配显示格式,但记录写入时应视为只读。
|
||||
|
||||
支持字段:`style.format`
|
||||
|
||||
默认值 / 约束:
|
||||
- `style.format` 默认 `yyyy/MM/dd`
|
||||
- 可用格式:`yyyy/MM/dd`、`yyyy/MM/dd HH:mm`、`yyyy/MM/dd HH:mm Z`、`yyyy-MM-dd`、`yyyy-MM-dd HH:mm`、`yyyy-MM-dd HH:mm Z`、`MM-dd`、`MM/dd/yyyy`、`dd/MM/yyyy`
|
||||
|
||||
```json
|
||||
{ "type": "created_at", "name": "创建时间" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "type": "updated_at", "name": "更新时间", "style": { "format": "yyyy/MM/dd HH:mm" } }
|
||||
```
|
||||
|
||||
### 3.6 user / group_chat
|
||||
|
||||
人员字段和群字段都支持 `multiple`。
|
||||
`user` 支持 `default_value`:人员 CellValue 数组,元素可用 `{ "id": "ou_xxx" }` 或 `{ "$slot": "current_user" }`;不要猜用户 ID。`group_chat` 不支持默认值。
|
||||
|
||||
默认值 / 约束:
|
||||
- `multiple` 默认 `true`
|
||||
- `user` 字段支持 `default_value` 配置,`group_chat` 字段不支持 `default_value` 配置。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "user",
|
||||
"name": "负责人",
|
||||
"multiple": true,
|
||||
"default_value": [{ "$slot": "current_user" }, { "id": "ou_xxx" }]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{ "type": "group_chat", "name": "负责群", "multiple": true }
|
||||
```
|
||||
|
||||
### 3.7 created_by / updated_by
|
||||
|
||||
系统创建人 / 系统修改人字段;记录写入时应视为只读。
|
||||
|
||||
```json
|
||||
{ "type": "created_by", "name": "创建人" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "type": "updated_by", "name": "更新人" }
|
||||
```
|
||||
|
||||
### 3.8 link
|
||||
|
||||
关联字段;`link_table` 必填。
|
||||
|
||||
支持字段:`link_table`、`bidirectional`、`bidirectional_link_field_name`
|
||||
|
||||
默认值 / 约束:
|
||||
- `link_table` 必填
|
||||
- `link` 字段的单元格表示“当前记录关联到的对侧表记录集合”
|
||||
- `bidirectional` 默认 `false`
|
||||
- `bidirectional=true` 时,会在被关联表自动创建一个反向关联字段。任一侧记录的关联关系发生变更时,另一侧对应记录会自动同步更新
|
||||
- `bidirectional_link_field_name` 仅在 `bidirectional=true` 时使用
|
||||
- 关联字段筛选:这个功能在 Base 前端支持,属于 UI-only 属性,OpenAPI 里不支持,CLI 不能读取、创建或更新;不要根据接口返回缺失判断未配置
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "link",
|
||||
"name": "关联任务",
|
||||
"link_table": "任务表"
|
||||
}
|
||||
```
|
||||
|
||||
双向关联:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "link",
|
||||
"name": "关联任务",
|
||||
"link_table": "任务表",
|
||||
"bidirectional": true,
|
||||
"bidirectional_link_field_name": "反向关联"
|
||||
}
|
||||
```
|
||||
|
||||
更新时注意:
|
||||
- `link` 不允许转换为其他类型,其他类型也不能转换为 `link`。
|
||||
- 现有 `link` 字段的 `bidirectional` 不能改。
|
||||
|
||||
### 3.9 formula
|
||||
|
||||
公式字段;`expression` 必填。创建/更新前先读 [formula-field-guide.md](formula-field-guide.md) 学习公式语法。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "formula",
|
||||
"name": "合计",
|
||||
"expression": "1+1"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.10 lookup
|
||||
|
||||
查找引用字段;`from`、`select`、`where` 必填,`aggregate` 可选。创建/更新前先读 [lookup-field-guide.md](lookup-field-guide.md)。
|
||||
|
||||
支持字段:`from`、`select`、`where`、`aggregate`
|
||||
|
||||
默认值 / 约束:
|
||||
- `from`、`select`、`where` 必填
|
||||
- `aggregate` 默认 `raw_value` 代表不进行聚合,直接返回 select 回的原始值
|
||||
- `aggregate` 可用:`raw_value`、`sum`、`average`、`counta`、`unique_counta`、`max`、`min`、`unique`
|
||||
- `where.logic` 默认 `and`,仅支持 `and` / `or`
|
||||
- `where.conditions` 至少 1 条
|
||||
- `conditions` 每项是三元组 `[field, op, value?]`
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "lookup",
|
||||
"name": "状态汇总",
|
||||
"from": "任务表",
|
||||
"select": "状态",
|
||||
"where": {
|
||||
"logic": "and",
|
||||
"conditions": [
|
||||
["负责人", "==", { "type": "field_ref", "field": "当前负责人" }],
|
||||
["状态", "non_empty", null]
|
||||
]
|
||||
},
|
||||
"aggregate": "raw_value"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.11 auto_number
|
||||
|
||||
自动编号字段;创建时不写 `style.rules` 会使用默认规则:`NO.001`。更新已有自动编号字段时应显式提交目标 `style.rules`,因为 `+field-update` 会把新的编号规则重新应用到已有编号。
|
||||
|
||||
最小写法:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "auto_number",
|
||||
"name": "编号"
|
||||
}
|
||||
```
|
||||
|
||||
支持字段:`style.rules`
|
||||
|
||||
默认值 / 约束:
|
||||
- `style.rules` 是规则数组,数量 `1..9`
|
||||
- 默认规则:
|
||||
|
||||
```json
|
||||
{
|
||||
"style": {
|
||||
"rules": [
|
||||
{ "type": "text", "text": "NO." },
|
||||
{ "type": "incremental_number", "length": 3 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `text`
|
||||
|
||||
支持字段:`text`
|
||||
|
||||
```json
|
||||
{ "type": "text", "text": "TASK-" }
|
||||
```
|
||||
|
||||
#### `incremental_number`
|
||||
|
||||
支持字段:`length`
|
||||
|
||||
默认值 / 约束:
|
||||
- `length` 取值 `1..9`
|
||||
|
||||
```json
|
||||
{ "type": "incremental_number", "length": 4 }
|
||||
```
|
||||
|
||||
#### `created_time`
|
||||
|
||||
支持字段:`date_format`
|
||||
|
||||
默认值 / 约束:
|
||||
- `date_format` 可用:`yyyyMMdd`、`yyyyMM`、`yyMM`、`MMdd`、`yyyy`、`MM`、`dd`
|
||||
|
||||
```json
|
||||
{ "type": "created_time", "date_format": "yyyyMMdd" }
|
||||
```
|
||||
|
||||
自定义规则:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "auto_number",
|
||||
"name": "编号",
|
||||
"style": {
|
||||
"rules": [
|
||||
{ "type": "text", "text": "TASK-" },
|
||||
{ "type": "created_time", "date_format": "yyyyMMdd" },
|
||||
{ "type": "incremental_number", "length": 4 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.12 attachment / location / checkbox
|
||||
|
||||
```json
|
||||
{ "type": "attachment", "name": "附件" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "type": "location", "name": "位置" }
|
||||
```
|
||||
|
||||
写入必须使用 `{lng,lat}`。location 读回会包含 `full_address`;筛选和 `location -> text` 类型转换按 `full_address` 字符串处理,只有公式能访问坐标。
|
||||
|
||||
```json
|
||||
{ "type": "checkbox", "name": "完成" }
|
||||
```
|
||||
|
||||
## 4. 创建与更新
|
||||
|
||||
- `+field-create`:按目标字段配置直接构造 `--json`。
|
||||
- `+field-update`:使用同样的 JSON 结构,但语义是 `PUT`;建议先 `+field-get`,再按目标完整状态提交,并带 `--yes`。当 `type` 是 `auto_number` 时,更新编号规则本身就会把新规则应用到已有编号,无需额外参数,也不要在 JSON 里塞额外的底层实现参数。
|
||||
|
||||
## 5. 暂不支持字段
|
||||
|
||||
Object(对象字段)、Button(按钮字段)、Stage(流程字段)暂时都没有被 CLI 支持。这些字段会展示为 `not_support` 字段并被保护:不允许修改,不允许读取内容。
|
||||
|
||||
## 6. 易错点
|
||||
|
||||
- `select` 只有一个类型;不要写 `single_select` / `multi_select`,用 `multiple` 控制是否多选。
|
||||
- `number` 的精度、货币、进度、评分配置都放在 `style` 下,不要写顶层 `precision`。
|
||||
- `datetime` 是手动日期字段;系统时间请改用 `created_at` / `updated_at`。
|
||||
- `formula` / `lookup` 没读 guide 前不要直接写。
|
||||
- 只有 `text`、`number`、静态 `select`、`datetime`、`user` 支持 `default_value`;清空统一传 `"default_value": null`。其他字段类型不要配置默认值。
|
||||
189
.claude/skills/lark-base/references/lark-base-field-update.md
Normal file
189
.claude/skills/lark-base/references/lark-base-field-update.md
Normal file
@ -0,0 +1,189 @@
|
||||
# base +field-update
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
更新一个已有字段。
|
||||
|
||||
## 推荐命令
|
||||
|
||||
```bash
|
||||
lark-cli base +field-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--field-id <field_id> \
|
||||
--json '{"name":"状态","type":"select","multiple":false,"default_value":["Doing"],"options":[{"name":"Todo","hue":"Blue","lightness":"Lighter"},{"name":"Doing","hue":"Orange","lightness":"Light"},{"name":"Done","hue":"Green","lightness":"Light"}]}' \
|
||||
--yes
|
||||
|
||||
lark-cli base +field-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--field-id <field_id> \
|
||||
--json '{"name":"负责人","type":"user","multiple":false,"default_value":null,"description":"用于标记记录的直接负责人"}' \
|
||||
--yes
|
||||
|
||||
lark-cli base +field-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--field-id <field_id> \
|
||||
--json '{"name":"编号","type":"auto_number","style":{"rules":[{"type":"text","text":"TASK-"},{"type":"created_time","date_format":"yyyyMM"},{"type":"text","text":"-"},{"type":"incremental_number","length":4}]}}' \
|
||||
--yes
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--base-token <token>` | 是 | Base Token |
|
||||
| `--table-id <id_or_name>` | 是 | 表 ID 或表名 |
|
||||
| `--field-id <id_or_name>` | 是 | 字段 ID 或字段名 |
|
||||
| `--json <body>` | 是 | 字段属性 JSON 对象 |
|
||||
| `--yes` | 是 | 确认执行高风险字段更新 |
|
||||
|
||||
> 这是**高风险写入操作**。`+field-update` 使用 `PUT` 全量字段定义语义;改变字段类型或关键配置可能影响整列已有数据的解释、展示或可用性。CLI 层要求显式传 `--yes`;如果用户已经明确目标和期望更新,可直接执行并带上 `--yes`。
|
||||
|
||||
## API 入参详情
|
||||
|
||||
**HTTP 方法和路径:**
|
||||
|
||||
```
|
||||
PUT /open-apis/base/v3/bases/:base_token/tables/:table_id/fields/:field_id
|
||||
```
|
||||
|
||||
当 `--json.type` 是 `auto_number` 时,仍然走同一个 v3 字段更新接口:更新自动编号规则后,接口现状就会把新规则应用到已有编号(这是接口默认行为,只是 agent 通常不知道),因此**不需要**任何额外开关或参数。只需要正常提交目标自动编号字段定义即可;如果用户要求“将修改用于已有编号”,直接执行这次 `+field-update` 就能达到效果,不要在 `--json` 里额外添加任何参数去“触发”重排。
|
||||
|
||||
## JSON 值规范
|
||||
|
||||
- `--json` 必须是 **JSON 对象**,顶层直接传字段定义。
|
||||
- 更新语义是 `PUT`(全量字段配置更新),不要只传零散片段;至少显式包含 `name`、`type`,并补齐该类型所需关键配置。
|
||||
- 所有字段类型都支持可选 `description`;支持纯文本,也支持 Markdown 链接。
|
||||
- 需要字段默认值时传 `default_value`,直接使用字段对应 CellValue;传 `null` 清空,省略表示不修改现有默认值。完整规则见 [lark-base-field-json.md](lark-base-field-json.md)。
|
||||
- `select` 更新时:`options` 仍按对象数组传,避免混入无效字段。
|
||||
- `link` 更新限制:
|
||||
- 不能把非 `link` 字段改成 `link`,也不能把 `link` 改成非 `link`。
|
||||
- 现有 `link` 字段的 `bidirectional` 不能改。
|
||||
- `auto_number` 更新的 `style.rules` 支持 `text`、`created_time`、`incremental_number`。
|
||||
|
||||
**推荐更新示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "状态",
|
||||
"type": "select",
|
||||
"multiple": false,
|
||||
"default_value": ["Doing"],
|
||||
"options": [
|
||||
{ "name": "Todo", "hue": "Blue", "lightness": "Lighter" },
|
||||
{ "name": "Doing", "hue": "Orange", "lightness": "Light" },
|
||||
{ "name": "Done", "hue": "Green", "lightness": "Light" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "负责人",
|
||||
"type": "user",
|
||||
"multiple": false,
|
||||
"description": "用于标记记录的直接负责人"
|
||||
}
|
||||
```
|
||||
|
||||
## 返回重点
|
||||
|
||||
- 返回 `field` 和 `updated: true`。
|
||||
- `updated:true` 只表示更新请求成功,不表示字段结构、已有记录值或下游能力已经完成验证。`+field-update` 无法知道更新前的字段类型,因此成功响应会推荐执行 `+field-get`;若发生类型转换,还要抽样读取记录值。
|
||||
- 如果响应中的 `field.type` 与提交的 `type` 不一致,必须把它当作待核验的类型不匹配;不能返回完成态,也不能只根据其中任一类型推断更新成功。
|
||||
- 如果 API 报告本次更新没有产生任何变更(no-op),命令会如实返回该错误;这通常说明目标字段已是期望状态,不要机械重试同一份 `+field-update`。需要确认当前字段完整状态时执行 `+field-get`。
|
||||
- 如果返回 `field_get_recommended:true` 或 `next_step:"field_get"`,按提示读回字段;`auto_number` 更新后还应抽样读记录值确认编号已按新规则生成。
|
||||
|
||||
## 工作流
|
||||
|
||||
|
||||
1. 建议先用 `+field-get` 拉现状,再做最小化修改。
|
||||
2. `formula/lookup` 类型更新前先阅读对应指南。
|
||||
3. 如果更新 `auto_number`,理解为“更新编号规则,同时把新规则应用到已有编号”;执行后按返回提示读回字段并在必要时抽样记录值。
|
||||
4. 如果这次更新会改变字段 `type` 先按下方“字段类型变更规则”判断能否执行。如果不修改 `type`,大多数场景都相对安全。
|
||||
|
||||
## 字段类型变更规则
|
||||
|
||||
字段类型变更采用白名单机制:**只允许白名单转换**;未命中白名单时,**不建议用 CLI 转换字段类型** 除非用户明确知道风险并同意。
|
||||
|
||||
### 允许直接转换 type
|
||||
|
||||
先 `+field-get` / `+field-list` 看结构,再抽样读值;只有命中以下规则时,转换才是比较安全的。
|
||||
|
||||
#### 相对安全
|
||||
|
||||
| 目标类型 | 允许的源类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `text` | `number`、`select`、`datetime`、`created_at`、`updated_at`、`location`(只保留 `full_address`)、`auto_number`、`checkbox` | 保留字符串表示;丢失原类型语义和结构化能力 |
|
||||
| `number` | `text`、`number`、`datetime`、`created_at`、`updated_at`、`checkbox` | 保留可解析的数字值;无法解析的值会变空,原文本格式会丢失 |
|
||||
| `datetime` | `text`、`number`、`datetime`、`created_at`、`updated_at` | 保留可解析的时间字符串和时间戳;无法解析的值会变空,原文本格式会丢失 |
|
||||
| `select` | `text -> select`、`number -> select`、`single select -> multi select` | 只有完全匹配目标选项名的值会转成对应选项;没匹配上的值会被丢弃 |
|
||||
|
||||
#### 可执行但会截断 / 重算
|
||||
|
||||
- `select(multi) -> select(single)`: 只保留第一个值,其余值会被丢弃。
|
||||
- `user(multi) -> user(single)`: 只保留第一个人员,其余值会被丢弃。
|
||||
- `group_chat(multi) -> group_chat(single)`: 只保留第一个群,其余值会被丢弃。
|
||||
|
||||
#### 无状态字段可直接转换
|
||||
|
||||
- `created_at`、`created_by`、`updated_at`、`updated_by`、`formula`、`lookup`: 这类字段值由系统或计算逻辑生成,不承载独立存储数据;可以执行类型转换,不必担心破坏原始记录值,但仍要做下游读回验证。
|
||||
|
||||
### 一律不要用 CLI 转换
|
||||
|
||||
以下场景全部视为黑名单;默认要求用户改到 Web 页面手动完成,或改走“新建字段 + 数据迁移”。
|
||||
|
||||
- `any -> checkbox`
|
||||
- `any -> user`
|
||||
- `any -> group_chat`
|
||||
- `any -> attachment`
|
||||
- `any -> location`
|
||||
- `link` 类型变更
|
||||
- 任意涉及动态 / 静态选项来源切换的 `select` 类型变更
|
||||
|
||||
### 可例外继续执行的场景
|
||||
|
||||
只有在**整列数据丢失可接受**时,才允许对黑名单场景例外执行。
|
||||
|
||||
- `EmptyColumn`: 该列为空
|
||||
- `FreshTableInit`: 新建空表初始化
|
||||
- `PrimaryFieldBootstrap`: 主列不能删,只能更新完成初始化
|
||||
- `ExplicitLossAccepted`: 用户明确接受整列数据丢失
|
||||
|
||||
不满足以上条件时,不要转换。
|
||||
|
||||
### 非白名单场景如何处理
|
||||
|
||||
- 命中白名单时:建议直接原地转换,再做读回验证。
|
||||
- 未命中白名单时:先询问用户是否仍要执行转换,并明确说明风险:
|
||||
- 无状态字段除外;这类字段可以直接转换
|
||||
- 可能整列变空
|
||||
- 可能只保留第一个值
|
||||
- 可能只保留字符串表示,丢失原类型语义和结构化能力
|
||||
- 可能影响视图 / 筛选 / 排序 / 公式 / lookup / 写入引用
|
||||
- 如果用户不接受风险:不要执行转换。
|
||||
|
||||
### 完成态验证
|
||||
|
||||
- `FieldReadback`: 读回字段结构,确认 `type` / `multiple` / `style` / `options`
|
||||
- `NoopReadback`: `+field-update` 返回 no-op 错误时,只能说明 API 报告没有产生变更;可以跳过重复 update,但不能替代 `FieldReadback`
|
||||
- `ValueReadback`: 抽样读回转换后的单元格值
|
||||
- `DownstreamReadback`: 若涉及看板 / 分组 / 排序 / lookup / 公式,继续读回结果
|
||||
- `CompletionRule`: 结构、值、下游能力都正确,才能回复“已完成”
|
||||
|
||||
## 坑点
|
||||
|
||||
- ⚠️ 这是全量字段属性更新语义,不是 patch。
|
||||
- ⚠️ 这是高风险写入操作,执行时必须带 `--yes`。
|
||||
- ⚠️ 当 `type` 是 `formula` 或 `lookup` 时,先阅读对应指南再执行。
|
||||
|
||||
## 参考
|
||||
|
||||
- 更新前读取当前字段,确认现有 `type` 和具体配置细节,再决定是原地更新还是新建字段迁移。
|
||||
- [lark-base-field-json.md](lark-base-field-json.md) — 字段 JSON 规范(推荐)
|
||||
- [formula-field-guide.md](formula-field-guide.md) — formula 指南(更新公式前必读)
|
||||
- [lookup-field-guide.md](lookup-field-guide.md) — lookup 指南(更新查找引用前必读)
|
||||
71
.claude/skills/lark-base/references/lark-base-form-detail.md
Normal file
71
.claude/skills/lark-base/references/lark-base-form-detail.md
Normal file
@ -0,0 +1,71 @@
|
||||
# base +form-detail
|
||||
|
||||
通过表单分享 token 读取表单详情。只读操作,适合在提交表单前解析题目结构、必填项、显示条件和附件提交所需的 Base token。
|
||||
|
||||
## 何时使用
|
||||
|
||||
- 用户给出 `/share/base/form/{shareToken}` 表单分享链接,先提取最后一段作为 `--share-token`。
|
||||
- 准备调用 `+form-submit` 前,必须先用 `+form-detail` 读取 `questions[]`。
|
||||
- 只知道分享链接、还不知道 `base-token` / `table-id` / `form-id` 时,用 `+form-detail`;已在 Base 内部管理表单时,才用 `+form-get`。
|
||||
|
||||
```bash
|
||||
lark-cli base +form-detail --share-token <share_token> --format pretty
|
||||
```
|
||||
|
||||
## 读取重点
|
||||
|
||||
`+form-detail` 返回的关键字段:
|
||||
|
||||
| 字段 | 用途 |
|
||||
|---|---|
|
||||
| `base_token` | 表单所属 Base;提交附件时必须传给 `+form-submit --base-token` |
|
||||
| `questions[].id` | 题目标识,通常对应字段 ID |
|
||||
| `questions[].title` | 提交时使用的字段名/题目名,以真实返回为准 |
|
||||
| `questions[].type` | 决定值格式;与字段类型和 `lark-base-cell-value.md` 对齐 |
|
||||
| `questions[].required` | 判断必填项 |
|
||||
| `questions[].filter` | 判断题目是否对当前提交可见;被隐藏的问题不要填写 |
|
||||
|
||||
题目除固定字段外,会按类型携带动态配置,例如 `select.options` / `select.multiple`、`number.style`、`datetime.style.format`、`user.multiple`、`link.link_table`、`formula.expression`、`lookup.from/select/where/aggregate`。提交前按返回结构构造值,不要猜题目类型或选项。
|
||||
|
||||
## filter 显示条件
|
||||
|
||||
`questions[].filter` 控制题目显示/隐藏:
|
||||
|
||||
```json
|
||||
{
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{"field_name": "是否携带家属", "operator": "is", "value": ["是"]},
|
||||
{"field_name": "参与人数", "operator": "isGreater", "value": [1]}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `conjunction` 为 `and` / `or`,表示条件全部满足或任一满足。
|
||||
- `conditions[].field_name` 引用其他题目的 `title`。
|
||||
- `conditions[].operator` 常见为 `is`、`isNot`、`contains`、`doesNotContain`、`isEmpty`、`isNotEmpty`、`isGreater`、`isGreaterEqual`、`isLess`、`isLessEqual`。
|
||||
- `isEmpty` / `isNotEmpty` 不需要 `value`。
|
||||
- 附件题目的 filter 只适合 `isEmpty` / `isNotEmpty`。
|
||||
|
||||
如果当前已填写值不满足某题目的 `filter`,该题目视为隐藏,不应放入 `+form-submit --json.fields` 或 `--json.attachments`。
|
||||
|
||||
## 与 form-submit 的关系
|
||||
|
||||
提交普通字段:
|
||||
|
||||
```bash
|
||||
lark-cli base +form-submit \
|
||||
--share-token <share_token> \
|
||||
--json '{"fields":{"姓名":"张三","评分":5}}'
|
||||
```
|
||||
|
||||
提交附件字段:
|
||||
|
||||
```bash
|
||||
lark-cli base +form-submit \
|
||||
--share-token <share_token> \
|
||||
--base-token <base_token_from_form_detail> \
|
||||
--json '{"fields":{"姓名":"张三"},"attachments":{"附件":["./report.pdf"]}}'
|
||||
```
|
||||
|
||||
附件字段不要写进 `fields`;放在顶层 `attachments`,值为本地文件路径数组。
|
||||
@ -0,0 +1,118 @@
|
||||
# base +form-questions-create
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
向多维表格表单/问卷中批量添加问题。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 添加一个文本必填问题
|
||||
lark-cli base +form-questions-create \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"type":"text","title":"您的姓名是?","required":true}]'
|
||||
|
||||
# 添加多个问题(按顺序排列)
|
||||
lark-cli base +form-questions-create \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[
|
||||
{"type":"text","title":"您的姓名是?","required":true},
|
||||
{"type":"text","title":"您的联系方式是?","required":false}
|
||||
]'
|
||||
|
||||
# 添加单选题(带选项)
|
||||
lark-cli base +form-questions-create \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"type":"select","title":"满意度评价","required":true,"multiple":false,"options":[{"name":"非常满意","hue":"Green"},{"name":"满意","hue":"Blue"},{"name":"一般","hue":"Yellow"}]}]'
|
||||
|
||||
# 添加评分题
|
||||
lark-cli base +form-questions-create \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"type":"number","title":"服务评分","style":{"type":"rating","icon":"star","min":1,"max":5}}]'
|
||||
|
||||
# 添加带描述的问题(纯文本)
|
||||
lark-cli base +form-questions-create \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"type":"text","title":"您的姓名","description":"请填写真实姓名"}]'
|
||||
# 添加带描述的问题(含链接)
|
||||
lark-cli base +form-questions-create \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"type":"text","title":"反馈建议","description":"更多详情请查看[帮助文档](https://example.com/help)"}]'
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--base-token <token>` | 是 | Base Token(base_token) |
|
||||
| `--table-id <id>` | 是 | 数据表 ID |
|
||||
| `--form-id <id>` | 是 | 表单 ID |
|
||||
| `--questions <json>` | 是 | 问题 JSON 数组,最多 10 个(见下方格式) |
|
||||
| `--format` | 否 | 输出格式:json(默认)\| pretty \| table \| ndjson \| csv |
|
||||
| `--as` | 否 | 身份:user(默认)\| bot |
|
||||
| `--dry-run` | 否 | 预览 API 调用,不执行 |
|
||||
|
||||
## `--questions` 格式
|
||||
|
||||
每个问题对象支持以下字段:
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|-----------------------|------|------|
|
||||
| `title` | **是** | 问题标题(字段名) |
|
||||
| `type` | **是** | 题目类型:`text`、`number`、`select`、`datetime`、`user`、`attachment`、`location` |
|
||||
| `description` | 否 | 问题描述(纯文本或 Markdown 链接,如 `[文本](https://example.com)`) |
|
||||
| `required` | 否 | 是否必填(true/false) |
|
||||
| `option_display_mode` | 否 | 选项展示方式(仅 `select` 有效):`0`=下拉,`1`=纵向(默认),`2`=横向 |
|
||||
| `multiple` | 否 | 是否多选(`select`/`user` 类型有效,bool) |
|
||||
| `options` | 否 | 选项列表(仅 `select` 有效):`[{"name":"选项1","hue":"Blue"}]`,hue 可选:`Red`/`Orange`/`Yellow`/`Green`/`Blue`/`Purple`/`Gray` |
|
||||
| `style` | 否 | 字段样式配置(见下方说明) |
|
||||
|
||||
### `style` 字段说明
|
||||
|
||||
| 类型 | style 结构 | 说明 |
|
||||
|------|------|------|
|
||||
| `text` | `{"type":"plain"}` | 当前仅支持 `plain` |
|
||||
| `number` | `{"type":"plain","precision":2}` | precision 为小数位数 |
|
||||
| `number`(评分) | `{"type":"rating","icon":"star","min":1,"max":5}` | icon 可选:`star`/`heart`/`thumbsup`/`fire`/`smile`/`lightning`/`flower`/`number` |
|
||||
| `datetime` | `{"format":"yyyy/MM/dd"}` | format 可选:`yyyy/MM/dd`、`yyyy/MM/dd HH:mm`、`MM-dd`、`MM/dd/yyyy`、`dd/MM/yyyy` |
|
||||
|
||||
## 输出格式
|
||||
|
||||
返回创建成功的问题列表:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"items": [
|
||||
{"id": "q_001", "title": "您的姓名是?", "required": true}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 工作流
|
||||
|
||||
> [!CAUTION]
|
||||
> 这是**写入操作** — 执行前必须向用户确认。
|
||||
|
||||
1. 先用 `+form-questions-list` 查看现有问题
|
||||
2. 确认要添加的问题内容
|
||||
3. 执行命令并报告新建的问题 ID
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base](../SKILL.md) — 多维表格全部命令
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
@ -0,0 +1,92 @@
|
||||
# base +form-questions-update
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
批量更新多维表格表单/问卷中的问题(标题、描述、是否必填)。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 更新一个问题的标题
|
||||
lark-cli base +form-questions-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"id":"q_001","title":"您的真实姓名是?"}]'
|
||||
|
||||
# 同时更新多个问题
|
||||
lark-cli base +form-questions-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[
|
||||
{"id":"q_001","title":"姓名(必填)","required":true},
|
||||
{"id":"q_002","title":"联系方式","required":false}
|
||||
]'
|
||||
|
||||
# 更新问题描述(纯文本)
|
||||
lark-cli base +form-questions-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"id":"q_001","description":"请填写您的真实姓名"}]'
|
||||
# 更新问题描述(含链接)
|
||||
lark-cli base +form-questions-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"id":"q_001","description":"更多说明请参考[帮助文档](https://example.com/help)"}]'
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--base-token <token>` | 是 | Base Token(base_token) |
|
||||
| `--table-id <id>` | 是 | 数据表 ID |
|
||||
| `--form-id <id>` | 是 | 表单 ID |
|
||||
| `--questions <json>` | 是 | 问题更新 JSON 数组,最多 10 个(见下方格式) |
|
||||
| `--format` | 否 | 输出格式:json(默认)\| pretty \| table \| ndjson \| csv |
|
||||
| `--as` | 否 | 身份:user(默认)\| bot |
|
||||
| `--dry-run` | 否 | 预览 API 调用,不执行 |
|
||||
|
||||
## `--questions` 格式
|
||||
|
||||
每个问题对象必须包含 `id`,其余字段按需传入:
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | **是** | 问题 ID(field_id),不可修改 |
|
||||
| `title` | 否 | 新的问题标题 |
|
||||
| `description` | 否 | 新的问题描述(纯文本或 Markdown 链接,如 `[文本](https://example.com)`) |
|
||||
| `required` | 否 | 是否必填 |
|
||||
| `option_display_mode` | 否 | 选项展示方式(仅 `select` 有效):`0`=下拉,`1`=纵向(默认),`2`=横向 |
|
||||
|
||||
## 输出格式
|
||||
|
||||
返回更新后的问题列表:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"items": [
|
||||
{"id": "q_001", "title": "姓名(必填)", "required": true}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 工作流
|
||||
|
||||
> [!CAUTION]
|
||||
> 这是**写入操作** — 执行前必须向用户确认。
|
||||
|
||||
1. 先用 `+form-questions-list` 获取现有问题及其 `id`
|
||||
2. 构造包含 `id` 的更新数组
|
||||
3. 执行命令并报告更新结果
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base](../SKILL.md) — 多维表格全部命令
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
179
.claude/skills/lark-base/references/lark-base-form-submit.md
Normal file
179
.claude/skills/lark-base/references/lark-base-form-submit.md
Normal file
@ -0,0 +1,179 @@
|
||||
# base +form-submit
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
通过表单分享链接填写并提交多维表格表单。仅支持分享模式(share_token),支持填写普通字段值和上传本地文件作为附件。
|
||||
|
||||
> **⚠️ 高风险写操作(high-risk-write):** 本命令会向表单写入并提交数据,属于高风险写操作,必须额外传递 `--yes` 进行确认,否则会返回 `confirmation_required` 错误并退出。当用户明确要求提交且目标表单无歧义时,直接附加 `--yes`,无需再次询问。
|
||||
|
||||
## 填写前必读:先获取表单详情
|
||||
|
||||
**在调用 `+form-submit` 之前,必须先使用 `+form-detail` 获取表单详情。** 原因如下:
|
||||
|
||||
1. **字段类型匹配**:每个题目的 `type` 决定了值的格式(文本、数字、选项、人员、日期等),需根据类型正确构造 `fields` 中的值
|
||||
2. **必填校验**:通过 `questions[].required` 判断哪些题目为必填项,避免遗漏
|
||||
3. **显示条件过滤**:部分题目带有 `filter`(显示/隐藏逻辑),需根据用户已填的其他题目值判断该题目是否应该出现——**不应填写被 filter 隐藏的题目**
|
||||
4. **获取 base_token(附件场景必用)**:`+form-detail` 返回的 `data.base_token` 是该表单所属的多维表格标识。当表单包含附件字段时,提交时必须通过 `--base-token` 传入此值,因为附件需要上传到该 Base 的 Drive Media 中
|
||||
|
||||
典型流程:
|
||||
|
||||
```bash
|
||||
# 1️⃣ 先获取表单详情,了解所有题目
|
||||
lark-cli base +form-detail --share-token <share_token>
|
||||
|
||||
# 2️⃣ 根据返回的 questions 列表,按 type 格式化值、检查 required、判断 filter 条件
|
||||
|
||||
# 3️⃣ 再提交(高风险写操作,必须带 --yes)
|
||||
lark-cli base +form-submit \
|
||||
--share-token <share_token> \
|
||||
--json '{"fields":{...}}' \
|
||||
--yes
|
||||
```
|
||||
|
||||
`+form-detail` 的返回中要重点读取 `questions[].type`、`questions[].required`、题目 `filter` 和附件场景所需的 `data.base_token`。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 基本提交(填写普通字段)
|
||||
lark-cli base +form-submit \
|
||||
--share-token <share_token> \
|
||||
--json '{"fields":{"服务评分":5,"评价内容":"服务态度好"}}' \
|
||||
--yes
|
||||
|
||||
# 带附件提交(需要额外提供 --base-token)
|
||||
lark-cli base +form-submit \
|
||||
--share-token <share_token> \
|
||||
--base-token <base_token> \
|
||||
--json '{
|
||||
"fields": {"服务评分": 5, "评价内容": "好"},
|
||||
"attachments": {
|
||||
"附件字段名": ["./report.pdf", "./photo.png"],
|
||||
"另一个附件字段": ["./doc.docx"]
|
||||
}
|
||||
}' \
|
||||
--yes
|
||||
|
||||
# 使用应用身份(bot)
|
||||
lark-cli base +form-submit \
|
||||
--share-token <share_token> \
|
||||
--json '{"fields":{...}}' \
|
||||
--as bot \
|
||||
--yes
|
||||
|
||||
# 预览 API 调用(不实际执行,dry-run 无需 --yes)
|
||||
lark-cli base +form-submit \
|
||||
--share-token <share_token> \
|
||||
--json '{"fields":{...}}' \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--share-token <token>` | 是 | 表单分享 Token(必填),从表单分享链接中提取 |
|
||||
| `--base-token <token>` | 条件必填 | Base token;**当 `--json` 包含 `attachments` 时必须提供**,用于将附件上传到 Base Drive Media |
|
||||
| `--json <json>` | 是 | JSON 对象,包含 `"fields"`(普通字段值)和 `"attachments"`(附件上传),详见下方说明 |
|
||||
| `--yes` | 是 | 确认高风险写操作。本命令为 high-risk-write,不带 `--yes` 会返回 `confirmation_required` |
|
||||
| `--format` | 否 | 输出格式:json(默认)\| pretty \| table \| ndjson \| csv |
|
||||
| `--as` | 否 | 身份:user(默认)\| bot |
|
||||
| `--dry-run` | 否 | 预览 API 调用,不执行 |
|
||||
|
||||
### --json 结构说明
|
||||
|
||||
`--json` 是一个 JSON 对象,包含两个部分:
|
||||
|
||||
#### fields(普通字段)
|
||||
|
||||
`fields` 中的单元格值写法与 [`lark-base-cell-value.md`](lark-base-cell-value.md) 完全对齐,填写前应先阅读该文档了解各类型的构造规则:
|
||||
|
||||
```json
|
||||
{
|
||||
"文本字段": "Hello World",
|
||||
"电话字段": "13800000000",
|
||||
"超链接字段": "https://example.com",
|
||||
"数字字段": 12.5,
|
||||
"单选字段": "选项A",
|
||||
"多选字段": ["选项A", "选项B"],
|
||||
"时间字段": "2026-04-27 14:30:00",
|
||||
"复选框字段": true,
|
||||
"人员字段": [{ "id": "ou_7094d131420c8749632145f08fbf114a" }],
|
||||
"关联字段": [{ "id": "recXXXXXXXXXXXX" }],
|
||||
"地理位置字段": { "lng": 116.397428, "lat": 39.90923 }
|
||||
}
|
||||
```
|
||||
|
||||
> **注意:附件类型字段不要写在 `fields` 里。** `fields` 中不包含附件,附件有独立的填写方式,见下方「attachments(附件上传)」章节。
|
||||
|
||||
> 自动编号、公式、创建/修改人、创建/修改时间等系统字段会自动填入,无需手动传入。
|
||||
|
||||
#### attachments(附件上传)
|
||||
|
||||
**附件字段的填写方式与 `fields` 中的普通单元格完全不同**,不能在 `fields` 里传 `file_token` 或其他附件格式。必须将附件字段单独放在 `--json` 的顶层 `attachments` 对象中,值为**本地文件路径数组**(不是 token):
|
||||
|
||||
```json
|
||||
{
|
||||
"attachments": {
|
||||
"附件字段名": ["./report.pdf", "./photo.png"],
|
||||
"另一个附件字段": ["./doc.docx"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
CLI 收到路径后会自动完成以下流程:
|
||||
1. 校验所有文件(存在性、大小 ≤2GB、常规文件)
|
||||
2. 并行上传到 Base Drive Media(并发上限 5,跨字段重复路径自动去重)
|
||||
3. 获取 `file_token` 后合并到最终表单提交内容中
|
||||
|
||||
> 与 [`lark-base-cell-value.md`](lark-base-cell-value.md) 中 Record 场景的附件写法不同:Record 写入时附件走独立的 `+record-upload-attachment` 命令;而 `+form-submit` 只需在 `attachments` 中传本地路径,上传由 CLI 内部自动完成。
|
||||
|
||||
### 从分享链接提取 share-token
|
||||
|
||||
用户提供形如以下格式的表单分享链接时:
|
||||
|
||||
```
|
||||
https://www.example.com/share/base/form/shrbcvST8eZy0vk8zjVZ1CAXNye
|
||||
```
|
||||
|
||||
**提取方式:** 取 URL 路径最后一段作为 `--share-token`。
|
||||
|
||||
以上述链接为例:
|
||||
|
||||
- `share-token` = `shrbcvST8eZy0vk8zjVZ1CAXNye`
|
||||
|
||||
```bash
|
||||
lark-cli base +form-submit \
|
||||
--share-token shrbcvST8eZy0vk8zjVZ1CAXNye \
|
||||
--json '{"fields":{...}}' \
|
||||
--yes
|
||||
```
|
||||
|
||||
## 输出格式
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `can_submit_again` | bool | 是否可以再次填写 |
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"can_submit_again": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 提示
|
||||
|
||||
- **本命令为高风险写操作(high-risk-write),必须额外传递 `--yes` 确认**,否则返回 `confirmation_required` 并以非零码退出;`--dry-run` 预览除外
|
||||
- 本命令仅支持通过表单分享链接(share_token)提交,不支持通过 base_token + table_id + view_id 方式提交
|
||||
- **当 `--json` 包含 `attachments` 时,必须额外提供 `--base-token`**,因为附件上传到 Base Drive Media 需要指定目标 Base
|
||||
- 附件字段只需在 `--json.attachments` 中提供本地路径即可,CLI 自动完成校验、并行上传、Token 获取和合并写入
|
||||
- 限流:单应用 20 QPS,单用户 5 QPS
|
||||
- 权限要求:`base:form:update`;使用 attachments 时还需 `docs:document.media:upload`
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base](../SKILL.md) — 多维表格全部命令
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
@ -0,0 +1,59 @@
|
||||
# base +record-batch-create
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
批量创建记录。
|
||||
|
||||
## 适用场景(重点)
|
||||
|
||||
- 适合导入 CSV / Excel、外部系统一次性写入新数据。
|
||||
- 先把每条输入数据映射为独立的字段对象,再组装到 `create_records`。
|
||||
|
||||
## 推荐命令
|
||||
|
||||
```bash
|
||||
lark-cli base +record-batch-create --base-token <base_token> --table-id <table_id> \
|
||||
--json '{"create_records":[{"标题":"任务 A","状态":"Open"},{"标题":"任务 B","状态":"Done"}]}'
|
||||
|
||||
lark-cli base +record-batch-create --base-token <base_token> --table-id <table_id> --json @batch-create.json
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--base-token <token>` | 是 | Base Token |
|
||||
| `--table-id <id_or_name>` | 是 | 表 ID 或表名 |
|
||||
| `--json <body>` | 是 | 批量创建请求体,必须是 JSON 对象。支持直接传 JSON 字符串,或 `@<file_path>` 从文件读取 |
|
||||
|
||||
## API
|
||||
|
||||
`POST /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch_create`
|
||||
|
||||
## `--json` 结构
|
||||
|
||||
本节只说明 `+record-batch-create` 的外层 JSON 形状;CellValue 统一看 [lark-base-cell-value.md](lark-base-cell-value.md)。
|
||||
|
||||
对象形态:
|
||||
|
||||
```json
|
||||
{"create_records":[{"标题":"任务 A","状态":"Open"},{"标题":"任务 B","状态":"Done"}]}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `create_records` | `Array<Map<FieldNameOrID, CellValue>>` | 是 | 记录字段对象数组;每条记录可以提交不同字段,单次最多 200 条 |
|
||||
|
||||
## 返回重点
|
||||
|
||||
返回 `record_id_list` 和可选的 `ignored_fields`。
|
||||
|
||||
## 坑点
|
||||
|
||||
- 每个 `create_records` 元素都是独立的记录字段对象,只提交该记录需要写入的字段。
|
||||
- 单次最多 200 条,超出需分批写入。
|
||||
- `select` 字段只支持写入字段中已有的选项;构造 CellValue 前先用 `+field-list` 或 `+field-search-options` 确认目标选项存在。
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base-cell-value.md](lark-base-cell-value.md) — CellValue 格式规范
|
||||
@ -0,0 +1,54 @@
|
||||
# base +record-batch-update (batch update)
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
通过 `update_records` 为每条记录提交字段值。
|
||||
|
||||
## 推荐命令
|
||||
|
||||
```bash
|
||||
lark-cli base +record-batch-update --base-token <base_token> --table-id <table_id> \
|
||||
--json '{"update_records":{"<record_id_a>":{"状态":["完成"]},"<record_id_b>":{"分数":20}}}'
|
||||
|
||||
lark-cli base +record-batch-update --base-token <base_token> --table-id <table_id> --json @batch-update.json
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--base-token <token>` | 是 | Base Token |
|
||||
| `--table-id <id_or_name>` | 是 | 表 ID 或表名 |
|
||||
| `--json <body>` | 是 | 批量更新请求体,必须是 JSON 对象。支持直接传 JSON 字符串,或 `@<file_path>` 从文件读取 |
|
||||
|
||||
## API
|
||||
|
||||
`POST /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch_update`
|
||||
|
||||
## `--json` 结构
|
||||
|
||||
本节只说明 `+record-batch-update` 的外层 JSON 形状;CellValue 统一看 [lark-base-cell-value.md](lark-base-cell-value.md)。
|
||||
|
||||
对象形态:
|
||||
|
||||
```json
|
||||
{"update_records":{"recA":{"状态":["完成"]},"recB":{"分数":20}}}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `update_records` | `Map<RecordID, Map<FieldNameOrID, CellValue>>` | 是 | record ID 到字段更新对象的映射(单次最多 200 条) |
|
||||
|
||||
## 返回重点
|
||||
|
||||
成功响应只包含可选的 `ignored_fields`;没有忽略字段时 `data` 为空对象。请求不会预先校验 record ID 是否存在,因此需要确认实际写入结果时,应再用 `+record-get` 读回目标记录。
|
||||
|
||||
## 坑点
|
||||
|
||||
- 单次最多更新 200 条记录,超过会被接口校验拒绝。
|
||||
- 命令不会自动做字段/行映射转换,传什么就发什么。
|
||||
- 如果字段映射包含只读字段,返回里可能出现 `ignored_fields`;这些字段不会被更新。
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base-cell-value.md](lark-base-cell-value.md) — CellValue 格式规范
|
||||
@ -0,0 +1,43 @@
|
||||
# base +record-history-list
|
||||
|
||||
查询单条记录的变更历史。它返回历史事件,不返回记录当前值,也不支持整表审计扫描。
|
||||
|
||||
## 推荐命令
|
||||
|
||||
```bash
|
||||
lark-cli base +record-history-list \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--record-id <record_id>
|
||||
|
||||
lark-cli base +record-history-list \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--record-id <record_id> \
|
||||
--page-size 30 \
|
||||
--max-version <next_max_version>
|
||||
```
|
||||
|
||||
## 返回解释
|
||||
|
||||
- 历史条目通常按版本号降序返回,最新在前。
|
||||
- 每条历史包含版本号、操作人、操作时间、操作类型和字段变更。
|
||||
- `create_time` 是秒级 Unix 时间戳。
|
||||
- `field_changes` 描述字段变更,重点看字段名/字段类型、`before` 和 `after`。
|
||||
- `activity_type` 常见值:`create`(创建记录)、`update`(编辑记录)、`delete`(删除记录)。
|
||||
|
||||
以下字段类型的变化可能不会出现在 `field_changes` 中:
|
||||
|
||||
- 计算字段:`formula`、`lookup`
|
||||
- 系统字段:自动编号、创建时间、创建人、修改时间、修改人
|
||||
|
||||
## 翻页
|
||||
|
||||
- 首次请求不传 `--max-version`。
|
||||
- 如果返回 `has_more=true`,取返回中的 `next_max_version` 作为下一次请求的 `--max-version`。
|
||||
- `--page-size` 默认 30,最大 50。
|
||||
|
||||
## 注意
|
||||
|
||||
- `table-id` 和 `record-id` 必须来自同一张表。
|
||||
- 这是单条记录历史,不是表级审计;需要查多条记录时串行调用。
|
||||
@ -0,0 +1,63 @@
|
||||
# base +record-upsert
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
创建记录,或在带 `--record-id` 时更新记录。
|
||||
|
||||
## 推荐命令
|
||||
|
||||
```bash
|
||||
# 创建记录
|
||||
lark-cli base +record-upsert --base-token <base_token> --table-id <table_id> \
|
||||
--json '{"项目名称":"Apollo","状态":"进行中"}'
|
||||
|
||||
# 更新记录
|
||||
lark-cli base +record-upsert --base-token <base_token> --table-id <table_id> --record-id <record_id> \
|
||||
--json '{"项目名称":"Apollo","状态":"完成","完成时间":"2026-03-24 10:00:00"}'
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--base-token <token>` | 是 | Base Token |
|
||||
| `--table-id <id_or_name>` | 是 | 表 ID 或表名 |
|
||||
| `--record-id <id>` | 否 | 传入时走更新,不传时走创建 |
|
||||
| `--json <body>` | 是 | 字段写入对象,类型 `Map<FieldNameOrID, CellValue>` |
|
||||
|
||||
## API
|
||||
|
||||
- 创建:`POST /open-apis/base/v3/bases/:base_token/tables/:table_id/records`
|
||||
- 更新:带 `--record-id` 时改走 `PATCH /records/:record_id`
|
||||
|
||||
## `--json` 结构
|
||||
|
||||
- `--json` 必须是 **JSON object map**,形状是 `Map<FieldNameOrID, CellValue>`。
|
||||
- key 是字段名或字段 ID;value 是该字段的 `CellValue`。
|
||||
- 一次请求里同一字段只用一种标识,避免重复写入冲突。
|
||||
- 写入前先 `+field-list` 确认字段类型和字段名/ID。
|
||||
- CellValue 统一看 [lark-base-cell-value.md](lark-base-cell-value.md)。
|
||||
|
||||
```json
|
||||
{
|
||||
"项目名称": "Apollo",
|
||||
"状态": "进行中",
|
||||
"完成时间": "2026-03-24 10:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
## 返回重点
|
||||
|
||||
- 创建时返回 `record` 和 `created: true`。
|
||||
- 更新时返回 `record` 和 `updated: true`。
|
||||
- 如果写入了 `formula / lookup / created_at / updated_at / created_by / updated_by` 等只读字段,返回里可能出现 `ignored_fields`,这些字段不会被更新。
|
||||
|
||||
## 坑点
|
||||
|
||||
- 有 `--record-id` 就一定更新;不传就一定创建,不会自动查重或按业务键 upsert。
|
||||
- `select` 字段只支持写入字段中已有的选项;构造 CellValue 前先用 `+field-list` 或 `+field-search-options` 确认目标选项存在。
|
||||
- 这是写入操作,执行前必须确认目标表和字段。
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base-cell-value.md](lark-base-cell-value.md) — CellValue 格式规范
|
||||
65
.claude/skills/lark-base/references/lark-base-role-guide.md
Normal file
65
.claude/skills/lark-base/references/lark-base-role-guide.md
Normal file
@ -0,0 +1,65 @@
|
||||
# Base advanced permission and role guide
|
||||
|
||||
This guide is the entry point for Base advanced permissions and roles. Use it to choose commands and understand safety boundaries. For the permission JSON itself, use [role-config.md](role-config.md) as the SSOT.
|
||||
|
||||
## Command selection
|
||||
|
||||
| Goal | Command | Notes |
|
||||
|------|---------|-------|
|
||||
| Enable advanced permissions | `+advperm-enable` | Required before creating or updating roles. Caller must be a Base admin. |
|
||||
| Disable advanced permissions | `+advperm-disable` | High-risk write. Disabling invalidates existing custom roles. |
|
||||
| Locate roles | `+role-list` | Returns role summaries. Use `+role-get` for full config. |
|
||||
| Inspect one role | `+role-get` | Use before updating a role or deciding whether a role can be deleted. |
|
||||
| Create a custom role | `+role-create` | Supports `custom_role` only. Read [role-config.md](role-config.md) before constructing `--json`. |
|
||||
| Update a role | `+role-update` | Delta merge. Read current config first, then send only intended changes. |
|
||||
| Delete a role | `+role-delete` | Custom roles only. System roles cannot be deleted. |
|
||||
|
||||
## Safety boundaries
|
||||
|
||||
- Role operations require advanced permissions to be enabled and the caller to be a Base admin.
|
||||
- `+role-create` creates custom roles only.
|
||||
- `+role-delete` is only for custom roles. System roles such as editor/reader can be configured within supported limits, but cannot be deleted.
|
||||
- `+role-update` uses delta merge: omitted fields remain unchanged, but identity fields such as `role_name` and `role_type` should match the current target role.
|
||||
- `+advperm-disable` invalidates existing custom roles; confirm the target Base and user intent before passing `--yes`.
|
||||
|
||||
## Common Fewshots
|
||||
|
||||
Use these fewshots for simple role changes. For table, field, record, dashboard, docx, or filter permission details, switch to [role-config.md](role-config.md).
|
||||
|
||||
Create a custom role that keeps copy/download disabled:
|
||||
|
||||
```bash
|
||||
lark-cli base +role-create \
|
||||
--base-token <base_token> \
|
||||
--json '{"role_name":"Reviewer","role_type":"custom_role","base_rule_map":{"copy":false,"download":false}}'
|
||||
```
|
||||
|
||||
Rename a role while preserving its type:
|
||||
|
||||
```bash
|
||||
lark-cli base +role-update \
|
||||
--base-token <base_token> \
|
||||
--role-id <role_id> \
|
||||
--json '{"role_name":"Finance Reviewer","role_type":"custom_role"}' \
|
||||
--yes
|
||||
```
|
||||
|
||||
Grant read-only access to one table:
|
||||
|
||||
```bash
|
||||
lark-cli base +role-update \
|
||||
--base-token <base_token> \
|
||||
--role-id <role_id> \
|
||||
--json '{"role_name":"Finance Reviewer","role_type":"custom_role","table_rule_map":{"Orders":{"perm":"read_only"}}}' \
|
||||
--yes
|
||||
```
|
||||
|
||||
## JSON SSOT
|
||||
|
||||
Use [role-config.md](role-config.md) for:
|
||||
|
||||
- `AdvPermBaseRoleConfig` top-level structure.
|
||||
- `base_rule_map`, `table_rule_map`, `dashboard_rule_map`, and `docx_rule_map`.
|
||||
- Table, view, field, record, dashboard, and docx permission values.
|
||||
- Filter permission JSON.
|
||||
- Default permission strategy and risk rules.
|
||||
191
.claude/skills/lark-base/references/lark-base-view-set-filter.md
Normal file
191
.claude/skills/lark-base/references/lark-base-view-set-filter.md
Normal file
@ -0,0 +1,191 @@
|
||||
# base +view-set-filter
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
更新视图筛选配置。
|
||||
|
||||
## 1. 顶层规则
|
||||
|
||||
- `--json` 必须是 JSON 对象。
|
||||
- 顶层结构是 `{logic?, conditions?}`。
|
||||
- `logic` 默认 `and`;推荐只用 canonical 值 `and` / `or`。
|
||||
- `conditions` 默认空数组。
|
||||
- 每条条件写成 tuple:`[field, operator, value?]`。
|
||||
- `empty` / `non_empty` 可写成 2 项:`[field, "empty"]`、`[field, "non_empty"]`。
|
||||
- 支持 `filter` 的视图类型:`grid`、`kanban`、`gallery`、`calendar`、`gantt`。
|
||||
|
||||
## 2. operator
|
||||
|
||||
可用 operator:
|
||||
- `==`
|
||||
- `!=`
|
||||
- `>`
|
||||
- `>=`
|
||||
- `<`
|
||||
- `<=`
|
||||
- `intersects`
|
||||
- `disjoint`
|
||||
- `empty`
|
||||
- `non_empty`
|
||||
|
||||
## 3. value 写法
|
||||
|
||||
### `text`
|
||||
|
||||
用字符串:
|
||||
|
||||
```json
|
||||
["标题", "intersects", "发布"]
|
||||
```
|
||||
|
||||
### `location`
|
||||
|
||||
location 筛选只按 `full_address` 字符串匹配,不能直接按经纬度筛选;优先使用 `intersects` 做包含匹配,例如查深圳:
|
||||
|
||||
```json
|
||||
["位置", "intersects", "深圳"]
|
||||
```
|
||||
|
||||
不推荐写 `["位置", "==", "深圳"]` 这类精确匹配,除非确保筛选值与完整 `full_address` 完全一致。
|
||||
|
||||
### `number` / `auto_number`
|
||||
|
||||
用数字:
|
||||
|
||||
```json
|
||||
["工时", ">=", 3.5]
|
||||
```
|
||||
|
||||
### `select`
|
||||
|
||||
用选项名数组:
|
||||
|
||||
```json
|
||||
["状态", "intersects", ["Doing", "Blocked"]]
|
||||
```
|
||||
|
||||
### `user` / `created_by` / `updated_by`
|
||||
|
||||
用对象数组:
|
||||
|
||||
> **人员筛选:不要猜 ID。** 不知道 `open_id` 时,先用 `lark-contact` 查 id:`lark-cli contact +search-user --query "<姓名/邮箱/手机号>" --as user`。
|
||||
|
||||
```json
|
||||
["负责人", "intersects", [{ "id": "ou_xxx" }]]
|
||||
```
|
||||
|
||||
### `group_chat`
|
||||
|
||||
用对象数组:
|
||||
|
||||
> **群组筛选:不要猜 ID。** 不知道 `chat_id` 时,先用 `lark-im` 搜群:`lark-cli im +chat-search --query "<群名关键词>" --as user`;取结果里的 `oc_xxx`。
|
||||
|
||||
```json
|
||||
["负责群", "intersects", [{ "id": "oc_xxx" }]]
|
||||
```
|
||||
|
||||
### `link`
|
||||
|
||||
用记录 id 对象数组:
|
||||
|
||||
```json
|
||||
["关联任务", "intersects", [{ "id": "rec_xxx" }]]
|
||||
```
|
||||
|
||||
### `checkbox`
|
||||
|
||||
用布尔值:
|
||||
|
||||
```json
|
||||
["完成", "==", true]
|
||||
```
|
||||
|
||||
### `datetime` / `created_at` / `updated_at`
|
||||
|
||||
用相对时间关键字或 `ExactDate(...)`:
|
||||
|
||||
```json
|
||||
["截止时间", "==", "ExactDate(2026-01-01)"]
|
||||
```
|
||||
|
||||
```json
|
||||
["截止时间", "==", "ExactDate(2026-01-01 11:30)"]
|
||||
```
|
||||
|
||||
```json
|
||||
["截止时间", "==", "Today"]
|
||||
```
|
||||
|
||||
可用关键字:
|
||||
- `Today`
|
||||
- `Yesterday`
|
||||
- `Tomorrow`
|
||||
|
||||
### `formula` / `lookup`
|
||||
|
||||
- 筛选值类型由字段计算结果类型动态决定。
|
||||
- 拿不准时,先把 `value` 当作单个字符串填入做一次尝试。
|
||||
- 如果报错,再按错误提示把 `value` 改成对应类型。
|
||||
|
||||
字符串示例:
|
||||
|
||||
```json
|
||||
["风险说明", "intersects", "高风险"]
|
||||
```
|
||||
|
||||
数字示例:
|
||||
|
||||
```json
|
||||
["汇总分", ">=", 80]
|
||||
```
|
||||
|
||||
## 4. 推荐命令
|
||||
|
||||
```bash
|
||||
lark-cli base +view-set-filter \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--view-id <view_id> \
|
||||
--json '{"logic":"and","conditions":[["状态","intersects",["Doing"]],["负责人","intersects",[{"id":"ou_xxx"}]],["截止时间","empty"]]}'
|
||||
```
|
||||
|
||||
## 5. JSON 写法
|
||||
|
||||
```json
|
||||
{
|
||||
"logic": "and",
|
||||
"conditions": [
|
||||
["状态", "intersects", ["Doing"]],
|
||||
["负责人", "intersects", [{ "id": "ou_xxx" }]],
|
||||
["截止时间", "empty"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
清空写法:
|
||||
|
||||
```json
|
||||
{
|
||||
"conditions": []
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 使用建议
|
||||
|
||||
- 先读取当前筛选配置,理解现有 `logic` 和 `conditions` 的组合关系;只替换用户要求变更的条件,未提到的条件默认保留。
|
||||
- 优先传字段 id,不要依赖字段名。
|
||||
- 拿不准字段 type 或真实取值时,先用 `+field-list` / `+record-list` 确认,再按对应字段类型的 value 写法构造条件;别按字段名猜 type、凭印象猜枚举取值。
|
||||
- 需要清空全部筛选时,直接传 `{"conditions":[]}`。
|
||||
|
||||
## 7. 易错点
|
||||
|
||||
- 本 tuple DSL 由 `+view-set-filter` 与 `+record-list` / `+record-search` 的 `--filter-json` 共用;不要写成 `+data-query` 的对象风格 `{"field_name":...,"operator":...}`(会报校验失败)。
|
||||
- 标量类字段(`text` / `number` / `datetime` 等)的 value 用标量、别包成数组(各类型详见 value 写法一节)。
|
||||
- `user` / `group_chat` / `link` 不要写成单个标量。
|
||||
- `empty` / `non_empty` 不要硬塞无意义的 value。
|
||||
- 日期条件稳定写法用 `ExactDate(...)` 或 `Today` / `Yesterday` / `Tomorrow`。
|
||||
- `formula` / `lookup` 的 value 形状不固定;拿不准时先读当前 filter 或字段定义,或根据错误提示修正类型。
|
||||
|
||||
## 8. 参考
|
||||
|
||||
- [lookup-field-guide.md](lookup-field-guide.md)
|
||||
830
.claude/skills/lark-base/references/lark-base-workflow-guide.md
Normal file
830
.claude/skills/lark-base/references/lark-base-workflow-guide.md
Normal file
@ -0,0 +1,830 @@
|
||||
# Workflow guide
|
||||
|
||||
本文档是 Workflow 的入口指南,帮助选择步骤组合、理解创建/更新边界,并引导到 steps JSON SSOT。
|
||||
|
||||
> **配套文档**:
|
||||
> - Workflow 的数据结构参考:[lark-base-workflow-schema.md](lark-base-workflow-schema.md)
|
||||
> - 创建/更新时重点构造 `title`、`status` 和 `steps`;复杂度集中在 `steps[].type/data/next`
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 最简单的 Workflow
|
||||
|
||||
新增记录时发送消息通知:
|
||||
|
||||
```json
|
||||
{
|
||||
"client_token": "1704067200",
|
||||
"title": "新订单自动通知",
|
||||
"steps": [
|
||||
{
|
||||
"id": "trigger_1",
|
||||
"type": "AddRecordTrigger",
|
||||
"title": "监控新订单",
|
||||
"next": "action_1",
|
||||
"data": {
|
||||
"table_name": "订单表",
|
||||
"watched_field_name": "订单号"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "action_1",
|
||||
"type": "LarkMessageAction",
|
||||
"title": "发送通知",
|
||||
"next": null,
|
||||
"data": {
|
||||
"receiver": [{ "value_type": "user", "value": {"id": "ou_xxxx", "name": "张三"} }],
|
||||
"send_to_everyone": false,
|
||||
"title": [{ "value_type": "text", "value": "新订单提醒" }],
|
||||
"content": [
|
||||
{ "value_type": "text", "value": "收到新订单" }
|
||||
],
|
||||
"btn_list": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 场景速查表
|
||||
|
||||
| 场景 | 步骤组合 | 示例 |
|
||||
|------|---------|------|
|
||||
| 新增触发+通知 | AddRecordTrigger → LarkMessageAction | [下方](#示例1-新增记录触发--发送消息) |
|
||||
| 按钮点击+调用外部接口+写入日志 | ButtonTrigger → HTTPClientAction → AddRecordAction | [下方](#示例-6-按钮触发--调用外部接口--写入同步日志) |
|
||||
| 定时+循环 | TimerTrigger → FindRecordAction → Loop → LarkMessageAction | [下方](#示例2-定时触发--查找记录--循环遍历--发送消息) |
|
||||
| 条件判断 | ... → IfElseBranch → 分支处理 | [下方](#示例3-条件分支-ifelsebranch) |
|
||||
| 多路分类 | ... → SwitchBranch → 多分支处理 | [下方](#示例4-多路分支-switchbranch) |
|
||||
| 复杂组合 | 定时+查找+循环+分支+消息 | [下方](#示例5-组合场景-定时查找循环分支消息) |
|
||||
|
||||
---
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 示例 1: 新增记录触发 + 发送消息
|
||||
|
||||
**场景**: 当订单表新增记录时,发送飞书消息通知负责人。
|
||||
|
||||
```json
|
||||
{
|
||||
"client_token": "1704067201",
|
||||
"title": "新订单自动通知",
|
||||
"steps": [
|
||||
{
|
||||
"id": "step_trigger",
|
||||
"type": "AddRecordTrigger",
|
||||
"title": "新增订单时触发",
|
||||
"next": "step_notify",
|
||||
"data": {
|
||||
"table_name": "订单表",
|
||||
"watched_field_name": "订单号",
|
||||
"condition_list": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_notify",
|
||||
"type": "LarkMessageAction",
|
||||
"title": "发送订单通知",
|
||||
"next": null,
|
||||
"data": {
|
||||
"receiver": [{ "value_type": "ref", "value": "$.step_trigger.fldManager" }],
|
||||
"send_to_everyone": false,
|
||||
"title": [{ "value_type": "text", "value": "新订单提醒" }],
|
||||
"content": [
|
||||
{ "value_type": "text", "value": "客户 " },
|
||||
{ "value_type": "ref", "value": "$.step_trigger.fldCustomer" },
|
||||
{ "value_type": "text", "value": " 创建了新订单,金额:¥" },
|
||||
{ "value_type": "ref", "value": "$.step_trigger.fldAmount" }
|
||||
],
|
||||
"btn_list": [
|
||||
{
|
||||
"text": "查看订单",
|
||||
"btn_action": "openLink",
|
||||
"link": [{ "value_type": "ref", "value": "$.step_trigger.recordLink" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `AddRecordTrigger` 监控 `table_name` 表的 `watched_field_name` 字段
|
||||
- 使用 `ref` 引用触发器输出的字段值(注意是 fieldId,不是字段名)
|
||||
- `recordLink` 是触发器内置输出,表示记录链接
|
||||
|
||||
---
|
||||
|
||||
### 示例 2: 定时触发 + 查找记录 + 循环遍历 + 发送消息
|
||||
|
||||
**场景**: 每天早上 9 点,查找所有待处理订单,给每个客户发送提醒。
|
||||
|
||||
```json
|
||||
{
|
||||
"client_token": "1704067202",
|
||||
"title": "每日待处理订单提醒",
|
||||
"steps": [
|
||||
{
|
||||
"id": "step_timer",
|
||||
"type": "TimerTrigger",
|
||||
"title": "每天早上9点触发",
|
||||
"next": "step_find_orders",
|
||||
"data": {
|
||||
"rule": "DAILY",
|
||||
"start_time": "2025-01-01 09:00",
|
||||
"is_never_end": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_find_orders",
|
||||
"type": "FindRecordAction",
|
||||
"title": "查找所有待处理订单",
|
||||
"next": "step_loop_customers",
|
||||
"data": {
|
||||
"table_name": "订单表",
|
||||
"field_names": ["客户名称", "订单金额", "客户联系方式"],
|
||||
"should_proceed_when_no_results": false,
|
||||
"filter_info": {
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"field_name": "状态",
|
||||
"operator": "is",
|
||||
"value": [{ "value_type": "option", "value": { "name": "待处理" } }]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_loop_customers",
|
||||
"type": "Loop",
|
||||
"title": "遍历每个订单",
|
||||
"children": {
|
||||
"links": [
|
||||
{ "kind": "loop_start", "to": "step_send_reminder" }
|
||||
]
|
||||
},
|
||||
"next": null,
|
||||
"data": {
|
||||
"loop_mode": "continue",
|
||||
"max_loop_times": 100,
|
||||
"data": [{
|
||||
"value_type": "ref",
|
||||
"value": "$.step_find_orders.fieldRecords"
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_send_reminder",
|
||||
"type": "LarkMessageAction",
|
||||
"title": "发送催办消息",
|
||||
"next": null,
|
||||
"data": {
|
||||
"receiver": [{
|
||||
"value_type": "ref",
|
||||
"value": "$.step_loop_customers.item.fldContact"
|
||||
}],
|
||||
"send_to_everyone": false,
|
||||
"title": [{ "value_type": "text", "value": "订单处理提醒" }],
|
||||
"content": [
|
||||
{ "value_type": "text", "value": "您好,您的订单 " },
|
||||
{ "value_type": "ref", "value": "$.step_loop_customers.item.fldName" },
|
||||
{ "value_type": "text", "value": " 金额 ¥" },
|
||||
{ "value_type": "ref", "value": "$.step_loop_customers.item.fldAmount" },
|
||||
{ "value_type": "text", "value": " 正在处理中。" }
|
||||
],
|
||||
"btn_list": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `Loop.data` 必须传入 `ref` 类型的数据源(通常是 FindRecordAction 的 `fieldRecords`)
|
||||
- `Loop.children.links` 必须包含 `kind: "loop_start"` 的链接指向循环体
|
||||
- 循环体内用 `$.{loopStepId}.item.{fieldId}` 引用当前遍历记录的字段
|
||||
- `$.{loopStepId}.index` 获取当前索引(从 0 开始)
|
||||
|
||||
---
|
||||
|
||||
### 示例 3: 条件分支(IfElseBranch)
|
||||
|
||||
**场景**: 根据订单金额判断,大额订单通知主管审批,小额订单自动通过。
|
||||
|
||||
```json
|
||||
{
|
||||
"client_token": "1704067203",
|
||||
"title": "订单金额自动判断",
|
||||
"steps": [
|
||||
{
|
||||
"id": "step_trigger",
|
||||
"type": "AddRecordTrigger",
|
||||
"title": "新增订单时触发",
|
||||
"next": "step_check_amount",
|
||||
"data": {
|
||||
"table_name": "订单表",
|
||||
"watched_field_name": "订单金额"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_check_amount",
|
||||
"type": "IfElseBranch",
|
||||
"title": "判断是否为大额订单",
|
||||
"children": {
|
||||
"links": [
|
||||
{ "kind": "if_true", "to": "step_notify_manager", "label": "high", "desc": "金额>=10000" },
|
||||
{ "kind": "if_false", "to": "step_auto_approve", "label": "normal", "desc": "金额<10000" }
|
||||
]
|
||||
},
|
||||
"next": "step_log",
|
||||
"data": {
|
||||
"condition": {
|
||||
"conjunction": "or",
|
||||
"conditions": [
|
||||
{
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"left_value": { "value_type": "ref", "value": "$.step_trigger.fldAmount" },
|
||||
"operator": "isGreaterEqual",
|
||||
"right_value": [{ "value_type": "number", "value": 10000 }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_notify_manager",
|
||||
"type": "LarkMessageAction",
|
||||
"title": "通知主管审批大额订单",
|
||||
"next": "step_log",
|
||||
"data": {
|
||||
"receiver": [{ "value_type": "user", "value": {"id": "ou_manager", "name": "主管"} }],
|
||||
"send_to_everyone": false,
|
||||
"title": [{ "value_type": "text", "value": "大额订单待审批" }],
|
||||
"content": [
|
||||
{ "value_type": "text", "value": "有大额订单 ¥" },
|
||||
{ "value_type": "ref", "value": "$.step_trigger.fldAmount" },
|
||||
{ "value_type": "text", "value": " 需要您审批" }
|
||||
],
|
||||
"btn_list": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_auto_approve",
|
||||
"type": "SetRecordAction",
|
||||
"title": "自动标记小额订单为已审核",
|
||||
"next": "step_log",
|
||||
"data": {
|
||||
"table_name": "订单表",
|
||||
"ref_info": { "step_id": "step_trigger" },
|
||||
"field_values": [
|
||||
{
|
||||
"field_name": "审批状态",
|
||||
"value": [{ "value_type": "option", "value": { "name": "已自动审核" } }]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_log",
|
||||
"type": "GenerateAiTextAction",
|
||||
"title": "生成订单处理日志",
|
||||
"next": null,
|
||||
"data": {
|
||||
"prompt": [
|
||||
{ "value_type": "text", "value": "请生成订单处理日志,金额:" },
|
||||
{ "value_type": "ref", "value": "$.step_trigger.fldAmount" }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `IfElseBranch.children.links` 必须包含 `if_true` 和 `if_false` 两个分支
|
||||
- `next` 指向两个分支汇合后的步骤(可选,为 null 则分支结束)
|
||||
- `condition` 使用 OrGroup 结构,支持 `(A and B) or (C and D)` 的复杂条件
|
||||
- 分支内可以用 `ref_info` 引用触发记录,用 `filter_info` 批量筛选记录
|
||||
|
||||
---
|
||||
|
||||
### 示例 4: 多路分支(SwitchBranch)
|
||||
|
||||
**场景**: 根据订单优先级(P0/P1/P2)执行不同的处理流程。
|
||||
|
||||
```json
|
||||
{
|
||||
"client_token": "1704067204",
|
||||
"title": "按优先级分类处理订单",
|
||||
"steps": [
|
||||
{
|
||||
"id": "step_trigger",
|
||||
"type": "AddRecordTrigger",
|
||||
"title": "新增订单时触发",
|
||||
"next": "step_classify",
|
||||
"data": {
|
||||
"table_name": "订单表",
|
||||
"watched_field_name": "优先级"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_classify",
|
||||
"type": "SwitchBranch",
|
||||
"title": "按优先级分类",
|
||||
"children": {
|
||||
"links": [
|
||||
{ "kind": "case", "to": "step_p0_handler", "label": "p0", "desc": "P0-紧急" },
|
||||
{ "kind": "case", "to": "step_p1_handler", "label": "p1", "desc": "P1-高优先级" },
|
||||
{ "kind": "case", "to": "step_p2_handler", "label": "p2", "desc": "P2-普通" },
|
||||
{ "kind": "case", "to": "step_other_handler", "label": "other", "desc": "其他" }
|
||||
]
|
||||
},
|
||||
"next": null,
|
||||
"data": {
|
||||
"mode": "exclusive",
|
||||
"no_match_action": "classifyToOther",
|
||||
"child_branch_list": [
|
||||
{
|
||||
"name": "P0-紧急",
|
||||
"condition": {
|
||||
"conjunction": "or",
|
||||
"conditions": [
|
||||
{
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"left_value": { "value_type": "ref", "value": "$.step_trigger.fldPriority" },
|
||||
"operator": "is",
|
||||
"right_value": [{ "value_type": "option", "value": { "name": "P0" } }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "P1-高优先级",
|
||||
"condition": {
|
||||
"conjunction": "or",
|
||||
"conditions": [
|
||||
{
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"left_value": { "value_type": "ref", "value": "$.step_trigger.fldPriority" },
|
||||
"operator": "is",
|
||||
"right_value": [{ "value_type": "option", "value": { "name": "P1" } }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "P2-普通",
|
||||
"condition": {
|
||||
"conjunction": "or",
|
||||
"conditions": [
|
||||
{
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"left_value": { "value_type": "ref", "value": "$.step_trigger.fldPriority" },
|
||||
"operator": "is",
|
||||
"right_value": [{ "value_type": "option", "value": { "name": "P2" } }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_p0_handler",
|
||||
"type": "LarkMessageAction",
|
||||
"title": "P0紧急处理",
|
||||
"next": null,
|
||||
"data": {
|
||||
"receiver": [{ "value_type": "user", "value": {"id": "ou_director", "name": "总监"} }],
|
||||
"send_to_everyone": false,
|
||||
"title": [{ "value_type": "text", "value": "🚨 P0 紧急订单" }],
|
||||
"content": [{ "value_type": "text", "value": "有新的 P0 紧急订单需要立即处理" }],
|
||||
"btn_list": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_p1_handler",
|
||||
"type": "SetRecordAction",
|
||||
"title": "标记高优先级",
|
||||
"next": null,
|
||||
"data": {
|
||||
"table_name": "订单表",
|
||||
"ref_info": { "step_id": "step_trigger" },
|
||||
"field_values": [
|
||||
{ "field_name": "处理状态", "value": [{ "value_type": "text", "value": "高优先级待处理" }] }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_p2_handler",
|
||||
"type": "Delay",
|
||||
"title": "普通订单延迟处理",
|
||||
"next": null,
|
||||
"data": { "duration": 60 }
|
||||
},
|
||||
{
|
||||
"id": "step_other_handler",
|
||||
"type": "SetRecordAction",
|
||||
"title": "标记其他订单",
|
||||
"next": null,
|
||||
"data": {
|
||||
"table_name": "订单表",
|
||||
"ref_info": { "step_id": "step_trigger" },
|
||||
"field_values": [
|
||||
{ "field_name": "处理状态", "value": [{ "value_type": "text", "value": "待分类" }] }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `SwitchBranch` 适合 3 路及以上的分支场景(少于 3 路用 `IfElseBranch` 更简洁)
|
||||
- `children.links` 中 `kind: "case"` 的 `label` 对应 `child_branch_list` 中的条件
|
||||
- `mode: "exclusive"` 表示排他执行(第一个匹配的分支执行后停止)
|
||||
- `no_match_action: "classifyToOther"` 表示无匹配时走最后一个 `case`(兜底分支)
|
||||
|
||||
---
|
||||
|
||||
### 示例 5: 组合场景(定时+查找+循环+分支+消息)
|
||||
|
||||
**场景**: 每天早上 9 点,查找昨天的订单,按金额分级,给不同级别的销售发送不同的通知。
|
||||
|
||||
```json
|
||||
{
|
||||
"client_token": "1704067205",
|
||||
"title": "每日订单分级通知",
|
||||
"steps": [
|
||||
{
|
||||
"id": "step_timer",
|
||||
"type": "TimerTrigger",
|
||||
"title": "每天早上9点触发",
|
||||
"next": "step_find_orders",
|
||||
"data": {
|
||||
"rule": "DAILY",
|
||||
"start_time": "2025-01-01 09:00",
|
||||
"is_never_end": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_find_orders",
|
||||
"type": "FindRecordAction",
|
||||
"title": "查找昨天所有订单",
|
||||
"next": "step_loop",
|
||||
"data": {
|
||||
"table_name": "订单表",
|
||||
"field_names": ["订单号", "客户名称", "金额", "销售负责人"],
|
||||
"should_proceed_when_no_results": false,
|
||||
"filter_info": {
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{ "field_name": "创建时间", "operator": "isGreaterEqual", "value": [{ "value_type": "date", "value": "yesterday" }] }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_loop",
|
||||
"type": "Loop",
|
||||
"title": "遍历每个订单",
|
||||
"children": {
|
||||
"links": [
|
||||
{ "kind": "loop_start", "to": "step_classify" }
|
||||
]
|
||||
},
|
||||
"next": "step_summary",
|
||||
"data": {
|
||||
"loop_mode": "continue",
|
||||
"max_loop_times": 500,
|
||||
"data": [{ "value_type": "ref", "value": "$.step_find_orders.fieldRecords" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_classify",
|
||||
"type": "SwitchBranch",
|
||||
"title": "按金额分类",
|
||||
"children": {
|
||||
"links": [
|
||||
{ "kind": "case", "to": "step_vip_notify", "label": "vip", "desc": "VIP >= 10万" },
|
||||
{ "kind": "case", "to": "step_normal_notify", "label": "normal", "desc": "普通 < 10万" }
|
||||
]
|
||||
},
|
||||
"next": null,
|
||||
"data": {
|
||||
"mode": "exclusive",
|
||||
"no_match_action": "fail",
|
||||
"child_branch_list": [
|
||||
{
|
||||
"name": "VIP订单",
|
||||
"condition": {
|
||||
"conjunction": "or",
|
||||
"conditions": [
|
||||
{
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"left_value": { "value_type": "ref", "value": "$.step_loop.item.fldAmount" },
|
||||
"operator": "isGreaterEqual",
|
||||
"right_value": [{ "value_type": "number", "value": 100000 }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "普通订单",
|
||||
"condition": {
|
||||
"conjunction": "or",
|
||||
"conditions": [
|
||||
{
|
||||
"conjunction": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"left_value": { "value_type": "ref", "value": "$.step_loop.item.fldAmount" },
|
||||
"operator": "isLess",
|
||||
"right_value": [{ "value_type": "number", "value": 100000 }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_vip_notify",
|
||||
"type": "LarkMessageAction",
|
||||
"title": "VIP订单通知",
|
||||
"next": null,
|
||||
"data": {
|
||||
"receiver": [{ "value_type": "ref", "value": "$.step_loop.item.fldSales" }],
|
||||
"send_to_everyone": false,
|
||||
"title": [{ "value_type": "text", "value": "🌟 VIP大额订单" }],
|
||||
"content": [
|
||||
{ "value_type": "text", "value": "恭喜!您有一笔 VIP 订单 ¥" },
|
||||
{ "value_type": "ref", "value": "$.step_loop.item.fldAmount" },
|
||||
{ "value_type": "text", "value": ",客户:" },
|
||||
{ "value_type": "ref", "value": "$.step_loop.item.fldCustomer" }
|
||||
],
|
||||
"btn_list": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_normal_notify",
|
||||
"type": "LarkMessageAction",
|
||||
"title": "普通订单通知",
|
||||
"next": null,
|
||||
"data": {
|
||||
"receiver": [{ "value_type": "ref", "value": "$.step_loop.item.fldSales" }],
|
||||
"send_to_everyone": false,
|
||||
"title": [{ "value_type": "text", "value": "新订单通知" }],
|
||||
"content": [
|
||||
{ "value_type": "text", "value": "您有一笔新订单 ¥" },
|
||||
{ "value_type": "ref", "value": "$.step_loop.item.fldAmount" }
|
||||
],
|
||||
"btn_list": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_summary",
|
||||
"type": "GenerateAiTextAction",
|
||||
"title": "生成日报",
|
||||
"next": null,
|
||||
"data": {
|
||||
"prompt": [
|
||||
{ "value_type": "text", "value": "请生成昨日订单处理日报" }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 示例 6: 按钮触发 + 调用外部接口 + 写入同步日志
|
||||
|
||||
**场景**: 在「客户线索表」里给每条记录配置一个“同步到 CRM”按钮。销售点击按钮后,Workflow 调用外部 CRM 接口同步当前线索,再在「同步日志表」新增一条记录,方便后续审计和排查。
|
||||
|
||||
```json
|
||||
{
|
||||
"client_token": "1704067206",
|
||||
"title": "线索一键同步到 CRM",
|
||||
"steps": [
|
||||
{
|
||||
"id": "step_button_trigger",
|
||||
"type": "ButtonTrigger",
|
||||
"title": "点击同步到 CRM 按钮时触发",
|
||||
"next": "step_call_crm_api",
|
||||
"data": {
|
||||
"button_type": "buttonField",
|
||||
"table_name": "客户线索表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_call_crm_api",
|
||||
"type": "HTTPClientAction",
|
||||
"title": "调用 CRM 同步接口",
|
||||
"next": "step_add_sync_log",
|
||||
"data": {
|
||||
"method": "POST",
|
||||
"url": [
|
||||
{ "value_type": "text", "value": "https://api.example-crm.com/v1/leads/sync" }
|
||||
],
|
||||
"headers": [
|
||||
{ "key": "Content-Type", "value": [{ "value_type": "text", "value": "application/json" }] },
|
||||
{ "key": "X-System", "value": [{ "value_type": "text", "value": "lark_base_workflow" }] }
|
||||
],
|
||||
"body_type": "raw",
|
||||
"raw_body": [
|
||||
{ "value_type": "text", "value": "{\"lead_name\":\"" },
|
||||
{ "value_type": "ref", "value": "$.step_button_trigger.fldLeadName" },
|
||||
{ "value_type": "text", "value": "\",\"mobile\":\"" },
|
||||
{ "value_type": "ref", "value": "$.step_button_trigger.fldMobile" },
|
||||
{ "value_type": "text", "value": "\",\"company\":\"" },
|
||||
{ "value_type": "ref", "value": "$.step_button_trigger.fldCompany" },
|
||||
{ "value_type": "text", "value": "\",\"owner\":\"" },
|
||||
{ "value_type": "ref", "value": "$.step_button_trigger.fldOwner" },
|
||||
{ "value_type": "text", "value": "\",\"source_record_id\":\"" },
|
||||
{ "value_type": "ref", "value": "$.step_button_trigger.recordId" },
|
||||
{ "value_type": "text", "value": "\"}" }
|
||||
],
|
||||
"response_type": "json",
|
||||
"response_value": "{\"success\":true,\"message\":\"lead synced successfully\"}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_add_sync_log",
|
||||
"type": "AddRecordAction",
|
||||
"title": "写入同步日志",
|
||||
"next": null,
|
||||
"data": {
|
||||
"table_name": "同步日志表",
|
||||
"field_values": [
|
||||
{
|
||||
"field_name": "线索名称",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_button_trigger.fldLeadName" }]
|
||||
},
|
||||
{
|
||||
"field_name": "手机号",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_button_trigger.fldMobile" }]
|
||||
},
|
||||
{
|
||||
"field_name": "公司名称",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_button_trigger.fldCompany" }]
|
||||
},
|
||||
{
|
||||
"field_name": "负责人",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_button_trigger.fldOwner" }]
|
||||
},
|
||||
{
|
||||
"field_name": "来源记录ID",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_button_trigger.recordId" }]
|
||||
},
|
||||
{
|
||||
"field_name": "同步状态",
|
||||
"value": [{ "value_type": "text", "value": "已提交 CRM 同步" }]
|
||||
},
|
||||
{
|
||||
"field_name": "同步是否成功",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_call_crm_api.body.success" }]
|
||||
},
|
||||
{
|
||||
"field_name": "同步结果说明",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_call_crm_api.body.message" }]
|
||||
},
|
||||
{
|
||||
"field_name": "备注",
|
||||
"value": [{ "value_type": "text", "value": "由按钮触发自动发起同步请求" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `ButtonTrigger` 适合“人工确认后再执行”的场景,比如同步 CRM、推送 ERP、发起审批等
|
||||
- `button_type: "buttonField"` 表示按钮挂在记录上,因此可以直接引用当前记录的字段和值
|
||||
- `HTTPClientAction.raw_body` 可以通过 `text + ref + text` 的方式动态拼接 JSON 请求体
|
||||
- `HTTPClientAction` 的输出引用规则是:`response_type=none` 时不可引用;`response_type=text` 时只能用 `$.stepId` 引整个文本;`response_type=json` 时用 `$.stepId.body` 引整个 body、用 `$.stepId.body.字段名` 引 body 中字段,同时 `$.stepId.status_code` 表示 HTTP 返回状态码
|
||||
- `HTTPClientAction.response_value` 中声明了哪些字段,后续节点就只能引用这些字段;例如 `$.step_call_crm_api.body.success`、`$.step_call_crm_api.body.message`
|
||||
- `AddRecordAction` 常用于写日志表、操作审计表、同步结果表,便于追踪谁在什么时候触发了外部调用
|
||||
- 示例里的 `fldLeadName` / `fldMobile` / `fldCompany` / `fldOwner` 只是占位的 fieldId,请以实际表字段 ID 为准
|
||||
|
||||
---
|
||||
|
||||
## 构造技巧
|
||||
|
||||
### Loop 构造要点
|
||||
|
||||
1. **数据源**: `Loop.data` 必须传入 `ref` 类型,通常是 `FindRecordAction` 的 `fieldRecords`
|
||||
2. **循环体**: `children.links` 必须包含 `kind: "loop_start"` 指向循环体入口
|
||||
3. **引用**: 循环体内用 `$.{loopStepId}.item.{fieldId}` 引用当前元素
|
||||
4. **索引**: 用 `$.{loopStepId}.index` 获取当前索引(从 0 开始)
|
||||
|
||||
### 分支构造要点
|
||||
|
||||
1. **IfElseBranch**:
|
||||
- 适合二元判断(是/否、大于/小于)
|
||||
- `children.links` 必须包含 `if_true` 和 `if_false`
|
||||
- 可以用 `next` 指向汇合点
|
||||
|
||||
2. **SwitchBranch**:
|
||||
- 适合多路分类(3路及以上)
|
||||
- `label` 对应 `child_branch_list` 中的条件顺序
|
||||
- 建议加一个兜底分支(其他)
|
||||
|
||||
### 字段值构造
|
||||
|
||||
| 字段类型 | value_type | 示例 |
|
||||
|---------|------------|------|
|
||||
| 文本 | `text` | `{"value_type": "text", "value": "张三"}` |
|
||||
| 数字 | `number` | `{"value_type": "number", "value": 100}` |
|
||||
| 单选 | `option` | `{"value_type": "option", "value": {"name": "已完成"}}` |
|
||||
| 人员 | `user` | `{"value_type": "user", "value": {"id": "ou_xxxx"}}` |
|
||||
| 引用 | `ref` | `{"value_type": "ref", "value": "$.step_1.fldxxx"}` |
|
||||
|
||||
---
|
||||
|
||||
## 常见错误避免
|
||||
|
||||
### Top 10 高频错误
|
||||
|
||||
| # | 错误信息 | 原因 | 解决方案 |
|
||||
|---|---------|------|---------|
|
||||
| 1 | `path "xxx" does not exist in the output path tree` | ref 引用路径错误或 stepId 不存在 | 检查 stepId 是否在 steps 数组中;使用 fieldId 而非字段名;确保路径以 `$.` 开头 |
|
||||
| 2 | `recordInfo.conditions must be non-empty` | `condition_list` 为空数组 `[]` | 改用 `null` 或省略该字段 |
|
||||
| 3 | `At least one of filter info and ref info is required` | SetRecordAction/FindRecordAction 缺少定位条件 | 必须提供 `filter_info` 或 `ref_info` 之一 |
|
||||
| 4 | `client token is empty` | 缺少 `client_token` | 每次请求传入唯一值(时间戳或随机字符串) |
|
||||
| 5 | `valueType 'text' not allowed for fieldType '3'` | select 类型字段值格式错误 | 改用 `option` 类型 |
|
||||
| 6 | `Undefined Step Type` | 使用了不支持的 StepType | 使用 `AddRecordTrigger` 而非 `CreateRecordTrigger` |
|
||||
| 7 | `prompt references an unknown reference from step` | 引用的 stepId 不存在 | 确保引用的 step 在同一 workflow 的 steps 数组中 |
|
||||
| 8 | `[2200] Internal Error` | 1. steps[].id 重复 2. next/children.links 引用了不存在的 step | 确保所有 step id 唯一;检查引用关系 |
|
||||
| 9 | 工作流结构不完整 | Branch/Loop 节点缺少 `children` | 仅 Branch(IfElseBranch/SwitchBranch)和 Loop 节点需要 `children`,Trigger/Action 节点无需设置 |
|
||||
| 10 | 嵌套分支过于复杂 | 多层 IfElseBranch 嵌套 | 3+ 路分支用 SwitchBranch 替代嵌套 IfElseBranch |
|
||||
|
||||
### 其他常见错误
|
||||
|
||||
**1. condition_list 为空数组**
|
||||
```json
|
||||
// ❌ 错误
|
||||
{ "condition_list": [] }
|
||||
|
||||
// ✅ 正确
|
||||
{ "condition_list": null }
|
||||
// 或省略该字段
|
||||
```
|
||||
|
||||
**2. filter_info 和 ref_info 同时提供**
|
||||
```json
|
||||
// ❌ 错误
|
||||
{ "filter_info": {...}, "ref_info": {...} }
|
||||
|
||||
// ✅ 正确(二选一)
|
||||
{ "filter_info": {...}, "ref_info": null }
|
||||
{ "filter_info": null, "ref_info": {...} }
|
||||
```
|
||||
|
||||
**3. 使用字段名而非 fieldId**
|
||||
```json
|
||||
// ❌ 错误
|
||||
{ "value": "$.step_1.客户名称" }
|
||||
|
||||
// ✅ 正确
|
||||
{ "value": "$.step_1.fldXXXXXXXX" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base-workflow-schema.md](lark-base-workflow-schema.md) — 字段定义参考
|
||||
- 创建/更新前先确认真实表名、字段名和目标 workflow ID;`steps` 结构按 schema 构造,不凭自然语言猜 `type`
|
||||
1071
.claude/skills/lark-base/references/lark-base-workflow-schema.md
Normal file
1071
.claude/skills/lark-base/references/lark-base-workflow-schema.md
Normal file
File diff suppressed because it is too large
Load Diff
512
.claude/skills/lark-base/references/lookup-field-guide.md
Normal file
512
.claude/skills/lark-base/references/lookup-field-guide.md
Normal file
@ -0,0 +1,512 @@
|
||||
# Base Lookup Field Configuration Guide
|
||||
|
||||
## Mandatory Read Acknowledgement
|
||||
|
||||
When creating or updating a lookup field with `lark-cli base +field-create/+field-update --json ...` and `type` is `lookup`, you should read this guide first and only then add `--i-have-read-guide` to the command.
|
||||
|
||||
Do **not** proactively add `--i-have-read-guide` before reading this guide. Without it, the CLI will fail fast and direct you back to this guide.
|
||||
|
||||
When using `+field-update`, also pass `--yes`: field update is a high-risk `PUT` operation because changing a field definition can affect the whole column.
|
||||
|
||||
## Default strategy
|
||||
|
||||
**Use Formula fields by default for cross-table references and aggregations.** Only use Lookup fields when the user explicitly requests a Lookup field. Formula is a strict superset of Lookup — anything Lookup can do, Formula can do with a single expression.
|
||||
|
||||
## Usage
|
||||
|
||||
When creating a lookup field, the Agent should:
|
||||
|
||||
1. Get all table names: `lark-cli base +table-list --base-token <base>` — returns `items[].table_name`
|
||||
2. Get table structure: `lark-cli base +table-get --base-token <base> --table-id <table>` — returns `fields[]`
|
||||
3. If the lookup references other tables, also get those tables' structures
|
||||
4. Determine the four elements: from (source table), select (source field), where (filter), aggregate (aggregation)
|
||||
5. Construct the Lookup field JSON and submit it to create or update the field
|
||||
|
||||
**Key constraints**:
|
||||
|
||||
- Table names and field names must **exactly match** those returned by `+table-list` / `+table-get`
|
||||
- The `from` table must be in the same Base
|
||||
|
||||
---
|
||||
|
||||
## Section 1: Core Concepts — Four-Element Model
|
||||
|
||||
A Lookup field is defined by five fields:
|
||||
|
||||
| Field | Meaning | JSON key | Required |
|
||||
|-------|---------|----------|----------|
|
||||
| **type** | Must be `"lookup"` | `type` | Yes |
|
||||
| **from** | Source table to pull data from | `from` | Yes |
|
||||
| **select** | Field in the source table to retrieve | `select` | Yes |
|
||||
| **where** | Filter conditions on the source table | `where` | Yes (at least one condition) |
|
||||
| **aggregate** | How to aggregate multiple matching records | `aggregate` | No (default: `raw_value`) |
|
||||
|
||||
**SQL analogy**:
|
||||
|
||||
```
|
||||
SELECT [select field]
|
||||
FROM [from table]
|
||||
WHERE [filter conditions]
|
||||
GROUP BY [aggregate function]
|
||||
```
|
||||
|
||||
**Row-level matching (most important concept)**:
|
||||
|
||||
A Lookup field is computed row-by-row — for each row in the current table, it filters the source table to find "related" records. **The filter defines what "related" means.**
|
||||
|
||||
```
|
||||
Current table row 1 → filter source table → matching records → select field → aggregate → result
|
||||
Current table row 2 → filter source table → matching records → select field → aggregate → result
|
||||
...
|
||||
```
|
||||
|
||||
**Rule: Whenever the current table and the source table have a row-level correspondence (matching by some field value), you must specify a filter.**
|
||||
|
||||
---
|
||||
|
||||
## Section 2: Lookup vs Link vs Formula
|
||||
|
||||
Lookup and Link serve **different purposes**. Creating a Lookup does NOT require a Link field to exist first.
|
||||
|
||||
| Dimension | Link | Lookup | Formula |
|
||||
|-----------|------|--------|---------|
|
||||
| Purpose | Establish record relationships (read-write) | Pull and aggregate data from another table (read-only) | Compute values from expressions (read-only) |
|
||||
| When to use | "link" / "associate" / "bind" two tables | "look up" / "reference" / "aggregate" / "count" from another table | Calculations, text manipulation, conditional logic |
|
||||
|
||||
**Common mistake**: Creating a Link field just to create a Lookup. If two tables share a matching text/number field, Lookup can match directly — no Link required.
|
||||
|
||||
**Selection decision tree**:
|
||||
|
||||
```
|
||||
What does the user need?
|
||||
├─ "Link"/"associate"/"bind" records between tables → Link
|
||||
├─ "Look up"/"reference"/"aggregate"/"count" from another table → Lookup
|
||||
│ ├─ Needs aggregation (sum/count/average)? → Lookup + aggregate
|
||||
│ └─ Just reference a value? → Lookup (aggregate = null)
|
||||
├─ Calculations/text manipulation within current table → Formula
|
||||
└─ Access linked record's field → Prefer Lookup (more intuitive), or Formula chain access
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 3: Filter Condition Rules
|
||||
|
||||
**You must provide a `where` with at least one condition.** Improper conditions cause every row to pull all records from the source table.
|
||||
|
||||
### The Iron Rule: field belongs to source table
|
||||
|
||||
```
|
||||
filter condition:
|
||||
field → must be a field in the FROM table (source table)
|
||||
value → constant or reference to a field in the CURRENT table
|
||||
```
|
||||
|
||||
### How to find the matching field pair
|
||||
|
||||
**With a Link field (most common)**: The match is between the **Link field** and the **target table's primary field**.
|
||||
|
||||
```
|
||||
Link is in the source table → source.linkField matches current.primaryField
|
||||
Link is in the current table → source.primaryField matches current.linkField
|
||||
```
|
||||
|
||||
**Without a Link field**: Two tables share a field with the same meaning — match directly.
|
||||
|
||||
### Where condition structure
|
||||
|
||||
Each condition is a **tuple** (array) of 2 or 3 elements: `[field, operator, value?]`
|
||||
|
||||
```json
|
||||
{
|
||||
"logic": "and",
|
||||
"conditions": [
|
||||
["<source table field>", "<operator>", { "type": "constant", "value": "<val>" }]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
For `empty` / `non_empty`, the value can be omitted (2-element tuple):
|
||||
|
||||
```json
|
||||
["<source table field>", "empty"]
|
||||
```
|
||||
|
||||
### Two value formats
|
||||
|
||||
**Constant value** — for fixed conditions (e.g., "status is completed"):
|
||||
|
||||
```json
|
||||
["状态", "==", { "type": "constant", "value": "已完成" }]
|
||||
```
|
||||
|
||||
**Field reference** — for dynamic per-row matching (e.g., "match current row's project"):
|
||||
|
||||
```json
|
||||
["项目名", "==", { "type": "field_ref", "field": "项目名" }]
|
||||
```
|
||||
|
||||
**Decision guide**: Fixed condition (e.g., "status is completed") → `constant`. Dynamic condition (e.g., "match current record's project ID") → `field_ref`.
|
||||
|
||||
### Constant value format by field type
|
||||
|
||||
The `value` inside `{ "type": "constant", "value": ... }` varies by field type:
|
||||
|
||||
| Field type | Constant value format | Example |
|
||||
|-----------|----------------------|---------|
|
||||
| `text` | String | `"已完成"` |
|
||||
| `number` | Number | `100`, `0.8` |
|
||||
| `datetime` / `created_at` / `updated_at` | String | `"ExactDate(2025-01-01)"`, `"ExactDate(2025-01-01 09:30)"`, `"Today"`, `"Yesterday"`, `"Tomorrow"` |
|
||||
| `select` (`multiple=false/true`) | Option name array | `["Todo"]`, `["Todo", "Done"]` |
|
||||
| `link` | Record reference array | `[{ "id": "rec_xxx" }]`, `[{ "id": "rec_xxx" }, { "id": "rec_yyy" }]` |
|
||||
| `user` / `created_by` / `updated_by` | User reference array | `[{ "id": "ou_xxx" }]`, `[{ "id": "ou_xxx" }, { "id": "ou_yyy" }]` |
|
||||
| `checkbox` | Boolean | `true`, `false` |
|
||||
| `attachment` / `location` | Only `empty` / `non_empty` | value must be `null` or omitted |
|
||||
| `auto_number` | Not supported for constant comparison | Use dynamic field\_ref instead |
|
||||
| `formula` / `lookup` (exact type) | Follow the underlying type rules | — |
|
||||
| `formula` / `lookup` (fuzzy type) | String | `"some text"` |
|
||||
|
||||
**`datetime` notes**:
|
||||
- Supported datetime constant values are `ExactDate(...)`, `Today`, `Yesterday`, `Tomorrow`
|
||||
- Date-only fields use `ExactDate(YYYY-MM-DD)`
|
||||
- Fields that include time use `ExactDate(YYYY-MM-DD HH:mm)`
|
||||
- For complex or relative date filtering, consider using a Formula field instead
|
||||
|
||||
### Dynamic field reference — set comparison semantics
|
||||
|
||||
When using `{ "type": "field_ref", "field": "..." }`, values from both sides are first **converted to sets** at runtime, then compared using set operations:
|
||||
|
||||
- **`==`**: Sets are exactly equal (strict matching)
|
||||
- **`intersects`**: Sets have a non-empty intersection (most commonly used)
|
||||
|
||||
**Conversion rules by field type**:
|
||||
|
||||
| Field type | Converted to |
|
||||
|-----------|-------------|
|
||||
| `text` | Single-element string set |
|
||||
| `number` / `auto_number` / `datetime` | Single-element number set |
|
||||
| `select` (`multiple=false/true`) | Set of option name strings |
|
||||
| `user` / `created_by` / `updated_by` | Set of user name strings |
|
||||
| `link` | Set of linked records' primary field string representations |
|
||||
| `formula` / `lookup` | The computed value set |
|
||||
|
||||
**Examples**:
|
||||
- User field `["name1", "name2"]` **intersects** text `"name1"` → true; **==** text `"name1"` → false (sets not equal)
|
||||
- User field `["name1"]` **==** text `"name1"` → true (single-element sets are equal)
|
||||
- Link field referencing records → converted to primary field strings, then compared
|
||||
|
||||
### Supported operators
|
||||
|
||||
| Operator | Meaning | Applicable field types |
|
||||
|----------|---------|-----------------|
|
||||
| `==` | Equal (exact match) | All types |
|
||||
| `!=` | Not equal | All types |
|
||||
| `>` | Greater than | `number`, `datetime` |
|
||||
| `>=` | Greater than or equal | `number`, `datetime` |
|
||||
| `<` | Less than | `number`, `datetime` |
|
||||
| `<=` | Less than or equal | `number`, `datetime` |
|
||||
| `intersects` | Has intersection (non-empty overlap) | All types (most commonly used for dynamic field\_ref) |
|
||||
| `disjoint` | No intersection | All types |
|
||||
| `empty` | Field is empty | All types (value must be null or omitted) |
|
||||
| `non_empty` | Field is not empty | All types (value must be null or omitted) |
|
||||
|
||||
### Constraints
|
||||
|
||||
- **Only one level of and/or** — nesting (e.g., `{ and: [{ or: [...] }] }`) is not supported
|
||||
- **At least one condition** — empty conditions array will error
|
||||
|
||||
---
|
||||
|
||||
## Section 4: Aggregate Rules
|
||||
|
||||
| Aggregate | Common user phrasing | Select field should be | Result type |
|
||||
|-----------|---------------------|----------------------|-------------|
|
||||
| `sum` | "total" / "sum" / "cumulative amount" | `number` field (e.g., amount) | Number |
|
||||
| `average` | "average" / "mean" | `number` field | Number |
|
||||
| `max` | "maximum" / "latest" / "most recent" | `number` / `datetime` field | Same as source |
|
||||
| `min` | "minimum" / "earliest" | `number` / `datetime` field | Same as source |
|
||||
| `counta` | "count" / "how many" / "total number" | Any field | Number |
|
||||
| `unique_counta` | "count distinct" / "how many different" | Field to deduplicate | Number |
|
||||
| `unique` | "list distinct" / "which ones" / "show different" | Field to display | List |
|
||||
| `raw_value` | "list all" / "show all values" (default) | Field to display | List |
|
||||
|
||||
**Common confusion**: `unique` returns a **deduplicated list**, `unique_counta` returns a **count**. "Which categories are involved" → `unique`; "How many categories" → `unique_counta`.
|
||||
|
||||
**Important**:
|
||||
- Enum values are **snake_case lowercase**: `sum` not `Sum`, `average` not `Average`
|
||||
- **Count is `counta`, NOT `count`** — this is the most common enum mistake
|
||||
|
||||
---
|
||||
|
||||
## Section 5: Hard Constraints
|
||||
|
||||
1. **Always write a filter**: The `where` field is required with at least one condition. Whenever the current table and source table have row-level correspondence, the condition should express that relationship.
|
||||
2. **Lookup fields are read-only**: Cell values cannot be manually set.
|
||||
3. **Create Lookup after all dependent fields exist**: The source table and referenced fields must exist before creating the Lookup field.
|
||||
4. **Source table must be in the same Base**: Cross-Base lookups are not supported.
|
||||
5. **Changing `from` requires changing `select`**: Updating the source table without updating the select field will error.
|
||||
|
||||
---
|
||||
|
||||
## Section 6: Decision Trees
|
||||
|
||||
### How to build the filter
|
||||
|
||||
```
|
||||
Step 1: Analyze the filtering semantics in the user's request
|
||||
"Count artworks per exhibition" → filter: belongs to exhibition = current exhibition
|
||||
"Sum completed order amounts" → filter: status = completed AND project = current project
|
||||
|
||||
Step 2: Find the matching field pair
|
||||
├─ Tables have a Link relationship?
|
||||
│ ├─ Link is in source table → source.linkField matches current.primaryField
|
||||
│ └─ Link is in current table → source.primaryField matches current.linkField
|
||||
├─ Tables share same-meaning text/number field? → source.field matches current.field
|
||||
└─ Also need constant filtering? → AND combination
|
||||
```
|
||||
|
||||
### Which aggregate?
|
||||
|
||||
```
|
||||
How to handle multiple matching records?
|
||||
├─ Show all values as-is → raw_value (default)
|
||||
├─ Show deduplicated list → unique
|
||||
├─ Sum → sum
|
||||
├─ Average → average
|
||||
├─ Maximum / minimum → max / min
|
||||
├─ Count records → counta
|
||||
└─ Count distinct → unique_counta
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 7: Common Configuration Patterns
|
||||
|
||||
> Patterns are categorized by **filter matching method**. Aggregate choice is independent — see Section 4.
|
||||
|
||||
### Pattern 1: Aggregate from a linked table (Link is in the source table)
|
||||
|
||||
**Scenario**: "Count artworks per exhibition", "Sum order amounts per project"
|
||||
|
||||
When the source table has a Link pointing to the current table:
|
||||
|
||||
```
|
||||
Exhibition table: ExhibitionName (primaryField) ← current table
|
||||
Artwork table: ArtworkName (primaryField), ← source table (Link is here)
|
||||
Exhibition (Link → Exhibition table)
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "lookup",
|
||||
"name": "Artwork Count",
|
||||
"from": "Artwork table",
|
||||
"select": "ArtworkName",
|
||||
"aggregate": "counta",
|
||||
"where": {
|
||||
"logic": "and",
|
||||
"conditions": [
|
||||
["Exhibition", "intersects", { "type": "field_ref", "field": "ExhibitionName" }]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Reference a linked record's field (Link is in the current table)
|
||||
|
||||
**Scenario**: "Show supplier's contact person", "Display warehouse manager"
|
||||
|
||||
When the current table has a Link pointing to the source table:
|
||||
|
||||
```
|
||||
Supplier table: SupplierName (primaryField), Contact (Text) ← source table
|
||||
Inventory table: ProductName (primaryField), ← current table (Link is here)
|
||||
Supplier (Link → Supplier table)
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "lookup",
|
||||
"name": "Supplier Contact",
|
||||
"from": "Supplier table",
|
||||
"select": "Contact",
|
||||
"where": {
|
||||
"logic": "and",
|
||||
"conditions": [
|
||||
["SupplierName", "intersects", { "type": "field_ref", "field": "Supplier" }]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: Match by same-meaning field (no Link)
|
||||
|
||||
**Scenario**: "Sum order amounts per project" (tables share a "ProjectName" field but no Link)
|
||||
|
||||
```
|
||||
Project table: ProjectName (primaryField) ← current table
|
||||
Order table: OrderID (primaryField), ProjectName (Text), ← source table
|
||||
Amount (Number)
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "lookup",
|
||||
"name": "Order Total",
|
||||
"from": "Order table",
|
||||
"select": "Amount",
|
||||
"aggregate": "sum",
|
||||
"where": {
|
||||
"logic": "and",
|
||||
"conditions": [
|
||||
["ProjectName", "==", { "type": "field_ref", "field": "ProjectName" }]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 4: Dynamic matching + constant filtering
|
||||
|
||||
**Scenario**: "Only count completed orders", "Only sum approved budgets"
|
||||
|
||||
Combine row-level matching with fixed-value filtering using `logic: "and"`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "lookup",
|
||||
"name": "Completed Order Amount",
|
||||
"from": "Order table",
|
||||
"select": "Amount",
|
||||
"aggregate": "sum",
|
||||
"where": {
|
||||
"logic": "and",
|
||||
"conditions": [
|
||||
["Manager", "==", { "type": "field_ref", "field": "EmployeeName" }],
|
||||
["Status", "==", { "type": "constant", "value": "Completed" }]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 5: Date filtering with constant value
|
||||
|
||||
**Scenario**: "Look up orders created after 2025-01-01", "Sum today's sales"
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "lookup",
|
||||
"name": "Recent Orders",
|
||||
"from": "Order table",
|
||||
"select": "Amount",
|
||||
"aggregate": "sum",
|
||||
"where": {
|
||||
"logic": "and",
|
||||
"conditions": [
|
||||
["ProjectName", "==", { "type": "field_ref", "field": "ProjectName" }],
|
||||
["CreatedDate", ">=", { "type": "constant", "value": "ExactDate(2025-01-01)" }]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 8: Anti-Pattern Collection
|
||||
|
||||
### Mistake 1: Omitting where (most common)
|
||||
|
||||
```json
|
||||
// Wrong: no where, every row pulls all records
|
||||
{ "type": "lookup", "name": "Artwork Count", "from": "Artwork table", "select": "ArtworkName", "aggregate": "counta" }
|
||||
|
||||
// Correct: where with Link relationship
|
||||
{ "type": "lookup", "name": "Artwork Count", "from": "Artwork table", "select": "ArtworkName", "aggregate": "counta",
|
||||
"where": { "logic": "and", "conditions": [
|
||||
["Exhibition", "intersects", { "type": "field_ref", "field": "ExhibitionName" }]
|
||||
]}}
|
||||
```
|
||||
|
||||
### Mistake 2: Wrong value type — confusing constant vs field_ref
|
||||
|
||||
```json
|
||||
// Wrong: using constant for a dynamic join
|
||||
["ProjectName", "==", { "type": "constant", "value": "ProjectName" }]
|
||||
|
||||
// Correct: use field_ref for dynamic per-row matching
|
||||
["ProjectName", "==", { "type": "field_ref", "field": "ProjectName" }]
|
||||
```
|
||||
|
||||
### Mistake 3: Using `count` instead of `counta`
|
||||
|
||||
```json
|
||||
// Wrong
|
||||
{ "aggregate": "count" }
|
||||
|
||||
// Correct
|
||||
{ "aggregate": "counta" }
|
||||
```
|
||||
|
||||
### Mistake 4: Wrong case for aggregate values
|
||||
|
||||
```json
|
||||
// Wrong
|
||||
{ "aggregate": "SUM" }
|
||||
{ "aggregate": "Sum" }
|
||||
|
||||
// Correct — snake_case lowercase
|
||||
{ "aggregate": "sum" }
|
||||
{ "aggregate": "average" }
|
||||
```
|
||||
|
||||
### Mistake 5: Nested where conditions
|
||||
|
||||
```json
|
||||
// Wrong: nesting not supported
|
||||
{ "logic": "and", "conditions": [
|
||||
{ "logic": "or", "conditions": [...] }
|
||||
]}
|
||||
|
||||
// Correct: only one level
|
||||
{ "logic": "and", "conditions": [cond1, cond2, cond3] }
|
||||
```
|
||||
|
||||
### Mistake 6: Confusing Lookup with Link
|
||||
|
||||
The user says "aggregate order amounts" — use Lookup, not Link. Link establishes relationships; Lookup retrieves and aggregates data.
|
||||
|
||||
### Mistake 7: Using object format instead of tuple for conditions
|
||||
|
||||
```json
|
||||
// Wrong: object format
|
||||
{ "fieldRef": "Status", "operator": "is", "value": { "type": "constant", "value": "Done" } }
|
||||
|
||||
// Correct: tuple format [field, operator, value?]
|
||||
["Status", "==", { "type": "constant", "value": "Done" }]
|
||||
```
|
||||
|
||||
### Mistake 8: Missing `type` field
|
||||
|
||||
```json
|
||||
// Wrong: no type field
|
||||
{ "name": "Total", "from": "Orders", "select": "Amount", "aggregate": "sum", "where": { ... } }
|
||||
|
||||
// Correct: must include type
|
||||
{ "type": "lookup", "name": "Total", "from": "Orders", "select": "Amount", "aggregate": "sum", "where": { ... } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 9: Constraint Summary
|
||||
|
||||
- `type` must be `"lookup"` — this field is required in the request body
|
||||
- `where` is required with at least one condition — always specify a filter
|
||||
- Conditions use **tuple format**: `[field, operator, value?]` — NOT object format
|
||||
- Lookup fields are read-only — values cannot be manually set
|
||||
- Source table and referenced fields must exist before creating the Lookup
|
||||
- Condition field (first element of tuple) must reference a field in the source table, not the current table
|
||||
- Where supports only one level of and/or — no nesting
|
||||
- Aggregate values are snake_case lowercase: `sum`, `counta`, `unique_counta` (NOT `count`)
|
||||
- Operators: `==`, `!=`, `>`, `>=`, `<`, `<=`, `intersects`, `disjoint`, `empty`, `non_empty`
|
||||
- Table and field names must exactly match `+table-get` output
|
||||
- `datetime` constant values use string format: `ExactDate(YYYY-MM-DD)` / `ExactDate(YYYY-MM-DD HH:mm)` / `Today` / `Yesterday` / `Tomorrow`
|
||||
- `select` constant values use option names;
|
||||
- `link` / `user` constant values use `{id}` object arrays
|
||||
549
.claude/skills/lark-base/references/role-config.md
Normal file
549
.claude/skills/lark-base/references/role-config.md
Normal file
@ -0,0 +1,549 @@
|
||||
# Base role permission JSON SSOT
|
||||
|
||||
> **入口指南**: [lark-base-role-guide.md](lark-base-role-guide.md) | **相关命令**: `+role-create` · `+role-update` · `+role-get`
|
||||
|
||||
本文档是角色权限 JSON(AdvPermBaseRoleConfig)的单一事实来源(SSOT),供 `+role-create` 和 `+role-update` 构造 `--json` 参数时参考。
|
||||
|
||||
## 📋 目录
|
||||
|
||||
- [顶层结构 (AdvPermBaseRoleConfig)](#顶层结构-advpermbaseroleconfig)
|
||||
- [角色类型 (RoleType)](#角色类型-roletype)
|
||||
- [读取与更新角色](#读取与更新角色)
|
||||
- [Base 级权限 (BaseRuleMap)](#base-级权限-baserulemap)
|
||||
- [仪表盘权限 (DashboardRule)](#仪表盘权限-dashboardrule)
|
||||
- [文档权限 (DocxRule)](#文档权限-docxrule)
|
||||
- [数据表权限 (TableRule)](#数据表权限-tablerule)
|
||||
- [表级权限 (TablePerm)](#表级权限-tableperm)
|
||||
- [视图权限 (ViewRule)](#视图权限-viewrule)
|
||||
- [字段权限 (FieldRule)](#字段权限-fieldrule)
|
||||
- [记录权限 (RecordRule)](#记录权限-recordrule)
|
||||
- [筛选条件 (FilterRuleGroup)](#筛选条件-filterrulegroup)
|
||||
- [默认权限策略与风控规则](#默认权限策略与风控规则)
|
||||
- [默认关闭项](#默认关闭项)
|
||||
- [权限对象选择](#权限对象选择)
|
||||
- [记录操作默认策略](#记录操作默认策略)
|
||||
- [field_perms 构造 SOP](#field_perms-构造-sop)
|
||||
- [视图权限默认策略](#视图权限默认策略)
|
||||
|
||||
---
|
||||
|
||||
## 顶层结构 (AdvPermBaseRoleConfig)
|
||||
|
||||
```json
|
||||
{
|
||||
"role_name": "财务审核员",
|
||||
"role_type": "custom_role",
|
||||
"base_rule_map": { "copy": false, "download": false },
|
||||
"table_rule_map": { "订单表": { "perm": "edit", "...": "..." } },
|
||||
"dashboard_rule_map": { "销售看板": { "perm": "read_only" } },
|
||||
"docx_rule_map": { "文档A": { "perm": "edit", "allow_download": true } }
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|----|------|
|
||||
| `role_name` | string | 是 | 角色名称,不能为空 |
|
||||
| `role_type` | string | 是 | 角色类型,见 [RoleType](#角色类型-roletype) |
|
||||
| `base_rule_map` | map\<string, bool\> | 是 | Base 级权限,见 [BaseRuleMap](#base-级权限-baserulemap) |
|
||||
| `table_rule_map` | map\<string, TableRule\> | 否 | 数据表权限,key 为表名 |
|
||||
| `dashboard_rule_map` | map\<string, DashboardRule\> | 否 | 仪表盘权限,key 为仪表盘名称 |
|
||||
| `docx_rule_map` | map\<string, DocxRule\> | 否 | 文档权限(仅单品模式),key 为文档名称 |
|
||||
|
||||
---
|
||||
|
||||
## 角色类型 (RoleType)
|
||||
|
||||
| 值 | 说明 |
|
||||
|------|------|
|
||||
| `editor` | 系统角色:编辑者 |
|
||||
| `reader` | 系统角色:阅读者 |
|
||||
| `custom_role` | 自定义角色 |
|
||||
|
||||
**注意**:
|
||||
- 创建接口(`+role-create`)仅支持 `custom_role`
|
||||
- 更新接口(`+role-update`)支持 `editor` / `reader` / `custom_role`
|
||||
|
||||
---
|
||||
|
||||
## 读取与更新角色
|
||||
|
||||
- `+role-list` 用于定位角色,返回角色摘要;系统角色和自定义角色都可能出现在列表中。
|
||||
- `+role-get` 返回完整权限配置。更新前先用它确认当前 `role_name`、`role_type` 和已有权限结构。
|
||||
- `+role-update` 是 delta merge,只提交需要变更的字段;但 `role_name` 和 `role_type` 仍要带当前值,避免误改角色身份信息。
|
||||
- `+role-delete` 仅适用于自定义角色;系统角色可以在权限上限内调整配置,但不可删除。
|
||||
|
||||
---
|
||||
|
||||
## Base 级权限 (BaseRuleMap)
|
||||
|
||||
1. 默认值均为 `false`,当需要启用时设置为 `true`。
|
||||
2. 在新增角色和修改角色时需要默认带上这个字段,**严禁**在用户未明确要求的情况下将其设置为 `true`。
|
||||
|
||||
```json
|
||||
{
|
||||
"base_rule_map": {
|
||||
"copy": true,
|
||||
"download": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Key | 说明 |
|
||||
|-----|------|
|
||||
| `copy` | 允许复制多维表格内容 |
|
||||
| `download` | 允许创建副本、下载、打印多维表格 |
|
||||
|
||||
---
|
||||
|
||||
## 仪表盘权限 (DashboardRule)
|
||||
|
||||
```json
|
||||
{
|
||||
"dashboard_rule_map": {
|
||||
"销售看板": { "perm": "read_only" },
|
||||
"内部数据": { "perm": "no_perm" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `perm` | string | 仪表盘权限 |
|
||||
|
||||
**perm 可选值**:
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `read_only` | 仅可阅读 |
|
||||
| `no_perm` | 无权限 |
|
||||
|
||||
---
|
||||
|
||||
## 文档权限 (DocxRule)
|
||||
|
||||
> ⚠️ 仅在单品模式(`is_base_solo = true`)下可用。
|
||||
|
||||
```json
|
||||
{
|
||||
"docx_rule_map": {
|
||||
"文档A": { "perm": "edit", "allow_download": true },
|
||||
"文档B": { "perm": "read_only" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `perm` | string | 是 | 文档权限 |
|
||||
| `allow_download` | bool | 否 | 是否允许下载/导出 |
|
||||
|
||||
**perm 可选值**:
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `edit` | 可编辑 |
|
||||
| `read_only` | 仅可阅读 |
|
||||
| `no_perm` | 无权限 |
|
||||
|
||||
---
|
||||
|
||||
## 数据表权限 (TableRule)
|
||||
|
||||
```json
|
||||
{
|
||||
"table_rule_map": {
|
||||
"订单表": {
|
||||
"perm": "edit",
|
||||
"view_rule": { "..." : "..." },
|
||||
"record_rule": { "..." : "..." },
|
||||
"field_rule": { "..." : "..." }
|
||||
},
|
||||
"用户表": {
|
||||
"perm": "read_only"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `perm` | string | 表级权限,见 [TablePerm](#表级权限-tableperm) |
|
||||
| `view_rule` | ViewRule | 视图权限配置 |
|
||||
| `record_rule` | RecordRule | 记录权限配置 |
|
||||
| `field_rule` | FieldRule | 字段权限配置 |
|
||||
|
||||
**注意**: 当 `perm` 为 `no_perm` 时,`view_rule`、`record_rule`、`field_rule` 均无须再设置。
|
||||
|
||||
---
|
||||
|
||||
### 表级权限 (TablePerm)
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `manage` | 可管理 |
|
||||
| `edit` | 可编辑 |
|
||||
| `read_only` | 仅可阅读 |
|
||||
| `no_perm` | 无权限(此时不能再设置视图、记录和字段的权限) |
|
||||
|
||||
---
|
||||
|
||||
### 视图权限 (ViewRule)
|
||||
|
||||
```json
|
||||
{
|
||||
"view_rule": {
|
||||
"allow_edit": true,
|
||||
"visibility": {
|
||||
"all_visible": false,
|
||||
"visible_views": ["表格视图", "看板视图"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|----------------------------|
|
||||
| `allow_edit` | bool | 可新增、删除、修改视图;表权限为 `edit` 时默认为 `true`,表权限为 `read_only` 或用户明确限制时为 `false` |
|
||||
| `visibility` | object | 可见的视图配置 |
|
||||
| `visibility.all_visible` | bool | 是否全部可见 |
|
||||
| `visibility.visible_views` | []string | 可见视图名称 列表 |
|
||||
|
||||
**⚠️ 核心规则:`view_rule` 必须同时包含 `allow_edit` 和 `visibility` 两个字段,缺一不可。**
|
||||
|
||||
输出 `view_rule` 时,**必须**使用以下完整结构,根据场景选择对应模板:
|
||||
|
||||
```json
|
||||
// 情况 A:表权限为 edit 且用户未明确限制 → allow_edit 默认为 true,全部可见
|
||||
{
|
||||
"view_rule": {
|
||||
"allow_edit": true,
|
||||
"visibility": {
|
||||
"all_visible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 情况 B:表权限为 read_only,或用户明确说不可编辑视图 → 全部可见、不可编辑
|
||||
{
|
||||
"view_rule": {
|
||||
"allow_edit": false,
|
||||
"visibility": {
|
||||
"all_visible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 情况 C:用户提及了具体视图 → 仅指定视图可见(allow_edit 仍按 A/B 规则判断)
|
||||
{
|
||||
"view_rule": {
|
||||
"allow_edit": true,
|
||||
"visibility": {
|
||||
"all_visible": false,
|
||||
"visible_views": ["表格视图", "看板视图"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:
|
||||
- 当 `all_visible` 为 `false` 时,`visible_views` 不可为空,必须指定至少一个可见视图
|
||||
- `biz_type` 为 `query_form_view` 的视图不可放在 `visible_views` 中(不能配置可见性)
|
||||
|
||||
---
|
||||
|
||||
### 字段权限 (FieldRule)
|
||||
|
||||
```json
|
||||
{
|
||||
"field_rule": {
|
||||
"field_perm_mode": "specify",
|
||||
"field_perms": {
|
||||
"金额": "edit",
|
||||
"备注": "read",
|
||||
"密码": "no_perm"
|
||||
},
|
||||
"allow_edit_and_modify_option_fields": [],
|
||||
"allow_edit_and_download_file_fields": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `field_perm_mode` | string | 字段权限模式 |
|
||||
| `field_perms` | map\<string, string\> | 字段名 → 权限,仅 `field_perm_mode` 为 `specify` 时有效 |
|
||||
| `allow_edit_and_modify_option_fields` | []string | 允许增删改选项的字段名列表 |
|
||||
| `allow_edit_and_download_file_fields` | []string | 允许下载附件的字段名列表 |
|
||||
|
||||
**field_perm_mode 可选值**:
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `all_edit` | 所有字段可编辑,但选项不可增删改 |
|
||||
| `all_read` | 所有字段可读 |
|
||||
| `specify` | 指定字段权限(可进一步设置 `field_perms` 和选项增删改权限) |
|
||||
| `no_perm` | 无权限 |
|
||||
|
||||
**field_perms 中单个字段的权限值**:
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `edit` | 可编辑(含新增和阅读权限) |
|
||||
| `create` | 可新增(含阅读权限) |
|
||||
| `read` | 可阅读 |
|
||||
| `no_perm` | 无权限 |
|
||||
|
||||
**⚠️ field_perms 重要规则**:
|
||||
1. 写入前必须先查看字段的 `type`
|
||||
2. `formula` / `lookup` / `auto_number` 类型字段**必须强制**降级为 `read` 或 `no_perm`,**严禁**设为 `edit`
|
||||
3. 必须输出除 4 个系统字段外的所有字段
|
||||
4. `allow_edit_and_modify_option_fields`:仅当用户明确要求"允许增删改选项"时才配置,否则必须为空数组 `[]`。仅支持 `select` 类型字段
|
||||
5. `allow_edit_and_download_file_fields`:用户没有要求时不要设置,且仅 `field_perm_mode` 为 `specify` 时才能设置
|
||||
|
||||
---
|
||||
|
||||
### 记录权限 (RecordRule)
|
||||
|
||||
```json
|
||||
{
|
||||
"record_rule": {
|
||||
"record_operations": ["add"],
|
||||
"edit_filter_rule_group": {
|
||||
"conjunction": "and",
|
||||
"filter_rules": [
|
||||
{
|
||||
"conjunction": "and",
|
||||
"filters": [
|
||||
{
|
||||
"field_name": "部门",
|
||||
"operator": "is",
|
||||
"filter_values": ["财务部"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"other_record_all_read": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `record_operations` | []string | 记录操作权限,仅 `TablePerm = edit` 时有效 |
|
||||
| `edit_filter_rule_group` | FilterRuleGroup | 可编辑记录的筛选条件,范围为所有记录时此字段为空 |
|
||||
| `other_record_all_read` | bool | 是否可阅读所有记录。都可读时为 `true`,其他情况为 `false` |
|
||||
| `read_filter_rule_group` | FilterRuleGroup | 可阅读记录的额外筛选规则。仅当可阅读范围与可编辑范围不一致时设置(依赖 `other_record_all_read = false`) |
|
||||
|
||||
**record_operations 可选值**:
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `add` | 可新增记录 |
|
||||
| `delete` | 可删除记录 |
|
||||
|
||||
---
|
||||
|
||||
### 筛选条件 (FilterRuleGroup)
|
||||
|
||||
```json
|
||||
{
|
||||
"conjunction": "and",
|
||||
"filter_rules": [
|
||||
{
|
||||
"conjunction": "and",
|
||||
"filters": [
|
||||
{
|
||||
"field_name": "部门",
|
||||
"operator": "is",
|
||||
"filter_values": ["财务部"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**FilterRuleGroup 结构**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `conjunction` | string | 逻辑连接词:`and` / `or` |
|
||||
| `filter_rules` | []FilterRule | 筛选规则数组 |
|
||||
|
||||
**FilterRule 结构**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `conjunction` | string | 逻辑连接词,默认 `and` |
|
||||
| `filters` | []Filter | 筛选条件数组 |
|
||||
|
||||
**Filter 结构**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `field_name` | string | 是 | 字段名。仅限 `can_filter` 为 `true` 的字段。若服务端要求当前用户类条件,可按 API 返回结构处理 |
|
||||
| `operator` | string | 是 | 操作符,见下表 |
|
||||
| `field_type` | string | 否 | 通常由服务端 filterFiller 补全;Agent 判断字段类型时以 `+field-list` / 字段操作接口的 `type` 为准,常见可筛选类型包括 `select`、`user`、`created_by`、`number` 及部分 `formula` / `lookup` |
|
||||
| `reference_type` | string | 条件 | 引用类型。`field_type` 为公式或引用字段时必须赋值,其他情况不能赋值 |
|
||||
| `filter_values` | []string | 条件 | 筛选值。`operator` 为 `isEmpty` / `isNotEmpty` 时不设置,字段类型为 `user` 时也无需设置,其他情况必须设置。值为选项的 `name` |
|
||||
| `field_ui_type` | string | 条件 | 该字段有值时一定要填 |
|
||||
| `is_invalid` | bool | 否 | 判断筛选条件是否有效 |
|
||||
|
||||
**operator 可选值**:
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `is` | 等于 |
|
||||
| `isNot` | 不等于 |
|
||||
| `contains` | 包含 |
|
||||
| `doesNotContain` | 不包含 |
|
||||
| `isEmpty` | 为空 |
|
||||
| `isNotEmpty` | 不为空 |
|
||||
| `isGreater` | 大于 |
|
||||
| `isGreaterEqual` | 大于等于 |
|
||||
| `isLess` | 小于 |
|
||||
| `isLessEqual` | 小于等于 |
|
||||
|
||||
**注意**:
|
||||
- `field_type`、`field_ui_type`、`reference_type` 在创建/更新角色时由服务端 filterFiller 自动补全,客户端通常只需传 `field_name`、`operator`、`filter_values`
|
||||
|
||||
---
|
||||
|
||||
## 默认权限策略与风控规则
|
||||
|
||||
构造角色配置 JSON 时,采用 **默认拒绝与权限最小化** 策略。用户未明确提及的权限一律不开放,不因"合理猜测""常见做法"主动扩展权限范围。
|
||||
|
||||
### 默认关闭项
|
||||
|
||||
以下能力在用户未明确说明时**默认关闭**:
|
||||
|
||||
| 能力 | 默认值 | 开启条件 |
|
||||
|------|--------|----------|
|
||||
| 未提及的数据表的任何访问 | `no_perm` | 用户明确提及该表 |
|
||||
| 仪表盘访问 | 不配置 | 用户明确提及该仪表盘 |
|
||||
| `base_rule_map.copy` | `false` | 用户明确要求"允许复制" |
|
||||
| `base_rule_map.download` | `false` | 用户明确要求"允许下载/打印/副本" |
|
||||
|
||||
### 默认开启项(条件性)
|
||||
|
||||
以下能力在特定条件下**默认开启**,用户明确限制时才排除:
|
||||
|
||||
| 能力 | 默认值 | 排除条件 |
|
||||
|------|--------|----------|
|
||||
| `record_operations` 中的 `delete` | **包含**(`perm = edit` 时) | 用户明确限制时才排除 |
|
||||
| `view_rule.allow_edit` | **`true`**(`perm = edit` 时) | 用户明确限制"不可编辑视图"或 `perm = read_only` 时设为 `false` |
|
||||
|
||||
---
|
||||
|
||||
### Editor / Reader 的权限上限规则
|
||||
1. 对 Editor 与 Reader,系统允许修改其权限配置,但同时施加以下封顶约束:
|
||||
2. Reader 的任一权限项 不允许超过「仅可阅读」
|
||||
3. Reader 不允许拥有任何可编辑、可新增、可删除相关权限; Editor 的权限可被修改,但其能力范围受高级权限能力封顶。
|
||||
|
||||
### 权限对象选择
|
||||
|
||||
**注意**:
|
||||
- 仅对用户明确指向的权限对象生成配置(明确提及的表名、仪表盘名,或可解析为唯一对象的指代如"当前表""这张表")
|
||||
- **严禁**基于业务常识、岗位职责、名称相似性或其他角色的历史配置推断或扩展权限对象
|
||||
- 用户未明确提及的对象不生成任何权限配置,视为 `no_perm`
|
||||
|
||||
---
|
||||
|
||||
### 记录操作默认策略
|
||||
|
||||
**注意**:
|
||||
- 用户未提及时,表权限为 `edit` 时默认同时包含 `add` 和 `delete`,默认不包含 `delete` 的情况仅适用于用户明确限制操作的场景
|
||||
- 阅读范围默认对齐编辑范围:用户仅描述可编辑范围、未说明阅读范围时,可阅读范围与可编辑范围保持一致,不主动扩大
|
||||
- 当可读范围与可编辑范围一致时,**不得**生成 `read_filter_rule_group`;应设置 `other_record_all_read = false` 且 `read_filter_rule_group = null`
|
||||
|
||||
**⚠️ 记录操作限制**:
|
||||
1. `perm` 为 `read_only` 时,`record_rule.record_operations` **只能为空**
|
||||
2. 同步表(`is_sync = true`)**严禁**新增和删除记录
|
||||
|
||||
---
|
||||
|
||||
### field_perms 构造 SOP
|
||||
|
||||
在生成 `field_perms` 时,**严禁**依赖模糊的"继承"概念,必须按以下步骤执行:
|
||||
|
||||
| 步骤 | 操作 | 说明 |
|
||||
|------|------|------|
|
||||
| 1. 基准设定 | `perm = edit` → 全部字段预设 `"edit"`;`perm = read_only` → 全部预设 `"read"` | 基于 `base_table_info` 中的全量字段 |
|
||||
| 2. 物理降级 | `formula` / `lookup` / `auto_number` 及系统字段 → 强制降级为 `"read"` | 不可变字段严禁设为 `edit` |
|
||||
| 3. 用户覆盖 | 仅对用户**显式指定**了特定权限的字段应用 `no_perm` / `read` / `create` | 未显式指定的保持基准值 |
|
||||
| 4. 反筛选误判 | 用于 `filter_rules` 的字段,若基准为 `"edit"` 且用户未要求降级 → **保持 `"edit"`** | 筛选条件不影响字段可编辑性 |
|
||||
| 5. 筛选依赖兜底 | 出现在 `filter_rules` 中的字段**不允许**遗漏,权限至少为 `"read"` | 最终校验步骤 |
|
||||
|
||||
**⚠️ field_perm_mode 选择规则**:
|
||||
1. 用户以"所有字段""全字段"等整体性表述描述且不要求选项增删改时,**必须**使用 `all_edit` / `all_read`,**严禁**变为逐字段 `specify`
|
||||
2. 仅在以下情况使用 `specify`:用户明确提出字段级差异需求、不同字段权限目标存在显著差异、或明确要求配置选项增删改权限
|
||||
3. 系统字段硬性约束导致的自动降级**不视为**差异,不触发 `specify`
|
||||
4. 对"仅""只能""部分"等约束定语,范围外的字段按定语的反方向设置
|
||||
|
||||
**⚠️ 同步表限制**: `is_sync = true` 的表**严禁**设置字段为 `edit` 或 `create`
|
||||
|
||||
---
|
||||
|
||||
### 视图权限默认策略
|
||||
|
||||
**判断流程(必须按顺序执行,命中即停)**:
|
||||
|
||||
1. **先判断用户是否提及了具体视图名称**(如"看板视图可见""甘特图不可编辑"等)
|
||||
- **是** → `all_visible = false`,`visible_views` 仅包含用户明确提及为"可见"的视图名称(非 viewID);未提及的视图视为不可见
|
||||
- **否**(用户完全未提及任何视图)→ `all_visible = true`
|
||||
2. `allow_edit` 在表权限为 `edit` 时**默认为 `true`**;仅当用户明确限制"不可编辑视图"时才设为 `false`。设为 `true` 时仍**必须**包含 `visibility` 字段(参考视图权限 情况 A)
|
||||
3. `all_visible` 为 `false` 时,`visible_views` **不可为空**,必须至少包含一个视图
|
||||
|
||||
**❌ 常见错误 — 缺少 `visibility` 字段:**
|
||||
```json
|
||||
// 错误!缺少 visibility
|
||||
{ "view_rule": { "allow_edit": false } }
|
||||
```
|
||||
**✅ 正确写法:**
|
||||
```json
|
||||
// 即使全部可见,也必须显式写出 visibility
|
||||
{ "view_rule": { "allow_edit": false, "visibility": { "all_visible": true } } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 字段类型与筛选算子的强约束关系
|
||||
|
||||
当字段被用于记录筛选条件时,字段操作接口返回的 `type` 与可用算子存在固定绑定关系:
|
||||
|
||||
**`user` / `created_by` 类型字段:**
|
||||
- 仅允许使用 `contains` 算子
|
||||
- 不允许使用 `is`、`isNot` 等精确匹配算子
|
||||
- 这是当前成员匹配模式,筛选条件中无需填写具体成员值;不要在 `filter_values` 中写入姓名或用户 ID
|
||||
|
||||
**`select` (`multiple=false`) 类型字段:**
|
||||
- `is` 与 `isNot` 算子仅允许用于匹配**单一选项**,不得用于多个值
|
||||
- 当用户表达"字段值等于/不等于某一个具体选项"(如"出勤状态不等于出勤")时,Agent 必须使用 `is` / `isNot`,且 filter_values 仅包含单一值。
|
||||
- 当用户表达"字段值等于/不等于多个选项集合"(如"学历不是专科和其他")时,Agent 必须使用 `contains` / `doesNotContain`,并将多个选项填入 filter_values。
|
||||
- `contains` / `doesNotContain`中的filter_values可包含多个值,表示或关系
|
||||
|
||||
**`select` (`multiple=true`) 类型字段:**
|
||||
- `is` / `isNot`:filter_values 允许填写多个选项
|
||||
- 当 operator = is 且勾选 A、B 时,语义为该字段**同时包含** A 和 B(A&B),不是"等于 A 或等于 B"
|
||||
- 当用户表达"包含任一选项"时,除了可以使用 contains 实现外,也可以使用 is 并且配套通过 filter_rules.conjunction = or 实现
|
||||
- `contains` / `doesNotContain`:用于表达"包含任一选项/不包含任一选项",filter_values 可填写多个选项(系统按"任一匹配"处理);若要表达"等于 A 或等于 B",应拆成多条筛选条件并用「或」组合。
|
||||
|
||||
**百分比字段**
|
||||
- 对于 query 中“数字”的筛选条件时,如果涉及到百分比,要原封不动地还原用户给你的数值(百分比都变成小数)。比如“大于 20%”则变成“大于 0.2”、“xx 率小于 60”则变成“小于 0.6”。
|
||||
|
||||
### 被用于筛选的字段的 field_perms 权限强制要求
|
||||
|
||||
当某字段(系统字段没有此要求)被用于「满足特定条件的记录」中的筛选条件时,系统将根据当前数据表权限与记录权限,自动施加以下**不可变约束**:
|
||||
|
||||
**筛选字段的读写一致性:**
|
||||
- 若表权限为 edit,且字段类型属于【可编辑字段】,则筛选字段必须保持 edit 权限,除非用户显式要求降级。
|
||||
- 严禁因为字段被用作筛选条件而将其降级为 read。筛选条件仅要求字段可见,不要求字段只读。
|
||||
|
||||
**新增记录时的字段最低权限:**
|
||||
- 当且仅当记录权限包含「可新增记录」时,字段至少为可新增(create),用于保证在新增记录时筛选条件字段可被正确写入。
|
||||
- 若当前记录权限为「仅可阅读」,则不触发该约束。
|
||||
|
||||
**字段是否可编辑(edit)不作强制要求**,由具体权限方案决定,不属于 infra 强制约束范围。
|
||||
|
||||
上述由系统自动施加的字段权限,不可被手动取消或降级。
|
||||
Reference in New Issue
Block a user