PicoBot/skills/lark-mail/references/lark-mail-html.md
ooodc a7883dbed9 refactor(todo): 重构待办事项管理逻辑及更新状态规则
- 移除 TodoItem 中的 priority、created_at 和 updated_at 字段
- 强制每个任务都必须有唯一 id,且由用户负责生成
- 修改合并模式逻辑,merge=true 下保留未提及的旧任务
- 支持已完成和已取消任务重新激活(状态改回 pending 或 in_progress)
- 禁止 in_progress 状态退回到 pending,必须标记为 completed 或 cancelled
- 优化状态转换校验,允许特定状态间合法切换
- 简化任务变更消息,移除详细的新增/更新/移除统计
- 更新文档和示例,明确 id 必须由用户生成和使用
- 修复和补充测试,增强状态转换和合并模式验证
- 调整任务时间戳生成逻辑,统一使用当前时间及索引
- 该变更提供更合理的任务状态机械及管理模式,提升稳定性和易用性
2026-06-13 09:22:33 +08:00

334 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# 邮件 HTML 写法指南
> **前置条件:** 先阅读 [`../../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解通用安全规则。本文档定义 lark-cli mail 写信场景下的 HTML / CSS / URL 写法、LarkSuite mail-editor 原生格式、可复制片段、3 套场景模板。
**CRITICAL 邮件是重要的对外交流渠道,请你保证书写语言凝练扼要**
**CRITICAL 电子邮件的 HTML 不是 Web 开发的 HTML请你务必遵守本文档中提及的常用邮件格式书写规范**
**CRITICAL 请务必使用 shortcut 来进行邮件内容编辑 `+send` / `+draft-create` / `+reply` / `+reply-all` / `+forward`)或 `+draft-edit` 的 body op严禁自行拼接 EML**
你可以参考 **官方模板库** [`../assets/templates/`](../assets/templates) — 提供部分场景模板,可供参考
> 请注意,邮件内容编辑相关的 shortcut 内置 HTML lint 工具,处于安全考虑和格式适配,你输入的 HTML 可能会被自动调整
## 风格底线
- **邮件标题小于50字** 邮件主题行 `--subject` 应控制在 50 字内,避免超长标题带来理解困难
- **多用列表、表格**:不要堆叠过长的文本段落,请擅长使用列表`<ul>` / `<ol>`或分段 `<p>`
- **列表书写规则****不要**用 `<p>一、...</p><p>二、...</p>` 这种「中文编号 + 段落」的列表样式,"①②③"、"1) 2) 3)的机械写法也请摒弃;请擅长使用列表格式 `<ul>` / `<ol>`
- **正文长度自适应**:不限制正文长度,但要求**首屏要见到关键信息**。
## 格式书写规范
电子邮件的 HTML 受客户端兼容性与安全沙箱约束,跟 Web 浏览器 HTML 不是同一规范体系。下面是飞书邮箱已验证的最纯净、最美观写法,请直接复制使用。
### 段落
```html
<p>文字</p>
```
### 标题
```html
<h1>一级标题26px自动加粗</h1>
<h2>二级标题22px</h2>
<h3>三级标题20px</h3>
<h4>四级标题18px</h4>
```
### 加粗
```html
<b>加粗文字</b>
```
### 斜体
```html
<i>斜体文字</i>
```
### 下划线
```html
<u>下划线文字</u>
```
### 删除线
```html
<s>删除文字</s>
```
### 字号
```html
<span style="font-size:18px">放大到 18px</span>
```
### 字体
```html
<span style="font-family:'Courier New',monospace">等宽字体</span>
```
### 文字颜色
```html
<span style="color:rgb(245,74,69)">红色文字</span>
```
### 换行
```html
第一行<br>第二行
```
### 分隔
```html
<hr>
```
### 列表
```html
<!-- 无序列表 -->
<ul><li></li></ul>
<!-- 有序列表 -->
<ol><li></li></ol>
<!-- 多级列表通用规则(适用于下面两个示例):
- <ul>/<ol> 的直接子节点必须是 <li>HTML 规范不允许 <ul> 直接套 <ul>
- 子列表必须嵌套在父 <li> 内,不要拆成多个独立 ol/ul 兄弟
- 每级 list-style-type 用不同符号区分层级disc/circle/square 或 decimal/lower-alpha/lower-roman
- 子级用 margin-left:24px 视觉缩进 -->
<!-- 多级有序列表(全 ol 三级嵌套decimal → lower-alpha → lower-roman -->
<ol data-list-number="true" style="margin:0px;padding-left:0px;list-style-position:inside">
<li class="temp-li number1" data-li-line="true" data-list="number1" data-ol-id="demo-ol" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:decimal;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<b><span style="font-family:inherit"><span style="color:rgb(31,35,41)">第一级decimal</span></span></b>
<ol data-list-number="true" style="margin:0px 0px 0px 24px;padding-left:0px;list-style-position:inside">
<li class="temp-li number2" data-li-line="true" data-list="number2" data-ol-id="demo-ol" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:lower-alpha;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第二级lower-alpha缩进 24px</span></span>
<ol data-list-number="true" style="margin:0px 0px 0px 24px;padding-left:0px;list-style-position:inside">
<li class="temp-li number3" data-li-line="true" data-list="number3" data-ol-id="demo-ol" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:lower-roman;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第三级lower-roman再缩进 24px</span></span>
</li>
</ol>
</li>
<li class="temp-li number2" data-li-line="true" data-list="number2" data-ol-id="demo-ol" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:lower-alpha;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第二级(同层)</span></span>
</li>
</ol>
</li>
<li class="temp-li number1" data-li-line="true" data-list="number1" data-ol-id="demo-ol" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:decimal;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<b><span style="font-family:inherit"><span style="color:rgb(31,35,41)">第一级(接续编号)</span></span></b>
</li>
</ol>
<!-- 多级无序列表(全 ul 三级嵌套disc → circle → square -->
<ul data-list-bullet="true" style="margin:0px;padding-left:0px;list-style-position:inside">
<li class="temp-li bullet1" data-li-line="true" data-list="bullet1" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:disc;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第一级disc</span></span>
<ul data-list-bullet="true" style="margin:0px 0px 0px 24px;padding-left:0px;list-style-position:inside">
<li class="temp-li bullet2" data-li-line="true" data-list="bullet2" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:circle;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第二级circle缩进 24px</span></span>
<ul data-list-bullet="true" style="margin:0px 0px 0px 24px;padding-left:0px;list-style-position:inside">
<li class="temp-li bullet3" data-li-line="true" data-list="bullet3" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:square;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第三级square再缩进 24px</span></span>
</li>
</ul>
</li>
<li class="temp-li bullet2" data-li-line="true" data-list="bullet2" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:circle;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第二级(同层)</span></span>
</li>
</ul>
</li>
<li class="temp-li bullet1" data-li-line="true" data-list="bullet1" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:disc;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第一级(同层)</span></span>
</li>
</ul>
```
### 表格
```html
<table style="border-collapse:collapse">
<thead>
<tr style="background-color:rgb(242,243,245)">
<th rowspan="2" style="border:1px solid rgb(222,224,227);padding:8px;vertical-align:middle">A</th>
<th colspan="2" style="border:1px solid rgb(222,224,227);padding:8px;text-align:center">B</th>
<th rowspan="2" style="border:1px solid rgb(222,224,227);padding:8px;vertical-align:middle">C</th>
</tr>
<tr style="background-color:rgb(242,243,245)">
<th style="border:1px solid rgb(222,224,227);padding:8px">B1</th>
<th style="border:1px solid rgb(222,224,227);padding:8px">B2</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border:1px solid rgb(222,224,227);padding:8px">a1</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">b1-1</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">b2-1</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">c1</td>
</tr>
<tr>
<td style="border:1px solid rgb(222,224,227);padding:8px">a2</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">b1-2</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">b2-2</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">c2</td>
</tr>
</tbody>
</table>
```
### 链接
```html
<a href="https://www.larkoffice.com" style="color:rgb(20,86,240);text-decoration:none">链接文字</a>
```
### AT 用户
```html
<a id="at-user-1" href="mailto:user@example.com" style="cursor:pointer;color:rgb(20,86,240);padding:2px;text-decoration:none;border-radius:999em;margin:0px 2px">@姓名</a>
```
**必填字段** `id="at-user-N"``mailto:` 和姓名文本
### 引用
```html
<blockquote style="padding-left:12px;color:rgb(100,106,115);border-left:2px solid rgb(187,191,196);margin:0px">引用文字</blockquote>
```
### 文字高亮(荧光笔风格)
```html
<span style="background-color:rgb(255,200,220);color:rgb(31,35,41)">关键里程碑</span>
<span style="background-color:rgb(255,225,140);color:rgb(31,35,41)">待跟进</span>
<span style="background-color:rgb(190,230,200);color:rgb(31,35,41)">已完成</span>
```
### 文字强调
```html
<b><span style="font-family:inherit"><span style="color:rgb(245,74,69)">红色加粗</span></span></b>
<i><span style="font-family:inherit"><span style="color:rgb(0,0,0)">斜体</span></span></i>
<u><span style="font-family:inherit"><span style="color:rgb(0,0,0)">下划线</span></span></u>
<s><span style="font-family:inherit"><span style="color:rgb(0,0,0)">删除线</span></span></s>
```
### 居中 / 左对齐 / 右对齐
```html
<div style="text-align:center">居中</div>
<div style="text-align:left">左对齐(默认)</div>
<div style="text-align:right">右对齐</div>
```
### 盒模型
```html
<div style="margin:8px;padding:12px;width:300px">外边距 8px / 内边距 12px / 宽度 300px</div>
```
### 边框
```html
<div style="border:1px solid rgb(222,224,227);border-radius:8px;padding:8px">圆角描边</div>
```
### 透明
```html
<span style="opacity:0.5">半透明文字</span>
```
### 颜色(推荐调色盘)
```html
<!-- 主黑(正文) -->
<span style="color:rgb(31,35,41)">主文本</span>
<!-- 副灰(次要说明 / 时间 / 备注) -->
<span style="color:rgb(100,106,115)">副文本</span>
<!-- 浅灰(三级文本 / 占位) -->
<span style="color:rgb(143,149,158)">浅灰文本</span>
<!-- LarkSuite 蓝(链接 / mention -->
<span style="color:rgb(20,86,240)">蓝色文字</span>
<!-- LarkSuite 深蓝(重点标题) -->
<span style="color:rgb(36,91,219)">深蓝标题</span>
<!-- 警示红(错误 / 失败 / 红色加粗) -->
<span style="color:rgb(245,74,69)">警示红</span>
<!-- 紧急橙(紧急 / 阻塞 / 环比上升) -->
<span style="color:rgb(255,140,40)">紧急橙</span>
```
### URL scheme
```html
<a href="https://example.com">外链</a>
<a href="mailto:user@example.com">邮件链接</a>
<img src="cid:abc"> <!-- 内嵌图片,配合 --inline 参数 -->
<img src="data:image/png;base64,iVBOR..."> <!-- base64 内嵌图片 -->
```
## 官方 HTML 模板
仓库 [`../assets/templates/`](../assets/templates/) 内预制了若干场景模板,按 LarkSuite mail-editor 原生格式写好。**注意:模板是静态 HTML没有变量替换能力AI 需要手工把模板里的样例文本替换成本次邮件的真实内容。**
| 文件 | 说明 |
|---------------------------------|----------|
| `newsletter--weekly-brief.html` | 资讯周报 |
| `weekly--personal-report.html` | 工作周报(个人) |
| `weekly--team-report.html` | 工作周报(团队) |
| `research--market-report.html` | 调研报告 |
| `job-application--resume.html` | 简历邮件 |
跟飞书 OAPI 个人邮件模板(`mail.user_mailbox.templates`不同——OAPI 模板是用户邮箱里的"我的模板",跨客户端可见;这里是仓库里的静态 HTML 文件AI 单次套用即可。
### AI 套用流程
1. **判断是否能用模板** — 看用户当前要写的邮件类型(周报 / 调研 / 简历 / 资讯 / ...)能否对上 [`../assets/templates/`](../assets/templates/) 里的某个文件;不匹配就跳过模板,直接按写法规范从零写。
2. **Read 整个 HTML** — 用 Read 工具完整读取选定的模板文件,理解骨架(章节标题 / 列表层级 / 占位文本 / mention chip / 段落顺序)。
3. **替换文本内容** — 把模板里的样例文字换成用户当前邮件的真实内容;保留所有 inline style / class / data-* 等结构性属性不动;列表条目 / 表格行可按需增删;不需要的整段(如「风险」「下周计划」)整段删除即可,不要留空骨架。
4. **调写信 shortcut 生成草稿** — 把替换后的 HTML 通过 `--body` 参数交给写信链路(推荐 `+draft-create` 先存草稿、用户复核后再 `+send`
```bash
lark-cli mail +draft-create --as user \
--to alice@example.com --subject 'Q3 团队周报' \
--body "$(cat skills/lark-mail/assets/templates/weekly--team-report.html)"
```
实际使用时 `$(cat ...)` 可换成 AI 替换文本后写入的本地副本,或直接把替换后的 HTML 字符串作为 `--body` 的值。
5. **拿到草稿链接给用户复核** — 写信 shortcut 返回 `reference` 字段(草稿打开链接),把它给用户在飞书邮箱 UI 里打开核对,再决定下一步发送 / 编辑。
## 写信 shortcut 的 lint 返回值
写信链路(`+send` / `+draft-create` / `+reply` / `+reply-all` / `+forward` / `+draft-edit` body op调用 `emlbuilder` 之前会强制 lint 净化 HTML但 **默认 envelope 不携带任何 lint 字段**(既无 `*_count` 也无 finding 数组envelope 保持小巧供 AI 消费。每个写信 shortcut 默认 envelope 的字段集合:
| 字段 | 出现条件 | 说明 |
|------|---------|------|
| `compose_hint` | 6 个 shortcut 默认都附 | 固定英文文案,提示 AI / 用户在组合 HTML 前阅读本文 |
| `draft_edit_hint` | **仅** `+draft-create` 默认附(其他 5 个 shortcut 不附) | 固定英文文案,提示拿到 `draft_id` 后改稿走 `+draft-edit --draft-id <id>` 而不是重跑 `+draft-create` 产生重复草稿 |
| `draft_id` / `message_id` | OAPI 写入成功后写回 | `+draft-create` / `+draft-edit` 返回 `draft_id``+send` / `+reply` / `+reply-all` / `+forward` 返回 `message_id` |
需要看 lint 详情时加 `--show-lint-details`
```bash
lark-cli mail +draft-create --show-lint-details \
--to alice@example.com --subject 'Hi' --body '<p>正文</p>'
```
加了 `--show-lint-details` 后 envelope 同时返回 `lint_applied[]` / `original_blocked[]` 两个完整 Finding 数组(每条含 `rule_id` / `severity` / `tag_or_attr` / `excerpt` / `hint`**不再返回任何 `*_count` 字段** —— 调用方需要 count 时直接 `len(lint_applied)` / `len(original_blocked)`。**默认场景不要加这个 flag**,徒增 token 消耗。
如果只是想预览 lint 会怎么改 HTML建议直接用 [`+lint-html`](./lark-mail-lint-html.md) 命令——它本来就返回完整 `warnings[]` / `errors[]` + `cleaned_html`,比写信链路 `--show-lint-details` 更清晰。
## 相关文档
- [`+lint-html` 用法](./lark-mail-lint-html.md)
- 写信 shortcut: [`+send`](./lark-mail-send.md) / [`+draft-create`](./lark-mail-draft-create.md) / [`+reply`](./lark-mail-reply.md) / [`+reply-all`](./lark-mail-reply-all.md) / [`+forward`](./lark-mail-forward.md) / [`+draft-edit`](./lark-mail-draft-edit.md)