diff --git a/src/shared/components/FlowchartEditor/CustomNode.vue b/src/shared/components/FlowchartEditor/CustomNode.vue index 6795875..4221103 100644 --- a/src/shared/components/FlowchartEditor/CustomNode.vue +++ b/src/shared/components/FlowchartEditor/CustomNode.vue @@ -28,7 +28,6 @@ @keydown.enter="handleSaveEdit" @keydown.escape="handleCancelEdit" @click.stop - @focusout="handleSaveEdit" /> @@ -96,25 +95,19 @@ const displayLabel = computed( const handleDelete = () => emit("delete", props.id) const handleMouseDown = (event: MouseEvent) => { - // 检查是否点击在连线点区域 const target = event.target as HTMLElement if (target.closest(".vue-flow__handle")) { - // 如果在连线点区域,禁用节点拖拽 event.preventDefault() - return false } } const handleDragStart = (event: DragEvent) => { - if (isEditing.value) { - return - } + if (isEditing.value) return - // 检查是否在连线点区域开始拖拽 const target = event.target as HTMLElement if (target.closest(".vue-flow__handle")) { event.preventDefault() - return false + return } if (event.dataTransfer) { @@ -137,10 +130,7 @@ const handleDoubleClick = (event: MouseEvent) => { const handleSaveEdit = () => { if (isEditing.value) { - // 保存编辑的文本 - if (editText.value.trim()) { - emit("update", props.id, editText.value.trim()) - } + emit("update", props.id, editText.value.trim()) isEditing.value = false removeGlobalClickHandler() } diff --git a/src/shared/components/FlowchartEditor/index.vue b/src/shared/components/FlowchartEditor/index.vue index 90acce5..d157149 100644 --- a/src/shared/components/FlowchartEditor/index.vue +++ b/src/shared/components/FlowchartEditor/index.vue @@ -31,7 +31,7 @@ withDefaults(defineProps(), { }) // Vue Flow 实例 -const { addNodes, addEdges, removeNodes, removeEdges } = useVueFlow() +const { addEdges, removeNodes, removeEdges } = useVueFlow() // 节点和边的响应式数据 const nodes = ref([]) @@ -42,7 +42,12 @@ const { canUndo, canRedo, saveState, undo, redo } = useHistory() const problemStore = useProblemStore() const { problem } = storeToRefs(problemStore) -// 缓存管理 +// 缓存管理:用 computed key 支持题目 ID 异步加载后自动切换到正确的 storage +const cacheKey = computed(() => + problem.value?._id + ? `flowchart-editor-data-problem-${problem.value!._id}` + : "flowchart-editor-data", +) const { isSaving, lastSaved, @@ -50,13 +55,7 @@ const { saveToCache, loadFromCache, clearCache, -} = useCache( - nodes, - edges, - problem.value?._id - ? `flowchart-editor-data-problem-${problem.value!._id}` - : "flowchart-editor-data", -) +} = useCache(nodes, edges, cacheKey) // 拖拽处理 const { onDragOver, onDragLeave, onDrop, isDragOver, screenDragPos } = useDnD() @@ -84,26 +83,16 @@ const { } = useFlowOperations( nodes, edges, - addNodes, addEdges, removeNodes, removeEdges, saveState, ) -// 拖拽处理包装 -const handleDragOver = (event: DragEvent) => { - onDragOver(event) -} - -const handleDragLeave = () => { - onDragLeave() -} - -const handleDrop = (event: DragEvent) => { - // 处理正常的节点创建拖拽 +const handleDrop = async (event: DragEvent) => { const newNode = onDrop(event) if (newNode) { + await nextTick() saveState(nodes.value, edges.value) } } @@ -219,8 +208,8 @@ defineExpose({ , + edges: Ref, + storageKey: MaybeRefOrGetter = "flowchart-editor-data", ) { const isSaving = ref(false) const lastSaved = ref(null) const hasUnsavedChanges = ref(false) - // 使用 useStorage 管理数据存储 + // 使用 useStorage 管理数据存储,支持响应式 key(题目 ID 异步加载时自动切换) const storedData = useStorage<{ nodes: Node[] edges: Edge[] @@ -25,9 +25,8 @@ export function useCache( timestamp: "", }) - // 防抖保存 + // 防抖保存:isSaving 在 watch 中置 true,保存完成后置 false,使 UI 能感知保存中状态 const debouncedSave = useDebounceFn(() => { - isSaving.value = true storedData.value.nodes = nodes.value storedData.value.edges = edges.value storedData.value.timestamp = new Date().toISOString() @@ -68,11 +67,12 @@ export function useCache( hasUnsavedChanges.value = false } - // 监听节点和边的变化 + // 监听节点和边的变化,isSaving 在此置 true 以覆盖防抖等待窗口 watch( [nodes, edges], () => { hasUnsavedChanges.value = true + isSaving.value = true debouncedSave() }, { deep: true }, diff --git a/src/shared/components/FlowchartEditor/useFlowOperations.ts b/src/shared/components/FlowchartEditor/useFlowOperations.ts index 6d6ad50..e9d41d4 100644 --- a/src/shared/components/FlowchartEditor/useFlowOperations.ts +++ b/src/shared/components/FlowchartEditor/useFlowOperations.ts @@ -1,16 +1,17 @@ import type { Ref } from "vue" import type { Node, Edge, Connection } from "@vue-flow/core" +import { useVueFlow } from "@vue-flow/core" import { getRandomId } from "utils/functions" export function useFlowOperations( nodes: Ref, edges: Ref, - addNodes: (nodes: Node[]) => void, addEdges: (edges: Edge[]) => void, removeNodes: (nodeIds: string[]) => void, removeEdges: (edgeIds: string[]) => void, saveState: (nodes: Node[], edges: Edge[]) => void, ) { + const { findNode, getSelectedNodes, getSelectedEdges } = useVueFlow() const getAutoLabel = ( sourceNode: Node | undefined, targetNode: Node | undefined, @@ -95,23 +96,16 @@ export function useFlowOperations( saveState(nodes.value, edges.value) } - // 节点更新 + // 节点更新,空标签时清除自定义标签(恢复默认类型名称) const handleNodeUpdate = (nodeId: string, newLabel: string) => { - const nodeIndex = nodes.value.findIndex((node) => node.id === nodeId) - - if (nodeIndex !== -1) { - const oldNode = nodes.value[nodeIndex] - - // 创建新的节点对象以确保响应式更新 - const updatedNode = { - ...oldNode, - data: { - ...oldNode.data, - customLabel: newLabel, - }, + const node = findNode(nodeId) + if (node) { + if (newLabel) { + node.data = { ...node.data, customLabel: newLabel } + } else { + const { customLabel: _, ...rest } = node.data + node.data = rest } - - nodes.value[nodeIndex] = updatedNode saveState(nodes.value, edges.value) } } @@ -125,8 +119,8 @@ export function useFlowOperations( // 删除选中的节点和边 const deleteSelected = () => { - const selectedNodes = nodes.value.filter((node) => (node as any).selected) - const selectedEdges = edges.value.filter((edge) => (edge as any).selected) + const selectedNodes = getSelectedNodes.value + const selectedEdges = getSelectedEdges.value if (selectedNodes.length > 0) { removeNodes(selectedNodes.map((node) => node.id))