"use client"; import React, { useState, useEffect, useMemo, useCallback, useRef, startTransition, } from "react"; import { Box, Paper, Typography, List, ListItem, ListItemButton, ListItemText, ListItemIcon, Chip, IconButton, Collapse, FormControl, InputLabel, Select, MenuItem, Tooltip, Stack, Divider, InputBase, CircularProgress, } from "@mui/material"; import { Search, MyLocation, ExpandMore, ExpandLess, FilterList, Clear, DeviceHub, } from "@mui/icons-material"; import { useMap } from "@app/OlMap/MapComponent"; import { GeoJSON } from "ol/format"; import { Point } from "ol/geom"; import config from "@/config/config"; interface SCADADevice { id: string; name: string; type: string; coordinates: [number, number]; status: "在线" | "离线" | "警告" | "错误"; properties?: Record; } interface SCADADeviceListProps { devices?: SCADADevice[]; onDeviceClick?: (device: SCADADevice) => void; onZoomToDevice?: (coordinates: [number, number]) => void; multiSelect?: boolean; selectedDeviceIds?: string[]; onSelectionChange?: (ids: string[]) => void; } const SCADADeviceList: React.FC = ({ devices = [], onDeviceClick, multiSelect = true, selectedDeviceIds, onSelectionChange, }) => { const [searchQuery, setSearchQuery] = useState(""); const [selectedType, setSelectedType] = useState("all"); const [selectedStatus, setSelectedStatus] = useState("all"); const [isExpanded, setIsExpanded] = useState(true); const [internalSelection, setInternalSelection] = useState([]); const [pendingSelection, setPendingSelection] = useState( null ); const [internalDevices, setInternalDevices] = useState([]); const [loading, setLoading] = useState(true); const [inputValue, setInputValue] = useState(""); const debounceTimerRef = useRef(null); // 防抖更新搜索查询 const debouncedSetSearchQuery = useCallback((value: string) => { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } // 根据输入长度调整防抖延迟:短输入延迟更长,长输入响应更快 const delay = value.length <= 2 ? 200 : 100; debounceTimerRef.current = setTimeout(() => { setSearchQuery(value); }, delay); }, []); const activeSelection = selectedDeviceIds ?? internalSelection; const map = useMap(); // 移到此处,确保在条件检查前调用 useEffect(() => { if (selectedDeviceIds) { setInternalSelection(selectedDeviceIds); } }, [selectedDeviceIds]); // 添加 useEffect 来延迟调用 onSelectionChange,避免在渲染时触发父组件的 setState useEffect(() => { if (pendingSelection !== null) { onSelectionChange?.(pendingSelection); setPendingSelection(null); } }, [pendingSelection, onSelectionChange]); // 初始化 SCADA 设备列表 useEffect(() => { const fetchScadaDevices = async () => { setLoading(true); try { const url = `${config.mapUrl}/TJWater/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=TJWater:geo_scada&outputFormat=application/json`; const response = await fetch(url); if (!response.ok) throw new Error("Failed to fetch SCADA devices"); const json = await response.json(); const features = new GeoJSON().readFeatures(json); const data = features.map((feature) => ({ id: feature.get("id") || feature.getId(), name: feature.get("id") || feature.getId(), type: feature.get("type") === "pipe_flow" ? "流量" : "压力", status: ["在线", "离线", "警告", "错误"][ Math.floor(Math.random() * 4) ] as "在线" | "离线" | "警告" | "错误", coordinates: (feature.getGeometry() as Point)?.getCoordinates() as [ number, number ], properties: feature.getProperties(), })); setInternalDevices(data); console.log("Fetched SCADA devices:", data); } catch (error) { console.error("Error fetching SCADA devices:", error); } finally { setLoading(false); } }; fetchScadaDevices(); }, []); const effectiveDevices = devices.length > 0 ? devices : internalDevices; // 获取设备类型列表 const deviceTypes = useMemo(() => { const types = Array.from( new Set(effectiveDevices.map((device) => device.type)) ); return types.sort(); }, [effectiveDevices]); // 获取设备状态列表 const deviceStatuses = useMemo(() => { const statuses = Array.from( new Set(effectiveDevices.map((device) => device.status)) ); return statuses.sort(); }, [effectiveDevices]); // 创建设备索引 Map,使用设备 ID 作为键 const deviceIndex = useMemo(() => { const index = new Map(); effectiveDevices.forEach((device) => { index.set(device.id, device); }); return index; }, [effectiveDevices]); // 过滤设备列表 const filteredDevices = useMemo(() => { if ( searchQuery === "" && selectedType === "all" && selectedStatus === "all" ) { return effectiveDevices; } const searchLower = searchQuery.toLowerCase(); return effectiveDevices.filter((device) => { if (searchQuery === "") return true; const nameLower = device.name.toLowerCase(); const idLower = device.id.toLowerCase(); const matchesSearch = nameLower.indexOf(searchLower) !== -1 || idLower.indexOf(searchLower) !== -1; const matchesType = selectedType === "all" || device.type === selectedType; const matchesStatus = selectedStatus === "all" || device.status === selectedStatus; return matchesSearch && matchesType && matchesStatus; }); }, [effectiveDevices, searchQuery, selectedType, selectedStatus]); // 状态颜色映射 const getStatusColor = (status: string) => { switch (status) { case "online": return "success"; case "offline": return "default"; case "warning": return "warning"; case "error": return "error"; default: return "default"; } }; // 状态图标映射 const getStatusIcon = (status: string) => { switch (status) { case "online": return "●"; case "offline": return "○"; case "warning": return "▲"; case "error": return "✕"; default: return "●"; } }; // 处理设备点击 const handleDeviceClick = (device: SCADADevice, event?: React.MouseEvent) => { onDeviceClick?.(device); setInternalSelection((prev) => { const exists = prev.includes(device.id); const nextSelection = multiSelect ? exists ? prev.filter((id) => id !== device.id) : [...prev, device.id] : exists ? [] : [device.id]; setPendingSelection(nextSelection); // 设置待处理的 selection,延迟调用 return nextSelection; }); }; // 处理缩放到设备 const handleZoomToDevice = (device: SCADADevice) => { map ?.getView() .fit(new Point(device.coordinates), { maxZoom: 15, duration: 1000 }); }; // 清除搜索 const handleClearSearch = useCallback(() => { setInputValue(""); startTransition(() => { setSearchQuery(""); }); }, []); // 重置所有筛选条件 const handleResetFilters = useCallback(() => { setInputValue(""); startTransition(() => { setSearchQuery(""); setSelectedType("all"); setSelectedStatus("all"); }); }, []); // 清理定时器 useEffect(() => { return () => { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } }; }, []); return ( {/* 头部控制栏 */} SCADA 设备列表 setIsExpanded(!isExpanded)} sx={{ color: "white" }} > {isExpanded ? : } {/* 搜索和筛选栏 */} {/* 搜索框 */} { setInputValue(e.target.value); debouncedSetSearchQuery(e.target.value); }} inputProps={{ "aria-label": "search devices" }} /> {searchQuery && ( <> )} {/* 筛选器 */} 设备类型 状态 {/* 筛选结果统计 */} 共找到 {filteredDevices.length} 个设备 {devices.length !== filteredDevices.length && ` (共 ${effectiveDevices.length} 个设备)`} {/* 设备列表 */} {loading ? ( ) : filteredDevices.length === 0 ? ( {searchQuery || selectedType !== "all" || selectedStatus !== "all" ? "未找到匹配的设备" : "暂无 SCADA 设备"} ) : ( {filteredDevices.map((device, index) => ( handleDeviceClick(device, event)} sx={{ "&.Mui-selected": { backgroundColor: "primary.50", borderLeft: 3, borderColor: "primary.main", }, "&:hover": { backgroundColor: "grey.50", }, }} > {getStatusIcon(device.status)} {device.name} } secondary={ ID: {device.id} 坐标: {device.coordinates[0].toFixed(6)},{" "} {device.coordinates[1].toFixed(6)} } slotProps={{ secondary: { component: "div", // 使其支持多行 }, }} /> { event.stopPropagation(); handleZoomToDevice(device); }} sx={{ ml: 1, color: "primary.main", "&:hover": { backgroundColor: "primary.50", }, }} > {index < filteredDevices.length - 1 && ( )} ))} )} ); }; export default SCADADeviceList;