新增定位到SCADA设备时的动画闪烁标识

This commit is contained in:
JIANG
2025-10-31 18:05:12 +08:00
parent 82fb3e1581
commit d4f8b9fd32
2 changed files with 128 additions and 14 deletions

View File

@@ -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<SCADADataPanelProps> = ({
});
setTimeSeries(result);
setLoadingState("success");
console.debug(
`[SCADADataPanel] 数据刷新成功 (${reason}),共 ${result.length} 条记录。`
);
} catch (err) {
console.error("[SCADADataPanel] 获取时序数据失败", err);
setError(err instanceof Error ? err.message : "未知错误");
setLoadingState("error");
}

View File

@@ -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<SCADADeviceListProps> = ({
const [highlightLayer, setHighlightLayer] =
useState<VectorLayer<VectorSource> | null>(null);
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
const [blinkingFeature, setBlinkingFeature] = useState<Feature | null>(null);
const blinkListenerKeyRef = useRef<any>(null);
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
@@ -294,9 +297,117 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
// 处理缩放到设备
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<SCADADeviceListProps> = ({
setHighlightLayer(highlightLayer);
return () => {
// 清除闪烁效果监听器
if (blinkListenerKeyRef.current) {
unByKey(blinkListenerKeyRef.current);
blinkListenerKeyRef.current = null;
}
map.removeLayer(highlightLayer);
};
}, [map]);
@@ -639,9 +755,9 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
sx={{ fontSize: "0.7rem", height: 20 }}
/>
<Chip
label={
"可靠度" + getReliability(device.reliability)
}
label={`可靠度: ${getReliability(
device.reliability
)}`}
size="small"
variant="outlined"
sx={{ fontSize: "0.7rem", height: 20 }}