新增定位到SCADA设备时的动画闪烁标识
This commit is contained in:
@@ -81,11 +81,13 @@ const fetchFromBackend = async (
|
|||||||
const ids = deviceIds.join(",");
|
const ids = deviceIds.join(",");
|
||||||
const starttime = dayjs(range.from).format("YYYY-MM-DD HH:mm:ss");
|
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 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 {
|
try {
|
||||||
const response = await fetch(url);
|
const response = await fetch(cleaningSCADAUrl);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
@@ -317,11 +319,7 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
});
|
});
|
||||||
setTimeSeries(result);
|
setTimeSeries(result);
|
||||||
setLoadingState("success");
|
setLoadingState("success");
|
||||||
console.debug(
|
|
||||||
`[SCADADataPanel] 数据刷新成功 (${reason}),共 ${result.length} 条记录。`
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[SCADADataPanel] 获取时序数据失败", err);
|
|
||||||
setError(err instanceof Error ? err.message : "未知错误");
|
setError(err instanceof Error ? err.message : "未知错误");
|
||||||
setLoadingState("error");
|
setLoadingState("error");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,12 @@ import { useMap } from "@app/OlMap/MapComponent";
|
|||||||
import { GeoJSON } from "ol/format";
|
import { GeoJSON } from "ol/format";
|
||||||
import VectorLayer from "ol/layer/Vector";
|
import VectorLayer from "ol/layer/Vector";
|
||||||
import VectorSource from "ol/source/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 Feature from "ol/Feature";
|
||||||
import { Point } from "ol/geom";
|
import { Point } from "ol/geom";
|
||||||
|
import { getVectorContext } from "ol/render";
|
||||||
|
import { unByKey } from "ol/Observable";
|
||||||
import config from "@/config/config";
|
import config from "@/config/config";
|
||||||
import { get } from "http";
|
|
||||||
|
|
||||||
const STATUS_OPTIONS: {
|
const STATUS_OPTIONS: {
|
||||||
value: "online" | "offline" | "warning" | "error";
|
value: "online" | "offline" | "warning" | "error";
|
||||||
@@ -105,6 +106,8 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
const [highlightLayer, setHighlightLayer] =
|
const [highlightLayer, setHighlightLayer] =
|
||||||
useState<VectorLayer<VectorSource> | null>(null);
|
useState<VectorLayer<VectorSource> | null>(null);
|
||||||
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
||||||
|
const [blinkingFeature, setBlinkingFeature] = useState<Feature | null>(null);
|
||||||
|
const blinkListenerKeyRef = useRef<any>(null);
|
||||||
|
|
||||||
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
@@ -294,9 +297,117 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
|
|
||||||
// 处理缩放到设备
|
// 处理缩放到设备
|
||||||
const handleZoomToDevice = (device: SCADADevice) => {
|
const handleZoomToDevice = (device: SCADADevice) => {
|
||||||
map
|
if (!map) return;
|
||||||
?.getView()
|
|
||||||
.fit(new Point(device.coordinates), { maxZoom: 15, duration: 1000 });
|
// 缩放到设备位置
|
||||||
|
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);
|
setHighlightLayer(highlightLayer);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
// 清除闪烁效果监听器
|
||||||
|
if (blinkListenerKeyRef.current) {
|
||||||
|
unByKey(blinkListenerKeyRef.current);
|
||||||
|
blinkListenerKeyRef.current = null;
|
||||||
|
}
|
||||||
map.removeLayer(highlightLayer);
|
map.removeLayer(highlightLayer);
|
||||||
};
|
};
|
||||||
}, [map]);
|
}, [map]);
|
||||||
@@ -639,9 +755,9 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
sx={{ fontSize: "0.7rem", height: 20 }}
|
sx={{ fontSize: "0.7rem", height: 20 }}
|
||||||
/>
|
/>
|
||||||
<Chip
|
<Chip
|
||||||
label={
|
label={`可靠度: ${getReliability(
|
||||||
"可靠度" + getReliability(device.reliability)
|
device.reliability
|
||||||
}
|
)}`}
|
||||||
size="small"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{ fontSize: "0.7rem", height: 20 }}
|
sx={{ fontSize: "0.7rem", height: 20 }}
|
||||||
|
|||||||
Reference in New Issue
Block a user