diff --git a/VERSION b/VERSION index aa53fc84..4b9d1879 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.21.7 +0.21.8 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6f450ac8..2cd632e0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,20 @@ This project follows the repository versioning rule: - `feature` -> `+0.1.0` - `bugfix` -> `+0.0.1` +## 0.21.8 + +Released: 2026-03-27 + +### Highlights + +- Improved Earth-layer performance controls so satellite and cable toggles now fully unload their scene/runtime state instead of only hiding objects. + +### Improved + +- Improved the Earth satellite toggle to stop position/trail updates and release satellite rendering resources while disabled, then reload on demand when re-enabled. +- Improved the Earth cable toggle to unload submarine cable and landing-point objects while disabled so drag and interaction cost drops with the layer turned off. +- Improved Earth data reload behavior so disabled satellite and cable layers stay disabled instead of being implicitly reloaded during refresh. + ## 0.21.7 Released: 2026-03-27 diff --git a/frontend/package.json b/frontend/package.json index 20d02796..6ba6f91d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "planet-frontend", - "version": "0.21.7", + "version": "0.21.8", "private": true, "dependencies": { "@ant-design/icons": "^5.2.6", diff --git a/frontend/public/earth/js/controls.js b/frontend/public/earth/js/controls.js index 3f69576b..f5eee2f6 100644 --- a/frontend/public/earth/js/controls.js +++ b/frontend/public/earth/js/controls.js @@ -3,14 +3,18 @@ import { CONFIG, EARTH_CONFIG } from "./constants.js"; import { updateZoomDisplay, showStatusMessage } from "./ui.js"; import { toggleTerrain } from "./earth.js"; -import { reloadData, clearLockedObject } from "./main.js"; import { - toggleSatellites, + reloadData, + clearLockedObject, + setCablesEnabled, + setSatellitesEnabled, +} from "./main.js"; +import { toggleTrails, getShowSatellites, getSatelliteCount, } from "./satellites.js"; -import { toggleCables, getShowCables } from "./cables.js"; +import { getShowCables } from "./cables.js"; import { toggleBGP, getShowBGP, getBGPCount } from "./bgp.js"; export let autoRotate = true; @@ -324,19 +328,24 @@ function setupTerrainControls() { showStatusMessage(showTerrain ? "地形已显示" : "地形已隐藏", "info"); }); - bindListener(satellitesBtn, "click", function () { + bindListener(satellitesBtn, "click", async function () { const showSats = !getShowSatellites(); if (!showSats) { clearLockedObject(); } - toggleSatellites(showSats); - this.classList.toggle("active", showSats); - const tooltip = this.querySelector(".tooltip"); - if (tooltip) tooltip.textContent = showSats ? "隐藏卫星" : "显示卫星"; - const satelliteCountEl = document.getElementById("satellite-count"); - if (satelliteCountEl) - satelliteCountEl.textContent = getSatelliteCount() + " 颗"; - showStatusMessage(showSats ? "卫星已显示" : "卫星已隐藏", "info"); + try { + await setSatellitesEnabled(showSats); + if (!showSats) { + showStatusMessage("卫星已隐藏", "info"); + } else { + const satelliteCountEl = document.getElementById("satellite-count"); + if (satelliteCountEl) { + satelliteCountEl.textContent = `${getSatelliteCount()} 颗`; + } + } + } catch (error) { + console.error("切换卫星显示失败:", error); + } }); bindListener(bgpBtn, "click", function () { @@ -365,16 +374,16 @@ function setupTerrainControls() { showStatusMessage(nextShowTrails ? "轨迹已显示" : "轨迹已隐藏", "info"); }); - bindListener(cablesBtn, "click", function () { + bindListener(cablesBtn, "click", async function () { const showNextCables = !getShowCables(); if (!showNextCables) { clearLockedObject(); } - toggleCables(showNextCables); - this.classList.toggle("active", showNextCables); - const tooltip = this.querySelector(".tooltip"); - if (tooltip) tooltip.textContent = showNextCables ? "隐藏线缆" : "显示线缆"; - showStatusMessage(showNextCables ? "线缆已显示" : "线缆已隐藏", "info"); + try { + await setCablesEnabled(showNextCables); + } catch (error) { + console.error("切换线缆显示失败:", error); + } }); bindListener(reloadBtn, "click", async () => { diff --git a/frontend/public/earth/js/main.js b/frontend/public/earth/js/main.js index 802ba48e..f509c468 100644 --- a/frontend/public/earth/js/main.js +++ b/frontend/public/earth/js/main.js @@ -38,6 +38,8 @@ import { resetLandingPointVisualState, getShowCables, clearCableData, + getLandingPoints, + toggleCables, } from "./cables.js"; import { createSatellites, @@ -126,6 +128,10 @@ let initialized = false; let destroyed = false; let isDataLoading = false; let currentLoadToken = 0; +let cablesEnabled = true; +let satellitesEnabled = true; +let cableToggleToken = 0; +let satelliteToggleToken = 0; const clock = new THREE.Clock(); const interactionRaycaster = new THREE.Raycaster(); @@ -344,6 +350,124 @@ function buildLoadErrorMessage(errors) { .join(";"); } +function updateSatelliteToggleUi(enabled, satelliteCount = getSatelliteCount()) { + const satBtn = document.getElementById("toggle-satellites"); + if (satBtn) { + satBtn.classList.toggle("active", enabled); + const tooltip = satBtn.querySelector(".tooltip"); + if (tooltip) tooltip.textContent = enabled ? "隐藏卫星" : "显示卫星"; + } + + const satelliteCountEl = document.getElementById("satellite-count"); + if (satelliteCountEl) { + satelliteCountEl.textContent = `${enabled ? satelliteCount : 0} 颗`; + } +} + +function updateCableToggleUi(enabled) { + const cableBtn = document.getElementById("toggle-cables"); + if (cableBtn) { + cableBtn.classList.toggle("active", enabled); + const tooltip = cableBtn.querySelector(".tooltip"); + if (tooltip) tooltip.textContent = enabled ? "隐藏线缆" : "显示线缆"; + } + + const cableCountEl = document.getElementById("cable-count"); + if (cableCountEl) { + cableCountEl.textContent = `${enabled ? getCableLines().length : 0}个`; + } + + const landingPointCountEl = document.getElementById("landing-point-count"); + if (landingPointCountEl) { + landingPointCountEl.textContent = `${enabled ? getLandingPoints().length : 0}个`; + } + + const statusEl = document.getElementById("cable-status-summary"); + if (statusEl && !enabled) { + statusEl.textContent = "0/0 运行中"; + } +} + +async function ensureCablesEnabled() { + if (!scene || !camera || !renderer || destroyed) { + return 0; + } + + const earth = getEarth(); + if (!earth) return 0; + + cablesEnabled = true; + const requestToken = ++cableToggleToken; + + clearCableData(earth); + const [cableCount] = await Promise.all([ + loadGeoJSONFromPath(scene, earth), + loadLandingPoints(scene, earth), + ]); + + if (requestToken !== cableToggleToken || !cablesEnabled || destroyed) { + clearCableData(earth); + return 0; + } + + toggleCables(true); + updateCableToggleUi(true); + setLegendItems("cables", getCableLegendItems()); + refreshLegend(); + return cableCount; +} + +function disableCables() { + cablesEnabled = false; + cableToggleToken += 1; + clearCableData(getEarth()); + updateCableToggleUi(false); + setLegendItems("cables", getCableLegendItems()); + refreshLegend(); +} + +async function ensureSatellitesEnabled() { + if (!scene || !camera || !renderer || destroyed) return 0; + + const earth = getEarth(); + if (!earth) return 0; + + satellitesEnabled = true; + const requestToken = ++satelliteToggleToken; + + if (!getSatellitePoints()) { + createSatellites(scene, earth); + } + + clearSatelliteData(); + const satelliteCount = await loadSatellites(); + + if ( + requestToken !== satelliteToggleToken || + !satellitesEnabled || + destroyed + ) { + resetSatelliteState(); + return 0; + } + + updateSatellitePositions(POSITION_UPDATE_FORCE_DELTA, true); + toggleSatellites(true); + updateSatelliteToggleUi(true, satelliteCount); + setLegendItems("satellites", getSatelliteLegendItems()); + refreshLegend(); + return satelliteCount; +} + +function disableSatellites() { + satellitesEnabled = false; + satelliteToggleToken += 1; + resetSatelliteState(); + updateSatelliteToggleUi(false, 0); + setLegendItems("satellites", getSatelliteLegendItems()); + refreshLegend(); +} + function updateStatsSummary() { updateEarthStats({ cableCount: getCableLines().length, @@ -479,25 +603,8 @@ async function loadData(showWhiteSphere = false) { } const results = await Promise.allSettled([ - loadGeoJSONFromPath(scene, earth), - loadLandingPoints(scene, earth), - (async () => { - clearSatelliteData(); - const satelliteCount = await loadSatellites(); - const satelliteCountEl = document.getElementById("satellite-count"); - if (satelliteCountEl) { - satelliteCountEl.textContent = `${satelliteCount} 颗`; - } - updateSatellitePositions(POSITION_UPDATE_FORCE_DELTA, true); - toggleSatellites(true); - const satBtn = document.getElementById("toggle-satellites"); - if (satBtn) { - satBtn.classList.add("active"); - const tooltip = satBtn.querySelector(".tooltip"); - if (tooltip) tooltip.textContent = "隐藏卫星"; - } - return satelliteCount; - })(), + cablesEnabled ? ensureCablesEnabled() : Promise.resolve(0), + satellitesEnabled ? ensureSatellitesEnabled() : Promise.resolve(0), (async () => { clearBGPData(earth); const bgpResult = await loadBGPAnomalies(scene, earth); @@ -526,13 +633,10 @@ async function loadData(showWhiteSphere = false) { errors.push({ label: "电缆", reason: results[0].reason }); } if (results[1].status === "rejected") { - errors.push({ label: "登陆点", reason: results[1].reason }); + errors.push({ label: "卫星", reason: results[1].reason }); } if (results[2].status === "rejected") { - errors.push({ label: "卫星", reason: results[2].reason }); - } - if (results[3].status === "rejected") { - errors.push({ label: "BGP异常", reason: results[3].reason }); + errors.push({ label: "BGP异常", reason: results[2].reason }); } if (errors.length > 0) { @@ -545,6 +649,8 @@ async function loadData(showWhiteSphere = false) { } updateStatsSummary(); + updateCableToggleUi(cablesEnabled); + updateSatelliteToggleUi(satellitesEnabled); setLegendItems("cables", getCableLegendItems()); setLegendItems("satellites", getSatelliteLegendItems()); setLegendItems("bgp", getBGPLegendItems()); @@ -565,6 +671,75 @@ export async function reloadData() { await loadData(true); } +export async function setCablesEnabled(enabled) { + if (enabled === cablesEnabled) { + updateCableToggleUi(enabled); + return getCableLines().length; + } + + if (!enabled) { + clearLockedObject(); + hideInfoCard(); + disableCables(); + showStatusMessage("线缆已隐藏", "info"); + return 0; + } + + setLoadingMessage("正在加载线缆数据...", "重建海缆与登陆点对象"); + setLoading(true); + hideError(); + + try { + const cableCount = await ensureCablesEnabled(); + showStatusMessage("线缆已显示", "info"); + return cableCount; + } catch (error) { + cablesEnabled = false; + clearCableData(getEarth()); + updateCableToggleUi(false); + const message = `线缆加载失败: ${error?.message || String(error)}`; + showError(message); + showStatusMessage(message, "error"); + throw error; + } finally { + setLoading(false); + } +} + +export async function setSatellitesEnabled(enabled) { + if (enabled === satellitesEnabled) { + updateSatelliteToggleUi(enabled); + return getSatelliteCount(); + } + + if (!enabled) { + clearLockedObject(); + hideInfoCard(); + disableSatellites(); + return 0; + } + + setLoadingMessage("正在加载卫星数据...", "重建卫星点位与轨迹缓存"); + setLoading(true); + hideError(); + + try { + const satelliteCount = await ensureSatellitesEnabled(); + showStatusMessage("卫星已显示", "info"); + return satelliteCount; + } catch (error) { + satellitesEnabled = false; + resetSatelliteState(); + updateSatelliteToggleUi(false, 0); + const message = `卫星加载失败: ${error?.message || String(error)}`; + showError(message); + showStatusMessage(message, "error"); + throw error; + } finally { + setLoading(false); + } +} + function setupEventListeners() { const handleResize = () => onWindowResize(); const handleMouseMove = (event) => onMouseMove(event);