diff --git a/frontend/src/components/canvas/CanvasNodeView.vue b/frontend/src/components/canvas/CanvasNodeView.vue index cfc2ab0..e16fdcf 100644 --- a/frontend/src/components/canvas/CanvasNodeView.vue +++ b/frontend/src/components/canvas/CanvasNodeView.vue @@ -16,6 +16,7 @@ const portLabels = { top: '上', right: '右', bottom: '下', left: '左' } const imgLoaded = ref(false) const imgBroken = ref(false) +const imgStatus = ref('') const imgThumb = computed(() => thumbUrl(props.node.src)) const imgOriginal = computed(() => generatedUrl(props.node.src)) const imgOriginalAbs = computed(() => { @@ -70,6 +71,10 @@ function onImgError(e) { } imgLoaded.value = true imgBroken.value = true + imgStatus.value = '' + fetch(imgOriginal.value, { credentials: 'include' }) + .then((r) => { imgStatus.value = String(r.status) }) + .catch(() => { imgStatus.value = '网络错误' }) } function startEdit() { @@ -140,6 +145,7 @@ defineExpose({ startEdit, commitNote }) +
@@ -180,6 +186,7 @@ defineExpose({ startEdit, commitNote }) {{ node.error || '请检查提示词后重试' }}
+ @@ -188,8 +195,7 @@ defineExpose({ startEdit, commitNote }) position: absolute; border-radius: 14px; border: 1px solid var(--glass-border); - overflow: hidden; - contain: layout style paint; + contain: layout style; user-select: none; touch-action: none; cursor: grab; @@ -197,6 +203,12 @@ defineExpose({ startEdit, commitNote }) box-shadow: var(--glass-shadow); transition: box-shadow 0.15s ease, border-color 0.15s ease; } +.cv-node__clip { + position: absolute; + inset: 0; + border-radius: 13px; + overflow: hidden; +} .cv-node.is-selected { border-color: rgb(124 92 252 / 0.7); box-shadow: var(--glass-shadow-lg), 0 0 0 3px rgb(124 92 252 / 0.25), 0 10px 30px var(--accent-glow); @@ -212,9 +224,12 @@ defineExpose({ startEdit, commitNote }) display: block; background: var(--surface-2); } +.cv-node__missing small { display: block; margin-top: 4px; font-weight: 500; opacity: 0.7; } .cv-node__missing { width: 100%; height: 100%; + align-content: center; + text-align: center; display: grid; place-items: center; color: var(--fg-3); @@ -341,10 +356,10 @@ html.dark .cv-node__note { color: rgb(255 255 255 / 0.88); } } .cv-port.is-active::after { background: var(--m-orange); } .cv-port:focus-visible { outline: 2px solid var(--m-orange); outline-offset: -2px; border-radius: 50%; } -.cv-port--top { top: 0; left: 50%; transform: translateX(-50%); } -.cv-port--right { right: 0; top: 50%; transform: translateY(-50%); } -.cv-port--bottom { bottom: 0; left: 50%; transform: translateX(-50%); } -.cv-port--left { left: 0; top: 50%; transform: translateY(-50%); } +.cv-port--top { top: -17px; left: 50%; transform: translateX(-50%); } +.cv-port--right { right: -17px; top: 50%; transform: translateY(-50%); } +.cv-port--bottom { bottom: -17px; left: 50%; transform: translateX(-50%); } +.cv-port--left { left: -17px; top: 50%; transform: translateY(-50%); } /* 右下角缩放方块(节点 contain:paint,只能放内侧) */ .cv-size { diff --git a/frontend/src/components/canvas/CanvasStage.vue b/frontend/src/components/canvas/CanvasStage.vue index 34af5d1..92ce3e3 100644 --- a/frontend/src/components/canvas/CanvasStage.vue +++ b/frontend/src/components/canvas/CanvasStage.vue @@ -55,10 +55,10 @@ const nodeById = computed(() => { }) function portPoint(n, side) { - if (side === 'top') return { x: n.x + n.w / 2, y: n.y + 16 } - if (side === 'right') return { x: n.x + n.w - 16, y: n.y + n.h / 2 } - if (side === 'bottom') return { x: n.x + n.w / 2, y: n.y + n.h - 16 } - if (side === 'left') return { x: n.x + 16, y: n.y + n.h / 2 } + if (side === 'top') return { x: n.x + n.w / 2, y: n.y } + if (side === 'right') return { x: n.x + n.w, y: n.y + n.h / 2 } + if (side === 'bottom') return { x: n.x + n.w / 2, y: n.y + n.h } + if (side === 'left') return { x: n.x, y: n.y + n.h / 2 } return { x: n.x + n.w / 2, y: n.y + n.h / 2 } } function portDirection(side, fallback) { @@ -186,7 +186,13 @@ const edgePaths = computed(() => { const c2 = shifted(p2, v2, d) let path if (manualBend) { - path = roundedPath([p1, shifted(p1, v1, 30), manualBend, shifted(p2, v2, 30), p2]) + const L = Math.hypot(p2.x - p1.x, p2.y - p1.y) || 1 + const tx = (p2.x - p1.x) / L + const ty = (p2.y - p1.y) / L + const d1 = Math.max(40, Math.hypot(manualBend.x - p1.x, manualBend.y - p1.y) * 0.5) + const d2 = Math.max(40, Math.hypot(p2.x - manualBend.x, p2.y - manualBend.y) * 0.5) + path = `M ${p1.x} ${p1.y} C ${p1.x + v1.x * d1} ${p1.y + v1.y * d1}, ${manualBend.x - tx * d1} ${manualBend.y - ty * d1}, ${manualBend.x} ${manualBend.y} ` + + `C ${manualBend.x + tx * d2} ${manualBend.y + ty * d2}, ${p2.x + v2.x * d2} ${p2.y + v2.y * d2}, ${p2.x} ${p2.y}` } else { path = `M ${p1.x} ${p1.y} C ${c1.x} ${c1.y}, ${c2.x} ${c2.y}, ${p2.x} ${p2.y}` const obstacles = props.nodes @@ -441,8 +447,13 @@ function applyMove(e) { clearSelection() } const cur = screenToWorld(p.x, p.y, vp) - act.edge.bendX = Math.round(act.bendX + cur.x - act.start.x) - act.edge.bendY = Math.round(act.bendY + cur.y - act.start.y) + const a = nodeById.value.get(act.edge.from) + const b = nodeById.value.get(act.edge.to) + if (!a || !b) return + const p1 = portPoint(a, act.edge.fromSide) + const p2 = portPoint(b, act.edge.toSide) + act.edge.bendX = Math.round(cur.x - (p1.x + p2.x) / 2) + act.edge.bendY = Math.round(cur.y - (p1.y + p2.y) / 2) return } if (act.mode === 'resize') {