From d4f8b9fd32b4ab1434f5ffcbb506808ccc6355ae Mon Sep 17 00:00:00 2001 From: JIANG Date: Fri, 31 Oct 2025 18:05:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AE=9A=E4=BD=8D=E5=88=B0SC?= =?UTF-8?q?ADA=E8=AE=BE=E5=A4=87=E6=97=B6=E7=9A=84=E5=8A=A8=E7=94=BB?= =?UTF-8?q?=E9=97=AA=E7=83=81=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/olmap/SCADADataPanel.tsx | 10 +- src/components/olmap/SCADADeviceList.tsx | 132 +++++++++++++++++++++-- 2 files changed, 128 insertions(+), 14 deletions(-) diff --git a/src/components/olmap/SCADADataPanel.tsx b/src/components/olmap/SCADADataPanel.tsx index 40abc87..7cfa304 100644 --- a/src/components/olmap/SCADADataPanel.tsx +++ b/src/components/olmap/SCADADataPanel.tsx @@ -81,11 +81,13 @@ const fetchFromBackend = async ( const ids = deviceIds.join(","); const starttime = dayjs(range.from).format("YYYY-MM-DD HH:mm:ss"); const endtime = dayjs(range.to).format("YYYY-MM-DD HH:mm:ss"); + // 清洗数据接口 + const cleaningSCADAUrl = `${config.backendUrl}/querycleaningscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; - const url = `${config.backendUrl}/querycleaningscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; + const originSCADAUrl = `${config.backendUrl}/queryscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; try { - const response = await fetch(url); + const response = await fetch(cleaningSCADAUrl); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -317,11 +319,7 @@ const SCADADataPanel: React.FC = ({ }); setTimeSeries(result); setLoadingState("success"); - console.debug( - `[SCADADataPanel] 数据刷新成功 (${reason}),共 ${result.length} 条记录。` - ); } catch (err) { - console.error("[SCADADataPanel] 获取时序数据失败", err); setError(err instanceof Error ? err.message : "未知错误"); setLoadingState("error"); } diff --git a/src/components/olmap/SCADADeviceList.tsx b/src/components/olmap/SCADADeviceList.tsx index e881a19..7baa6d4 100644 --- a/src/components/olmap/SCADADeviceList.tsx +++ b/src/components/olmap/SCADADeviceList.tsx @@ -44,11 +44,12 @@ import { useMap } from "@app/OlMap/MapComponent"; import { GeoJSON } from "ol/format"; import VectorLayer from "ol/layer/Vector"; import VectorSource from "ol/source/Vector"; -import { Stroke, Style, Circle } from "ol/style"; +import { Stroke, Style, Circle, Fill } from "ol/style"; import Feature from "ol/Feature"; import { Point } from "ol/geom"; +import { getVectorContext } from "ol/render"; +import { unByKey } from "ol/Observable"; import config from "@/config/config"; -import { get } from "http"; const STATUS_OPTIONS: { value: "online" | "offline" | "warning" | "error"; @@ -105,6 +106,8 @@ const SCADADeviceList: React.FC = ({ const [highlightLayer, setHighlightLayer] = useState | null>(null); const [highlightFeatures, setHighlightFeatures] = useState([]); + const [blinkingFeature, setBlinkingFeature] = useState(null); + const blinkListenerKeyRef = useRef(null); const debounceTimerRef = useRef(null); @@ -294,9 +297,117 @@ const SCADADeviceList: React.FC = ({ // 处理缩放到设备 const handleZoomToDevice = (device: SCADADevice) => { - map - ?.getView() - .fit(new Point(device.coordinates), { maxZoom: 15, duration: 1000 }); + if (!map) return; + + // 缩放到设备位置 + map.getView().fit(new Point(device.coordinates), { + maxZoom: 18, + duration: 1000, + }); + + // 创建闪烁效果 + createBlinkingEffect(device.coordinates); + }; + + // 创建闪烁效果 + const createBlinkingEffect = (coordinates: [number, number]) => { + if (!map || !highlightLayer) return; + + // 清除之前的闪烁效果 + if (blinkListenerKeyRef.current) { + unByKey(blinkListenerKeyRef.current); + blinkListenerKeyRef.current = null; + } + + // 创建闪烁点要素 + const blinkFeature = new Feature({ + geometry: new Point(coordinates), + }); + setBlinkingFeature(blinkFeature); + + const duration = 2000; // 闪烁持续时间 + const start = Date.now(); + + // 使用图层的 postrender 事件实现闪烁动画 + const listenerKey = highlightLayer.on("postrender", (event) => { + const elapsed = Date.now() - start; + if (elapsed > duration) { + // 动画结束 + unByKey(listenerKey); + setBlinkingFeature(null); + blinkListenerKeyRef.current = null; + map.render(); // 最后渲染一次以清除效果 + return; + } + + const vectorContext = getVectorContext(event); + const flashGeom = blinkFeature.getGeometry(); + + if (!flashGeom) return; + + // 计算闪烁效果 + const progress = elapsed / duration; + // 使用正弦波创建脉冲效果,增加频率以产生更快的闪烁 + const rawOpacity = + Math.abs(Math.sin(progress * Math.PI * 6)) * (1 - progress); + // 确保 opacity 不会太小,避免科学计数法导致的颜色解析错误 + const opacity = Math.max(rawOpacity, 0.01); + const radius = 10 + (1 - progress) * 10; // 从20逐渐减小到10 + + // 当透明度太低时直接跳过渲染 + if (opacity < 0.02) { + map.render(); + return; + } + + // 绘制外圈(黄色光晕) + vectorContext.setStyle( + new Style({ + image: new Circle({ + radius: radius * 1.5, + fill: new Fill({ + color: `rgba(255, 255, 0, ${(opacity * 0.3).toFixed(3)})`, + }), + }), + }) + ); + vectorContext.drawGeometry(flashGeom); + + // 绘制中圈(亮黄色) + vectorContext.setStyle( + new Style({ + image: new Circle({ + radius: radius, + fill: new Fill({ + color: `rgba(255, 255, 0, ${(opacity * 0.6).toFixed(3)})`, + }), + stroke: new Stroke({ + color: `rgba(255, 200, 0, ${opacity.toFixed(3)})`, + width: 2, + }), + }), + }) + ); + vectorContext.drawGeometry(flashGeom); + + // 绘制内核(白色中心) + vectorContext.setStyle( + new Style({ + image: new Circle({ + radius: radius * 0.3, + fill: new Fill({ + color: `rgba(255, 255, 255, ${opacity.toFixed(3)})`, + }), + }), + }) + ); + vectorContext.drawGeometry(flashGeom); + + // 继续渲染下一帧 + map.render(); + }); + + blinkListenerKeyRef.current = listenerKey; }; // 清除搜索 @@ -352,6 +463,11 @@ const SCADADeviceList: React.FC = ({ setHighlightLayer(highlightLayer); return () => { + // 清除闪烁效果监听器 + if (blinkListenerKeyRef.current) { + unByKey(blinkListenerKeyRef.current); + blinkListenerKeyRef.current = null; + } map.removeLayer(highlightLayer); }; }, [map]); @@ -639,9 +755,9 @@ const SCADADeviceList: React.FC = ({ sx={{ fontSize: "0.7rem", height: 20 }} />