fix(canvas): 连接点外置、拖线平滑跟手、图片失败显示状态码
This commit is contained in:
@@ -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 })
|
||||
<button title="置顶" @click.stop="emit('node-action', { type: 'front' })">⤒</button>
|
||||
<button class="cv-tools__del" title="删除" @click.stop="emit('node-action', { type: 'delete' })">✕</button>
|
||||
</div>
|
||||
<div class="cv-node__clip">
|
||||
<!-- 图片节点 -->
|
||||
<template v-if="node.type === 'image'">
|
||||
<img
|
||||
@@ -153,7 +159,7 @@ defineExpose({ startEdit, commitNote })
|
||||
@load="onImgLoad"
|
||||
@error="onImgError"
|
||||
>
|
||||
<div v-else class="cv-node__missing">图片不可用</div>
|
||||
<div v-else class="cv-node__missing">图片不可用<small v-if="imgStatus">HTTP {{ imgStatus }}</small></div>
|
||||
<div v-if="node.prompt" class="cv-node__cap" :title="node.prompt">{{ node.prompt }}</div>
|
||||
</template>
|
||||
|
||||
@@ -180,6 +186,7 @@ defineExpose({ startEdit, commitNote })
|
||||
<span class="cv-node__err" :title="node.error || ''">{{ node.error || '请检查提示词后重试' }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user