From f49aa3ba3a648343f165c49be17c5c02b2609029 Mon Sep 17 00:00:00 2001 From: yarnom Date: Tue, 11 Nov 2025 14:46:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8C=89=E5=88=9B=E5=BB=BA=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/frontend/src/main.ts | 34 +++++++++++++++++++++++++++------- core/internal/data/store.go | 7 ++++--- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/core/frontend/src/main.ts b/core/frontend/src/main.ts index 4b81764..8242292 100644 --- a/core/frontend/src/main.ts +++ b/core/frontend/src/main.ts @@ -58,6 +58,8 @@ export class AppComponent implements OnInit, AfterViewInit { private CLUSTER_THRESHOLD = 10; private tileOverlayGroup: any; private tileLastList: any[] = []; + private refreshTimer: any; + private mapEventsBound = false; tileTimes: string[] = []; tileIndex = -1; tileZ = 7; tileY = 40; tileX = 102; @@ -74,6 +76,9 @@ export class AppComponent implements OnInit, AfterViewInit { const start = new Date(now.getTime() - 24*3600*1000); // 当前时间前24小时 this.end = toLocal(end); this.start = toLocal(start); + + // 每10分钟检查并刷新数据(状态与站点),无刷新页面 + this.refreshTimer = setInterval(() => { this.refreshDataTick(); }, 10 * 60 * 1000); } ngAfterViewInit() { this.initMap(); this.reloadTileTimesAndShow(); } @@ -85,6 +90,16 @@ export class AppComponent implements OnInit, AfterViewInit { private async loadStations() { this.stations = await this.api.getStations(); this.updateStationsOnMap(); } + private async refreshDataTick() { + try { + await Promise.all([this.loadStatus(), this.loadStations()]); + // 若已选择设备并显示图表,则同时刷新历史与预报数据(不触发滚动/动画) + if (this.showPanels && this.decimalId) { + await this.query(true); + } + } catch {} + } + private getTiandituKey(): string { const anyWin = (window as any); return anyWin.TIANDITU_KEY || '0c260b8a094a4e0bc507808812cefdac'; @@ -212,7 +227,7 @@ export class AppComponent implements OnInit, AfterViewInit { this.stationSource.addFeature(f); }); // click to select - if (this.map) { + if (this.map && !this.mapEventsBound) { this.map.on('singleclick', async (evt:any) => { const olAny: any = (window as any).ol; const features = this.map.getFeaturesAtPixel(evt.pixel, { layerFilter: (l:any)=> l===this.stationLayer || l===this.clusterLayer }); @@ -242,6 +257,7 @@ export class AppComponent implements OnInit, AfterViewInit { if (el) el.style.cursor = (features && features.length>0) ? 'pointer' : ''; this.showTileTooltip(evt); }); + this.mapEventsBound = true; } } @@ -336,7 +352,7 @@ export class AppComponent implements OnInit, AfterViewInit { return `RS485-${hex}`; } - async query() { + async query(suppressUX: boolean = false) { const dec = this.decimalId.trim(); if (!dec) return; const sid = this.makeStationIdFromHex(dec); @@ -354,17 +370,21 @@ export class AppComponent implements OnInit, AfterViewInit { this.isLoading = false; } this.showPanels = true; - this.isMapCollapsed = true; + if (!suppressUX) this.isMapCollapsed = true; const st = this.stations.find(s => s.station_id === sid); const ol: any = (window as any).ol; - if (st && ol && typeof st.longitude === 'number' && typeof st.latitude === 'number' && this.map) { - this.map.getView().animate({ center: ol.proj.fromLonLat([st.longitude, st.latitude]), zoom: 11, duration: 400 }); + if (!suppressUX) { + if (st && ol && typeof st.longitude === 'number' && typeof st.latitude === 'number' && this.map) { + this.map.getView().animate({ center: ol.proj.fromLonLat([st.longitude, st.latitude]), zoom: 11, duration: 400 }); + } } this.selectedLocation = (st && st.location) ? st.location : ''; const titleName = st?.name || st?.station_alias || st?.station_id || ''; this.selectedTitle = titleName ? `${titleName}${this.selectedLocation ? ` | ${this.selectedLocation}` : ''}` : (this.selectedLocation || ''); - setTimeout(()=>{ try{ this.map.updateSize(); }catch{} }, 300); - this.scrollToChart(); + if (!suppressUX) { + setTimeout(()=>{ try{ this.map.updateSize(); }catch{} }, 300); + this.scrollToChart(); + } } onSelectStation(ev: { stationId: string, hex: string }) { diff --git a/core/internal/data/store.go b/core/internal/data/store.go index 0cadce6..894a8d4 100644 --- a/core/internal/data/store.go +++ b/core/internal/data/store.go @@ -18,7 +18,7 @@ func OnlineDevices() int { func Stations() ([]types.Station, error) { const query = ` - SELECT DISTINCT s.station_id, + SELECT s.station_id, COALESCE(s.station_alias, '') as station_alias, COALESCE(s.password, '') as station_name, 'WH65LP' as device_type, @@ -33,8 +33,9 @@ func Stations() ([]types.Station, error) { FROM stations s LEFT JOIN rs485_weather_data r ON s.station_id = r.station_id WHERE s.station_id LIKE 'RS485-%' - GROUP BY s.station_id, s.station_alias, s.password, s.latitude, s.longitude, s.name, s.location, s.z, s.y, s.x - ORDER BY s.station_id` + GROUP BY s.station_id, s.station_alias, s.password, s.latitude, s.longitude, s.name, s.location, s.z, s.y, s.x, s.created_at + ORDER BY (COALESCE(MAX(r.timestamp), '1970-01-01'::timestamp) > NOW() - INTERVAL '5 minutes') DESC, + s.created_at DESC` rows, err := DB().Query(query) if err != nil { return nil, err