# 发送 Interactive 卡片工作流

用户需要发送一张飞书互动卡片时，遵循本工作流。每次都必须严格按步骤执行。

---

## 入口分支：文字 / 图片 / 图片+文字组合

判断用户输入类型：

- **纯文字诉求**（无图片）→ 跳到 Step 1「文字诉求路径」。
- **纯图片**（截图 / 设计稿，无额外文字说明内容）→ 走「以图片为输入」路径，图片既是内容源也是风格源。
- **图片 + 文字组合** → 走「以图片为输入」路径，但**图片仅当样式/布局参考，内容来源以文字为准**（见第 5 点）。

### 以图片为输入时的处理

1. **分析图片**：从图片中提取视觉风格信息——
   - 配色方案（色环定位）、间距节奏、层级关系、分组方式、组件类型
   - 图片类型（见第 2 点）

2. **判断图片类型**，决定保真策略：

   | 图片类型 | 判断依据 | 构造策略 |
   |---|---|---|
   | **飞书卡片截图** | 能识别出 header / body / components 等飞书卡片结构特征 | **高保真复刻**：将截图中每一块视觉结构映射到相近的卡片 2.0 组件；复刻后仍需过 P0–P7 硬 Gate |
   | **其它设计稿 / 海报 / 网页 UI** | 无飞书卡片结构特征 | **风格萃取 + 按原则重构**：提取配色、间距、层级关系等风格 token；布局按 P0–P7 原则重构（**不像素级仿制**），产出说明偏差 |

3. **确定内容来源**：
   - **纯图片**：从图片提取内容/信息点（文字、字段、操作）作为诉求（喂给 P0）
   - **图片 + 文字组合**：**以文字/文档为内容来源**，图片仅提供样式和布局参考。将文字内容按图片风格组织进卡片

4. **冲突处理**：当图片样式与 P0–P7 或卡片组件能力冲突时——
   - 飞书卡片截图：在组件能力允许范围内**尽量保真**，冲突处微调并告知用户偏差原因
   - 其它设计稿 / 海报 / UI：**以 P0–P7 为准**，图片仅当风格参考，冲突时不硬搬

5. 分析明确后，向用户简要说明你的**类型判断结论 + 保真策略 + 内容来源方案**。然后进入 Step 2 加载组件文档，进入构造。

---

## Step 1（文字诉求路径）：分析意图，输出设计方案

**目标**：在动手写 JSON 之前，先明确所有决策并告知用户。Step 2 的文档加载量取决于这里的组件列表，所以要尽量在这一步想清楚。

分析以下内容并向用户简要说明：

1. **版本**：Card 2.0 支持的组件更丰富，**推荐使用 Card 2.0**；仅当用户明确要求 1.0 时才用 1.0。
2. **组件组合**：在 `lark-im-card-style.md` 「意图 → 组件组合」表里匹配最接近的意图行，参考推荐组件组合和该行的 `header.template` 颜色（部分行为"无 header"）。推荐组合仅供参考，**最终选型以符合用户意图为准**；使用 Card 2.0 时，可同时参考 `card-2.0-schema.md` 中的组件概述来补充或调整组件选择。
3. **交互类型**（若有）：是否含会回调服务端的交互组件，以及是否有纯跳转（open_url）。回调分两类：① `select` / `multi_select` / `input` / `picker` / `overflow` 操作即默认回调；② `button` / `checker` / `interactive_container` 需显式配置 `behaviors`；`form` 提交统一回调。细则见 Step 5。
4. **宽度模式**：`compact`(400px) 适合通知/祝福/轻提醒（内容精简、单焦点）；`default`(≤600px) 适合大多数场景；`fill`(撑满) 适合数据看板、含 `table` 的宽表格。默认 `default`，有明确理由才偏离。

> 输出示例："Card 2.0，header green，`default`，组件：`column_set` / `column` / `markdown`，无交互。"

---

