fix: 社区点击自动跳转步骤4 + 边显示 + 区分节点样式

修复:
- loadClusterDetail: 无map时自动goStep(4)并等待初始化
- focusClusterOnMap: 边已正确添加到_clusterFocusLayer
- renderMapData: 集群聚焦时保留_clusterFocusLayer
- 节点样式按度数区分: 高流量(>20对端)大圆深色, 孤立节点小圆浅色
- 节点提示显示对端数和服务器标签
- clearClusterFocus简化,去除无用_clusterNodeLayer

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
TianXuan Developer
2026-07-24 20:59:46 +08:00
parent cd68d7c9af
commit eb5758b77b
@@ -1432,10 +1432,19 @@ function renderMapData() {
if (SA.hiddenClusters.has(n.cluster_id)) return;
const color = getSubnetColor(n.ip);
// Larger circles, easy to click
const peerCount = n.peer_count || 0;
// High-degree servers: larger, brighter
// Singletons: smaller, dimmer
const isHighDeg = peerCount > 20;
const isSingleton = peerCount <= 1;
const radius = isHighDeg ? 10 : (isSingleton ? 4 : 7);
const fillOpacity = isHighDeg ? 0.95 : (isSingleton ? 0.5 : 0.85);
const weight = isHighDeg ? 3 : (isSingleton ? 1 : 2);
const borderColor = isHighDeg ? '#222' : (isSingleton ? '#ccc' : '#fff');
const circle = L.circleMarker([n.lat, n.lon], {
radius: 7, fillColor: color, color: '#fff', weight: 2,
opacity: 0.95, fillOpacity: 0.85,
radius, fillColor: color, color: borderColor, weight,
opacity: 0.95, fillOpacity,
interactive: true, bubblingMouseEvents: false,
});
circle.bindTooltip(buildNodeTooltip(n), {sticky: true, direction: 'top', offset: [0, -8]});
@@ -1476,14 +1485,21 @@ function renderMapData() {
SA.pointLayer = L.layerGroup(markers).addTo(SA.map);
SA.centerLayer = L.layerGroup(centerMarkers).addTo(SA.map);
renderLegend();
// Restore cluster focus if active
if (SA._clusterFocus && _clusterFocusLayer && !SA.map.hasLayer(_clusterFocusLayer)) {
_clusterFocusLayer.addTo(SA.map);
}
}
function buildNodeTooltip(n) {
const p = n.ip ? n.ip.split('.') : [];
const sn = p.length >= 3 ? p[0]+'.'+p[1]+'.'+p[2]+'.0/24' : '';
const srv = n.is_server ? ' 🖥' : '';
return `<div class="sa-point-tooltip"><b>${escapeHtml(n.ip)}</b>${srv}<br>` +
const pc = n.peer_count || 0;
const label = pc > 20 ? ' 🔥服务器' : (n.is_server ? ' 🖥' : '');
const degHint = pc > 0 ? `<span style="color:#e76f51;">${pc} 对端</span><br>` : '';
return `<div class="sa-point-tooltip"><b>${escapeHtml(n.ip)}</b>${label}<br>` +
(n.host ? `<span style="color:#2a9d8f;">${escapeHtml(n.host)}</span><br>` : '') +
degHint +
(sn ? `<span style="color:#888;">子网: ${sn}</span><br>` : '') +
`${escapeHtml(n.city||'')}, ${escapeHtml(n.country||'')}<br>` +
(n.isp ? `<span style="color:#666;">${escapeHtml(n.isp)}</span><br>` : '') +
@@ -2184,8 +2200,28 @@ function loadClusterDetail(label) {
.then(data => {
hideLoading();
if (data.error) { showError(data.error); return; }
focusClusterOnMap(data);
showClusterDetailPanelV2(data);
// Ensure map exists — go to step 4 if needed
if (!SA.map) {
goStep(4);
// Wait for map to finish initializing, then focus
let attempts = 0;
function waitThenFocus() {
if (SA.map) {
focusClusterOnMap(data);
showClusterDetailPanelV2(data);
} else if (attempts < 30) {
attempts++;
setTimeout(waitThenFocus, 200);
} else {
// Fallback: just show panel without map focus
showClusterDetailPanelV2(data);
}
}
setTimeout(waitThenFocus, 500);
} else {
focusClusterOnMap(data);
showClusterDetailPanelV2(data);
}
})
.catch(err => { hideLoading(); showError('加载失败: ' + err.message); });
}
@@ -2320,8 +2356,8 @@ function focusClusterOnMap(data) {
function clearClusterFocus() {
if (_clusterFocusLayer && SA.map) { SA.map.removeLayer(_clusterFocusLayer); _clusterFocusLayer = null; }
if (_clusterNodeLayer && SA.map) { SA.map.removeLayer(_clusterNodeLayer); _clusterNodeLayer = null; }
SA._clusterFocus = null;
SA._highlightedNodeEdges = null;
// Restore normal markers
if (SA.map && SA.nodes && SA.nodes.length) {