diff --git a/src/app/OlMap/Controls/HistoryDataPanel.tsx b/src/app/OlMap/Controls/HistoryDataPanel.tsx index b73fbfd..c8a8232 100644 --- a/src/app/OlMap/Controls/HistoryDataPanel.tsx +++ b/src/app/OlMap/Controls/HistoryDataPanel.tsx @@ -45,8 +45,8 @@ export interface TimeSeriesPoint { } export interface SCADADataPanelProps { - /** 选中的设备 ID 列表 */ - deviceIds: string[]; + /** 选中的要素信息列表,格式为 [[id, type], [id, type]] */ + featureInfos: [string, string][]; /** 数据类型: realtime-查询模拟值和监测值, none-仅查询监测值, scheme-查询策略模拟值和监测值 */ type?: "realtime" | "scheme" | "none"; /** 策略类型 */ @@ -55,7 +55,7 @@ export interface SCADADataPanelProps { scheme_name?: string; /** 自定义数据获取器,默认使用后端 API */ fetchTimeSeriesData?: ( - deviceIds: string[], + featureInfos: [string, string][], range: { from: Date; to: Date }, type?: "realtime" | "scheme" | "none", scheme_type?: string, @@ -75,28 +75,36 @@ type LoadingState = "idle" | "loading" | "success" | "error"; * 从后端 API 获取 SCADA 数据 */ const fetchFromBackend = async ( - deviceIds: string[], + featureInfos: [string, string][], range: { from: Date; to: Date }, type: "realtime" | "scheme" | "none" = "realtime", scheme_type?: string, scheme_name?: string ): Promise => { - if (deviceIds.length === 0) { + if (featureInfos.length === 0) { return []; } - const device_ids = deviceIds.join(","); + // 提取设备 ID 列表 + const featureIds = featureInfos.map(([id]) => id); + + const feature_ids = featureIds.join(","); const start_time = dayjs(range.from).toISOString(); const end_time = dayjs(range.to).toISOString(); - // 监测值数据接口 - const rawDataUrl = `${config.BACKEND_URL}/timescaledb/scada/by-ids-field-time-range?device_ids=${device_ids}&field=raw_value&start_time=${start_time}&end_time=${end_time}`; - // 清洗数据接口 - const cleanedDataUrl = `${config.BACKEND_URL}/timescaledb/scada/by-ids-field-time-range?device_ids=${device_ids}&field=cleaned_value&start_time=${start_time}&end_time=${end_time}`; + // 将 featureInfos 转换为后端期望的格式: id1:type1,id2:type2 + const feature_infos = featureInfos + .map(([id, type]) => `${id}:${type}`) + .join(","); + + // 监测值数据接口(use_cleaned=false) + const rawDataUrl = `${config.BACKEND_URL}/timescaledb/composite/element-scada?element_id=${feature_ids}&start_time=${start_time}&end_time=${end_time}&use_cleaned=false`; + // 清洗数据接口(use_cleaned=true) + const cleanedDataUrl = `${config.BACKEND_URL}/timescaledb/composite/element-scada?element_id=${feature_ids}&start_time=${start_time}&end_time=${end_time}&use_cleaned=true`; // 模拟数据接口 - const simulationDataUrl = `${config.BACKEND_URL}/timescaledb/composite/scada-simulation?device_ids=${device_ids}&start_time=${start_time}&end_time=${end_time}`; + const simulationDataUrl = `${config.BACKEND_URL}/timescaledb/composite/element-simulation?feature_infos=${feature_infos}&start_time=${start_time}&end_time=${end_time}`; // 策略模拟数据接口 - const schemeSimulationDataUrl = `${config.BACKEND_URL}/timescaledb/composite/scada-simulation?device_ids=${device_ids}&start_time=${start_time}&end_time=${end_time}&scheme_type=${scheme_type}&scheme_name=${scheme_name}`; + const schemeSimulationDataUrl = `${config.BACKEND_URL}/timescaledb/composite/element-simulation?feature_infos=${feature_infos}&start_time=${start_time}&end_time=${end_time}&scheme_type=${scheme_type}&scheme_name=${scheme_name}`; try { if (type === "none") { @@ -110,13 +118,13 @@ const fetchFromBackend = async ( .catch(() => null), ]); - const cleanedData = transformBackendData(cleanedRes, deviceIds); - const rawData = transformBackendData(rawRes, deviceIds); + const cleanedData = transformBackendData(cleanedRes, featureIds); + const rawData = transformBackendData(rawRes, featureIds); return mergeTimeSeriesData( cleanedData, rawData, - deviceIds, + featureIds, "clean", "raw" ); @@ -134,9 +142,9 @@ const fetchFromBackend = async ( .catch(() => null), ]); - const cleanedData = transformBackendData(cleanedRes, deviceIds); - const rawData = transformBackendData(rawRes, deviceIds); - const schemeSimData = transformBackendData(schemeSimRes, deviceIds); + const cleanedData = transformBackendData(cleanedRes, featureIds); + const rawData = transformBackendData(rawRes, featureIds); + const schemeSimData = transformBackendData(schemeSimRes, featureIds); // 合并三组数据 const timeMap = new Map>(); @@ -148,7 +156,7 @@ const fetchFromBackend = async ( timeMap.set(point.timestamp, {}); } const values = timeMap.get(point.timestamp)!; - deviceIds.forEach((deviceId) => { + featureIds.forEach((deviceId) => { const value = point.values[deviceId]; if (value !== undefined) { values[`${deviceId}_${suffix}`] = value; @@ -184,9 +192,9 @@ const fetchFromBackend = async ( .catch(() => null), ]); - const cleanedData = transformBackendData(cleanedRes, deviceIds); - const rawData = transformBackendData(rawRes, deviceIds); - const simulationData = transformBackendData(simulationRes, deviceIds); + const cleanedData = transformBackendData(cleanedRes, featureIds); + const rawData = transformBackendData(rawRes, featureIds); + const simulationData = transformBackendData(simulationRes, featureIds); // 合并三组数据 const timeMap = new Map>(); @@ -198,7 +206,7 @@ const fetchFromBackend = async ( timeMap.set(point.timestamp, {}); } const values = timeMap.get(point.timestamp)!; - deviceIds.forEach((deviceId) => { + featureIds.forEach((deviceId) => { const value = point.values[deviceId]; if (value !== undefined) { values[`${deviceId}_${suffix}`] = value; @@ -384,7 +392,7 @@ const emptyStateMessages: Record< }; const SCADADataPanel: React.FC = ({ - deviceIds, + featureInfos, type = "realtime", scheme_type, scheme_name, @@ -396,6 +404,12 @@ const SCADADataPanel: React.FC = ({ return fetchTimeSeriesData; }, [fetchTimeSeriesData]); + // 从 featureInfos 中提取设备 ID 列表 + const deviceIds = useMemo( + () => featureInfos.map(([id]) => id), + [featureInfos] + ); + const [from, setFrom] = useState(() => dayjs().subtract(1, "day")); const [to, setTo] = useState(() => dayjs()); const [activeTab, setActiveTab] = useState(defaultTab); @@ -405,7 +419,7 @@ const SCADADataPanel: React.FC = ({ const [deviceLabels, setDeviceLabels] = useState>({}); const [selectedSource, setSelectedSource] = useState< "raw" | "clean" | "sim" | "all" - >(() => (deviceIds.length === 1 ? "all" : "clean")); + >(() => (featureInfos.length === 1 ? "all" : "clean")); const draggableRef = useRef(null); // 获取 SCADA 设备信息,生成 deviceLabels @@ -466,7 +480,7 @@ const SCADADataPanel: React.FC = ({ try { const { from: rangeFrom, to: rangeTo } = normalizedRange; const result = await customFetcher( - deviceIds, + featureInfos, { from: rangeFrom.toDate(), to: rangeTo.toDate(), @@ -483,7 +497,7 @@ const SCADADataPanel: React.FC = ({ } }, [ - deviceIds, + featureInfos, customFetcher, hasDevices, normalizedRange, @@ -500,14 +514,14 @@ const SCADADataPanel: React.FC = ({ } else { setTimeSeries([]); } - }, [deviceIds.join(",")]); + }, [JSON.stringify(featureInfos)]); // 当设备数量变化时,调整数据源选择 useEffect(() => { - if (deviceIds.length > 1 && selectedSource === "all") { + if (featureInfos.length > 1 && selectedSource === "all") { setSelectedSource("clean"); } - }, [deviceIds.length, selectedSource]); + }, [featureInfos.length, selectedSource]); const columns: GridColDef[] = useMemo(() => { const base: GridColDef[] = [ @@ -863,7 +877,7 @@ const SCADADataPanel: React.FC = ({ = ({ hiddenButtons, queryType }) => { const [showHistoryPanel, setShowHistoryPanel] = useState(false); const [highlightLayer, setHighlightLayer] = useState | null>(null); - const [featureId, setFeatureId] = useState(""); // 样式状态管理 - 在 Toolbar 中管理,带有默认样式 const [layerStyleStates, setLayerStyleStates] = useState([ @@ -198,19 +196,6 @@ const Toolbar: React.FC = ({ hiddenButtons, queryType }) => { } }, [activeTools, map, handleMapClickSelectFeatures]); - // 监听 highlightFeature 变化,更新 featureId - useEffect(() => { - if (highlightFeature) { - const id = highlightFeature.getProperties().id; - if (id) { - setFeatureId(id); - } - console.log("高亮要素 ID:", id); - } else { - setFeatureId(""); - } - }, [highlightFeature]); - // 处理工具栏按钮点击事件 const handleToolClick = (tool: string) => { // 样式工具的特殊处理 - 只有再次点击时才会取消激活和关闭 @@ -671,7 +656,36 @@ const Toolbar: React.FC = ({ hiddenButtons, queryType }) => { )} {showHistoryPanel && ( { + if (!highlightFeature || !showHistoryPanel) return []; + const properties = highlightFeature.getProperties(); + const id = properties.id; + if (!id) return []; + + // 从图层名称推断类型 + const layerId = + highlightFeature.getId()?.toString().split(".")[0] || ""; + let type = "unknown"; + + if (layerId.includes("pipe")) { + type = "pipe"; + } else if (layerId.includes("junction")) { + type = "junction"; + } else if (layerId.includes("tank")) { + type = "tank"; + } else if (layerId.includes("reservoir")) { + type = "reservoir"; + } else if (layerId.includes("pump")) { + type = "pump"; + } else if (layerId.includes("valve")) { + type = "valve"; + } + // 仅处理 type 为 pipe 或 junction 的情况 + if (type !== "pipe" && type !== "junction") { + return []; + } + return [[id, type]]; + })()} scheme_type="burst_Analysis" scheme_name={schemeName} type={queryType as "realtime" | "scheme" | "none"}