Skip to content

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