|
|
@@ -961,8 +961,10 @@ function showLockButton(nodeEl, immediate = false) {
|
|
|
|
|
|
// 创建按钮的函数
|
|
|
const createBtn = () => {
|
|
|
- // 先清除其他节点的按钮
|
|
|
- d3.selectAll('.lock-btn').remove()
|
|
|
+ // 先清除当前 SVG 内其他节点的按钮(不影响另一边)
|
|
|
+ if (svgRef.value) {
|
|
|
+ d3.select(svgRef.value).selectAll('.lock-btn').remove()
|
|
|
+ }
|
|
|
|
|
|
const isLocked = !!store.lockedHoverNodeId
|
|
|
|
|
|
@@ -1025,7 +1027,10 @@ function hideLockButton() {
|
|
|
clearTimeout(showButtonTimer)
|
|
|
showButtonTimer = null
|
|
|
}
|
|
|
- d3.selectAll('.lock-btn').interrupt().remove()
|
|
|
+ // 只清除当前 SVG 内的按钮
|
|
|
+ if (svgRef.value) {
|
|
|
+ d3.select(svgRef.value).selectAll('.lock-btn').interrupt().remove()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 处理锁定按钮点击
|
|
|
@@ -1033,16 +1038,15 @@ function handleLockClick() {
|
|
|
const startNodeId = store.selectedNodeId
|
|
|
if (store.lockedHoverNodeId) {
|
|
|
store.clearLockedHover()
|
|
|
- hideLockButton()
|
|
|
+ // 清除所有锁定按钮(两边都清除)
|
|
|
+ d3.selectAll('.lock-btn').interrupt().remove()
|
|
|
} else if (startNodeId) {
|
|
|
store.lockCurrentHover(startNodeId)
|
|
|
- // 更新按钮显示
|
|
|
- const btn = d3.select('.lock-btn')
|
|
|
- if (!btn.empty()) {
|
|
|
- btn.interrupt()
|
|
|
- .text(' 🔓解锁')
|
|
|
- .attr('fill', '#f6ad55')
|
|
|
- }
|
|
|
+ // 更新所有锁定按钮状态(两边同步)
|
|
|
+ d3.selectAll('.lock-btn')
|
|
|
+ .interrupt()
|
|
|
+ .text(' 🔓解锁')
|
|
|
+ .attr('fill', '#f6ad55')
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1278,7 +1282,7 @@ watch(() => store.focusEdgeEndpoints, (endpoints) => {
|
|
|
watch(() => store.highlightedNodeIds.size, updateHighlight)
|
|
|
|
|
|
// 监听 hover 状态变化(用于左右联动)
|
|
|
-watch(() => store.hoverPathNodes.size, () => {
|
|
|
+watch([() => store.hoverPathNodes.size, () => store.hoverNodeId], () => {
|
|
|
if (!svgRef.value) return
|
|
|
const svg = d3.select(svgRef.value)
|
|
|
|
|
|
@@ -1294,9 +1298,21 @@ watch(() => store.hoverPathNodes.size, () => {
|
|
|
if (store.hoverSource === 'graph') {
|
|
|
zoomToPathNodes(store.hoverPathNodes)
|
|
|
}
|
|
|
+
|
|
|
+ // 在对应节点上显示锁定按钮(无论来源)
|
|
|
+ if (store.hoverNodeId) {
|
|
|
+ const nodeInfo = nodeElements[store.hoverNodeId]
|
|
|
+ if (nodeInfo?.element) {
|
|
|
+ showLockButton(nodeInfo.element)
|
|
|
+ }
|
|
|
+ }
|
|
|
} else {
|
|
|
// 清除 hover,恢复原有高亮
|
|
|
updateHighlight()
|
|
|
+ // 如果没有锁定,隐藏按钮
|
|
|
+ if (!store.lockedHoverNodeId) {
|
|
|
+ hideLockButton()
|
|
|
+ }
|
|
|
}
|
|
|
})
|
|
|
|
|
|
@@ -1358,6 +1374,27 @@ watch(() => store.selectedPostIndex, (newIdx) => {
|
|
|
selectedPostIdx.value = newIdx
|
|
|
})
|
|
|
|
|
|
+// 恢复锁定的 hover 状态(重新渲染后调用)
|
|
|
+function restoreLockedHover() {
|
|
|
+ if (!store.lockedHoverNodeId || !svgRef.value) return
|
|
|
+
|
|
|
+ const svg = d3.select(svgRef.value)
|
|
|
+ const allNodes = svg.selectAll('.tree-node, .match-node, .walked-node')
|
|
|
+ const allLinks = svg.selectAll('.tree-link, .match-link, .walked-link')
|
|
|
+ const allLabels = svg.selectAll('.match-score, .walked-score')
|
|
|
+
|
|
|
+ // 恢复高亮效果
|
|
|
+ if (store.hoverPathNodes.size > 0) {
|
|
|
+ applyHoverHighlight(allNodes, allLinks, allLabels, store.hoverPathNodes)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 恢复锁定按钮
|
|
|
+ const lockedNodeInfo = nodeElements[store.lockedHoverNodeId]
|
|
|
+ if (lockedNodeInfo?.element) {
|
|
|
+ showLockButton(lockedNodeInfo.element, true)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 监听布局变化,过渡结束后重新适应视图
|
|
|
function handleTransitionEnd(e) {
|
|
|
if (['width', 'height', 'flex', 'flex-grow', 'flex-shrink'].includes(e.propertyName)) {
|
|
|
@@ -1365,7 +1402,10 @@ function handleTransitionEnd(e) {
|
|
|
renderTree()
|
|
|
nextTick(() => {
|
|
|
renderWalkedLayer() // 重新渲染游走层
|
|
|
- nextTick(updateHighlight) // 重新应用高亮状态
|
|
|
+ nextTick(() => {
|
|
|
+ updateHighlight() // 重新应用高亮状态
|
|
|
+ restoreLockedHover() // 恢复锁定的 hover 状态
|
|
|
+ })
|
|
|
})
|
|
|
})
|
|
|
}
|