diff --git a/frontend/src/components/canvas/CanvasNodeView.vue b/frontend/src/components/canvas/CanvasNodeView.vue index a59d6fa..4c1b7f1 100644 --- a/frontend/src/components/canvas/CanvasNodeView.vue +++ b/frontend/src/components/canvas/CanvasNodeView.vue @@ -99,6 +99,8 @@ defineExpose({ startEdit, commitNote }) :data-node-id="node.id" @dblclick="onSelfDblClick" > + + @@ -375,7 +506,6 @@ defineExpose({ width: 1px; height: 1px; overflow: visible; - pointer-events: none; } .cv-edge { fill: none; @@ -386,6 +516,25 @@ defineExpose({ stroke: #7c6cff; stroke-width: 2; } +.cv-edge--sel { + stroke: #ff7b8f; + stroke-width: 2.5; + filter: drop-shadow(0 0 3px rgb(255 123 143 / 0.6)); +} +.cv-edge-hit { + fill: none; + stroke: transparent; + stroke-width: 16; + pointer-events: stroke; + cursor: pointer; +} +.cv-linkline { + fill: none; + stroke: #a99dff; + stroke-width: 2; + stroke-dasharray: 7 6; + pointer-events: none; +} .cv-marquee { position: absolute; border: 1px solid #7c6cff; diff --git a/frontend/src/views/CanvasView.vue b/frontend/src/views/CanvasView.vue index 069bca0..fec013f 100644 --- a/frontend/src/views/CanvasView.vue +++ b/frontend/src/views/CanvasView.vue @@ -323,14 +323,28 @@ function viewportCenterWorld() { } function deleteSelection() { - if (!selectionIds.value.length) return const dead = new Set(selectionIds.value) - doc.nodes = doc.nodes.filter((n) => !dead.has(n.id)) - doc.edges = doc.edges.filter((e) => !dead.has(e.from) && !dead.has(e.to)) + const deadEdges = stage.value?.consumeSelectedEdges() || [] + if (!dead.size && !deadEdges.length) return + if (dead.size) { + doc.nodes = doc.nodes.filter((n) => !dead.has(n.id)) + doc.edges = doc.edges.filter((e) => !dead.has(e.from) && !dead.has(e.to)) + } + if (deadEdges.length) { + const gone = new Set(deadEdges) + doc.edges = doc.edges.filter((e) => !gone.has(e.id)) + } selectionIds.value = [] pushHistory() } +// ---- 手动连线 ---- +function onLinkCreate({ from, to }) { + if (doc.edges.some((e) => e.from === from && e.to === to)) return // 去重 + doc.edges.push({ id: newId('e'), from, to }) + pushHistory() +} + // ---- 画布管理 ---- async function switchCanvas(id) { @@ -557,6 +571,7 @@ function onKeydown(e) { e.preventDefault() deleteSelection() } else if (e.key === 'Escape') { + stage.value?.cancelLink() stage.value?.clearSelection() previewSrc.value = '' pickerOpen.value = false @@ -660,6 +675,7 @@ const saveLabel = computed(() => ({ :edges="doc.edges" @selection="onSelection" @move-end="onMoveEnd" + @link-create="onLinkCreate" @note-commit="onNoteCommit" @img-size="onImgSize" @viewport-rest="onViewportRest" @@ -672,11 +688,14 @@ const saveLabel = computed(() => ({ @@ -934,6 +953,14 @@ const saveLabel = computed(() => ({ color: #9aa1b2; word-break: break-all; } +.cv-nomodels { + font-size: 11px; + color: #d8a657; + line-height: 1.6; + border: 1px dashed rgb(216 166 87 / 0.4); + border-radius: 8px; + padding: 8px 10px; +} .cv-tips { font-size: 11px; color: #5f6675;