## Step 2：按需加载组件文档

> ⚠️ **仅 Card 2.0 适用**：`card-2.0-schema.md`、`components/` 明细都是 2.0 结构。若 Step 1 定为 **Card 1.0**（含 Step 4 降级场景），这些**不可参考**，跳过本步，直接按 1.0 结构构造。

**目标**：读组件明细 + 「好看的标准」，不全量加载。

> 组件列表来源：**文字路径** = Step 1 的设计方案；**图片路径** = 入口分支图片分析阶段确定的组件列表。

1. 阅读 `card-2.0-schema.md` —— 同时满足两个目的：① 了解组件概述，辅助组件选型；② 找到各组件的明细文档路由链接。**仅读一次，不重复加载。**
2. 按路由逐个读取 `components/<tag>.md`（如 `components/column_set.md`、`components/button.md`）
3. 阅读 `lark-im-card-style.md` 开头的「**好看的标准（P0–P7）**」和「视觉规范」——这是 Step 3 构造和自检的裁判基准，**构造前先内化**。

---

## Step 3：构造卡片 JSON

按 Step 2 中对应版本的根结构骨架构造卡片，组件选型遵循 Step 1（或图片分析阶段）的设计方案。

- Card 2.0 必须有 `"schema": "2.0"`，否则卡片不渲染
- `form` 容器内按钮用 `form_action_type: "submit"`，不写 `behaviors`
- `column_set` 的子节点只能是 `column`，不能直接放其他组件
- `table` **只能放 body 根节点**，不能嵌套进 `column_set` / `interactive_container` 等容器
- `collapsible_panel` 内**不能包含 `form`**；`interactive_container` 内**不能包含 `form`/`table`**

### 发送前硬 Gate（按 P0–P7 自检，不过不许进 Step 4）

构造完成后，逐条用 `lark-im-card-style.md` 的「好看的标准」做**结构化自检**。**P0 + P1–P3 是阻断项，任一不过必须回到本步修正后重判**，不得带病发送。

**阻断项（必须全过）：**
- [ ] **P0 符合诉求**：把用户诉求拆成信息点清单，逐点在 JSON 里找到承载组件；需要的操作（按钮/表单/跳转）都齐备；无与诉求无关的填充
- [ ] **P1 层级**：body 内有且仅有**一个**最强焦点；标题用 `**加粗**` 与正文拉开，次要信息用 grey
- [ ] **P2 分组**：同主题字段收进同一容器，不同主题分容器；**没有「一路 hr 平铺」或多主题挤在同一 markdown**
- [ ] **P3 复杂度适中**：视觉块 **2–5** 个、主色系 ≤3；且 >1 个块、至少含一个非纯文本结构元素（背景块/指标卡/图标/表格）——**既不能纯文字流水账，也不能堆砌过载**

**基础卫生（应满足）：**
- [ ] **P4 对比**：标题与正文在字号或粗细上至少差一档；正文不滥用 `#/##/###`（指标卡数值放大除外）
- [ ] **P5 对齐**：不滥用散设 `margin`，间距优先交容器；间距取值种类 ≤4；非末尾顶级容器间距一致

**加分项（尽量满足）：**
- [ ] **P6 语义一致**：同色同义（红=降/警、绿=升/成、grey=次要）；主色系起始色与 header 一致、取邻近色环
- [ ] **P7 健壮**：并列/指标列默认 `weighted`/`none`、慎用 `stretch`；必要时配 `config.style.color` light/dark

---

## Step 4：发送卡片

```bash
# 发送到群聊
lark-cli im +messages-send --chat-id oc_xxx --msg-type interactive --content '<card_json>'

# 发送给指定用户（私聊）
lark-cli im +messages-send --user-id ou_xxx --msg-type interactive --content '<card_json>'
```

