diff --git a/src/components/olmap/SCADADataPanel.tsx b/src/components/olmap/SCADADataPanel.tsx index e1b69ef..d695194 100644 --- a/src/components/olmap/SCADADataPanel.tsx +++ b/src/components/olmap/SCADADataPanel.tsx @@ -88,35 +88,53 @@ const fetchFromBackend = async ( return []; } - 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 device_ids = deviceIds.join(","); + const start_time = dayjs(range.from).toISOString(); + const end_time = dayjs(range.to).toISOString(); // 清洗数据接口 - const cleaningSCADAUrl = `${config.BACKEND_URL}/querycleaningscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; + const cleaningDataUrl = `${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}`; // 原始数据 - const rawSCADAUrl = `${config.BACKEND_URL}/queryscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; + const rawDataUrl = `${config.BACKEND_URL}/timescaledb/scada/by-ids-field-time-range?device_ids=${device_ids}&field=monitored_value&start_time=${start_time}&end_time=${end_time}`; // 模拟数据接口 - const simulationSCADAUrl = `${config.BACKEND_URL}/querysimulationscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; - try { - let response; - response = await fetch(cleaningSCADAUrl); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - let transformedData = transformBackendData(data, deviceIds); + const simulationDataUrl = `${config.BACKEND_URL}/timescaledb/composite/scada-simulation?device_ids=${device_ids}&start_time=${start_time}&end_time=${end_time}`; - // 如果清洗数据接口返回空结果,使用原始数据接口 - if (transformedData.length === 0) { - console.log("[SCADADataPanel] 清洗数据接口无结果,使用原始数据接口"); - response = await fetch(rawSCADAUrl); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - transformedData = transformBackendData(data, deviceIds); + try { + // 优先查询清洗数据和模拟数据 + const [cleaningRes, simulationRes] = await Promise.all([ + fetch(cleaningDataUrl) + .then((r) => (r.ok ? r.json() : null)) + .catch(() => null), + fetch(simulationDataUrl) + .then((r) => (r.ok ? r.json() : null)) + .catch(() => null), + ]); + + const cleaningData = transformBackendData(cleaningRes, deviceIds); + const simulationData = transformBackendData(simulationRes, deviceIds); + + // 如果清洗数据有数据,返回清洗和模拟数据 + if (cleaningData.length > 0) { + return mergeTimeSeriesData( + cleaningData, + simulationData, + deviceIds, + "clean", + "sim" + ); + } else { + // 如果清洗数据没有数据,查询原始数据,返回模拟和原始数据 + const rawRes = await fetch(rawDataUrl) + .then((r) => (r.ok ? r.json() : null)) + .catch(() => null); + const rawData = transformBackendData(rawRes, deviceIds); + return mergeTimeSeriesData( + simulationData, + rawData, + deviceIds, + "sim", + "raw" + ); } - return transformedData; } catch (error) { console.error("[SCADADataPanel] 从后端获取数据失败:", error); throw error; @@ -178,6 +196,48 @@ const transformBackendData = ( return []; }; +/** + * 合并两个时间序列数据,为每个设备添加后缀 + */ +const mergeTimeSeriesData = ( + data1: TimeSeriesPoint[], + data2: TimeSeriesPoint[], + deviceIds: string[], + suffix1: string, + suffix2: string +): TimeSeriesPoint[] => { + const timeMap = new Map>(); + + const processData = (data: TimeSeriesPoint[], suffix: string) => { + data.forEach((point) => { + if (!timeMap.has(point.timestamp)) { + timeMap.set(point.timestamp, {}); + } + const values = timeMap.get(point.timestamp)!; + deviceIds.forEach((deviceId) => { + const value = point.values[deviceId]; + if (value !== undefined) { + values[`${deviceId}_${suffix}`] = value; + } + }); + }); + }; + + processData(data1, suffix1); + processData(data2, suffix2); + + const result = Array.from(timeMap.entries()).map(([timestamp, values]) => ({ + timestamp, + values, + })); + + result.sort( + (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() + ); + + return result; +}; + const defaultFetcher = fetchFromBackend; const formatTimestamp = (timestamp: string) => @@ -220,7 +280,11 @@ const buildDataset = ( }); } else { deviceIds.forEach((id) => { - const value = point.values[id]; + const value = + point.values[`${id}_clean`] ?? + point.values[`${id}_raw`] ?? + point.values[`${id}_sim`] ?? + point.values[id]; entry[id] = typeof value === "number" ? Number.isFinite(value) @@ -269,23 +333,26 @@ const SCADADataPanel: React.FC = ({ deviceIds: string[], range: { from: Date; to: Date } ): Promise => { - 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 device_ids = deviceIds.join(","); + const start_time = dayjs(range.from).toISOString(); + const end_time = dayjs(range.to).toISOString(); - const cleaningUrl = `${config.BACKEND_URL}/querycleaningscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; - const rawUrl = `${config.BACKEND_URL}/queryscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; - const simUrl = `${config.BACKEND_URL}/querysimulationscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; + // 清洗数据接口 + const cleaningDataUrl = `${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}`; + // 原始数据 + const rawDataUrl = `${config.BACKEND_URL}/timescaledb/scada/by-ids-field-time-range?device_ids=${device_ids}&field=monitored_value&start_time=${start_time}&end_time=${end_time}`; + // 模拟数据接口 + const simulationDataUrl = `${config.BACKEND_URL}/timescaledb/composite/scada-simulation?device_ids=${device_ids}&start_time=${start_time}&end_time=${end_time}`; try { const [cleanRes, rawRes, simRes] = await Promise.all([ - fetch(cleaningUrl) + fetch(cleaningDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), - fetch(rawUrl) + fetch(rawDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), - fetch(simUrl) + fetch(simulationDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), ]); @@ -442,21 +509,16 @@ const SCADADataPanel: React.FC = ({ try { const { from: rangeFrom, to: rangeTo } = normalizedRange; - const startTime = dayjs(rangeFrom).format("YYYY-MM-DD HH:mm:ss"); - const endTime = dayjs(rangeTo).format("YYYY-MM-DD HH:mm:ss"); + const startTime = dayjs(rangeFrom).toISOString(); + const endTime = dayjs(rangeTo).toISOString(); // 调用后端清洗接口 const response = await axios.post( - `${config.BACKEND_URL}/scadadevicedatacleaning/`, - null, - { - params: { - ids_list: deviceIds.join(","), // 修改:将数组转为逗号分隔字符串 - start_time: startTime, - end_time: endTime, - user_name: user.name, - }, - } + `${ + config.BACKEND_URL + }/timescaledb/composite/clean-scada?device_ids=${deviceIds.join( + "," + )}&start_time=${startTime}&end_time=${endTime}` ); console.log("[SCADADataPanel] 清洗响应:", response.data);