From 3bad968844cae0e07f4eee3d75a7178536788dcc Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sat, 23 May 2026 21:02:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9C=B0=E5=9B=BE=E7=93=A6?= =?UTF-8?q?=E7=89=87=E9=95=BF=E6=97=B6=E9=97=B4=E6=8C=82=E6=9C=BA=E5=90=8E?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E5=A4=B1=E8=B4=A5=EF=BC=9AObserver=20?= =?UTF-8?q?=E5=8E=BB=E9=87=8D=20+=20tileerror=20=E9=87=8D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Constraint: MutationObserver subtree=true 导致任意 class 变化触发全量瓦片重载,长期运行触发 CDN 限流 Tested: tsc --noEmit OK, ruff OK, pytest 184 passed --- frontend/hooks/useLeafletMap.ts | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/frontend/hooks/useLeafletMap.ts b/frontend/hooks/useLeafletMap.ts index 65620e40..6be6c823 100644 --- a/frontend/hooks/useLeafletMap.ts +++ b/frontend/hooks/useLeafletMap.ts @@ -39,6 +39,8 @@ const MAP_TILE_URLS = { dark: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png", light: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", } as const; +const TILE_RETRY_LIMIT = 2; +const TILE_RETRY_DELAY_BASE_MS = 500; const CITY_MARKER_DISPLAY_OFFSETS: Record< string, { x: number; y: number; zIndexOffset?: number } @@ -528,6 +530,21 @@ export function useLeafletMap({ subdomains: "abcd", }).addTo(map); + // Retry failed tiles (CartoCDN may throttle long-running sessions) + const tileRetries = new Map(); + tileLayer.on("tileerror", (ev: { tile: HTMLElement; coords: L.Coords }) => { + const { tile, coords } = ev; + const key = `${coords.z}/${coords.x}/${coords.y}`; + const attempts = tileRetries.get(key) ?? 0; + if (attempts >= TILE_RETRY_LIMIT) return; + tileRetries.set(key, attempts + 1); + const delay = TILE_RETRY_DELAY_BASE_MS * 2 ** attempts; + setTimeout(() => { + (tile as HTMLImageElement).src = tileLayer.getTileUrl(coords); + }, delay); + }); + map.on("moveend zoomend", () => tileRetries.clear()); + const nearbyLayer = L.layerGroup().addTo(map); mapRef.current = map; tileLayerRef.current = tileLayer; @@ -601,8 +618,13 @@ export function useLeafletMap({ const tileLayer = tileLayerRef.current; if (!tileLayer || typeof MutationObserver === "undefined") return; + let lastTileUrl = ""; + const syncMapTheme = () => { - tileLayer.setUrl(getMapTileUrl(containerRef.current)); + const nextUrl = getMapTileUrl(containerRef.current); + if (nextUrl === lastTileUrl) return; + lastTileUrl = nextUrl; + tileLayer.setUrl(nextUrl); }; syncMapTheme(); @@ -611,11 +633,12 @@ export function useLeafletMap({ attributeFilter: ["class"], attributes: true, }); - if (document.body) { - observer.observe(document.body, { + + const scanTerminal = document.querySelector(".scan-terminal"); + if (scanTerminal) { + observer.observe(scanTerminal, { attributeFilter: ["class"], attributes: true, - subtree: true, }); }