**发送失败时**：先对照下方常见失败列表排查，若能匹配则按对应处理方式修复后重新发送；否则根据错误信息修复 JSON 后重新发送。最多尝试 **3 次**。若 3 次后仍失败，**降级为 Card 1.0 卡片**重新构造并发送。**不参考之前发送 2.0 的记忆**，完全根据用户意图重新构造 1.0 卡片。1.0 无本地参考文档（components/、resource/ 均为 2.0）。
**常见失败列表**

| # | 错误信息 | 处理方式 |
|---|---|---|
| 1 | `there is an invalid user resource (at/person) in your card` | 卡片中含有 at/person 组件，但使用了无效的用户 ID。询问用户其真实的 open_id / user_id，替换后重新发送。 |

---

## Step 5：交互回调（可选）

若卡片含**会回调服务端的交互组件**，则**支持**监听 `card.action.trigger` 回调（是否监听由实际需求决定，非必须）：

**需显式配置 `behaviors: [{type:"callback"}]` 才会回调：**
- `button`（带 callback behavior）
- `checker` —— 未配置 behaviors 时仅本地勾选生效，不触发服务端回调
- `interactive_container` —— behaviors 为必填，支持 callback / open_url

**选中 / 输入即默认回调，无需显式 `behaviors`：**
- `select_static` / `multi_select_static` / `select_person` / `multi_select_person`
- `overflow` / `input` / `date_picker` / `picker_time` / `picker_datetime`

**form 提交统一回调（按钮用 `form_action_type: "submit"`，无需 behaviors）：**
- form 内所有表单组件的值通过 `action.form_value` 一次性回传

> 纯 `open_url` 跳转按钮在客户端本地跳转，不回调服务端。

如需处理回调（监听事件、读取字段、更新卡片），见 `../lark-im-card-action-reply.md`。

---

## Step 6：用户反馈修正（按需进入）

用户看到已发送卡片后提出修改意见时，遵循以下流程。**不要整卡重做，外科手术式修改。**

### 1. 定位改动范围

把用户意见逐条映射到具体组件和字段：

| 用户反馈类型 | 映射目标 |
|---|---|
| 文案/措辞不满意 | 对应 `markdown.content` / `button.text` / `header.title` |
| 颜色/风格不满意 | 对应 `background_style` / `font_color` / `header.template` / config color token |
| 布局/排列不满意 | 对应 `column_set.flex_mode` / `width` / `weight` / `padding` |
| 缺少某个字段/信息 | 新增 `div.fields` 条目或 `markdown` 行 |
| 某个块太拥挤/太空 | 调整 `padding` / `vertical_spacing` / `margin` |
| 交互行为问题 | 对应 `behaviors` / `confirm` / `disabled` |

### 2. 最小改动原则

- 只改被指出的组件，不动周边结构。
- 改完后**只对被修改组件所涉及的 P 项重新自检**（改颜色 → 过 P6；改分组 → 过 P1+P2；改间距 → 过 P5）。

### 3. 重发

修正完成后，重新发送一张新卡（同 Step 4），告知用户"已重新发送修正版"。

### 4. 执行前告知

向用户复述"我将修改 ×××"，确认后再执行，不要静默改动。

---

## 执行清单

- [ ] 入口：判断是文字诉求（→ Step 1）还是图片输入（→ 图片分支 → 判断类型→保真策略→组件映射）
- [ ] Step 1：分析意图，输出设计方案（版本 / 宽度模式 / 颜色 / 组件）
- [ ] Step 2：读 schema.md + 组件明细 + 「好看的标准 P0–P7」
- [ ] Step 3：构造 JSON → 过 P0–P7 硬 Gate（P0+P1–P3 阻断），不过先修
- [ ] Step 4：发送，失败按常见失败表排查重试（≤3 次）；仍失败则降级 Card 1.0 重构发送
- [ ] Step 5：若有交互，参考 ../lark-im-card-action-reply.md
- [ ] Step 6：用户提出修改意见时，定位组件→最小改动→原地更新或重发
