- 移除 TodoItem 中的 priority、created_at 和 updated_at 字段 - 强制每个任务都必须有唯一 id,且由用户负责生成 - 修改合并模式逻辑,merge=true 下保留未提及的旧任务 - 支持已完成和已取消任务重新激活(状态改回 pending 或 in_progress) - 禁止 in_progress 状态退回到 pending,必须标记为 completed 或 cancelled - 优化状态转换校验,允许特定状态间合法切换 - 简化任务变更消息,移除详细的新增/更新/移除统计 - 更新文档和示例,明确 id 必须由用户生成和使用 - 修复和补充测试,增强状态转换和合并模式验证 - 调整任务时间戳生成逻辑,统一使用当前时间及索引 - 该变更提供更合理的任务状态机械及管理模式,提升稳定性和易用性
186 lines
11 KiB
Markdown
186 lines
11 KiB
Markdown
# 流程图 (Flowchart)
|
||
|
||
适用于:各种业务流转图、决策树、审批流、时序控制逻辑、带条件判断的链路、系统架构拓扑等。
|
||
|
||
通用字段语义详见 `elements/schema.md`,通用布局原则详见 `elements/layout.md`;本文件只描述流程图场景下的选型边界与范式。
|
||
|
||
> [!IMPORTANT]
|
||
> **流程图必须走 DSL 路径,不再使用 Mermaid!**
|
||
> 复杂分支、判断、回路、跳级关系优先使用 `layout: "dagre"` 计算拓扑;如果只是规整的单线流水线,且卡片强对齐比自动拓扑更重要,也可以使用 Flex + 顶层 `connector` 组合实现。
|
||
|
||
## 美学规范
|
||
|
||
- **摒弃简陋节点,推崇全卡片化**:核心业务节点不要只用一个纯文本 `rect`。**应优先采用 Flex 组合卡片**(如:在 `vertical` frame 内上下组合【Emoji 标题项】和【补充说明项】),使得节点信息结构化、层级分明。
|
||
- **语义化色彩编排**:节点底色严禁随机分配。必须按状态语义映射:常规链路用浅蓝/浅紫、核心风控/检查用预警黄、成功通过用生命绿、失败熔断用危险红。边框颜色可同色系加深,以凸显卡片边缘。
|
||
- **统一判定逻辑**:条件分支必须使用 `diamond` 菱形节点,并且**严禁漏掉** `layoutOptions.edges` 边定义里的第三个标签参数(必须清晰写明"是/否"、"通过/拒绝")。
|
||
- **形状多样化**:合理搭配不同形状来表达语义 —— `ellipse` 用于外部实体/起终点、`diamond` 用于判断路由、`rect` 用于业务处理节点、`cylinder` 用于持久化存储。
|
||
|
||
## Layout 选型
|
||
|
||
| 模式 | 适用条件 | 核心配置 |
|
||
| ---------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
||
| **主体用 Dagre** | 有判断、分支、回路、回退、跳级关系的标准流程图 | 主体 frame 设定 `layout: "dagre"`,按需配置 `rankdir: "TB"` 或 `rankdir: "LR"`。 |
|
||
| **局部复合节点** | 流程中的某一步本身是一个小型 UI 组合体 | 外层仍用 `dagre`,复合步骤内部改用 `layout: "vertical"` / `"horizontal"`。此类节点为**不透明节点**,外层连线只能连到外壳。 |
|
||
| **透明子图** | 需按业务区域分组,且连线穿越区域边界 | 子容器声明 `layout: "dagre"` + `layoutOptions: { isCluster: true }`,成为透明子图。内部节点直接参与外层拓扑运算。 |
|
||
| **规整流水线** | 基本是单线 A → B → C → D,且卡片对齐要求极高 | 主体可用 Flex 排版,连线改用顶层 `connector`;不要为了"自动"而硬上 Dagre。 |
|
||
|
||
## 核心属性
|
||
|
||
- **`rankdir`**: `TB`(上下)或 `LR`(左右)。**强烈推荐优先使用 `LR`**,充分利用宽屏横向空间。
|
||
- **`edges`**: 在根 Dagre 的 `layoutOptions.edges` 中按 `[fromId, toId, "标签"]` 声明。**支持反向连接**实现闭环。所有 edges 统一写在**最外层根 Dagre**,不要写在 cluster 内部。
|
||
- **`ranksep` 与边文本**: 若边上标注了说明文字,**必须根据字数调大间距**:`ranksep = max(60, 字数 × 16)`。
|
||
- **自适应尺寸**: dagre 容器**必须**设定 `width: "fit-content"` 和 `height: "fit-content"`。
|
||
- **`clusterTitle`**: 透明子图可通过 `clusterTitle` 声明悬浮标题(自动吸附左上角、加粗 14px),搭配 `clusterTitleColor` 指定标题颜色。
|
||
|
||
## 两种嵌套模式
|
||
|
||
### 不透明节点(Opaque Node)
|
||
Dagre 内的子容器,只要未声明 `isCluster: true`,对外层 Dagre 就是具有确定宽高的原子节点。外层连线无法寻址其内部子节点。适合封装复杂的组合卡片(如带图标、版本号、多行描述的业务模块)。
|
||
|
||
### 透明子图(Compound Cluster)
|
||
子容器同时声明 `layout: "dagre"` 与 `layoutOptions: { isCluster: true }` 时,成为外层 Dagre 的复合子图。其内部子节点直接参与外层拓扑运算,连线可穿越子图边界。适合划分网络区域、功能层级、命名空间等边界容器。推荐搭配 `borderDash: "dashed"` 虚线边框 + 淡色背景。
|
||
|
||
## 骨架示例(推荐范本)
|
||
|
||
以下是一个混合架构拓扑的完整示例。它同时展示了**透明子图**(Kubernetes Zone,连线可穿透)和**不透明复合节点**(DB 集群、AI 引擎,连线只能连外壳)的标准写法,以及多种形状(ellipse / diamond / rect / cylinder)和语义化配色规范。
|
||
|
||
```json
|
||
{
|
||
"version": 2,
|
||
"nodes": [
|
||
{
|
||
"type": "frame",
|
||
"id": "root",
|
||
"x": 20, "y": 20,
|
||
"layout": "dagre",
|
||
"width": "fit-content", "height": "fit-content",
|
||
"padding": 60,
|
||
"fillColor": "#F8FAFC",
|
||
"borderColor": "#CBD5E1",
|
||
"borderWidth": 1,
|
||
"borderRadius": 16,
|
||
"layoutOptions": {
|
||
"rankdir": "LR",
|
||
"nodesep": 60,
|
||
"ranksep": 120,
|
||
"edges": [
|
||
["user", "k8s_ingress", "HTTPS request"],
|
||
["k8s_ingress", "web_pod", "Route UI"],
|
||
["k8s_ingress", "api_pod", "Route API"],
|
||
["web_pod", "api_pod", "Internal REST"],
|
||
["api_pod", "db_cluster", "SQL Query"],
|
||
["api_pod", "ai_service", "gRPC Stream"]
|
||
]
|
||
},
|
||
"children": [
|
||
{
|
||
"type": "ellipse", "id": "user", "text": "Global Users",
|
||
"width": 110, "height": 60,
|
||
"fillColor": "#E2E8F0", "borderColor": "#64748B", "borderWidth": 1,
|
||
"fontSize": 14, "textColor": "#334155"
|
||
},
|
||
{
|
||
"type": "frame", "id": "zone_k8s",
|
||
"layout": "dagre",
|
||
"layoutOptions": {
|
||
"isCluster": true,
|
||
"clusterTitle": "☸️ Kubernetes Zone (isCluster)",
|
||
"clusterTitleColor": "#2563EB"
|
||
},
|
||
"fillColor": "#EFF6FF", "borderColor": "#60A5FA",
|
||
"borderWidth": 2, "borderDash": "dashed", "borderRadius": 24,
|
||
"children": [
|
||
{
|
||
"type": "diamond", "id": "k8s_ingress", "text": "Nginx Ingress",
|
||
"width": 130, "height": 70,
|
||
"fillColor": "#DBEAFE", "borderColor": "#3B82F6", "borderWidth": 2,
|
||
"textColor": "#1E40AF"
|
||
},
|
||
{
|
||
"type": "rect", "id": "web_pod", "text": "Next.js SSR Pod",
|
||
"width": 140, "height": 48,
|
||
"fillColor": "#BFDBFE", "borderColor": "#2563EB", "borderWidth": 2,
|
||
"borderRadius": 8, "textColor": "#1E3A8A"
|
||
},
|
||
{
|
||
"type": "rect", "id": "api_pod", "text": "Go Lang API Pod",
|
||
"width": 140, "height": 48,
|
||
"fillColor": "#BFDBFE", "borderColor": "#2563EB", "borderWidth": 2,
|
||
"borderRadius": 8, "textColor": "#1E3A8A"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"type": "frame", "id": "db_cluster",
|
||
"layout": "vertical", "gap": 16, "padding": [20, 24],
|
||
"alignItems": "center",
|
||
"fillColor": "#F0FDF4", "borderColor": "#22C55E",
|
||
"borderWidth": 2, "borderRadius": 16,
|
||
"children": [
|
||
{
|
||
"type": "text", "id": "db_title",
|
||
"text": "🗄️ Highly Available DB (不透明)", "fontSize": 14, "textColor": "#14532D"
|
||
},
|
||
{
|
||
"type": "frame", "id": "db_row", "layout": "horizontal", "gap": 20,
|
||
"children": [
|
||
{
|
||
"type": "cylinder", "id": "db_master", "text": "Master",
|
||
"width": 80, "height": 50,
|
||
"fillColor": "#DCFCE7", "borderColor": "#16A34A", "borderWidth": 1,
|
||
"textColor": "#166534"
|
||
},
|
||
{
|
||
"type": "cylinder", "id": "db_replica", "text": "Replica",
|
||
"width": 80, "height": 50,
|
||
"fillColor": "#DCFCE7", "borderColor": "#16A34A", "borderWidth": 1,
|
||
"textColor": "#166534"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"type": "frame", "id": "ai_service",
|
||
"layout": "vertical", "gap": 10, "padding": [16, 20],
|
||
"alignItems": "center",
|
||
"fillColor": "#FAF5FF", "borderColor": "#A855F7",
|
||
"borderWidth": 2, "borderRadius": 12,
|
||
"children": [
|
||
{
|
||
"type": "text", "id": "ai_title",
|
||
"text": "🧠 Multi-Modal Engine (不透明)", "fontSize": 14, "textColor": "#6B21A8"
|
||
},
|
||
{
|
||
"type": "rect", "id": "ai_version",
|
||
"text": "v4.2.1-beta", "width": 90, "height": 22,
|
||
"fillColor": "#E9D5FF", "borderColor": "#C084FC", "borderWidth": 1,
|
||
"borderRadius": 4, "fontSize": 11, "textColor": "#581C87"
|
||
},
|
||
{
|
||
"type": "text", "id": "ai_desc",
|
||
"text": "Includes Vector Store\n& Transformer Blocks",
|
||
"fontSize": 12, "textColor": "#7E22CE", "textAlign": "center"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**范本要点**:
|
||
- `zone_k8s` 是**透明子图**(`isCluster: true` + `clusterTitle`),外部连线穿越虚线边界直达 `k8s_ingress`、`web_pod`、`api_pod`。
|
||
- `db_cluster` 和 `ai_service` 是**不透明节点**(`layout: "vertical"`),内部用 Flex 组合了多行结构化信息,对外层 Dagre 是固定宽高的原子。连线只能连到外壳 ID。
|
||
- 所有 `edges` 统一写在最外层根 Dagre 的 `layoutOptions` 中。
|
||
- 本范本中用到了 `ellipse`(外部实体)、`diamond`(路由判断)、`rect`(业务节点)、`cylinder`(数据库存储)四种形状。
|
||
|
||
## 陷阱与常见报错防范
|
||
|
||
- **误用 Mermaid**:只要用户没有带 `mermaid` 具体语法代码,哪怕描述明确是"流程图",也**强制使用 DSL 框架下的 Dagre 模式**。
|
||
- **重复画线**:`dagre` 里的所有子节点关系通过 `edges` 定义,引擎会自动生成连线。**绝对不要再去外层用 `connector` 节点重复连一次**。
|
||
- **穿透黑盒**:普通子容器是不透明节点,外部连线无法直接寻址其内部子节点(引擎会自动重定向至外壳)。若需穿透,必须声明 `layout: "dagre"` 与 `layoutOptions: { isCluster: true }`。
|
||
- **`id` 缺失**:只要是在 `edges` 里出现的标识符,`children` 里一定能找到同名 `id` 的节点对应,拼写必须完全一致。
|
||
- **宽度灾难**:Dagre 内容器禁止子框使用 `fill-container`,因为 dagre 父容器本身是被内容撑开的。
|