更改 SCADA 设备列表条目关键词;样式设置中新增彩虹色带;强制计算后清除计算范围内的缓存;
This commit is contained in:
@@ -104,6 +104,40 @@ const GRADIENT_PALETTES = [
|
|||||||
end: "rgba(148, 103, 189, 1)",
|
end: "rgba(148, 103, 189, 1)",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
// 离散彩虹色系 - 提供高区分度的颜色
|
||||||
|
const RAINBOW_PALETTES = [
|
||||||
|
{
|
||||||
|
name: "正向彩虹",
|
||||||
|
colors: [
|
||||||
|
"rgba(255, 0, 0, 1)", // 红 #FF0000
|
||||||
|
"rgba(255, 127, 0, 1)", // 橙 #FF7F00
|
||||||
|
"rgba(255, 215, 0, 1)", // 金黄 #FFD700
|
||||||
|
"rgba(199, 224, 0, 1)", // 黄绿 #C7E000
|
||||||
|
"rgba(76, 175, 80, 1)", // 中绿 #4CAF50
|
||||||
|
"rgba(0, 158, 115, 1)", // 青绿/翡翠 #009E73
|
||||||
|
"rgba(0, 188, 212, 1)", // 青/青色 #00BCD4
|
||||||
|
"rgba(33, 150, 243, 1)", // 天蓝 #2196F3
|
||||||
|
"rgba(63, 81, 181, 1)", // 靛青 #3F51B5
|
||||||
|
"rgba(142, 68, 173, 1)", // 紫 #8E44AD
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "反向彩虹",
|
||||||
|
colors: [
|
||||||
|
"rgba(142, 68, 173, 1)", // 紫 #8E44AD
|
||||||
|
"rgba(63, 81, 181, 1)", // 靛青 #3F51B5
|
||||||
|
"rgba(33, 150, 243, 1)", // 天蓝 #2196F3
|
||||||
|
"rgba(0, 188, 212, 1)", // 青/青色 #00BCD4
|
||||||
|
"rgba(0, 158, 115, 1)", // 青绿/翡翠 #009E73
|
||||||
|
"rgba(76, 175, 80, 1)", // 中绿 #4CAF50
|
||||||
|
"rgba(199, 224, 0, 1)", // 黄绿 #C7E000
|
||||||
|
"rgba(255, 215, 0, 1)", // 金黄 #FFD700
|
||||||
|
"rgba(255, 127, 0, 1)", // 橙 #FF7F00
|
||||||
|
"rgba(255, 0, 0, 1)", // 红 #FF0000
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// 预设分类方法
|
// 预设分类方法
|
||||||
const CLASSIFICATION_METHODS = [
|
const CLASSIFICATION_METHODS = [
|
||||||
{ name: "优雅分段", value: "pretty_breaks" },
|
{ name: "优雅分段", value: "pretty_breaks" },
|
||||||
@@ -164,6 +198,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
// 颜色方案选择
|
// 颜色方案选择
|
||||||
const [singlePaletteIndex, setSinglePaletteIndex] = useState(0);
|
const [singlePaletteIndex, setSinglePaletteIndex] = useState(0);
|
||||||
const [gradientPaletteIndex, setGradientPaletteIndex] = useState(0);
|
const [gradientPaletteIndex, setGradientPaletteIndex] = useState(0);
|
||||||
|
const [rainbowPaletteIndex, setRainbowPaletteIndex] = useState(0);
|
||||||
// 根据分段数生成相应数量的渐进颜色
|
// 根据分段数生成相应数量的渐进颜色
|
||||||
const generateGradientColors = useCallback(
|
const generateGradientColors = useCallback(
|
||||||
(segments: number): string[] => {
|
(segments: number): string[] => {
|
||||||
@@ -189,6 +224,29 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
},
|
},
|
||||||
[gradientPaletteIndex, parseColor]
|
[gradientPaletteIndex, parseColor]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 根据分段数生成彩虹色
|
||||||
|
const generateRainbowColors = useCallback(
|
||||||
|
(segments: number): string[] => {
|
||||||
|
const baseColors = RAINBOW_PALETTES[rainbowPaletteIndex].colors;
|
||||||
|
|
||||||
|
if (segments <= baseColors.length) {
|
||||||
|
// 如果分段数小于等于基础颜色数,均匀选取
|
||||||
|
const step = baseColors.length / segments;
|
||||||
|
return Array.from(
|
||||||
|
{ length: segments },
|
||||||
|
(_, i) => baseColors[Math.floor(i * step)]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 如果分段数大于基础颜色数,重复使用
|
||||||
|
return Array.from(
|
||||||
|
{ length: segments },
|
||||||
|
(_, i) => baseColors[i % baseColors.length]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[rainbowPaletteIndex]
|
||||||
|
);
|
||||||
// 保存当前图层的样式状态
|
// 保存当前图层的样式状态
|
||||||
const saveLayerStyle = useCallback(
|
const saveLayerStyle = useCallback(
|
||||||
(layerId?: string, newLegendConfig?: LegendStyleConfig) => {
|
(layerId?: string, newLegendConfig?: LegendStyleConfig) => {
|
||||||
@@ -350,7 +408,9 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
Array.from({ length: breaksLength }, () => {
|
Array.from({ length: breaksLength }, () => {
|
||||||
return SINGLE_COLOR_PALETTES[singlePaletteIndex].color;
|
return SINGLE_COLOR_PALETTES[singlePaletteIndex].color;
|
||||||
})
|
})
|
||||||
: generateGradientColors(breaksLength);
|
: styleConfig.colorType === "gradient"
|
||||||
|
? generateGradientColors(breaksLength)
|
||||||
|
: generateRainbowColors(breaksLength);
|
||||||
// 计算每个分段的线条粗细和点大小
|
// 计算每个分段的线条粗细和点大小
|
||||||
const dimensions: number[] =
|
const dimensions: number[] =
|
||||||
layerType === "linestring"
|
layerType === "linestring"
|
||||||
@@ -803,6 +863,73 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (styleConfig.colorType === "rainbow") {
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
variant="standard"
|
||||||
|
fullWidth
|
||||||
|
margin="dense"
|
||||||
|
className="mt-3"
|
||||||
|
>
|
||||||
|
<InputLabel>离散彩虹方案</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={rainbowPaletteIndex}
|
||||||
|
onChange={(e) => setRainbowPaletteIndex(Number(e.target.value))}
|
||||||
|
>
|
||||||
|
{RAINBOW_PALETTES.map((p, idx) => {
|
||||||
|
// 根据当前分段数生成该方案的预览颜色
|
||||||
|
const baseColors = p.colors;
|
||||||
|
const segments = styleConfig.segments;
|
||||||
|
let previewColors: string[];
|
||||||
|
|
||||||
|
if (segments <= baseColors.length) {
|
||||||
|
const step = baseColors.length / segments;
|
||||||
|
previewColors = Array.from(
|
||||||
|
{ length: segments },
|
||||||
|
(_, i) => baseColors[Math.floor(i * step)]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
previewColors = Array.from(
|
||||||
|
{ length: segments },
|
||||||
|
(_, i) => baseColors[i % baseColors.length]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem key={idx} value={idx}>
|
||||||
|
<Box
|
||||||
|
width="100%"
|
||||||
|
sx={{ display: "flex", alignItems: "center" }}
|
||||||
|
>
|
||||||
|
<Typography sx={{ marginRight: 1 }}>{p.name}</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "60%",
|
||||||
|
height: 16,
|
||||||
|
borderRadius: 2,
|
||||||
|
display: "flex",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{previewColors.map((color, colorIdx) => (
|
||||||
|
<Box
|
||||||
|
key={colorIdx}
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// 根据不同图层的类型和颜色分类方案显示不同的大小设置
|
// 根据不同图层的类型和颜色分类方案显示不同的大小设置
|
||||||
const getSizeSetting = () => {
|
const getSizeSetting = () => {
|
||||||
@@ -813,7 +940,9 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
} else if (styleConfig.colorType === "gradient") {
|
} else if (styleConfig.colorType === "gradient") {
|
||||||
const { start, end } = GRADIENT_PALETTES[gradientPaletteIndex];
|
const { start, end } = GRADIENT_PALETTES[gradientPaletteIndex];
|
||||||
colors = [start, end];
|
colors = [start, end];
|
||||||
} else if (styleConfig.colorType === "categorical") {
|
} else if (styleConfig.colorType === "rainbow") {
|
||||||
|
const rainbowColors = RAINBOW_PALETTES[rainbowPaletteIndex].colors;
|
||||||
|
colors = [rainbowColors[0], rainbowColors[rainbowColors.length - 1]];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedRenderLayer?.get("type") === "point") {
|
if (selectedRenderLayer?.get("type") === "point") {
|
||||||
@@ -1024,10 +1153,20 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
}
|
}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const index = e.target.value as number;
|
const index = e.target.value as number;
|
||||||
setSelectedRenderLayer(
|
const newLayer = index >= 0 ? renderLayers[index] : undefined;
|
||||||
index >= 0 ? renderLayers[index] : undefined
|
setSelectedRenderLayer(newLayer);
|
||||||
);
|
|
||||||
setStyleConfig((prev) => ({ ...prev, property: "" }));
|
// 检查新图层是否有缓存的样式,没有才清空
|
||||||
|
if (newLayer) {
|
||||||
|
const layerId = newLayer.get("value");
|
||||||
|
const cachedStyleState = layerStyleStates.find(
|
||||||
|
(state) => state.layerId === layerId
|
||||||
|
);
|
||||||
|
// 只有在没有缓存时才清空属性
|
||||||
|
if (!cachedStyleState) {
|
||||||
|
setStyleConfig((prev) => ({ ...prev, property: "" }));
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{renderLayers.map((layer, index) => {
|
{renderLayers.map((layer, index) => {
|
||||||
@@ -1102,13 +1241,13 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setStyleConfig((prev) => ({
|
setStyleConfig((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
colorType: e.target.value as "gradient" | "categorical",
|
colorType: e.target.value as "single" | "gradient" | "rainbow",
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MenuItem value="single">单一色</MenuItem>
|
<MenuItem value="single">单一色</MenuItem>
|
||||||
<MenuItem value="gradient">渐进色</MenuItem>
|
<MenuItem value="gradient">渐进色</MenuItem>
|
||||||
{/* <MenuItem value="categorical">分类色</MenuItem> */}
|
<MenuItem value="rainbow">离散彩虹</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
{getColorSetting()}
|
{getColorSetting()}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import { FiSkipBack, FiSkipForward } from "react-icons/fi";
|
|||||||
import { useData } from "../MapComponent";
|
import { useData } from "../MapComponent";
|
||||||
import { config, NETWORK_NAME } from "@/config/config";
|
import { config, NETWORK_NAME } from "@/config/config";
|
||||||
import { useMap } from "../MapComponent";
|
import { useMap } from "../MapComponent";
|
||||||
import { Network } from "inspector/promises";
|
|
||||||
const backendUrl = config.backendUrl;
|
const backendUrl = config.backendUrl;
|
||||||
|
|
||||||
interface TimelineProps {
|
interface TimelineProps {
|
||||||
@@ -139,10 +138,6 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(
|
|
||||||
// "Query Time:",
|
|
||||||
// queryTime.toLocaleDateString() + " " + queryTime.toLocaleTimeString()
|
|
||||||
// );
|
|
||||||
// 等待所有有效请求
|
// 等待所有有效请求
|
||||||
const responses = await Promise.all(requests);
|
const responses = await Promise.all(requests);
|
||||||
|
|
||||||
@@ -211,7 +206,6 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
|
|
||||||
// 播放时间间隔选项
|
// 播放时间间隔选项
|
||||||
const intervalOptions = [
|
const intervalOptions = [
|
||||||
// { value: 1000, label: "1秒" },
|
|
||||||
{ value: 2000, label: "2秒" },
|
{ value: 2000, label: "2秒" },
|
||||||
{ value: 5000, label: "5秒" },
|
{ value: 5000, label: "5秒" },
|
||||||
{ value: 10000, label: "10秒" },
|
{ value: 10000, label: "10秒" },
|
||||||
@@ -239,7 +233,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
}
|
}
|
||||||
debounceRef.current = setTimeout(() => {
|
debounceRef.current = setTimeout(() => {
|
||||||
setCurrentTime(value);
|
setCurrentTime(value);
|
||||||
}, 300); // 300ms 防抖延迟
|
}, 500); // 500ms 防抖延迟
|
||||||
},
|
},
|
||||||
[timeRange, minTime, maxTime]
|
[timeRange, minTime, maxTime]
|
||||||
);
|
);
|
||||||
@@ -441,6 +435,11 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 提前提取日期和时间值,避免异步操作期间被时间轴拖动改变
|
||||||
|
const calculationDate = selectedDate;
|
||||||
|
const calculationTime = currentTime;
|
||||||
|
const calculationDateStr = calculationDate.toISOString().split("T")[0];
|
||||||
|
|
||||||
setIsCalculating(true);
|
setIsCalculating(true);
|
||||||
// 显示处理中的通知
|
// 显示处理中的通知
|
||||||
open?.({
|
open?.({
|
||||||
@@ -451,8 +450,8 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
try {
|
try {
|
||||||
const body = {
|
const body = {
|
||||||
name: NETWORK_NAME,
|
name: NETWORK_NAME,
|
||||||
simulation_date: selectedDate.toISOString().split("T")[0], // YYYY-MM-DD
|
simulation_date: calculationDateStr, // YYYY-MM-DD
|
||||||
start_time: `${formatTime(currentTime)}:00`, // HH:MM:00
|
start_time: `${formatTime(calculationTime)}:00`, // HH:MM:00
|
||||||
duration: calculatedInterval,
|
duration: calculatedInterval,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -468,6 +467,44 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
// 清空当天当前时刻及之后的缓存
|
||||||
|
const currentDateStr = calculationDateStr;
|
||||||
|
const currentTimeInMinutes = calculationTime;
|
||||||
|
|
||||||
|
// 清空node缓存
|
||||||
|
const nodeCacheKeys = Array.from(nodeCacheRef.current.keys());
|
||||||
|
nodeCacheKeys.forEach((key) => {
|
||||||
|
const keyParts = key.split("_");
|
||||||
|
const cacheDate = keyParts[0].split("T")[0];
|
||||||
|
const cacheTimeStr = keyParts[0].split("T")[1];
|
||||||
|
|
||||||
|
if (cacheDate === currentDateStr && cacheTimeStr) {
|
||||||
|
const [hours, minutes] = cacheTimeStr.split(":");
|
||||||
|
const cacheTimeInMinutes = parseInt(hours) * 60 + parseInt(minutes);
|
||||||
|
|
||||||
|
if (cacheTimeInMinutes >= currentTimeInMinutes) {
|
||||||
|
nodeCacheRef.current.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清空link缓存
|
||||||
|
const linkCacheKeys = Array.from(linkCacheRef.current.keys());
|
||||||
|
linkCacheKeys.forEach((key) => {
|
||||||
|
const keyParts = key.split("_");
|
||||||
|
const cacheDate = keyParts[0].split("T")[0];
|
||||||
|
const cacheTimeStr = keyParts[0].split("T")[1];
|
||||||
|
|
||||||
|
if (cacheDate === currentDateStr && cacheTimeStr) {
|
||||||
|
const [hours, minutes] = cacheTimeStr.split(":");
|
||||||
|
const cacheTimeInMinutes = parseInt(hours) * 60 + parseInt(minutes);
|
||||||
|
|
||||||
|
if (cacheTimeInMinutes >= currentTimeInMinutes) {
|
||||||
|
linkCacheRef.current.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
open?.({
|
open?.({
|
||||||
type: "success",
|
type: "success",
|
||||||
message: "重新计算成功",
|
message: "重新计算成功",
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
|||||||
}
|
}
|
||||||
return styles;
|
return styles;
|
||||||
};
|
};
|
||||||
// 创建高亮图层 - 爆管管段标识样式
|
// 创建高亮图层
|
||||||
const highlightLayer = new VectorLayer({
|
const highlightLayer = new VectorLayer({
|
||||||
source: new VectorSource(),
|
source: new VectorSource(),
|
||||||
style: burstPipeStyle,
|
style: burstPipeStyle,
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
|||||||
anchor: [0.5, 1],
|
anchor: [0.5, 1],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
// 创建高亮图层 - 爆管管段标识样式
|
// 创建高亮图层
|
||||||
const highlightLayer = new VectorLayer({
|
const highlightLayer = new VectorLayer({
|
||||||
source: new VectorSource(),
|
source: new VectorSource(),
|
||||||
style: sensorStyle,
|
style: sensorStyle,
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
setTimeSeries([]);
|
setTimeSeries([]);
|
||||||
}
|
}
|
||||||
}, [deviceIds.join(","), hasDevices]);
|
}, [deviceIds.join(",")]); // 移除 hasDevices,因为它由 deviceIds 决定,避免潜在的依赖循环
|
||||||
|
|
||||||
const columns: GridColDef[] = useMemo(() => {
|
const columns: GridColDef[] = useMemo(() => {
|
||||||
const base: GridColDef[] = [
|
const base: GridColDef[] = [
|
||||||
|
|||||||
@@ -42,8 +42,13 @@ import { FixedSizeList } from "react-window";
|
|||||||
|
|
||||||
import { useMap } from "@app/OlMap/MapComponent";
|
import { useMap } from "@app/OlMap/MapComponent";
|
||||||
import { GeoJSON } from "ol/format";
|
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 Feature from "ol/Feature";
|
||||||
import { Point } from "ol/geom";
|
import { Point } from "ol/geom";
|
||||||
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";
|
||||||
@@ -57,12 +62,14 @@ const STATUS_OPTIONS: {
|
|||||||
interface SCADADevice {
|
interface SCADADevice {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
transmission_frequency: string;
|
||||||
|
reliability: number;
|
||||||
type: string;
|
type: string;
|
||||||
coordinates: [number, number];
|
|
||||||
status: {
|
status: {
|
||||||
value: "online" | "offline" | "warning" | "error";
|
value: "online" | "offline" | "warning" | "error";
|
||||||
name: "在线" | "离线" | "警告" | "错误";
|
name: "在线" | "离线" | "警告" | "错误";
|
||||||
};
|
};
|
||||||
|
coordinates: [number, number];
|
||||||
properties?: Record<string, any>;
|
properties?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +92,7 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
const [searchQuery, setSearchQuery] = useState<string>("");
|
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||||
const [selectedType, setSelectedType] = useState<string>("all");
|
const [selectedType, setSelectedType] = useState<string>("all");
|
||||||
const [selectedStatus, setSelectedStatus] = useState<string>("all");
|
const [selectedStatus, setSelectedStatus] = useState<string>("all");
|
||||||
|
const [selectedReliability, setSelectedReliability] = useState<string>("all");
|
||||||
const [isExpanded, setIsExpanded] = useState<boolean>(true);
|
const [isExpanded, setIsExpanded] = useState<boolean>(true);
|
||||||
const [internalSelection, setInternalSelection] = useState<string[]>([]);
|
const [internalSelection, setInternalSelection] = useState<string[]>([]);
|
||||||
const [pendingSelection, setPendingSelection] = useState<string[] | null>(
|
const [pendingSelection, setPendingSelection] = useState<string[] | null>(
|
||||||
@@ -94,6 +102,10 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [inputValue, setInputValue] = useState<string>("");
|
const [inputValue, setInputValue] = useState<string>("");
|
||||||
|
|
||||||
|
const [highlightLayer, setHighlightLayer] =
|
||||||
|
useState<VectorLayer<VectorSource> | null>(null);
|
||||||
|
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
||||||
|
|
||||||
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
// 防抖更新搜索查询
|
// 防抖更新搜索查询
|
||||||
@@ -139,6 +151,8 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
const data = features.map((feature) => ({
|
const data = features.map((feature) => ({
|
||||||
id: feature.get("id") || feature.getId(),
|
id: feature.get("id") || feature.getId(),
|
||||||
name: feature.get("id") || feature.getId(),
|
name: feature.get("id") || feature.getId(),
|
||||||
|
transmission_frequency: feature.get("transmission_frequency"),
|
||||||
|
reliability: feature.get("reliability"),
|
||||||
type: feature.get("type") === "pipe_flow" ? "流量" : "压力",
|
type: feature.get("type") === "pipe_flow" ? "流量" : "压力",
|
||||||
status: STATUS_OPTIONS[Math.floor(Math.random() * 4)],
|
status: STATUS_OPTIONS[Math.floor(Math.random() * 4)],
|
||||||
coordinates: (feature.getGeometry() as Point)?.getCoordinates() as [
|
coordinates: (feature.getGeometry() as Point)?.getCoordinates() as [
|
||||||
@@ -170,6 +184,20 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
// 获取设备状态列表
|
// 获取设备状态列表
|
||||||
const deviceStatuses = STATUS_OPTIONS;
|
const deviceStatuses = STATUS_OPTIONS;
|
||||||
|
|
||||||
|
// 可靠度文字映射
|
||||||
|
const getReliability = (reliability: number) => {
|
||||||
|
switch (reliability) {
|
||||||
|
case 1:
|
||||||
|
return "高";
|
||||||
|
case 2:
|
||||||
|
return "中";
|
||||||
|
case 3:
|
||||||
|
return "低";
|
||||||
|
default:
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 过滤设备列表
|
// 过滤设备列表
|
||||||
const filteredDevices = useMemo(() => {
|
const filteredDevices = useMemo(() => {
|
||||||
return effectiveDevices.filter((device) => {
|
return effectiveDevices.filter((device) => {
|
||||||
@@ -186,10 +214,21 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
selectedType === "all" || device.type === selectedType;
|
selectedType === "all" || device.type === selectedType;
|
||||||
const matchesStatus =
|
const matchesStatus =
|
||||||
selectedStatus === "all" || device.status.value === selectedStatus;
|
selectedStatus === "all" || device.status.value === selectedStatus;
|
||||||
|
const matchesReliability =
|
||||||
|
selectedReliability === "all" ||
|
||||||
|
getReliability(device.reliability) === selectedReliability;
|
||||||
|
|
||||||
return matchesSearch && matchesType && matchesStatus;
|
return (
|
||||||
|
matchesSearch && matchesType && matchesStatus && matchesReliability
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}, [effectiveDevices, searchQuery, selectedType, selectedStatus]);
|
}, [
|
||||||
|
effectiveDevices,
|
||||||
|
searchQuery,
|
||||||
|
selectedType,
|
||||||
|
selectedStatus,
|
||||||
|
selectedReliability,
|
||||||
|
]);
|
||||||
|
|
||||||
// 状态颜色映射
|
// 状态颜色映射
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
@@ -222,6 +261,17 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
return "●";
|
return "●";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// 传输频率文字对应
|
||||||
|
const getTransmissionFrequency = (transmission_frequency: string) => {
|
||||||
|
// 传输频率文本:00:01:00,00:05:00,00:10:00,00:30:00,01:00:00,转换为分钟数
|
||||||
|
const parts = transmission_frequency.split(":");
|
||||||
|
if (parts.length !== 3) return transmission_frequency;
|
||||||
|
const hours = parseInt(parts[0], 10);
|
||||||
|
const minutes = parseInt(parts[1], 10);
|
||||||
|
const seconds = parseInt(parts[2], 10);
|
||||||
|
const totalMinutes = hours * 60 + minutes + (seconds >= 30 ? 1 : 0);
|
||||||
|
return totalMinutes;
|
||||||
|
};
|
||||||
|
|
||||||
// 处理设备点击
|
// 处理设备点击
|
||||||
const handleDeviceClick = (device: SCADADevice, event?: React.MouseEvent) => {
|
const handleDeviceClick = (device: SCADADevice, event?: React.MouseEvent) => {
|
||||||
@@ -264,6 +314,7 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
setSearchQuery("");
|
setSearchQuery("");
|
||||||
setSelectedType("all");
|
setSelectedType("all");
|
||||||
setSelectedStatus("all");
|
setSelectedStatus("all");
|
||||||
|
setSelectedReliability("all");
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -273,6 +324,56 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
setPendingSelection([]);
|
setPendingSelection([]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 初始化管道图层和高亮图层
|
||||||
|
useEffect(() => {
|
||||||
|
if (!map) return;
|
||||||
|
// 获取地图的目标容器
|
||||||
|
const SCADASelectedStyle = () => {
|
||||||
|
return new Style({
|
||||||
|
image: new Circle({
|
||||||
|
stroke: new Stroke({ color: "yellow", width: 2 }),
|
||||||
|
radius: 15,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 创建高亮图层
|
||||||
|
const highlightLayer = new VectorLayer({
|
||||||
|
source: new VectorSource(),
|
||||||
|
style: SCADASelectedStyle,
|
||||||
|
maxZoom: 24,
|
||||||
|
minZoom: 12,
|
||||||
|
properties: {
|
||||||
|
name: "SCADA 选中高亮",
|
||||||
|
value: "scada_selected_highlight",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
map.addLayer(highlightLayer);
|
||||||
|
setHighlightLayer(highlightLayer);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
map.removeLayer(highlightLayer);
|
||||||
|
};
|
||||||
|
}, [map]);
|
||||||
|
|
||||||
|
// 高亮要素的函数
|
||||||
|
useEffect(() => {
|
||||||
|
if (!highlightLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const source = highlightLayer.getSource();
|
||||||
|
if (!source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 清除之前的高亮
|
||||||
|
source.clear();
|
||||||
|
// 添加新的高亮要素
|
||||||
|
highlightFeatures.forEach((feature) => {
|
||||||
|
if (feature instanceof Feature) {
|
||||||
|
source.addFeature(feature);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [selectedDeviceIds, highlightFeatures]);
|
||||||
// 清理定时器
|
// 清理定时器
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -364,14 +465,14 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
|
|
||||||
{/* 筛选器 */}
|
{/* 筛选器 */}
|
||||||
<Stack direction="row" spacing={2}>
|
<Stack direction="row" spacing={2}>
|
||||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
<FormControl size="small" sx={{ minWidth: 80 }}>
|
||||||
<InputLabel>设备类型</InputLabel>
|
<InputLabel>设备类型</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={selectedType}
|
value={selectedType}
|
||||||
label="设备类型"
|
label="设备类型"
|
||||||
onChange={(e) => setSelectedType(e.target.value)}
|
onChange={(e) => setSelectedType(e.target.value)}
|
||||||
>
|
>
|
||||||
<MenuItem value="all">全部类型</MenuItem>
|
<MenuItem value="all">全部</MenuItem>
|
||||||
{deviceTypes.map((type) => (
|
{deviceTypes.map((type) => (
|
||||||
<MenuItem key={type} value={type}>
|
<MenuItem key={type} value={type}>
|
||||||
{type}
|
{type}
|
||||||
@@ -380,14 +481,14 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl size="small" sx={{ minWidth: 100 }}>
|
<FormControl size="small" sx={{ minWidth: 80 }}>
|
||||||
<InputLabel>状态</InputLabel>
|
<InputLabel>状态</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={selectedStatus}
|
value={selectedStatus}
|
||||||
label="状态"
|
label="状态"
|
||||||
onChange={(e) => setSelectedStatus(e.target.value)}
|
onChange={(e) => setSelectedStatus(e.target.value)}
|
||||||
>
|
>
|
||||||
<MenuItem value="all">全部状态</MenuItem>
|
<MenuItem value="all">全部</MenuItem>
|
||||||
{deviceStatuses.map((status) => (
|
{deviceStatuses.map((status) => (
|
||||||
<MenuItem key={status.value} value={status.value}>
|
<MenuItem key={status.value} value={status.value}>
|
||||||
{status.name}
|
{status.name}
|
||||||
@@ -396,6 +497,20 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl size="small" sx={{ minWidth: 80 }}>
|
||||||
|
<InputLabel>可靠度</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={selectedReliability}
|
||||||
|
label="可靠度"
|
||||||
|
onChange={(e) => setSelectedReliability(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value="all">全部</MenuItem>
|
||||||
|
<MenuItem value="高">高</MenuItem>
|
||||||
|
<MenuItem value="中">中</MenuItem>
|
||||||
|
<MenuItem value="低">低</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
<Tooltip title="重置筛选条件">
|
<Tooltip title="重置筛选条件">
|
||||||
<IconButton onClick={handleResetFilters}>
|
<IconButton onClick={handleResetFilters}>
|
||||||
<FilterList />
|
<FilterList />
|
||||||
@@ -523,6 +638,14 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{ fontSize: "0.7rem", height: 20 }}
|
sx={{ fontSize: "0.7rem", height: 20 }}
|
||||||
/>
|
/>
|
||||||
|
<Chip
|
||||||
|
label={
|
||||||
|
"可靠度" + getReliability(device.reliability)
|
||||||
|
}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ fontSize: "0.7rem", height: 20 }}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
secondary={
|
secondary={
|
||||||
@@ -537,8 +660,11 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
variant="caption"
|
variant="caption"
|
||||||
color="text.secondary"
|
color="text.secondary"
|
||||||
>
|
>
|
||||||
坐标: {device.coordinates[0].toFixed(6)},{" "}
|
传输频率:{" "}
|
||||||
{device.coordinates[1].toFixed(6)}
|
{getTransmissionFrequency(
|
||||||
|
device.transmission_frequency
|
||||||
|
)}{" "}
|
||||||
|
分钟
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user