import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; import { devtools, persist } from 'zustand/middleware'; import type { CanvasNode, CanvasEdge, CanvasState, Viewport, ToolType, Position, Size, NodeType, } from '@/types/canvas'; // 生成唯一 ID const generateId = () => crypto.randomUUID(); // 默认视口 const defaultViewport: Viewport = { x: 0, y: 0, zoom: 1, }; // Store 状态接口 interface CanvasStore { // 项目信息 projectId: string | null; projectTitle: string; // 画布状态 nodes: CanvasNode[]; edges: CanvasEdge[]; selectedNodeIds: string[]; viewport: Viewport; tool: ToolType; // 历史记录(用于撤销/重做) history: CanvasState[]; historyIndex: number; // UI 状态 isPanelOpen: boolean; isGenerating: boolean; // 项目操作 setProject: (id: string | null, title?: string) => void; setProjectTitle: (title: string) => void; // 节点操作 addNode: (node: Omit) => string; updateNode: (id: string, updates: Partial) => void; deleteNode: (id: string) => void; deleteSelectedNodes: () => void; // 选择操作 selectNode: (id: string, multi?: boolean) => void; selectNodes: (ids: string[]) => void; clearSelection: () => void; selectAll: () => void; // 边操作 addEdge: (edge: Omit) => string; deleteEdge: (id: string) => void; // 视口操作 setViewport: (viewport: Partial) => void; zoomIn: () => void; zoomOut: () => void; zoomTo: (zoom: number) => void; fitToScreen: () => void; centerNode: (nodeId: string) => void; // 工具操作 setTool: (tool: ToolType) => void; // 历史操作 undo: () => void; redo: () => void; pushHistory: () => void; // UI 操作 togglePanel: () => void; setGenerating: (isGenerating: boolean) => void; // 批量操作 loadCanvas: (state: Partial) => void; clearCanvas: () => void; getCanvasState: () => CanvasState; // 节点位置操作 moveNode: (id: string, position: Position) => void; resizeNode: (id: string, size: Size) => void; // 辅助方法 getNode: (id: string) => CanvasNode | undefined; getSelectedNodes: () => CanvasNode[]; getNodesByType: (type: NodeType) => CanvasNode[]; } export const useCanvasStore = create()( devtools( persist( immer((set, get) => ({ // 初始状态 projectId: null, projectTitle: '未命名画布', nodes: [], edges: [], selectedNodeIds: [], viewport: defaultViewport, tool: 'select', history: [], historyIndex: -1, isPanelOpen: true, isGenerating: false, // 项目操作 setProject: (id, title) => set((state) => { state.projectId = id; if (title) state.projectTitle = title; }), setProjectTitle: (title) => set((state) => { state.projectTitle = title; }), // 节点操作 addNode: (nodeData) => { const id = generateId(); const now = new Date(); const node: CanvasNode = { ...nodeData, id, createdAt: now, updatedAt: now, }; set((state) => { state.nodes.push(node); }); // 添加到历史 get().pushHistory(); return id; }, updateNode: (id, updates) => set((state) => { const index = state.nodes.findIndex(n => n.id === id); if (index !== -1) { state.nodes[index] = { ...state.nodes[index], ...updates, updatedAt: new Date(), }; } }), deleteNode: (id) => { set((state) => { state.nodes = state.nodes.filter(n => n.id !== id); state.edges = state.edges.filter(e => e.source !== id && e.target !== id); state.selectedNodeIds = state.selectedNodeIds.filter(nid => nid !== id); }); get().pushHistory(); }, deleteSelectedNodes: () => { const { selectedNodeIds } = get(); set((state) => { state.nodes = state.nodes.filter(n => !selectedNodeIds.includes(n.id)); state.edges = state.edges.filter( e => !selectedNodeIds.includes(e.source) && !selectedNodeIds.includes(e.target) ); state.selectedNodeIds = []; }); get().pushHistory(); }, // 选择操作 selectNode: (id, multi = false) => set((state) => { if (multi) { const index = state.selectedNodeIds.indexOf(id); if (index === -1) { state.selectedNodeIds.push(id); } else { state.selectedNodeIds.splice(index, 1); } } else { state.selectedNodeIds = [id]; } }), selectNodes: (ids) => set((state) => { state.selectedNodeIds = ids; }), clearSelection: () => set((state) => { state.selectedNodeIds = []; }), selectAll: () => set((state) => { state.selectedNodeIds = state.nodes.map(n => n.id); }), // 边操作 addEdge: (edgeData) => { const id = generateId(); const edge: CanvasEdge = { ...edgeData, id }; set((state) => { // 检查是否已存在相同的边 const exists = state.edges.some( e => e.source === edge.source && e.target === edge.target ); if (!exists) { state.edges.push(edge); } }); return id; }, deleteEdge: (id) => set((state) => { state.edges = state.edges.filter(e => e.id !== id); }), // 视口操作 setViewport: (viewport) => set((state) => { state.viewport = { ...state.viewport, ...viewport }; }), zoomIn: () => set((state) => { state.viewport.zoom = Math.min(state.viewport.zoom * 1.2, 4); }), zoomOut: () => set((state) => { state.viewport.zoom = Math.max(state.viewport.zoom / 1.2, 0.1); }), zoomTo: (zoom) => set((state) => { state.viewport.zoom = Math.max(0.1, Math.min(zoom, 4)); }), fitToScreen: () => set((state) => { // TODO: 计算所有节点的边界并调整视口 state.viewport = { x: 0, y: 0, zoom: 1 }; }), centerNode: (nodeId) => { const node = get().getNode(nodeId); if (node) { set((state) => { state.viewport.x = -node.position.x - node.size.width / 2; state.viewport.y = -node.position.y - node.size.height / 2; }); } }, // 工具操作 setTool: (tool) => set((state) => { state.tool = tool; }), // 历史操作 undo: () => set((state) => { if (state.historyIndex > 0) { state.historyIndex -= 1; const prevState = state.history[state.historyIndex]; state.nodes = prevState.nodes; state.edges = prevState.edges; state.selectedNodeIds = prevState.selectedNodeIds; } }), redo: () => set((state) => { if (state.historyIndex < state.history.length - 1) { state.historyIndex += 1; const nextState = state.history[state.historyIndex]; state.nodes = nextState.nodes; state.edges = nextState.edges; state.selectedNodeIds = nextState.selectedNodeIds; } }), pushHistory: () => set((state) => { const currentState: CanvasState = { nodes: JSON.parse(JSON.stringify(state.nodes)), edges: JSON.parse(JSON.stringify(state.edges)), selectedNodeIds: [...state.selectedNodeIds], viewport: { ...state.viewport }, tool: state.tool, }; // 移除当前位置之后的历史 state.history = state.history.slice(0, state.historyIndex + 1); state.history.push(currentState); state.historyIndex = state.history.length - 1; // 限制历史记录数量 if (state.history.length > 50) { state.history = state.history.slice(-50); state.historyIndex = state.history.length - 1; } }), // UI 操作 togglePanel: () => set((state) => { state.isPanelOpen = !state.isPanelOpen; }), setGenerating: (isGenerating) => set((state) => { state.isGenerating = isGenerating; }), // 批量操作 loadCanvas: (canvasState) => set((state) => { if (canvasState.nodes) state.nodes = canvasState.nodes; if (canvasState.edges) state.edges = canvasState.edges; if (canvasState.selectedNodeIds) state.selectedNodeIds = canvasState.selectedNodeIds; if (canvasState.viewport) state.viewport = canvasState.viewport; if (canvasState.tool) state.tool = canvasState.tool; }), clearCanvas: () => set((state) => { state.nodes = []; state.edges = []; state.selectedNodeIds = []; state.viewport = defaultViewport; state.history = []; state.historyIndex = -1; }), getCanvasState: () => { const state = get(); return { nodes: state.nodes, edges: state.edges, selectedNodeIds: state.selectedNodeIds, viewport: state.viewport, tool: state.tool, }; }, // 节点位置操作 moveNode: (id, position) => set((state) => { const node = state.nodes.find(n => n.id === id); if (node) { node.position = position; node.updatedAt = new Date(); } }), resizeNode: (id, size) => set((state) => { const node = state.nodes.find(n => n.id === id); if (node) { node.size = size; node.updatedAt = new Date(); } }), // 辅助方法 getNode: (id) => get().nodes.find(n => n.id === id), getSelectedNodes: () => { const { nodes, selectedNodeIds } = get(); return nodes.filter(n => selectedNodeIds.includes(n.id)); }, getNodesByType: (type) => get().nodes.filter(n => n.type === type), })), { name: 'canvas-storage', partialize: (state) => ({ // 只持久化部分状态 projectId: state.projectId, projectTitle: state.projectTitle, }), } ), { name: 'canvas-store' } ) ); // 导出选择器 export const canvasSelectors = { nodes: (state: CanvasStore) => state.nodes, edges: (state: CanvasStore) => state.edges, selectedNodeIds: (state: CanvasStore) => state.selectedNodeIds, viewport: (state: CanvasStore) => state.viewport, tool: (state: CanvasStore) => state.tool, isGenerating: (state: CanvasStore) => state.isGenerating, hasSelection: (state: CanvasStore) => state.selectedNodeIds.length > 0, canUndo: (state: CanvasStore) => state.historyIndex > 0, canRedo: (state: CanvasStore) => state.historyIndex < state.history.length - 1, };