feat:移除了弹窗,服务器添加sls
This commit is contained in:
102
.agents/skills/lark-whiteboard/elements/connectors.md
Normal file
102
.agents/skills/lark-whiteboard/elements/connectors.md
Normal file
@ -0,0 +1,102 @@
|
||||
# 连线系统
|
||||
|
||||
## 连线策略
|
||||
|
||||
| 连线数 | 策略 |
|
||||
|--------|------|
|
||||
| ≤8 | 逐条画 |
|
||||
| 9-15 | 代表性连线(每层选 1-2 个节点连到下一层)|
|
||||
| >15 | 层到层连线,或回退精简分组 |
|
||||
|
||||
一个节点有 3+ 条连线时:入线从 top,出线从 bottom,同侧多条线用不同方向分散。
|
||||
|
||||
---
|
||||
|
||||
## connector 必须放根 nodes 数组
|
||||
|
||||
```typescript
|
||||
// 错误:connector 放在 frame children 里
|
||||
{ type: 'frame', children: [
|
||||
{ type: 'connector', ... } // 会导致 Schema 报错或无法连线!
|
||||
]}
|
||||
|
||||
// 正确:connector 放在根 nodes 数组
|
||||
const doc: WBDocument = {
|
||||
version: 2,
|
||||
nodes: [
|
||||
{ type: 'frame', id: 'box', ... },
|
||||
{ type: 'connector', ... }, // 必须和顶层 frame 平级
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 箭头默认值
|
||||
|
||||
- `endArrow` 省略时默认为 `'arrow'`(即连线末端默认带箭头)。
|
||||
- `startArrow` 省略时默认为 `'none'`(即连线起始端默认无箭头)。
|
||||
|
||||
---
|
||||
|
||||
## 连线技巧
|
||||
|
||||
```typescript
|
||||
// 自动绕线(推荐):仅需指定节点 id(引擎可自动推断最优出线方向),并使用 polyline 或 rightAngle 形状
|
||||
// 只要不传 waypoints,引擎会尝试自动避开障碍物并生成折线。
|
||||
{ type: 'connector', connector: {
|
||||
from: 'a', to: 'b', // fromAnchor 和 toAnchor 也可以省略,让引擎自己找最短路径
|
||||
lineShape: 'polyline', lineColor: '#000000', lineWidth: 2, endArrow: 'arrow' }}
|
||||
|
||||
// 精确坐标(做注解箭头)
|
||||
{ type: 'connector', connector: {
|
||||
from: { x: 150, y: 200 }, to: 'b', toAnchor: 'left',
|
||||
lineShape: 'curve', lineColor: '#BBBFC4', lineWidth: 2,
|
||||
lineStyle: 'dashed', endArrow: 'triangle' }}
|
||||
|
||||
// 手动控制路径点 waypoints(仅在需要强制固定路线、或者自动路由不符合预期时使用)
|
||||
// 注意:一旦提供了 waypoints,引擎将严格尊重这些点,不再进行自动避障。
|
||||
{ type: 'connector', connector: {
|
||||
from: { x: 300, y: 140 }, to: { x: 300, y: 340 },
|
||||
waypoints: [{ x: 350, y: 140 }, { x: 350, y: 340 }],
|
||||
lineShape: 'polyline', lineColor: '#000000', lineWidth: 2, endArrow: 'arrow' }}
|
||||
|
||||
// 绘制坐标轴/数轴(必须使用 straight,防止刻度文字触发自动避障导致线条弯曲)
|
||||
{ type: 'connector', connector: {
|
||||
from: { x: 100, y: 400 }, to: { x: 600, y: 400 },
|
||||
lineShape: 'straight', lineColor: '#000000', lineWidth: 2, endArrow: 'arrow' }}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **1. 形状选用要求(核心)**,需明确 `lineShape` 类型:
|
||||
> - **`'polyline'`(圆角折线)**:**默认首选**。适用于流程图、架构图等绝大多数场景。支持引擎的**自动绕线与避障**功能(只需指定 `from` 和 `to`)。
|
||||
> - **`'rightAngle'`(直角折线)**:适用于明确要求“总线/直角规约”、树状层级严格对齐的场景,同样支持**自动绕线与避障**。
|
||||
> - **`'straight'`(直线)**:不受自动避障机制的影响,适用于**坐标轴、数轴、几何图形边框、直接指向关系**等要求线条绝对笔直、不允许出现任何绕行或弯曲的场景。
|
||||
> - **`'curve'`(曲线)**:适用于优雅的跨层连线(S型弯)、自由发散的脑图分支、或做注解箭头时。
|
||||
> - **注意**:你需要根据当前绘制的图表类型和上下文语境,选择最合适的 `lineShape`。不要盲目全部使用 `polyline`,例如在绘制坐标系时必须主动切换为 `straight`。
|
||||
> **2. 间距要求**:有 connector 连线的卡片间 gap 需 ≥ 40,否则箭头挤在缝里看不清。
|
||||
> **3. 顶层约束**:`connector` 必须直接放在 `WBDocument.nodes`,**严禁**嵌套在 `children` 内。建议在数据末尾统一声明连线。
|
||||
>
|
||||
> [!TIP]
|
||||
> **自动绕线 vs 手动控制**
|
||||
> - **优先依赖自动绕线**:对于 `'polyline'` 和 `'rightAngle'`,引擎会自动规划路径并尝试避开障碍物(`fromAnchor` 和 `toAnchor` 也可省略,引擎会自动推断最优出线方向),这是最推荐的方式。
|
||||
> - **何时手动算 waypoints**:**仅在必要时**(例如自动路由不符合预期,或者必须强制走特定形状绕开特定元素时),才需要通过 `waypoints` 手动接管坐标序列。
|
||||
>
|
||||
> **连线标签**
|
||||
> - **连线文字说明**:需要文字说明时,可用 `label` 标注。
|
||||
|
||||
---
|
||||
|
||||
## 锚点方向规则
|
||||
|
||||
锚点(top/right/bottom/left)表示连线从节点的哪个边出发,方向含义与 CSS border 四边相同。
|
||||
|
||||
**注意:由于目前自动绕线功能支持省略锚点让引擎自动推断,以下规则主要适用于你想强制控制出线方向,或者使用直线/曲线时的场景。**
|
||||
|
||||
选择锚点时根据两个节点的相对位置:目标在下方用 `fromAnchor: 'bottom'` + `toAnchor: 'top'`,目标在右侧用 `fromAnchor: 'right'` + `toAnchor: 'left'`。如果手动指定了锚点,必须与节点的实际相对位置匹配,否则可能导致连线反向绕行。
|
||||
|
||||
**锚点绑定的常见范式**:
|
||||
- **同层横向推进**(目标在正右):`fromAnchor: "right"` -> `toAnchor: "left"`
|
||||
- **垂直下沉推进**(目标在正下):`fromAnchor: "bottom"` -> `toAnchor: "top"`
|
||||
- **跨层斜切推进**(目标在左下或右下):首选 **`fromAnchor: "bottom"` -> `toAnchor: "top"`**。由于线段自身带有重力倾向,从底部出线再弯曲进入下一层顶部,完美契合流水线的 S 型大弯,能画出最优雅顺滑的跨层曲线。**避免**使用左右锚点互相跨接。
|
||||
- **逆流回捞**(底部发散回指顶部原点):首选 **`fromAnchor: "top"` -> `toAnchor: "bottom"`** 配合 `lineStyle: "dashed"`。
|
||||
40
.agents/skills/lark-whiteboard/elements/content.md
Normal file
40
.agents/skills/lark-whiteboard/elements/content.md
Normal file
@ -0,0 +1,40 @@
|
||||
# 内容规划
|
||||
|
||||
核心原则:**信息量匹配用户需求的详细程度。** 用户说"画一个简单架构图"就画简单的,说"画一个完整的微服务架构"才画复杂的。不要自作主张**过度展开**。
|
||||
|
||||
**用户 prompt 简短/模糊时**(如"画个漏斗图"、"画个架构图"),不要只输出字面内容。应适当补充该领域合理的内容
|
||||
|
||||
## 信息量参考
|
||||
|
||||
| 用户需求 | 合理的信息量 |
|
||||
|---------|------------|
|
||||
| "画一个简单的 XX 架构图" | 3 层,每层 2-3 节点,无侧边栏 |
|
||||
| "画一个 XX 架构图"(普通请求) | 3-4 层,每层 3-4 节点 |
|
||||
| "画一个完整/详细的 XX 架构图" | 4-5 层,每层 4-6 节点,可加侧边栏(侧边栏最多 2-3 项)|
|
||||
| 流程图 | 6-10 步骤 + 1-2 个条件分支 |
|
||||
| 对比表 | 4-6 个维度,每格 1-2 行说明 |
|
||||
| 组织架构 | 3-4 层,每个父节点下 2-4 个子节点 |
|
||||
|
||||
**节点文字**:标题 + 简短说明(如"用户服务\n注册登录和权限管理"),不要写长段落。说明 12 字以内为佳。
|
||||
|
||||
## 分组
|
||||
|
||||
每组 2-5 个节点。超过 5 个拆成子组。
|
||||
|
||||
## 连线预判
|
||||
|
||||
| 连线数 | 策略 |
|
||||
|--------|------|
|
||||
| ≤8 | 逐条画 |
|
||||
| 9-15 | 代表性连线 |
|
||||
| >15 | 层到层,或回退精简 |
|
||||
|
||||
## 精简触发条件
|
||||
|
||||
布局放不下时才精简:
|
||||
|
||||
| 问题 | 精简方式 |
|
||||
|------|---------|
|
||||
| 节点文字放不下 | 缩短描述文字 |
|
||||
| 一行节点超过 5 个 | 拆成两排或合并同类 |
|
||||
| 连线交叉 | 减少连线数量 |
|
||||
80
.agents/skills/lark-whiteboard/elements/image.md
Normal file
80
.agents/skills/lark-whiteboard/elements/image.md
Normal file
@ -0,0 +1,80 @@
|
||||
# 图片准备 (Image Preparation)
|
||||
|
||||
> 本文件说明如何在画板 DSL 中使用图片节点。进入任何含图片的场景前,必须先完成图片准备流程。
|
||||
|
||||
## 概述
|
||||
|
||||
画板 DSL 支持 `type: 'image'` 节点,但图片不能直接使用 URL 或其他域的 token,**必须先上传到目标画板获取 `whiteboard` 域 media token**,然后在 DSL 中引用。
|
||||
|
||||
**核心规则**:不管图片从哪来(本地文件、URL、文档中的 `docx_image` token、其他域的 Drive token),都必须通过 `docs +media-upload --parent-type whiteboard --parent-node <目标画板token>` 上传,拿到画板专属的 media token 后才能在 DSL 中使用。直接使用非 `whiteboard` 域的 token 会导致画板 API 报 500(错误码 2891001)或图片在文档中消失。
|
||||
|
||||
## Step 0:图片准备流程
|
||||
|
||||
### 1. 获取图片到本地
|
||||
|
||||
根据图片来源选择对应方式:
|
||||
|
||||
| 图片来源 | 获取方式 |
|
||||
|---------|---------|
|
||||
| 本地文件 | 直接使用 |
|
||||
| 网络 URL | `curl -L -o photo.jpg "<URL>"` |
|
||||
| 文档中的图片 token | `lark-cli docs +media-download --token <token> --output ./photo.png` |
|
||||
| 其他域的 Drive token | `lark-cli docs +media-download --token <token> --output ./photo.png` |
|
||||
|
||||
**图片源选择(需要搜索图片时)**:
|
||||
|
||||
| 图片源类型 | 说明 |
|
||||
|-------|------|
|
||||
| 免费版权图库 | 支持按关键词搜索,图片无版权风险(CC0 或类似协议),图库种类丰富(人物/动物/风景/美食/建筑等),关键词能精准匹配图片内容 |
|
||||
| 直接 URL | 用户提供或已知的图片链接,最可靠 |
|
||||
|
||||
**选择图库的必要条件**:
|
||||
- **版权合规**:图片必须无版权纠纷风险,避免使用需要付费授权或有使用限制的图库
|
||||
- **关键词搜索**:支持按关键词搜索并返回相关图片,确保图片内容与主题匹配
|
||||
- **内容丰富**:图库图片种类多、数量大,能覆盖常见主题(宠物、美食、景点、产品等)
|
||||
|
||||
**严禁使用随机占位图服务**:某些图库仅提供随机占位图,URL 中的关键词参数不会影响返回的图片内容,下载的图片与主题完全无关。
|
||||
|
||||
### 2. 校验图片
|
||||
|
||||
```bash
|
||||
ls -l *.jpg # 确认每张文件大小不同;若大小相同则内容可能重复,需重新下载
|
||||
```
|
||||
|
||||
**图片内容审查(必须执行)**:
|
||||
- 下载完成后,确认文件是真实图片而非 HTML 错误页:若某张图片大小 < 1KB,很可能是下载失败返回了 HTML 错误页,需重新下载
|
||||
- **图片内容正确性只能在渲染后验证**:生成 DSL 并本地渲染 PNG 后,必须查看渲染结果,确认每张图片内容与主题相关(如宠物主题的图片确实是宠物,而非建筑/风景等不相关内容)
|
||||
- 若发现图片内容与主题不符,必须用更精确的关键词重新下载并重新上传
|
||||
|
||||
### 3. 上传到目标画板
|
||||
|
||||
**必须**使用 `docs +media-upload --parent-type whiteboard` 上传:
|
||||
|
||||
```bash
|
||||
lark-cli docs +media-upload --file ./photo1.jpg --parent-type whiteboard --parent-node <whiteboard_token>
|
||||
# 响应: { "file_token": "<media_token>", ... }
|
||||
```
|
||||
|
||||
逐张上传,收集每个 media token:
|
||||
|
||||
```bash
|
||||
lark-cli docs +media-upload --file ./photo1.jpg --parent-type whiteboard --parent-node <whiteboard_token> # → <media_token_1>
|
||||
lark-cli docs +media-upload --file ./photo2.jpg --parent-type whiteboard --parent-node <whiteboard_token> # → <media_token_2>
|
||||
lark-cli docs +media-upload --file ./photo3.jpg --parent-type whiteboard --parent-node <whiteboard_token> # → <media_token_3>
|
||||
```
|
||||
|
||||
### 4. 在 DSL 中引用
|
||||
|
||||
```json
|
||||
{ "type": "image", "id": "img-1", "width": 240, "height": 160, "image": { "src": "<media_token_1>" } }
|
||||
```
|
||||
|
||||
## 常见错误
|
||||
|
||||
| 错误现象 | 原因 | 解决 |
|
||||
|---------|------|------|
|
||||
| 画板 API 返回 500(2891001) | 使用了非 `whiteboard` 域 token(如 `docx_image`、Drive file token) | 下载图片后用 `docs +media-upload --parent-type whiteboard` 重新上传 |
|
||||
| 画板 API 返回 500 | 图片上传到了其他画板 | 重新上传到目标画板 |
|
||||
| 画板在文档中图片消失 | 图片 token 的资源域与画板不匹配 | 确保图片通过 `--parent-type whiteboard --parent-node <画板token>` 上传 |
|
||||
| 图片裂开/无法显示 | token 无效或已过期 | 重新上传获取新 token |
|
||||
| 图片内容与主题无关 | 使用了随机占位图服务 | 改用免费版权图库服务 |
|
||||
374
.agents/skills/lark-whiteboard/elements/layout.md
Normal file
374
.agents/skills/lark-whiteboard/elements/layout.md
Normal file
@ -0,0 +1,374 @@
|
||||
# 布局系统
|
||||
|
||||
## 布局决策
|
||||
|
||||
> 不要靠关键词猜布局。先分析信息结构,再决定布局策略。
|
||||
> 本文件负责说明通用布局原则与骨架模板;字段语义看 `elements/schema.md`,完整场景范式看各 `scenes/*.md`。
|
||||
|
||||
总原则:**先定主布局,再定子布局。**
|
||||
|
||||
**快速判断**:
|
||||
- **Flex**:按层分、按区排
|
||||
- **Dagre**:关系网密、流程链主导
|
||||
- **绝对定位**:空间位置承载信息(地理方位、拓扑坐标、物理面板等),用脚本计算坐标
|
||||
- **默认选择**:拿不准时优先用 **Flex**
|
||||
|
||||
|
||||
**Dagre 版式统一原则**:
|
||||
1. Dagre 解决的是**拓扑关系**,不是自动把画布铺满。
|
||||
2. Dagre 作为子容器嵌套时,默认是不透明节点(Opaque Node),先根据内部拓扑计算自身包围盒,再作为原子节点参与父层布局。若需连线穿透边界,须声明 `layout: "dagre"` + `layoutOptions: { isCluster: true }`。
|
||||
3. 混合布局时,Flex 更适合负责分区与层次,Dagre 更适合负责局部复杂关系;但如果 Dagre 本身就是主布局,也完全可以直接承担整张图的主体拓扑。
|
||||
4. 选用 Dagre 前先看三件事:**最长链路方向、分支是否对称、是否有长回边/重试回路**。哪一项失衡,哪一项就会把包围盒撑歪。
|
||||
5. 长回边、失败重试、跨层返回等关系,优先收敛到局部;必要时拆成局部流程区或旁路说明,不要让一条边把整个 Dagre 宽度拉爆。
|
||||
6. 若 Dagre 产物在父容器中出现明显单侧留白、宽高失衡或内容只占很小一部分,必须调整 `rankdir`、重构拓扑,或在父层补充对称信息区,不能原样交付。
|
||||
|
||||
**读代码画架构图**:扫目录结构(按层分 → Flex;按功能模块分 → 看依赖方向)→ grep import(单向→Flex;网状→ Dagre 或 Flex + Dagre)→ 拿不准 → 默认 Flex。
|
||||
|
||||
> **flex 容器内的 `x/y` 会被完全忽略!**
|
||||
|
||||
❌ 致命错误:
|
||||
```json
|
||||
{ "type": "frame", "layout": "vertical", "children": [
|
||||
{ "type": "rect", "x": 100, "y": 0, "text": "成都" },
|
||||
{ "type": "rect", "x": 540, "y": 0, "text": "康定" }
|
||||
]}
|
||||
```
|
||||
✅ 正确:用 `layout: "none"` 或放在顶层 nodes 用 x/y。
|
||||
|
||||
> **`layout: "none"`(绝对定位)的容器必须有明确的固定宽高!**
|
||||
|
||||
❌ 致命错误:
|
||||
```json
|
||||
{ "type": "frame", "layout": "none", "width": "fit-content", "height": "fit-content", "children": [
|
||||
{ "type": "rect", "x": 0, "y": 0, "text": "区域A" },
|
||||
{ "type": "rect", "x": 500, "y": 0, "text": "区域B" }
|
||||
]}
|
||||
```
|
||||
✅ 正确:必须给绝对定位容器明确的固定宽高:
|
||||
```json
|
||||
{ "type": "frame", "layout": "none", "width": 1064, "height": 680, "children": [
|
||||
{ "type": "rect", "x": 0, "y": 0, "text": "区域A" },
|
||||
{ "type": "rect", "x": 554, "y": 0, "text": "区域B" }
|
||||
]}
|
||||
```
|
||||
|
||||
**构建方式**:
|
||||
|
||||
| 布局类型 | 做法 |
|
||||
| ---------------------- | ----------------------------------------------------------------------------- |
|
||||
| 纯 Flex / Dagre | 直接写 JSON |
|
||||
| 混合布局 (Flex包Dagre) | 直接写 JSON(外层先做分区,局部复杂关系交给 Dagre;若被嵌套,默认为不透明节点) |
|
||||
| 极度依赖几何坐标的图 | 写脚本生成 JSON(node xxx.cjs) |
|
||||
| 需要精确避让的特殊线 | 脚本 + `--layout` 两阶段 |
|
||||
|
||||
---
|
||||
|
||||
## 网格方法论
|
||||
|
||||
核心理念:**先画网格,再填内容**。
|
||||
|
||||
先回答三个问题:
|
||||
1. **信息分几行几列?** 每组一行或一列
|
||||
2. **每格多大?** 等宽还是有主次?
|
||||
3. **行列间距多大?** 分区间 24-32px,同区内 12-16px
|
||||
|
||||
---
|
||||
|
||||
## 布局模式选择
|
||||
|
||||
| 模式 | 适用场景 | DSL 映射 |
|
||||
| ---- | ---------------------------- | -------------------------------------------------------- |
|
||||
| grid | 架构图、对比表、卡片墙、看板 | vertical frame 嵌套 horizontal frame |
|
||||
| flow | 复杂流程图、微服务交互 | `layout: "dagre"`,由引擎自动计算网状连线排版 |
|
||||
| tree | 组织架构、模块依赖 | `layout: "dagre"` 配 `rankdir: "TB"` 或根节点居中的 Flex |
|
||||
| free | 地理位置布局、物理面板还原 | `layout: "none"` + x/y |
|
||||
|
||||
大多数图表用 grid 或 flow 模式。只有节点坐标本身有强语义(如地图)时才用 free。
|
||||
|
||||
> 以上都是布局策略名称,DSL 的 `layout` 属性值只支持 `'horizontal'`、`'vertical'`、`'none'`、`'dagre'` 四种。
|
||||
|
||||
---
|
||||
|
||||
## DSL 与 CSS Flexbox 属性映射
|
||||
|
||||
| DSL 属性 | 对应的 CSS 心智模型 | 限制 |
|
||||
| -------------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `layout: 'horizontal'` | `flex-direction: row` | 不写 layout = 绝对定位 |
|
||||
| `layout: 'vertical'` | `flex-direction: column` | 同上 |
|
||||
| `layout: 'none'` | `position: absolute`(子节点用 x/y) | 子节点不能用 `fill-container`;容器必须有固定宽高 |
|
||||
| `layout: 'dagre'` | 类似 Mermaid / DOT 的有向图布局 | 宽高只支持 `fit-content`;先按拓扑算包围盒再参与父层布局;嵌套时默认为不透明节点 |
|
||||
| `width/height: 'fill-container'` | `flex: 1`(主轴)/ `align-self: stretch`(交叉轴) | 祖先必须有确定尺寸 |
|
||||
| `width/height: 'fit-content'` | `width/height: auto` | — |
|
||||
| `alignItems` | 同 CSS `align-items` | 仅 `'start'`/`'center'`/`'end'`/`'stretch'`(无 flex- 前缀) |
|
||||
| `justifyContent` | 同 CSS `justify-content` | 仅 `'start'`/`'center'`/`'end'`/`'space-between'`/`'space-around'` |
|
||||
| `gap` | 同 CSS `gap` | 必须显式写(不写节点会粘连) |
|
||||
| `padding` | 同 CSS `padding` | 必须显式写。支持 `number` / `[v,h]` / `[t,r,b,l]` |
|
||||
|
||||
`alignItems` 默认值为 `'start'`(CSS Flexbox 默认 `stretch`)。需要等高卡片时必须显式写 `alignItems: 'stretch'`。
|
||||
DSL 的语法是严格白名单,不能写原生 CSS 属性(不支持 `alignSelf`、`flexWrap`、`margin` 等)。
|
||||
|
||||
---
|
||||
|
||||
## DSL 注意事项
|
||||
|
||||
1. **frame 必须写 layout 属性**,不写时子节点全堆在左上角。
|
||||
|
||||
2. **fill-container 死锁陷阱**:使用 `fill-container` 时,祖先链中必须有固定宽度(或高度),否则和 `fit-content` 形成死锁,尺寸退化为 0。
|
||||
错误示例:
|
||||
```json
|
||||
{ "type": "frame", "layout": "horizontal", "width": "fit-content", "children": [
|
||||
{ "type": "rect", "width": "fill-container" }
|
||||
]}
|
||||
```
|
||||
正确示例:
|
||||
```json
|
||||
{ "type": "frame", "layout": "horizontal", "width": 1200, "children": [
|
||||
{ "type": "rect", "width": "fill-container" }
|
||||
]}
|
||||
```
|
||||
3. **不要给 Dagre 套固定宽高的外框**:Dagre 产物尺寸由拓扑决定,无法提前预知。父容器应使用 `fit-content` 自适应,或直接让 Dagre 作为顶层容器,不要用固定像素框住它。
|
||||
4. **`layout: 'none'` 的容器必须有固定宽高**,不要写成 `fit-content`,否则子节点绝对定位容易错乱。
|
||||
5. **含文字节点高度用 fit-content**,引擎不支持 overflow,写死高度会截断文字。
|
||||
6. **Shape 节点有内边距**:rect/ellipse/diamond/triangle 各边 12px;cylinder 垂直 +42px。
|
||||
7. **不支持 flex-wrap**,需要换行时用嵌套 frame 模拟。
|
||||
8. **图层顺序**:数组中越靠后的节点层级越高。需要叠加标注时放在数组最后。
|
||||
|
||||
---
|
||||
|
||||
## 布局选择指南
|
||||
|
||||
| 你要表达的关系 | 怎么排 | DSL 写法 |
|
||||
| -------------------------- | ------------------------ | ---------------------------------------------------------------------------- |
|
||||
| 先后顺序、层级从上到下 | 纵向堆叠 | `layout: 'vertical'` |
|
||||
| 并列、同等重要、可对比 | 横向等分 | `layout: 'horizontal'` + `alignItems: 'stretch'` + `width: 'fill-container'` |
|
||||
| 区域有名称,名称在侧边 | 侧标签 + 内容并排 | 横向 frame: [text(标签), frame(内容)] |
|
||||
| 多个大分区,各自独立 | 分区纵向排列 | 纵向 frame 包多个彩色 frame |
|
||||
| 一行放不下,需要换行 | 嵌套横向 frame 模拟换行 | 纵向 frame 包多个横向 frame |
|
||||
| 复杂的网状关系、拓扑图 | **Dagre 有向图自动布局** | `layout: 'dagre'` + `layoutOptions.edges` |
|
||||
| 节点位置本身有含义(地图) | 绝对定位 | `layout: 'none'` + x/y |
|
||||
|
||||
这些可以自由嵌套组合。比如:纵向堆叠(标题) + 分区纵向排列(多个层) + 每个层内横向等分(节点)。
|
||||
|
||||
---
|
||||
|
||||
## 布局示例
|
||||
|
||||
### 纵向堆叠(标题 + 内容)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "layout": "vertical", "gap": 28, "padding": 32,
|
||||
"width": 1200, "height": "fit-content",
|
||||
"children": [
|
||||
{ "type": "text", "width": "fill-container", "height": "fit-content",
|
||||
"text": "图表标题", "fontSize": 24, "textAlign": "center" },
|
||||
...内容...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 横向等分(并列元素)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "layout": "horizontal", "gap": 16, "padding": 0,
|
||||
"width": "fill-container", "height": "fit-content",
|
||||
"alignItems": "stretch",
|
||||
"children": [
|
||||
{ "type": "rect", "width": "fill-container", "height": "fit-content",
|
||||
"textAlign": "center", "verticalAlign": "middle", "text": "A" },
|
||||
{ "type": "rect", "width": "fill-container", "height": "fit-content",
|
||||
"textAlign": "center", "verticalAlign": "middle", "text": "B" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`alignItems: 'stretch'` + `width: 'fill-container'` = 等宽等高。
|
||||
|
||||
### 侧标签 + 内容
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "layout": "horizontal", "gap": 24, "padding": 0,
|
||||
"width": "fill-container", "height": "fit-content",
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{ "type": "text", "width": 160, "height": "fit-content",
|
||||
"text": "区域名称", "fontSize": 20, "textColor": "#1F2329", "textAlign": "right" },
|
||||
{ "type": "frame", "width": "fill-container", "height": "fit-content",
|
||||
...区域内容...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
不要用 frame 的 `title` 属性做标签——渲染为极小标题栏,不可读。
|
||||
|
||||
### 分区纵向排列
|
||||
|
||||
把内容划分为几个大区域,每个区域用不同颜色区分(颜色从 style 文件的色板选取):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "layout": "vertical", "gap": 28, "padding": 0,
|
||||
"width": "fill-container", "height": "fit-content",
|
||||
"children": [
|
||||
{ "type": "frame", "borderRadius": 8,
|
||||
"layout": "horizontal", "gap": 16, "padding": 20, ...区域1... },
|
||||
{ "type": "frame", "borderRadius": 8,
|
||||
"layout": "horizontal", "gap": 16, "padding": 20, ...区域2... }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 模拟换行
|
||||
|
||||
一行放不下时,拆成多个横向 frame:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "layout": "vertical", "gap": 8, "padding": 0,
|
||||
"children": [
|
||||
{ "type": "frame", "layout": "horizontal", "gap": 8, "padding": 0,
|
||||
"children": [item1, item2, item3, item4] },
|
||||
{ "type": "frame", "layout": "horizontal", "gap": 8, "padding": 0,
|
||||
"children": [item5, item6] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 复杂拓扑混合布局 (Dagre + Flex)
|
||||
|
||||
当你在处理**连线众多、关系杂乱的拓扑图 / 链路流程图 / 复杂架构图**时,不用手动去算每个节点坐标,优先考虑 **Flex + Dagre 的混合布局策略**。这主要包含两种维度的嵌套:
|
||||
|
||||
* **外层 Dagre + 内层 Flex(复杂节点)**:**这是最推荐的复杂架构画法**。整图拓扑交由 `layout: "dagre"` 自动计算并顺滑布线,而图中的节点不再只是单调的矩形,可以是一个用 Flex 自由拼装的复杂 `frame` 卡片(包含图标、主次标题、状态等),让节点承载更丰富的信息。
|
||||
* **外层 Flex + 内层 Dagre(局部流程)**:外层用 Flex 或绝对定位划分大的业务区域,而某个特定区域内部放入 `layout: "dagre"` 容器负责处理局部的业务流。
|
||||
* **嵌套前先做宽度预判**:Dagre 会根据拓扑尽情往两侧撑出包围盒。如果可能横跨导致溢出,优先改 `rankdir` 为 `TB`、缩短文案、调小 `nodesep/ranksep`,必要时将超长的链路拆成分步区。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "id": "arch_root",
|
||||
"layout": "dagre", "padding": 40,
|
||||
"width": "fit-content", "height": "fit-content",
|
||||
"layoutOptions": {
|
||||
"rankdir": "LR", "nodesep": 60, "ranksep": 100,
|
||||
"edges": [
|
||||
["client", "auth_svc", "request"],
|
||||
["auth_svc", "order_svc"],
|
||||
["order_svc", "order_db"]
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "frame", "id": "client",
|
||||
"layout": "vertical", "gap": 6, "padding": [12, 16],
|
||||
"alignItems": "center",
|
||||
"fillColor": "#F8FAFC", "borderColor": "#CBD5E1", "borderWidth": 2, "borderRadius": 10,
|
||||
"children": [
|
||||
{ "type": "text", "text": "Client App", "fontSize": 14, "textColor": "#0F172A" },
|
||||
{ "type": "text", "text": "React 18", "fontSize": 10, "textColor": "#64748B" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame", "id": "cluster_gateway",
|
||||
"layout": "dagre", "layoutOptions": { "isCluster": true, "clusterTitle": "Gateway Tier", "clusterTitleColor": "#15803D" },
|
||||
"fillColor": "#F0FDF4", "borderColor": "#86EFAC",
|
||||
"borderWidth": 2, "borderDash": "dashed", "borderRadius": 16,
|
||||
"children": [
|
||||
{ "type": "rect", "id": "auth_svc", "width": 120, "height": 40, "text": "Auth Service", "fillColor": "#DCFCE7", "borderColor": "#86EFAC", "borderWidth": 1, "borderRadius": 6, "fontSize": 12 },
|
||||
{ "type": "rect", "id": "order_svc", "width": 120, "height": 40, "text": "Order Service", "fillColor": "#DCFCE7", "borderColor": "#86EFAC", "borderWidth": 1, "borderRadius": 6, "fontSize": 12 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame", "id": "order_db",
|
||||
"layout": "vertical", "gap": 4, "padding": [10, 14],
|
||||
"alignItems": "center",
|
||||
"fillColor": "#FFFFFF", "borderColor": "#FECACA", "borderWidth": 2, "borderRadius": 10,
|
||||
"children": [
|
||||
{ "type": "cylinder", "width": 50, "height": 36, "fillColor": "#FCA5A5", "borderColor": "#DC2626", "borderWidth": 1 },
|
||||
{ "type": "text", "text": "Order DB", "fontSize": 12, "textColor": "#7F1D1D" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**示例要点**:
|
||||
- `client` 和 `order_db` 是 **Flex 复合节点**(不透明节点),内部用 vertical 布局组合多行信息,对外层 Dagre 是固定宽高的原子。
|
||||
- `cluster_gateway` 是 **透明子图**(`layout: "dagre"` + `isCluster: true`),外部连线可穿越边界直达 `auth_svc` 和 `order_svc`。
|
||||
- 所有 `edges` 统一写在最外层根 Dagre 的 `layoutOptions` 中。
|
||||
|
||||
**Dagre 嵌套排版规则**:
|
||||
|
||||
1. **不透明节点(Opaque Node)**:Dagre 内的子容器,无论其内部 layout 是 flex、absolute 还是 dagre,只要未声明 isCluster: true,对外层 Dagre 就是具有确定宽高的不透明原子节点。外层连线无法寻址其内部子节点。
|
||||
2. **连线兜底重定向(Edge Redirect Fallback)**:当 edges 引用了某不透明节点内部的子节点 ID 时,引擎自动将该连线端点重定向至其最近的不透明祖先节点。不报错,不产生悬空连线。
|
||||
3. **透明子图(Compound Cluster)**:子容器同时声明 `layout: "dagre"` 与 `layoutOptions: { isCluster: true }` 时,成为外层 Dagre 的复合子图。其内部子节点直接参与外层拓扑运算,连线可穿越子图边界。子图自身不执行独立排版,尺寸由外层 Dagre 根据内部节点包围盒自动撑开。
|
||||
|
||||
---
|
||||
|
||||
## 绝对定位
|
||||
|
||||
当节点位置本身有含义(拓扑图、地图、时间线轴)时用绝对定位。大多数图表优先用 Flex。
|
||||
|
||||
### 混合布局
|
||||
|
||||
模块内部用 Flex 自动排版,模块之间用绝对定位自由摆放。注意:承载这些模块的 `layout: "none"` 父容器必须先给出**固定宽高**,再在里面摆放子模块。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "layout": "none", "width": 1200, "height": 800,
|
||||
"children": [
|
||||
{
|
||||
"type": "frame", "id": "module-a", "x": 100, "y": 100,
|
||||
"width": 300, "height": "fit-content",
|
||||
"layout": "vertical", "gap": 8, "padding": 16,
|
||||
"children": [
|
||||
{ "type": "rect", "width": "fill-container", "height": "fit-content", "text": "内容1" },
|
||||
{ "type": "rect", "width": "fill-container", "height": "fit-content", "text": "内容2" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 两阶段绘图
|
||||
|
||||
先出骨架图导出坐标,再基于坐标补充连线和注解:
|
||||
|
||||
```bash
|
||||
npx -y @larksuite/whiteboard-cli@^0.2.13 -i skeleton.json -o step1.png -l coords.json
|
||||
```
|
||||
|
||||
`coords.json` 包含每个带 id 节点的精确坐标(absX, absY, width, height)。
|
||||
|
||||
---
|
||||
|
||||
## 常用间距和尺寸
|
||||
|
||||
| 参数 | 常用范围 | 说明 |
|
||||
| ---------------- | ----------- | ------------ |
|
||||
| 整图宽度 | 1000-1400px | — |
|
||||
| 分区之间间距 | 24-32px | — |
|
||||
| 同分区内节点间距 | 12-16px | — |
|
||||
| 有连线的节点间距 | >= 40px | 给箭头留空间 |
|
||||
| 分区内边距 | 16-24px | — |
|
||||
| 侧标签宽度 | 120-180px | — |
|
||||
|
||||
---
|
||||
|
||||
## 等大卡片
|
||||
|
||||
一排卡片需要等宽等高时,不要写固定像素:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "layout": "horizontal", "gap": 16, "padding": 0,
|
||||
"alignItems": "stretch",
|
||||
"children": [
|
||||
{ "type": "rect", "width": "fill-container", "height": "fit-content", "text": "A" },
|
||||
{ "type": "rect", "width": "fill-container", "height": "fit-content", "text": "B" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`alignItems: 'stretch'` + `width: 'fill-container'` = 等宽等高。
|
||||
357
.agents/skills/lark-whiteboard/elements/schema.md
Normal file
357
.agents/skills/lark-whiteboard/elements/schema.md
Normal file
@ -0,0 +1,357 @@
|
||||
# DSL Schema
|
||||
|
||||
> 本文件只说明 **DSL 里能写什么**:节点类型、字段、枚举值、硬约束。布局策略、组合方法、Dagre/Flex 心智模型统一放在 `elements/layout.md`。
|
||||
> `?` 表示该字段在 schema 层是 optional;若需要稳定产出,再参考对应 scene 或 layout 文件中的最佳实践。
|
||||
|
||||
**📝 布局引擎核心法则**:
|
||||
- **基本行为与 Flexbox 等同**:Frame 布局基于 Yoga 引擎。`layout: 'horizontal'` = `flex-direction: row`,`fill-container` = `flex: 1`,`fit-content` = `width: auto`,`gap` / `padding` / `alignItems` / `justifyContent` 语义相同。
|
||||
- **枚举值无 flex- 前缀**:一律使用 `'start'` / `'end'` 而非原生 CSS 的 `'flex-start'` / `'flex-end'`。
|
||||
- **默认对齐的差异**:`alignItems` 的默认值是 `'start'`(原生 CSS 默认是 `stretch`)。所以同排卡片需要等高时,**必须显式声名** `alignItems: 'stretch'`。
|
||||
- **Dagre 引擎的特殊性**:`layout: 'dagre'` 作为专属拓扑连线引擎,自身不支持 `fill-container` 宽高,对其父容器而言,它是一个自适应(打包裹)的黑盒。
|
||||
|
||||
## WBDocument
|
||||
|
||||
```typescript
|
||||
interface WBDocument {
|
||||
version: 2;
|
||||
nodes: WBNode[]; // 顶层节点。connector 必须放在这里,不能嵌套在 children 中
|
||||
}
|
||||
```
|
||||
|
||||
## 节点类型
|
||||
|
||||
### Frame(容器)
|
||||
|
||||
唯一可以包含子节点的类型。用于分组、布局、背景。
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: 'frame';
|
||||
id?: string;
|
||||
x?: number; y?: number; // Flex 子节点不需要 x/y
|
||||
width: WBSizeValue;
|
||||
height: WBSizeValue;
|
||||
layout: 'horizontal' | 'vertical' | 'none' | 'dagre'; // 布局模式
|
||||
gap: number; // 必须显式写(不写节点会粘连,容易出 bug)
|
||||
padding: number | [number, number] | [number, number, number, number]; // 必须显式写(不写内容贴边)
|
||||
justifyContent?: 'start' | 'center' | 'end' | 'space-between' | 'space-around';
|
||||
alignItems?: 'start' | 'center' | 'end' | 'stretch';
|
||||
layoutOptions?: { // 仅当 layout 为 'dagre' 时生效
|
||||
rankdir?: 'TB' | 'BT' | 'LR' | 'RL';
|
||||
nodesep?: number;
|
||||
edgesep?: number;
|
||||
ranksep?: number;
|
||||
edges?: Array<[string, string] | [string, string, string]>; // [fromId, toId, label?] 引擎自动排版子节点并生成贝塞尔曲线连线
|
||||
isCluster?: boolean; // 透明子图。为 true 时子节点参与父级 Dagre 拓扑运算,连线可穿越边界
|
||||
clusterTitle?: string; // 子图悬浮标题(自动吸附左上角)
|
||||
clusterTitleColor?: string; // 标题颜色 (HEX格式,如 "#8B5CF6")
|
||||
};
|
||||
fillColor?: string;
|
||||
borderColor?: string;
|
||||
borderWidth?: number;
|
||||
borderDash?: 'solid' | 'dashed' | 'dotted';
|
||||
borderRadius?: number;
|
||||
children?: WBNode[]; // 不能包含 connector
|
||||
}
|
||||
```
|
||||
|
||||
**Dagre 嵌套排版规则**:
|
||||
|
||||
1. **不透明节点(Opaque Node)**:Dagre 内的子容器,无论 `layout` 是 `flex`、`absolute` 还是 `dagre`,只要未声明 `isCluster: true`,对外层 Dagre 就是具有确定宽高的不透明原子节点。外层连线无法寻址其内部子节点。
|
||||
2. **连线兜底重定向(Edge Redirect Fallback)**:当 `edges` 引用了某不透明节点内部的子节点 ID 时,引擎自动将该连线端点重定向至其最近的不透明祖先节点。不报错,不产生悬空连线。
|
||||
3. **透明子图(Compound Cluster)**:子容器同时声明 `layout: "dagre"` 与 `layoutOptions: { isCluster: true }` 时,成为外层 Dagre 的复合子图。其内部子节点直接参与外层拓扑运算,连线可穿越子图边界。子图自身不执行独立排版,尺寸由外层 Dagre 根据内部节点包围盒自动撑开。
|
||||
|
||||
**isCluster 最小用法**:
|
||||
```json
|
||||
{
|
||||
"type": "frame", "id": "cluster_a",
|
||||
"layout": "dagre", "layoutOptions": { "isCluster": true },
|
||||
"fillColor": "#F0FDF4", "borderColor": "#86EFAC", "borderWidth": 2, "borderDash": "dashed", "borderRadius": 16,
|
||||
"children": [
|
||||
{ "type": "text", "text": "区域标题", "fontSize": 11, "textColor": "#15803D" },
|
||||
{ "type": "rect", "id": "node_inside", "width": 120, "height": 40, "text": "内部节点" }
|
||||
]
|
||||
}
|
||||
```
|
||||
> 注意:`edges` 必须写在**最外层的根 Dagre** 的 `layoutOptions` 中,不要写在 cluster 内部。
|
||||
**其他约束**:
|
||||
- `layout / gap / padding` 在 schema 层是 optional,但实际生成时推荐显式写出,避免依赖默认行为。
|
||||
- `layoutOptions` 仅在 `layout: 'dagre'` 时生效。
|
||||
- `children` 里不能出现 `connector`。
|
||||
|
||||
> **虚拟 frame 陷阱**:没有 `fillColor`、`borderColor`、`borderWidth` 的 frame 在编译时可能被当作纯布局容器跳过(子节点直接提升到父级)。如果给这种 frame 设了 `id` 并让外部 connector 连接它,编译后 frame 消失,connector 引用会失效。需要保留这个 frame 时,请给它加上不会被优化掉的外观属性。
|
||||
|
||||
### 基础图形
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: 'rect' | 'ellipse' | 'cylinder' | 'diamond' | 'triangle' | 'trapezoid';
|
||||
id?: string;
|
||||
x?: number; y?: number;
|
||||
opacity?: number; // 0-1,仅影响 fillColor 的透明度(对 frame/text/stickyNote 无效)
|
||||
vFlip?: boolean;
|
||||
hFlip?: boolean;
|
||||
width: WBSizeValue;
|
||||
height: WBSizeValue;
|
||||
fillColor?: string;
|
||||
borderColor?: string;
|
||||
borderWidth?: number;
|
||||
borderDash?: 'solid' | 'dashed' | 'dotted';
|
||||
borderRadius?: number;
|
||||
topWidth?: number; // 仅对 triangle / trapezoid 有效,梯形顶边宽度或三角形顶角截断宽度
|
||||
text?: string | WBTextRun[]; // 纯文本或富文本
|
||||
fontSize?: number;
|
||||
textColor?: string;
|
||||
textAlign?: 'left' | 'center' | 'right'; // Shape 默认 'center'(与 CSS 不同)
|
||||
verticalAlign?: 'top' | 'middle' | 'bottom'; // Shape 默认 'middle'(与 CSS 不同)
|
||||
}
|
||||
```
|
||||
|
||||
> **cylinder 约束**:cylinder 的弧度固定 16px,不随宽度缩放。宽度过大会变成扁椭圆。禁止 `width: "fill-container"`,必须用固定宽度 + `height: "fit-content"`。宽度根据文字长度选择,通常 120-200px。
|
||||
|
||||
> **Shape 内边距(TEXT_INSET)**:Shape 节点有强制内边距,fit-content 会自动补偿。
|
||||
> - rect / ellipse / diamond / triangle:上下左右各 12px
|
||||
> - cylinder:顶部弧形 32px + 底部弧形 10px(垂直 +42px),水平各 7px
|
||||
>
|
||||
> 需要手算固定尺寸时:`实际文字宽/高 + 对应 inset`。
|
||||
> 例:rect 内 14px 字号两行文字高 ~32px → `height >= 32 + 24 = 56px`
|
||||
|
||||
### Image(图片节点)
|
||||
|
||||
图片节点用于在画板中展示图片。图片不能直接使用 URL,必须先上传到飞书获取 media token。
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: 'image';
|
||||
id?: string;
|
||||
x?: number; y?: number;
|
||||
width: WBSizeValue; // 固定宽度,推荐 240 或 200
|
||||
height: WBSizeValue; // 固定高度,推荐按 3:2 比例(如 240×160 或 200×133)
|
||||
image: {
|
||||
src: string; // media token(通过 docs +media-upload --parent-type whiteboard 上传获取)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
> **关键约束**:
|
||||
> - `image.src` 必须是通过 `docs +media-upload --parent-type whiteboard --parent-node <画板token>` 上传后返回的 **media token**,不能是 URL 或 Drive file token
|
||||
> - 图片必须上传到**目标画板**,跨画板的 token 不可用
|
||||
> - 同一画板内所有 image 节点应使用统一的 width/height,保持视觉一致
|
||||
> - 图片宽高比推荐 3:2(如 240×160),避免变形
|
||||
> - 详细上传流程见 [`elements/image.md`](../elements/image.md)
|
||||
|
||||
### Text(纯文本节点)
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: 'text';
|
||||
id?: string;
|
||||
x?: number; y?: number;
|
||||
width: WBSizeValue;
|
||||
height: WBSizeValue;
|
||||
text?: string | WBTextRun[];
|
||||
fontSize?: number;
|
||||
textColor?: string;
|
||||
textAlign?: 'left' | 'center' | 'right';
|
||||
verticalAlign?: 'top' | 'middle' | 'bottom';
|
||||
}
|
||||
```
|
||||
|
||||
### StickyNote(便签)
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: 'stickyNote';
|
||||
id?: string;
|
||||
x?: number; y?: number;
|
||||
width: WBSizeValue;
|
||||
height: WBSizeValue;
|
||||
fillColor?: '#FEF1CE' | '#F5D1A7' | '#DFF5E5' | '#CDF7CC' | '#C9E8EF' | '#D6DCF3' | '#D3CCEE' | '#F1C5E7' | '#F6C8C8'; // 便签底色(仅支持这 9 种)
|
||||
text?: string | WBTextRun[];
|
||||
fontSize?: number;
|
||||
textColor?: string;
|
||||
textAlign?: 'left' | 'center' | 'right';
|
||||
verticalAlign?: 'top' | 'middle' | 'bottom';
|
||||
}
|
||||
```
|
||||
|
||||
### Connector(连线)
|
||||
|
||||
必须放在顶层 `nodes` 数组中,不能嵌套在 frame 的 `children` 里。
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: 'connector';
|
||||
id?: string;
|
||||
connector: {
|
||||
from: string | { x: number; y: number }; // 节点 id 或坐标
|
||||
to: string | { x: number; y: number };
|
||||
fromAnchor?: 'top' | 'right' | 'bottom' | 'left';
|
||||
toAnchor?: 'top' | 'right' | 'bottom' | 'left';
|
||||
lineShape?: 'straight' | 'polyline' | 'curve' | 'rightAngle'; // 直线、圆角折线、曲线、直角折线
|
||||
lineColor?: string;
|
||||
lineWidth?: number;
|
||||
lineStyle?: 'solid' | 'dashed' | 'dotted';
|
||||
startArrow?: 'none' | 'arrow' | 'triangle' | 'circle' | 'diamond';
|
||||
endArrow?: 'none' | 'arrow' | 'triangle' | 'circle' | 'diamond';
|
||||
waypoints?: { x: number; y: number }[]; // polyline 途经点
|
||||
label?: string; // 连线中间的标签文字
|
||||
labelPosition?: number; // 标签位置,0-1,默认 0.5(中点)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### SVG
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: 'svg';
|
||||
id?: string;
|
||||
x?: number; y?: number;
|
||||
opacity?: number;
|
||||
width: WBSizeValue;
|
||||
height: WBSizeValue;
|
||||
svg: { code: string }; // SVG 代码字符串
|
||||
}
|
||||
```
|
||||
|
||||
#### 渲染规范
|
||||
|
||||
SVG 通过 `image/svg+xml` Blob 加载到画布,**不在 HTML DOM 中**,因此存在严格限制:
|
||||
|
||||
**必须**:
|
||||
- 包含 `viewBox` 属性(如 `viewBox="0 0 24 24"`),引擎依赖它确定坐标系
|
||||
- 包含 `xmlns="http://www.w3.org/2000/svg"`(SVG 作为独立 `image/svg+xml` 解析时,XML 规范要求声明命名空间)
|
||||
|
||||
**允许的元素**(纯几何绘制):
|
||||
- 基本图形:`<rect>` `<circle>` `<ellipse>` `<line>` `<polyline>` `<polygon>` `<path>`
|
||||
- 渐变/滤镜:`<defs>` `<linearGradient>` `<radialGradient>` `<filter>` `<feGaussianBlur>` `<feMerge>`
|
||||
- 结构:`<g>` `<clipPath>` `<mask>` `<use>`
|
||||
|
||||
**禁止的元素**(字体和外部资源在 Blob 沙箱中无法加载):
|
||||
- `<text>` `<tspan>`(用同层 DSL rect 节点 + text 属性替代)
|
||||
- `<image>`(用同层 DSL image 节点替代)
|
||||
- `<foreignObject>`
|
||||
- 任何引用外部 URL 的属性(`xlink:href` 指向远程资源等)
|
||||
|
||||
#### 两种典型用法
|
||||
|
||||
**1. 背景装饰 SVG**(大尺寸,与 frame 同大小)
|
||||
|
||||
用于绘制连线、曲线、发光效果等几何背景。文字信息通过同一 frame 内的 rect 节点叠加:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "width": 1400, "height": 680, "layout": "none",
|
||||
"children": [
|
||||
{ "type": "svg", "x": 0, "y": 0, "width": 1400, "height": 680,
|
||||
"svg": { "code": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1400 680\" ...>...</svg>" } },
|
||||
{ "type": "rect", "x": 100, "y": 50, "width": 200, "height": 40,
|
||||
"text": "Label", "fillColor": "transparent" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**2. 内联图标 SVG**(24-48px,Feather/Lucide 风格)
|
||||
|
||||
用于卡片/按钮中的小图标,纯 stroke 线条:
|
||||
|
||||
```json
|
||||
{ "type": "svg", "width": 32, "height": 32,
|
||||
"svg": { "code": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#3B82F6\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/></svg>" } }
|
||||
```
|
||||
|
||||
### Icon(内置图标)
|
||||
|
||||
引用画板内置图标库的图标。比手写 SVG 更简单——只需指定 `name`。
|
||||
|
||||
```typescript
|
||||
{
|
||||
type: 'icon';
|
||||
id?: string;
|
||||
x?: number; y?: number;
|
||||
width?: WBSizeValue; // 默认 48
|
||||
height?: WBSizeValue; // 默认 48,保持正方形
|
||||
name: string; // 图标名称,从 npx -y @larksuite/whiteboard-cli@^0.2.13 --icons 输出中选取
|
||||
color?: string; // 可选颜色覆盖,hex 格式如 '#FF6600'
|
||||
}
|
||||
```
|
||||
|
||||
**获取可用图标**:规划好内容和布局后,运行以下命令查看所有可用图标名,从中选取:
|
||||
```bash
|
||||
npx -y @larksuite/whiteboard-cli@^0.2.13 --icons
|
||||
```
|
||||
|
||||
用法:
|
||||
```json
|
||||
{ "type": "icon", "id": "db", "name": "database", "width": 48, "height": 48 }
|
||||
```
|
||||
|
||||
**使用建议**:
|
||||
- 当图表中的节点代表具体事物(服务器、用户、数据库等)时,用图标比纯文字方块更直观
|
||||
- 一张图 3-8 个图标为宜,为关键组件配图标,次要节点用普通形状
|
||||
- 用 `color` 为图标指定合适的颜色, 比如与所在容器的配色一致
|
||||
- 图标可放在 frame 子元素中参与 flex 布局,连线可通过 id 连接到图标
|
||||
- 图标+文字组合:frame(vertical) 中放 icon + text,形成富组件
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "layout": "vertical", "gap": 8, "padding": 12,
|
||||
"alignItems": "center", "fillColor": "#F0F5FF", "borderColor": "#ADC6FF",
|
||||
"children": [
|
||||
{ "type": "icon", "id": "db-icon", "name": "database", "width": 36, "height": 36 },
|
||||
{ "type": "text", "text": "PostgreSQL", "fontSize": 12, "width": "fit-content", "height": "fit-content" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 富文本 WBTextRun
|
||||
|
||||
`text` 字段可以是纯字符串或 `WBTextRun[]` 数组。类似 HTML 内联样式:bold 对应 `<b>`,italic 对应 `<i>`,listType 对应 `<ol>/<ul>`。每个 run 是一段带样式的文字:
|
||||
|
||||
```typescript
|
||||
interface WBTextRun {
|
||||
content: string; // 文字内容,可含 \n 换行
|
||||
bold?: boolean;
|
||||
italic?: boolean;
|
||||
underline?: boolean;
|
||||
strikeThrough?: boolean;
|
||||
fontSize?: number;
|
||||
color?: string; // 文字颜色
|
||||
backgroundColor?: string; // 文字高亮背景
|
||||
hyperlink?: string;
|
||||
listType?: 'none' | 'ordered' | 'unordered';
|
||||
indent?: number; // 缩进级数
|
||||
quote?: boolean; // 引用块
|
||||
}
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"text": [
|
||||
{ "content": "标题文字\n", "bold": true, "fontSize": 16 },
|
||||
{ "content": "正文内容,", "fontSize": 14 },
|
||||
{ "content": "高亮部分", "backgroundColor": "#FEF1CE", "fontSize": 14 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`text` 和 `content` 中出现的双引号必须写成 `\"`,这是 JSON 规范要求。换行用 `\n`(JSON 中写为 `"第一行\n第二行"`,不要双重转义为 `\\n`)。
|
||||
|
||||
---
|
||||
|
||||
## 尺寸值 WBSizeValue
|
||||
|
||||
| 值 | 含义 | 注意 |
|
||||
| --------------------- | --------------------------- | -------------------------------------- |
|
||||
| `number` | 固定像素 | 任何场景 |
|
||||
| `'fit-content'` | 由内容决定大小 | 父级需要 Flex 布局 |
|
||||
| `'fit-content(N)'` | 同上,无内容时 fallback N | 同上 |
|
||||
| `'fill-container'` | 填满父级剩余空间 | 父级需要 Flex 布局,且祖先链有固定宽度 |
|
||||
| `'fill-container(N)'` | 同上,无 Flex 时 fallback N | — |
|
||||
|
||||
`fill-container` 在 `layout: 'none'`(绝对定位)下无效。`fit-content` 仍可用于含文字节点(引擎通过 Yoga measureFunc 测量文字尺寸)。
|
||||
318
.agents/skills/lark-whiteboard/elements/style.md
Normal file
318
.agents/skills/lark-whiteboard/elements/style.md
Normal file
@ -0,0 +1,318 @@
|
||||
# 配色系统
|
||||
|
||||
## 怎么上色(最重要)
|
||||
|
||||
上色步骤:
|
||||
|
||||
1. **找出图中有几个分组**(层级、分支、类别、阶段...)
|
||||
2. **为每个分组选一种不同颜色**(从色板中选 2-4 种颜色)
|
||||
3. **分组容器**用浅色填充 — 告诉读者"这块是一个整体"
|
||||
4. **分组内节点**用白色填充 + 该分组的深色 borderColor — 告诉读者"这些属于这个分组"
|
||||
|
||||
具体映射(经典色板):
|
||||
|
||||
| 分组 | 层容器 fillColor | 层容器 borderColor | 内部节点 borderColor |
|
||||
|------|----------------|-------------------|---------------------|
|
||||
| 第 1 组 | #F0F4FC(浅蓝) | #5178C6 | #5178C6 |
|
||||
| 第 2 组 | #EAE2FE(浅紫) | #8569CB | #8569CB |
|
||||
| 第 3 组 | #DFF5E5(浅绿) | #509863 | #509863 |
|
||||
| 第 4 组 | #FEF1CE(浅黄) | #D4B45B | #D4B45B |
|
||||
| 第 5 组 | #FEE3E2(浅红) | #D25D5A | #D25D5A |
|
||||
| 内部节点 | #FFFFFF | 跟随所属分组 | — |
|
||||
|
||||
**各类图表怎么上色**:
|
||||
- 架构图有 3 层 → 每层一种颜色,层背景浅色填充,层内节点白色+深色边框
|
||||
- 对比表有 3 列 → 每列表头一种颜色,该列数据单元格用同色边框
|
||||
- 组织架构有 4 个部门 → 每个部门一种颜色,子部门白色+同色边框
|
||||
- 流程图 → 起止节点一种颜色,判断节点一种颜色,步骤节点白色
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **用户配色优先。** 用户指定了色值/风格时以用户为准。用户只给 1-2 个色值时,推导完整色板:主色→浅底→深边框→灰调连线色。
|
||||
> 用户**未指定**配色时,必须从上方色板表中选取颜色,不要使用表中没有的自创色值(如 `#E8F3FF`、`#1664FF`、`#14C9C9` 等都不在色板中)。
|
||||
|
||||
---
|
||||
|
||||
## 结构规则
|
||||
|
||||
### 分组 — 不同层/分组必须用不同颜色
|
||||
|
||||
选 2-4 种颜色,每种代表一个分组。同组节点视觉完全一致(fillColor、borderColor 相同)。
|
||||
|
||||
### 分层 — 外重内轻
|
||||
|
||||
- 外层(大分区):浅色填充背景
|
||||
- 内层(具体节点):白色填充 + 分组色边框
|
||||
|
||||
### 清晰
|
||||
|
||||
- 所有节点有边框(borderWidth=2)
|
||||
- 间距不粘连(gap >= 8,有连线时 >= 40)
|
||||
- 文字在背景上清晰可读(fontSize >= 14)。文字与背景色对比度应足够(参考 WCAG 2.1:正文至少 4.5:1,标题至少 3:1)
|
||||
- 不要仅靠颜色区分信息——同时使用边框、形状或文字标签辅助,确保色觉障碍用户也能理解
|
||||
- 连线用灰色(#BBBFC4),不抢节点注意力
|
||||
|
||||
### 统一参数
|
||||
|
||||
| 参数 | 值 | 为什么 |
|
||||
|------|---|--------|
|
||||
| borderWidth | 2 | 让边框清晰可见 |
|
||||
| borderRadius | 8 | 统一的圆角,整洁 |
|
||||
| gap(最小值) | 8 | 元素不粘连 |
|
||||
| padding(最小值) | 8 | 内容不贴边 |
|
||||
| gap(有连线时) | 40 | 给箭头留空间 |
|
||||
| fontSize(正文) | >= 14 | 可读 |
|
||||
| fontSize(标题) | >= 24 | 醒目 |
|
||||
| fontSize(辅助) | >= 13 | 不费眼 |
|
||||
|
||||
---
|
||||
|
||||
## 色板选择指南
|
||||
|
||||
根据用户需求的关键词或场景选择合适的色板。未指定时默认使用"经典"色板。
|
||||
|
||||
| 色板 | 适用场景 | 关键词 |
|
||||
|------|---------|-------|
|
||||
| 经典 | 通用图表、说明文档 | 默认、通用 |
|
||||
| 商务 | 汇报、企业架构、正式文档 | 专业、正式、给老板看 |
|
||||
| 科技 | 技术架构、DevOps、监控 | 技术、炫酷、暗色 |
|
||||
| 清新 | 流程图、用户旅程、教程 | 清新、自然、轻松 |
|
||||
| 极简 | 论文配图、学术报告 | 学术、极简、黑白 |
|
||||
|
||||
---
|
||||
|
||||
## 预设色板
|
||||
|
||||
每套色板定义 7 个角色的颜色。**连线色是色板的一部分**,不同色板的连线色不同。
|
||||
|
||||
### 经典
|
||||
|
||||
| 角色 | fillColor | borderColor | textColor |
|
||||
|------|-----------|-------------|-----------|
|
||||
| 分区背景 | #F0F4FC | #5178C6 | #1F2329 |
|
||||
| 分组标题 | #EAE2FE | #8569CB | #1F2329 |
|
||||
| 内容节点 | #FFFFFF | #5178C6 | #1F2329 |
|
||||
| 第二分组 | #DFF5E5 | #509863 | #1F2329 |
|
||||
| 第三分组 | #FEF1CE | #D4B45B | #1F2329 |
|
||||
| 第四分组 | #FEE3E2 | #D25D5A | #1F2329 |
|
||||
| 强调/表头 | #1F2329 | #1F2329 | #FFFFFF |
|
||||
| 连线 | -- | -- | #BBBFC4 |
|
||||
|
||||
### 商务
|
||||
|
||||
| 角色 | fillColor | borderColor | textColor |
|
||||
|------|-----------|-------------|-----------|
|
||||
| 分区背景 | #EDF2F7 | #4A6FA5 | #1A202C |
|
||||
| 分组标题 | #D4E0ED | #4A6FA5 | #1A202C |
|
||||
| 内容节点 | #FFFFFF | #718BAE | #1A202C |
|
||||
| 第二分组 | #E8EDF3 | #5A7B9A | #1A202C |
|
||||
| 第三分组 | #F0F0F0 | #8895A7 | #1A202C |
|
||||
| 强调/表头 | #2D4A7A | #2D4A7A | #FFFFFF |
|
||||
| 连线 | -- | -- | #718BAE |
|
||||
|
||||
### 科技
|
||||
|
||||
| 角色 | fillColor | borderColor | textColor |
|
||||
|------|-----------|-------------|-----------|
|
||||
| 画布/分区背景 | #0F172A | #1E293B | #E2E8F0 |
|
||||
| 分组标题 | #1E293B | #3B82F6 | #E2E8F0 |
|
||||
| 内容节点 | #1E293B | #334155 | #E2E8F0 |
|
||||
| 第二分组 | #1E293B | #8B5CF6 | #E2E8F0 |
|
||||
| 第三分组 | #1E293B | #10B981 | #E2E8F0 |
|
||||
| 强调 | #2563EB | #3B82F6 | #FFFFFF |
|
||||
| 连线 | -- | -- | #475569 |
|
||||
|
||||
### 清新
|
||||
|
||||
| 角色 | fillColor | borderColor | textColor |
|
||||
|------|-----------|-------------|-----------|
|
||||
| 分区背景 | #F0FDF4 | #86EFAC | #14532D |
|
||||
| 分组标题 | #DCFCE7 | #4ADE80 | #14532D |
|
||||
| 内容节点 | #FFFFFF | #86EFAC | #14532D |
|
||||
| 第二分组 | #ECFDF5 | #6EE7B7 | #14532D |
|
||||
| 第三分组 | #F0FDFA | #5EEAD4 | #134E4A |
|
||||
| 强调 | #16A34A | #16A34A | #FFFFFF |
|
||||
| 连线 | -- | -- | #86EFAC |
|
||||
|
||||
### 极简
|
||||
|
||||
| 角色 | fillColor | borderColor | textColor |
|
||||
|------|-----------|-------------|-----------|
|
||||
| 分区背景 | #F8F9FA | #DEE2E6 | #212529 |
|
||||
| 分组标题 | #E9ECEF | #ADB5BD | #212529 |
|
||||
| 内容节点 | #FFFFFF | #CED4DA | #212529 |
|
||||
| 第二分组 | #F1F3F5 | #868E96 | #212529 |
|
||||
| 第三分组 | #F8F9FA | #ADB5BD | #212529 |
|
||||
| 强调/表头 | #495057 | #495057 | #FFFFFF |
|
||||
| 连线 | -- | -- | #ADB5BD |
|
||||
|
||||
---
|
||||
|
||||
## 各元素怎么画
|
||||
|
||||
> 以下示例使用经典色板。如果选了其他色板,替换对应颜色即可,结构保持不变。
|
||||
|
||||
### 图表标题
|
||||
|
||||
告诉读者"这张图讲什么"。大号深色文字,居中。
|
||||
|
||||
```json
|
||||
{ "type": "text", "fontSize": 24, "textColor": "#1F2329", "textAlign": "center" }
|
||||
```
|
||||
|
||||
### 分区背景
|
||||
|
||||
把相关的内容圈在一起,告诉读者"这些属于同一个大类"。浅色做 fillColor,对应深色做 borderColor。内部放白色节点。
|
||||
|
||||
```json
|
||||
{ "fillColor": "#F0F4FC", "borderColor": "#5178C6", "borderWidth": 2, "borderRadius": 8, "padding": 20 }
|
||||
```
|
||||
|
||||
### 分区标签
|
||||
|
||||
给分区一个名字。用独立 text 节点,不要用 frame 的 `title` 属性(会被渲染为极小标题栏)。
|
||||
|
||||
**所有分区标签统一用深色文字 `#1F2329`**,不要给每个标签用不同颜色——颜色区分通过层容器背景和边框体现,标签文字颜色保持一致。
|
||||
|
||||
```json
|
||||
{ "type": "text", "width": 180, "height": "fit-content", "text": "Access layer", "fontSize": 20, "textColor": "#1F2329", "textAlign": "right" }
|
||||
```
|
||||
|
||||
### 分组标题
|
||||
|
||||
告诉读者"这个子分组叫什么"。色板色填充 + 同色系深色边框。
|
||||
|
||||
```json
|
||||
{ "fillColor": "#EAE2FE", "borderColor": "#8569CB", "borderWidth": 2, "borderRadius": 8, "fontSize": 14, "textColor": "#1F2329" }
|
||||
```
|
||||
|
||||
### 内容节点
|
||||
|
||||
具体的信息项。白色填充,边框颜色跟随所属分组。
|
||||
|
||||
```json
|
||||
{ "fillColor": "#FFFFFF", "borderColor": "#5178C6", "borderWidth": 2, "borderRadius": 8, "fontSize": 14, "textColor": "#1F2329" }
|
||||
```
|
||||
|
||||
白色节点的 borderColor 取决于它所属的分组:
|
||||
```
|
||||
属于蓝色分组: fillColor="#FFFFFF" borderColor="#5178C6" borderWidth=2
|
||||
属于紫色分组: fillColor="#FFFFFF" borderColor="#8569CB" borderWidth=2
|
||||
独立节点: fillColor="#FFFFFF" borderColor="#DEE0E3" borderWidth=2
|
||||
```
|
||||
(注:以上为经典色板的值,其他色板替换对应的 borderColor)
|
||||
|
||||
### 表头
|
||||
|
||||
告诉读者"这一列/行是什么维度"。深色填充 + 白色文字。
|
||||
|
||||
```json
|
||||
{ "fillColor": "#1F2329", "borderColor": "#1F2329", "borderWidth": 2, "borderRadius": 0, "fontSize": 15, "textColor": "#FFFFFF", "textAlign": "center" }
|
||||
```
|
||||
|
||||
### 图标组件
|
||||
|
||||
icon + text 的组合卡片。icon 的 `color` 跟随所属分组的 borderColor,与其他节点视觉一致。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "layout": "vertical", "gap": 4, "padding": 12,
|
||||
"alignItems": "center", "fillColor": "#FFFFFF", "borderColor": "#5178C6", "borderWidth": 2, "borderRadius": 8,
|
||||
"children": [
|
||||
{ "type": "icon", "name": "server", "width": 36, "height": 36, "color": "#5178C6" },
|
||||
{ "type": "text", "width": "fit-content", "height": "fit-content", "text": "应用服务器", "fontSize": 12 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
icon color 需要结合上下文选择合适的颜色, 比如: 使用所属分组的borderColor
|
||||
|
||||
### textColor 规则
|
||||
|
||||
```
|
||||
- 正文:#1F2329(深色,在白底/浅色底上清晰)
|
||||
- 辅助说明:#646A73(弱化,不抢注意力)
|
||||
- 深色底上:#FFFFFF(反色,清晰可读)
|
||||
(以上为经典色板的值,其他色板参考对应 textColor 列)
|
||||
```
|
||||
|
||||
### 辅助说明
|
||||
|
||||
补充信息,不抢主角的注意力。灰色小字。
|
||||
|
||||
```json
|
||||
{ "fontSize": 13, "textColor": "#646A73" }
|
||||
```
|
||||
|
||||
### 连线
|
||||
|
||||
表达元素之间的关系或流向。使用色板中的连线色。
|
||||
|
||||
```json
|
||||
{ "lineColor": "#BBBFC4", "lineWidth": 2 }
|
||||
```
|
||||
|
||||
### 布局容器
|
||||
|
||||
纯粹用来排版的 frame,读者看不见它。不设 fillColor、borderColor。
|
||||
|
||||
```json
|
||||
{ "type": "frame", "layout": "vertical", "gap": 28, "padding": 32 }
|
||||
```
|
||||
|
||||
### 分组容器
|
||||
|
||||
用虚线框圈定一组节点,比分区背景更轻量。
|
||||
|
||||
```json
|
||||
{ "borderColor": "#DEE0E3", "borderWidth": 2, "borderDash": "dashed", "borderRadius": 8 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见错误
|
||||
|
||||
错误:每个节点一种颜色 -> 读者分不清谁和谁是一组
|
||||
```json
|
||||
{ "fillColor": "#8569CB" }, { "fillColor": "#5178C6" }, { "fillColor": "#509863" }
|
||||
```
|
||||
正确:同组节点视觉一致 -> 读者一眼看出关系
|
||||
```json
|
||||
{ "fillColor": "#FFFFFF", "borderColor": "#8569CB" }, { "fillColor": "#FFFFFF", "borderColor": "#8569CB" }
|
||||
```
|
||||
|
||||
错误:内外层都用重色 -> 读者不知道先看哪里
|
||||
```json
|
||||
{ "type": "frame", "fillColor": "#5178C6", "children": [{ "fillColor": "#8569CB" }] }
|
||||
```
|
||||
正确:外层浅色内层白色 -> 读者先看结构再看细节
|
||||
```json
|
||||
{ "type": "frame", "fillColor": "#F0F4FC", "children": [{ "fillColor": "#FFFFFF", "borderColor": "#5178C6" }] }
|
||||
```
|
||||
|
||||
错误:连线用和节点一样的彩色 -> 和节点颜色抢注意力
|
||||
```json
|
||||
{ "connector": { "lineColor": "#5178C6" } }
|
||||
```
|
||||
正确:连线用色板中的连线色 -> 衬托节点
|
||||
```json
|
||||
{ "connector": { "lineColor": "#BBBFC4" } }
|
||||
```
|
||||
|
||||
错误:节点没边框 -> 和背景融为一体,看不清边界
|
||||
```json
|
||||
{ "fillColor": "#FFFFFF" }
|
||||
```
|
||||
正确:节点有边框 -> 边界清晰
|
||||
```json
|
||||
{ "fillColor": "#FFFFFF", "borderColor": "#DEE0E3", "borderWidth": 2 }
|
||||
```
|
||||
|
||||
错误:全图黑白灰,没有颜色区分 -> 读者无法快速识别分组
|
||||
```json
|
||||
{ "fillColor": "#FFFFFF", "borderColor": "#DEE0E3" }
|
||||
```
|
||||
正确:不同分组用不同颜色 -> 一眼看出结构(蓝色分组 + 紫色分组)
|
||||
```json
|
||||
{ "fillColor": "#F0F4FC", "borderColor": "#5178C6" }
|
||||
{ "fillColor": "#EAE2FE", "borderColor": "#8569CB" }
|
||||
```
|
||||
73
.agents/skills/lark-whiteboard/elements/typography.md
Normal file
73
.agents/skills/lark-whiteboard/elements/typography.md
Normal file
@ -0,0 +1,73 @@
|
||||
# 排版规则
|
||||
|
||||
## 字号层级表
|
||||
|
||||
| 层级 | 字号 | 用途 | 对齐 |
|
||||
|------|------|------|------|
|
||||
| H1 | 24-28 | 图表标题(每图一个) | center |
|
||||
| H2 | 18-20 | 分区/层标签 | right(侧标签)或 center(顶部标签) |
|
||||
| H3 | 15-16 | 分组标题、卡片标题 | center 或 left |
|
||||
| Body | 14 | 正文、节点文字 | center(短标签)或 left(长文本) |
|
||||
| Caption | 13 | 辅助说明、注解 | left |
|
||||
|
||||
规则:
|
||||
- 同张图不超过 3 个字号层级
|
||||
- 同级节点 fontSize 必须完全相同
|
||||
- 相邻层级字号差 >= 4px
|
||||
|
||||
---
|
||||
|
||||
## 对齐规则
|
||||
|
||||
Shape 节点默认 `textAlign: 'center'` + `verticalAlign: 'middle'`(与 CSS 相反)。如需左对齐须显式声明。
|
||||
|
||||
| 内容类型 | 对齐方式 |
|
||||
|---------|---------|
|
||||
| 短文本(<=15 字) | center |
|
||||
| 长文本(>15 字) | left |
|
||||
| 侧标签(层名、分区名) | right |
|
||||
| 图表标题 | center |
|
||||
| 多行描述/段落 | left |
|
||||
|
||||
---
|
||||
|
||||
## 图表标题
|
||||
|
||||
用独立 text 节点,不要用 frame 的 `title` 属性。
|
||||
|
||||
- Flex 布局:放在最外层 frame 的第一个 child,`width: "fill-container"`
|
||||
- 绝对定位:width 设为图表整体宽度,`textAlign: "center"`
|
||||
|
||||
---
|
||||
|
||||
## 标题和描述拆成两个节点
|
||||
|
||||
一个卡片内展示名称和描述时,用 frame 包两个 text 节点,不要塞进同一个 shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame", "layout": "vertical", "gap": 4, "padding": 12,
|
||||
"width": "fill-container", "height": "fit-content",
|
||||
"borderWidth": 2, "borderRadius": 8,
|
||||
"children": [
|
||||
{ "type": "text", "width": "fill-container", "height": "fit-content",
|
||||
"text": "用户服务", "fontSize": 16 },
|
||||
{ "type": "text", "width": "fill-container", "height": "fit-content",
|
||||
"text": "处理注册登录和个人信息管理", "fontSize": 13 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 图标+文字组合
|
||||
|
||||
icon + text 纵向排列时:icon 宽高 36-48px,下方文字 fontSize 12-13,外层 frame gap 4-8。icon 比文字大 2-3 倍时视觉比例最佳。
|
||||
|
||||
---
|
||||
|
||||
## 尺寸规则
|
||||
|
||||
含文字节点 `height` 必须用 `'fit-content'`。写死高度会截断文字。
|
||||
|
||||
所有节点必须显式声明 `width` 和 `height`。
|
||||
Reference in New Issue
Block a user