Appearance
Feature: 画布系统
一句话目标
用户在画布上管理节点和视口,构建 AI 内容生产流水线的空间容器。
行为约束
约束 1:画布初始化
前置条件: 用户从 Workspace 点击打开一个画布 行为: 系统加载画布数据(节点、边、视口),初始化画布引擎,预加载 text/image/video 三种模型列表 后置条件: 画布中显示所有已保存的节点和连线,视口恢复到上次位置
约束 2:画布不存在时回退
前置条件: 用户访问的 canvasId 在持久化存储中不存在 行为: 显示错误提示 后置条件: 用户可返回 Workspace
约束 3:节点创建 — 工具栏
前置条件: 画布已加载 行为: 用户点击工具栏 "+" 按钮,弹出节点类型对话框,选择类型后在画布中心创建节点 后置条件: 新节点出现在画布中,默认模型已填充,数据已持久化
约束 4:节点创建 — 双击空白区域
前置条件: 画布已加载 行为: 用户双击画布空白区域,在点击位置弹出浮动菜单,选择类型后在该位置创建节点 后置条件: 同约束 3
约束 5:视口与选区操作
前置条件: 画布已加载 行为: 用户可平移(双指滑动/中键拖拽)、缩放(滚轮/底部滑块,范围 0.05~2.0)、框选(左键在空白区域拖拽,部分覆盖即选中) 后置条件: 视口变更自动持久化
约束 6:节点删除
前置条件: 画布中有节点 行为: 用户点击节点删除按钮或选中后按 Delete 键 后置条件: 节点被删除,数据已持久化(关联连线的删除见 edge-system)
约束 7:右键上下文菜单
前置条件: 画布已加载 行为: 右键在不同区域弹出对应上下文菜单:节点上 → 复制节点、复制为副本、删除节点;空白区域 → 粘贴节点。复制/粘贴/副本的详细行为见 node-copy后置条件: 选择操作后执行对应行为
状态机
画布页面生命周期
| 状态 | 页面内容 | 用户操作 |
|---|---|---|
| loading | 加载画布数据 + 预加载模型列表 | 不可交互 |
| ready | 画布完整渲染,节点/连线/视口恢复 | 可创建/编辑/连线/缩放 |
| error | 显示错误提示 + 返回 Workspace 入口 | 仅可返回 |
Acceptance Criteria
[x] AC-01:从 Workspace 打开画布后,节点和连线正确渲染
[x] AC-02:通过工具栏 "+" 创建三种类型节点
[x] AC-03:双击空白区域弹出浮动菜单创建节点
[x] AC-04:缩放范围限制在 0.05~2.0
[x] AC-05:节点拖拽、视口平移后数据自动持久化
[~] AC-06:右键节点弹出上下文菜单,包含复制节点、复制为副本、删除节点 → 详见 node-copy
[~] AC-07:右键空白区域弹出上下文菜单,包含粘贴节点(剪贴板无数据时置灰)
连线相关 AC 已迁移至 edge-system
BDD Scenarios
gherkin
Feature: 画布系统
Scenario: 从 Workspace 打开画布
Given Workspace 中存在一个包含 2 个节点和 1 条连线的画布
When 用户点击该画布
Then 画布编辑器打开,显示 2 个节点和 1 条连线
And 视口恢复到上次保存的位置
Scenario: 打开不存在的画布
Given 用户访问一个不存在的 canvasId
Then 页面显示错误提示
And 用户可点击返回 Workspace
Scenario: 通过工具栏创建节点
Given 画布编辑器已打开
When 用户点击工具栏 "+" 按钮
And 选择 "文本" 节点类型
Then 画布中出现一个文本节点
And 节点数据已自动持久化
Scenario: 双击空白区域创建节点
Given 画布编辑器已打开
When 用户双击画布空白区域
Then 在双击位置弹出浮动菜单
When 选择 "图片" 节点类型
Then 在双击位置出现一个图片节点
Scenario: 删除节点
Given 画布中有一个文本节点
When 用户选中该节点并按 Delete 键
Then 该节点从画布中移除
And 关联的连线同步删除
And 变更已自动持久化
Scenario: 视口缩放限制
Given 画布编辑器已打开
When 用户通过滚轮将缩放调至最小
Then 缩放值不低于 0.05
When 用户通过滚轮将缩放调至最大
Then 缩放值不超过 2.0
Scenario: 视口平移与持久化
Given 画布编辑器已打开
When 用户拖拽画布背景进行平移
Then 视口位置发生变化
And 新的视口位置已自动持久化
Scenario: 右键节点弹出上下文菜单
Given 画布中有一个文本节点
When 用户在该节点上右键
Then 弹出上下文菜单
And 菜单包含复制节点、复制为副本、删除节点选项
Scenario: 上下文菜单执行删除
Given 画布中有一个文本节点
When 用户在该节点上右键
And 用户点击 "删除节点"
Then 该节点从画布中移除
Scenario: 右键空白区域弹出上下文菜单
Given 画布编辑器已打开
When 用户在画布空白区域右键
Then 弹出画布级上下文菜单TDD Unit Test Pointers
- [x]
canvasStore.createCanvas()— 生成合法 UUID、默认名称、空节点/边、默认视口(0,0,zoom=1)→test/nuxt/canvasStore.test.ts - [x]
canvasStore.loadCanvas(id)— 存在的 id 返回画布对象,不存在的 id 返回 null →test/nuxt/canvasStore.test.ts - [x]
flowStore.createDefaultNodeData(type)— 三种节点类型各返回正确的默认数据结构和默认模型(通过addNode间接覆盖)→test/nuxt/flowStore.test.ts - [x]
flowStore.addNode(type, position)— 创建的节点包含 UUID、指定位置、默认数据 →test/nuxt/flowStore.test.ts - [x] flowStore 是 graph 唯一 mutable owner,初始化通过 CONNECT 快照 →
test/nuxt/flowStore.test.ts - [x]
canvasStore.saveViewport(id, viewport)— 防抖 1s 后通过 HTTP PUT 保存视口到后端 →test/nuxt/canvasStore.test.ts - [x]
isCanvasBlank(target)— 点击画布空白返回 true,点击节点/handle/edge 返回 false →test/nuxt/isCanvasBlank.test.ts
Out of Scope
- 节点内部的编辑逻辑(见各节点 spec)
- 连线的创建、校验、渲染(见 edge-system)