+
-
+
diff --git a/frontend/public/earth/js/cables.js b/frontend/public/earth/js/cables.js
index be42a82c..8bdfa33f 100644
--- a/frontend/public/earth/js/cables.js
+++ b/frontend/public/earth/js/cables.js
@@ -11,6 +11,7 @@ export let cableLines = [];
export let landingPoints = [];
export let lockedCable = null;
let cableIdMap = new Map();
+let cablesVisible = true;
function getCableColor(properties) {
if (properties.color) {
@@ -406,3 +407,17 @@ export function resetLandingPointVisualState() {
lp.scale.setScalar(1.0);
});
}
+
+export function toggleCables(show) {
+ cablesVisible = show;
+ cableLines.forEach(cable => {
+ cable.visible = cablesVisible;
+ });
+ landingPoints.forEach(lp => {
+ lp.visible = cablesVisible;
+ });
+}
+
+export function getShowCables() {
+ return cablesVisible;
+}
diff --git a/frontend/public/earth/js/constants.js b/frontend/public/earth/js/constants.js
index 79a796b1..d6e846a6 100644
--- a/frontend/public/earth/js/constants.js
+++ b/frontend/public/earth/js/constants.js
@@ -39,11 +39,11 @@ export const CABLE_COLORS = {
};
export const CABLE_CONFIG = {
- lockedOpacityMin: 0.6,
+ lockedOpacityMin: 0.2,
lockedOpacityMax: 1.0,
otherOpacity: 0.5,
otherBrightness: 0.6,
- pulseSpeed: 0.003,
+ pulseSpeed: 0.008,
pulseCoefficient: 0.4
};
diff --git a/frontend/public/earth/js/controls.js b/frontend/public/earth/js/controls.js
index 45d9cdc0..6ef89950 100644
--- a/frontend/public/earth/js/controls.js
+++ b/frontend/public/earth/js/controls.js
@@ -3,8 +3,9 @@
import { CONFIG, EARTH_CONFIG } from './constants.js';
import { updateZoomDisplay, showStatusMessage } from './ui.js';
import { toggleTerrain } from './earth.js';
-import { reloadData } from './main.js';
+import { reloadData, clearLockedObject } from './main.js';
import { toggleSatellites, toggleTrails, getShowSatellites, getSatelliteCount } from './satellites.js';
+import { toggleCables, getShowCables } from './cables.js';
export let autoRotate = true;
export let zoomLevel = 1.0;
@@ -23,33 +24,18 @@ export function setupControls(camera, renderer, scene, earth) {
function setupZoomControls(camera) {
let zoomInterval = null;
- let lastDirection = 0;
- let isSnapped = false;
+ let holdTimeout = null;
+ let startTime = 0;
+ const HOLD_THRESHOLD = 150;
+ const LONG_PRESS_TICK = 50;
+ const CLICK_STEP = 10;
const MIN_PERCENT = CONFIG.minZoom * 100;
const MAX_PERCENT = CONFIG.maxZoom * 100;
- function adjustZoom(direction) {
- const currentPercent = Math.round(zoomLevel * 100);
- let newPercent;
-
- if (direction > 0) {
- if (!isSnapped || currentPercent % 10 !== 0) {
- newPercent = Math.ceil(currentPercent / 10) * 10;
- if (newPercent <= currentPercent) newPercent = currentPercent + 10;
- isSnapped = true;
- } else {
- newPercent = currentPercent + 10;
- }
- } else {
- if (!isSnapped || currentPercent % 10 !== 0) {
- newPercent = Math.floor(currentPercent / 10) * 10;
- if (newPercent >= currentPercent) newPercent = currentPercent - 10;
- isSnapped = true;
- } else {
- newPercent = currentPercent - 10;
- }
- }
+ function doZoomStep(direction) {
+ let currentPercent = Math.round(zoomLevel * 100);
+ let newPercent = direction > 0 ? currentPercent + CLICK_STEP : currentPercent - CLICK_STEP;
if (newPercent > MAX_PERCENT) newPercent = MAX_PERCENT;
if (newPercent < MIN_PERCENT) newPercent = MIN_PERCENT;
@@ -58,13 +44,22 @@ function setupZoomControls(camera) {
applyZoom(camera);
}
- function startZoom(direction) {
- isSnapped = false;
- lastDirection = direction;
- adjustZoom(direction);
+ function doContinuousZoom(direction) {
+ let currentPercent = Math.round(zoomLevel * 100);
+ let newPercent = direction > 0 ? currentPercent + 1 : currentPercent - 1;
+
+ if (newPercent > MAX_PERCENT) newPercent = MAX_PERCENT;
+ if (newPercent < MIN_PERCENT) newPercent = MIN_PERCENT;
+
+ zoomLevel = newPercent / 100;
+ applyZoom(camera);
+ }
+
+ function startContinuousZoom(direction) {
+ doContinuousZoom(direction);
zoomInterval = setInterval(() => {
- adjustZoom(direction);
- }, 150);
+ doContinuousZoom(direction);
+ }, LONG_PRESS_TICK);
}
function stopZoom() {
@@ -72,29 +67,49 @@ function setupZoomControls(camera) {
clearInterval(zoomInterval);
zoomInterval = null;
}
+ if (holdTimeout) {
+ clearTimeout(holdTimeout);
+ holdTimeout = null;
+ }
}
- document.getElementById('zoom-in').addEventListener('mousedown', () => startZoom(1));
- document.getElementById('zoom-in').addEventListener('mouseup', stopZoom);
- document.getElementById('zoom-in').addEventListener('mouseleave', stopZoom);
- document.getElementById('zoom-in').addEventListener('touchstart', (e) => { e.preventDefault(); startZoom(1); });
- document.getElementById('zoom-in').addEventListener('touchend', stopZoom);
+ function handleMouseDown(direction) {
+ startTime = Date.now();
+ stopZoom();
+ holdTimeout = setTimeout(() => {
+ startContinuousZoom(direction);
+ }, HOLD_THRESHOLD);
+ }
- document.getElementById('zoom-out').addEventListener('mousedown', () => startZoom(-1));
- document.getElementById('zoom-out').addEventListener('mouseup', stopZoom);
+ function handleMouseUp(direction) {
+ const heldTime = Date.now() - startTime;
+ stopZoom();
+ if (heldTime < HOLD_THRESHOLD) {
+ doZoomStep(direction);
+ }
+ }
+
+ document.getElementById('zoom-in').addEventListener('mousedown', () => handleMouseDown(1));
+ document.getElementById('zoom-in').addEventListener('mouseup', () => handleMouseUp(1));
+ document.getElementById('zoom-in').addEventListener('mouseleave', stopZoom);
+ document.getElementById('zoom-in').addEventListener('touchstart', (e) => { e.preventDefault(); handleMouseDown(1); });
+ document.getElementById('zoom-in').addEventListener('touchend', () => handleMouseUp(1));
+
+ document.getElementById('zoom-out').addEventListener('mousedown', () => handleMouseDown(-1));
+ document.getElementById('zoom-out').addEventListener('mouseup', () => handleMouseUp(-1));
document.getElementById('zoom-out').addEventListener('mouseleave', stopZoom);
- document.getElementById('zoom-out').addEventListener('touchstart', (e) => { e.preventDefault(); startZoom(-1); });
- document.getElementById('zoom-out').addEventListener('touchend', stopZoom);
+ document.getElementById('zoom-out').addEventListener('touchstart', (e) => { e.preventDefault(); handleMouseDown(-1); });
+ document.getElementById('zoom-out').addEventListener('touchend', () => handleMouseUp(-1));
document.getElementById('zoom-value').addEventListener('click', function() {
- const startZoom = zoomLevel;
+ const startZoomVal = zoomLevel;
const targetZoom = 1.0;
- const startDistance = CONFIG.defaultCameraZ / startZoom;
+ const startDistance = CONFIG.defaultCameraZ / startZoomVal;
const targetDistance = CONFIG.defaultCameraZ / targetZoom;
animateValue(0, 1, 600, (progress) => {
const ease = 1 - Math.pow(1 - progress, 3);
- zoomLevel = startZoom + (targetZoom - startZoom) * ease;
+ zoomLevel = startZoomVal + (targetZoom - startZoomVal) * ease;
camera.position.z = CONFIG.defaultCameraZ / zoomLevel;
const distance = startDistance + (targetDistance - startDistance) * ease;
updateZoomDisplay(zoomLevel, distance.toFixed(0));
@@ -103,12 +118,6 @@ function setupZoomControls(camera) {
showStatusMessage('缩放已重置到100%', 'info');
});
});
-
- const slider = document.getElementById('zoom-slider');
- slider?.addEventListener('input', (e) => {
- zoomLevel = parseFloat(e.target.value);
- applyZoom(camera);
- });
}
function setupWheelZoom(camera, renderer) {
@@ -219,6 +228,9 @@ function setupTerrainControls() {
document.getElementById('toggle-satellites').addEventListener('click', function() {
const showSats = !getShowSatellites();
+ if (!showSats) {
+ clearLockedObject();
+ }
toggleSatellites(showSats);
this.classList.toggle('active', showSats);
this.querySelector('.tooltip').textContent = showSats ? '隐藏卫星' : '显示卫星';
@@ -235,6 +247,17 @@ function setupTerrainControls() {
showStatusMessage(showTrails ? '轨迹已显示' : '轨迹已隐藏', 'info');
});
+ document.getElementById('toggle-cables').addEventListener('click', function() {
+ const showCables = !getShowCables();
+ if (!showCables) {
+ clearLockedObject();
+ }
+ toggleCables(showCables);
+ this.classList.toggle('active', showCables);
+ this.querySelector('.tooltip').textContent = showCables ? '隐藏线缆' : '显示线缆';
+ showStatusMessage(showCables ? '线缆已显示' : '线缆已隐藏', 'info');
+ });
+
document.getElementById('reload-data').addEventListener('click', async () => {
await reloadData();
showStatusMessage('数据已重新加载', 'success');
diff --git a/frontend/public/earth/js/main.js b/frontend/public/earth/js/main.js
index fcda5cb1..e126d1e3 100644
--- a/frontend/public/earth/js/main.js
+++ b/frontend/public/earth/js/main.js
@@ -13,7 +13,7 @@ import {
hideTooltip
} from './ui.js';
import { createEarth, createClouds, createTerrain, createStars, createGridLines, toggleTerrain, getEarth } from './earth.js';
-import { loadGeoJSONFromPath, loadLandingPoints, handleCableClick, clearCableSelection, getCableLines, getCablesById, lockedCable as cableLocked, getCableState, setCableState, clearAllCableStates, applyLandingPointVisualState, resetLandingPointVisualState, getAllLandingPoints } from './cables.js';
+import { loadGeoJSONFromPath, loadLandingPoints, handleCableClick, clearCableSelection, getCableLines, getCablesById, lockedCable as cableLocked, getCableState, setCableState, clearAllCableStates, applyLandingPointVisualState, resetLandingPointVisualState, getAllLandingPoints, getShowCables } from './cables.js';
import { createSatellites, loadSatellites, updateSatellitePositions, toggleSatellites, toggleTrails, getShowSatellites, selectSatellite, getSatelliteData, getSatellitePoints, setSatelliteRingState, updateLockedRingPosition, updateHoverRingPosition, getSatellitePositions } from './satellites.js';
import { setupControls, getAutoRotate, getShowTerrain, zoomLevel, setAutoRotate, toggleAutoRotate, resetView } from './controls.js';
import { initInfoCard, showInfoCard, hideInfoCard, getCurrentType, setInfoCardNoBorder } from './info-card.js';
@@ -33,7 +33,7 @@ let lockedObjectType = null;
let dragStartTime = 0;
let isLongDrag = false;
-function clearLockedObject() {
+export function clearLockedObject() {
hoveredCable = null;
hoveredSatellite = null;
hoveredSatelliteIndex = null;
@@ -92,7 +92,7 @@ function applyCableVisualState() {
switch (state) {
case CABLE_STATE.LOCKED:
- c.material.opacity = 0.3 + pulse * 0.7;
+ c.material.opacity = CABLE_CONFIG.lockedOpacityMin + pulse * (CABLE_CONFIG.lockedOpacityMax - CABLE_CONFIG.lockedOpacityMin);
c.material.color.setRGB(1, 1, 1);
break;
case CABLE_STATE.HOVERED:
@@ -191,16 +191,27 @@ async function loadData(showWhiteSphere = false) {
setLoading(true);
try {
- console.log('开始加载电缆数据...');
- await loadGeoJSONFromPath(scene, getEarth());
- console.log('电缆数据加载完成');
- await loadLandingPoints(scene, getEarth());
- console.log('登陆点数据加载完成');
-
- const satCount = await loadSatellites();
- console.log(`卫星数据加载完成: ${satCount} 颗`);
- updateSatellitePositions();
- console.log('卫星位置已更新');
+ console.log('开始加载数据...');
+ await Promise.all([
+ (async () => {
+ await loadGeoJSONFromPath(scene, getEarth());
+ console.log('电缆数据加载完成');
+ await loadLandingPoints(scene, getEarth());
+ console.log('登陆点数据加载完成');
+ })(),
+ (async () => {
+ const satCount = await loadSatellites();
+ console.log(`卫星数据加载完成: ${satCount} 颗`);
+ updateSatellitePositions();
+ console.log('卫星位置已更新');
+ toggleSatellites(true);
+ const satBtn = document.getElementById('toggle-satellites');
+ if (satBtn) {
+ satBtn.classList.add('active');
+ satBtn.querySelector('.tooltip').textContent = '隐藏卫星';
+ }
+ })()
+ ]);
} catch (error) {
console.error('加载数据失败:', error);
showStatusMessage('加载数据失败: ' + error.message, 'error');
@@ -306,7 +317,7 @@ function onMouseMove(event, camera) {
hoveredSatelliteIndex = null;
}
- if (hasHoveredCable) {
+ if (hasHoveredCable && getShowCables()) {
const cable = intersects[0].object;
if (!isSameCable(cable, lockedObject)) {
hoveredCable = cable;
@@ -402,7 +413,7 @@ function onClick(event, camera, renderer) {
const intersects = raycaster.intersectObjects(frontCables);
const satIntersects = getShowSatellites() ? raycaster.intersectObject(getSatellitePoints()) : [];
- if (intersects.length > 0) {
+ if (intersects.length > 0 && getShowCables()) {
clearLockedObject();
const clickedCable = intersects[0].object;
diff --git a/frontend/public/earth/js/satellites.js b/frontend/public/earth/js/satellites.js
index 60b6c879..06a513dd 100644
--- a/frontend/public/earth/js/satellites.js
+++ b/frontend/public/earth/js/satellites.js
@@ -291,9 +291,9 @@ export function updateSatellitePositions(deltaTime = 0) {
trailColors[trailIdx + 1] = g * alpha;
trailColors[trailIdx + 2] = b * alpha;
} else {
- trailPositions[trailIdx] = 0;
- trailPositions[trailIdx + 1] = 0;
- trailPositions[trailIdx + 2] = 0;
+ trailPositions[trailIdx] = pos.x;
+ trailPositions[trailIdx + 1] = pos.y;
+ trailPositions[trailIdx + 2] = pos.z;
trailColors[trailIdx] = 0;
trailColors[trailIdx + 1] = 0;
trailColors[trailIdx + 2] = 0;
diff --git a/frontend/public/earth/js/ui.js b/frontend/public/earth/js/ui.js
index 67b82bb6..da36d359 100644
--- a/frontend/public/earth/js/ui.js
+++ b/frontend/public/earth/js/ui.js
@@ -25,7 +25,8 @@ export function updateZoomDisplay(zoomLevel, distance) {
const percent = Math.round(zoomLevel * 100);
document.getElementById('zoom-value').textContent = percent + '%';
document.getElementById('zoom-level').textContent = '缩放: ' + percent + '%';
- document.getElementById('zoom-slider').value = zoomLevel;
+ const slider = document.getElementById('zoom-slider');
+ if (slider) slider.value = zoomLevel;
document.getElementById('camera-distance').textContent = distance + ' km';
}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index e47a2973..fb11001b 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -4,6 +4,12 @@
box-sizing: border-box;
}
+html, body, #root {
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
diff --git a/frontend/src/pages/Earth/Earth.tsx b/frontend/src/pages/Earth/Earth.tsx
index 2697b2d2..da2896d1 100644
--- a/frontend/src/pages/Earth/Earth.tsx
+++ b/frontend/src/pages/Earth/Earth.tsx
@@ -3,9 +3,10 @@ function Earth() {