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