Appearance
模块:State Management
唯一职责:管理全局应用状态(画布列表、画布编辑状态、模型缓存),持久化到 localStorage。
边界
属于本模块:
- 画布列表的 CRUD(canvasStore)
- 画布数据的 localStorage 持久化
- Vue Flow 节点/边的状态管理和持久化同步(flowStore)
- 连线校验纯函数(flowValidation)
- 模型列表的异步获取与缓存(modelsStore)
不属于本模块:
- Vue Flow 实例的渲染和交互事件(由 Vue Flow 库 + Canvas Editor 负责)
- AI API 的 HTTP 调用(由 AI Service 负责)
- 组件级局部状态
对外接口
canvasStore
ts
interface CanvasStoreAPI {
// 状态
canvases: Ref<Canvas[]>
currentCanvas: Ref<Canvas | null>
// 方法
loadAllCanvases: () => void // 从 localStorage 加载
createCanvas: (name?: string) => Canvas // 创建并持久化
deleteCanvas: (id: string) => void // 删除并持久化
loadCanvas: (id: string) => Canvas | null // 加载到 currentCanvas
renameCanvas: (id: string, name: string) => void
saveCanvas: () => void // 持久化 currentCanvas
}flowStore
ts
interface FlowStoreAPI {
// 状态(只读)
nodes: Readonly<Ref<Node[]>>
edges: Readonly<Ref<Edge[]>>
// 方法
addNode: (type: NodeType, position: { x: number, y: number }) => Node
removeNode: (id: string) => void
updateNodeData: (id: string, data: Partial<CanvasNode['data']>) => void
addEdge: (edge: Omit<CanvasEdge, 'id'>) => CanvasEdge | null // null = 验证失败
addBatchEdges: (memberIds: string[], targetNodeId: string, handleType: HandleType) => CanvasEdge[] // 批量创建,逐条验证,单次 sync
removeEdge: (id: string) => void
loadFromCanvas: () => void // 从 currentCanvas 加载到 Vue Flow
syncToCanvas: () => void // Vue Flow → currentCanvas → localStorage
validateEdge: (newEdge: Omit<CanvasEdge, 'id'>) => EdgeValidationResult
}modelsStore
ts
interface ModelsStoreAPI {
fetchModels: (modality: ModelModality) => Promise<void> // 带缓存
getModels: (modality: ModelModality) => AiModelInfo[]
getAiModels: (modality: ModelModality) => AiModel[] // 简化视图
getDefaultModelKey: (modality: ModelModality) => string | undefined
isLoading: (modality: ModelModality) => boolean
getError: (modality: ModelModality) => string | undefined
}flowValidation(纯函数)
typescript
function validateEdge(edges, newEdge): EdgeValidationResult
function wouldCreateCycle(edges, source, target): boolean
interface EdgeValidationResult {
valid: boolean
reason?: 'self-connection' | 'cycle' | 'duplicate' | 'invalid-handle-direction' | 'uploaded-target'
}状态同步流
用户操作 → Vue Flow 内部状态变更
↓ (onNodeDragStop / onMoveEnd / onEdgesChange)
flowStore.syncToCanvas()
↓
canvasStore.currentCanvas 更新
↓
canvasStore.saveCanvas()
↓
localStorage 写入不变量
canvasStore.canvases与 localStorage 始终同步(每次写操作后立即persistAll)flowStore.addEdge/addBatchEdges在添加前必须通过validateEdge,验证失败返回null/ 跳过syncToCanvas过滤 group node(type === 'group')和关联边,不持久化到 localStoragemodelsStore.fetchModels对同一 modality 不重复请求(缓存 + loading 防重)- 节点 ID 在同一 Canvas 内唯一(由
crypto.randomUUID()保证)
错误场景
| 场景 | 模块行为 | 调用方职责 |
|---|---|---|
localStorage 读取时 JSON.parse 失败(数据损坏) | 返回空数据(空画布列表/空节点),不抛异常 | 页面展示空状态或错误提示 |
| localStorage 写入时配额已满(QuotaExceededError) | saveCanvas() / persistAll() 抛出异常 | 调用方捕获异常并提示用户 |
loadCanvas(id) 传入不存在的 id | 返回 null,currentCanvas 不变 | Canvas Editor 显示错误页面 |
fetchModels() 网络请求失败 | getError(modality) 返回错误信息,isLoading 恢复为 false | 消费方读取 getError 展示提示 |
syncToCanvas() 在 currentCanvas 为 null 时调用 | 静默忽略,不执行同步 | 无需处理 |
| 多标签页并发修改同一画布 | 不做冲突检测,后写入的覆盖先写入的(last-write-wins) | 当前不支持协作,无需处理 |
实现位置
| 角色 | 文件路径 |
|---|---|
| canvasStore | apps/web/stores/canvasStore.ts |
| flowStore | apps/web/stores/flowStore.ts |
| modelsStore | apps/web/stores/modelsStore.ts |
| flowValidation | apps/web/stores/flowValidation.ts |
| 测试 | apps/web/test/unit/flowValidation.test.ts |
| 测试 | apps/web/test/nuxt/flowStore.test.ts |
| 测试 | apps/web/test/nuxt/canvasStore.test.ts |