fix: 完善同事未完成的修改 — LAN 地址检测/画布删除撤销/连线删除按钮/Del 键/注释修正

- cmd/webui4frpc/main.go: 新增 primaryLANIPv4() 检测内网 LAN 地址,
  解决三台内网主机间集群发现失败 (reachableAddr 只回退到 hostname)
- internal/httpapi/handlers.go: applyCanvas 新增拓扑差异检测,
  画布删除连线时自动 RevokeTask 避免集群残留
- web/src/api.ts: 新增 addLink / deleteLink API 封装
- web/src/components/PortEdge.vue: × 删除按钮移到端口标签右上角 (绝对定位)
- web/src/views/CanvasView.vue: 启用 Del/Backspace 键删除选中连线
  (确认对话框 + 走 onDeleteEdge 逻辑), 修正注释
- embed dist 同步
This commit is contained in:
JianFeeeee
2026-08-24 01:03:14 +08:00
parent b39bd427fa
commit 2292ee7f3a
10 changed files with 258 additions and 84 deletions

View File

@ -65,6 +65,7 @@
@label-drag="onLabelDrag"
@toggle-disabled="onToggleDisabled"
@edit-group="onEditGroup"
@delete-edge="onDeleteEdge"
/>
</template>
</VueFlow>
@ -466,6 +467,61 @@ const editEdge = (edge: Edge) => {
)
}
// onDeleteEdge removes an edge from the canvas after a confirm dialog.
// The removed link is persisted on the next save: applyCanvas (PUT /canvas)
// replaces links wholesale and revokes any cluster task for links that
// disappeared. The × badge on the edge label is the quick entry; the
// Delete/Backspace key (keydown handler below) removes SELECTED edges.
const onDeleteEdge = async ({ edgeId }: { edgeId: string }) => {
const e = edges.value.find((x) => x.id === edgeId)
if (!e) return
try {
await ElMessageBox.confirm('确认删除该连线?删除后需点击「保存配置」生效。', '删除连线', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return // cancelled
}
removeEdgesSilently([edgeId])
}
// onCanvasKeydown handles Delete/Backspace to drop all SELECTED edges after a
// single shared confirmation (VueFlow's own delete-key handling stays disabled
// via :delete-key-code="null" because we must keep removal behind the same
// confirm dialog; nodes have their own delete buttons).
const onCanvasKeydown = async (ev: KeyboardEvent) => {
if (ev.key !== 'Delete' && ev.key !== 'Backspace') return
const tag = (ev.target as HTMLElement | null)?.tagName
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return
if (!canWrite.value) return
const selectedIds = edges.value
.filter((x): x is Edge & { selected?: boolean } => Boolean((x as Edge & { selected?: boolean }).selected))
.map((x) => x.id)
if (selectedIds.length === 0) return
ev.preventDefault()
try {
await ElMessageBox.confirm(
`确认删除选中的 ${selectedIds.length} 条连线?删除后需点击「保存配置」生效。`,
'删除连线',
{ confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' },
)
} catch {
return // cancelled
}
removeEdgesSilently(selectedIds)
}
// removeEdgesSilently drops the given edge ids from the canvas without its own
// prompt (the callers already confirmed). Marks the canvas dirty.
const removeEdgesSilently = (ids: string[]) => {
const gone = new Set(ids)
edges.value = edges.value.filter((x) => !gone.has(x.id))
assignLayers()
dirty.value = true
}
// onEdgeLabelClick finds the edge by id and opens its port editor.
const onEdgeLabelClick = ({ edgeId }: { edgeId: string }) => {
const e = edges.value.find((x) => x.id === edgeId)
@ -815,8 +871,10 @@ onMounted(() => {
attributes: true,
attributeFilter: ['data-theme'],
})
window.addEventListener('keydown', onCanvasKeydown)
})
onBeforeUnmount(() => {
window.removeEventListener('keydown', onCanvasKeydown)
themeObserver?.disconnect()
themeObserver = null
})