diff --git a/src/app/OlMap/Controls/HistoryDataPanel.tsx b/src/app/OlMap/Controls/HistoryDataPanel.tsx index 558a2fa..b73fbfd 100644 --- a/src/app/OlMap/Controls/HistoryDataPanel.tsx +++ b/src/app/OlMap/Controls/HistoryDataPanel.tsx @@ -90,7 +90,7 @@ const fetchFromBackend = async ( const end_time = dayjs(range.to).toISOString(); // 监测值数据接口 - const monitoredDataUrl = `${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 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}`; // 模拟数据接口 @@ -101,32 +101,32 @@ const fetchFromBackend = async ( try { if (type === "none") { // 查询清洗值和监测值 - const [cleanedRes, monitoredRes] = await Promise.all([ + const [cleanedRes, rawRes] = await Promise.all([ fetch(cleanedDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), - fetch(monitoredDataUrl) + fetch(rawDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), ]); const cleanedData = transformBackendData(cleanedRes, deviceIds); - const monitoredData = transformBackendData(monitoredRes, deviceIds); + const rawData = transformBackendData(rawRes, deviceIds); return mergeTimeSeriesData( cleanedData, - monitoredData, + rawData, deviceIds, "clean", - "monitored" + "raw" ); } else if (type === "scheme") { // 查询策略模拟值、清洗值和监测值 - const [cleanedRes, monitoredRes, schemeSimRes] = await Promise.all([ + const [cleanedRes, rawRes, schemeSimRes] = await Promise.all([ fetch(cleanedDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), - fetch(monitoredDataUrl) + fetch(rawDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), fetch(schemeSimulationDataUrl) @@ -135,14 +135,14 @@ const fetchFromBackend = async ( ]); const cleanedData = transformBackendData(cleanedRes, deviceIds); - const monitoredData = transformBackendData(monitoredRes, deviceIds); + const rawData = transformBackendData(rawRes, deviceIds); const schemeSimData = transformBackendData(schemeSimRes, deviceIds); // 合并三组数据 const timeMap = new Map>(); - [cleanedData, monitoredData, schemeSimData].forEach((data, index) => { - const suffix = ["clean", "monitored", "scheme_sim"][index]; + [cleanedData, rawData, schemeSimData].forEach((data, index) => { + const suffix = ["clean", "raw", "scheme_sim"][index]; data.forEach((point) => { if (!timeMap.has(point.timestamp)) { timeMap.set(point.timestamp, {}); @@ -172,11 +172,11 @@ const fetchFromBackend = async ( return result; } else { // realtime: 查询模拟值、清洗值和监测值 - const [cleanedRes, monitoredRes, simulationRes] = await Promise.all([ + const [cleanedRes, rawRes, simulationRes] = await Promise.all([ fetch(cleanedDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), - fetch(monitoredDataUrl) + fetch(rawDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), fetch(simulationDataUrl) @@ -185,14 +185,14 @@ const fetchFromBackend = async ( ]); const cleanedData = transformBackendData(cleanedRes, deviceIds); - const monitoredData = transformBackendData(monitoredRes, deviceIds); + const rawData = transformBackendData(rawRes, deviceIds); const simulationData = transformBackendData(simulationRes, deviceIds); // 合并三组数据 const timeMap = new Map>(); - [cleanedData, monitoredData, simulationData].forEach((data, index) => { - const suffix = ["clean", "monitored", "sim"][index]; + [cleanedData, rawData, simulationData].forEach((data, index) => { + const suffix = ["clean", "raw", "sim"][index]; data.forEach((point) => { if (!timeMap.has(point.timestamp)) { timeMap.set(point.timestamp, {}); @@ -351,7 +351,7 @@ const buildDataset = ( }; deviceIds.forEach((id) => { - ["clean", "monitored", "sim", "scheme_sim"].forEach((suffix) => { + ["clean", "raw", "sim", "scheme_sim"].forEach((suffix) => { const key = `${id}_${suffix}`; const value = point.values[key]; if (value !== undefined && value !== null) { @@ -520,23 +520,49 @@ const SCADADataPanel: React.FC = ({ ]; const dynamic = (() => { - return deviceIds.map((id) => ({ - field: id, - headerName: deviceLabels?.[id] ?? id, - minWidth: 140, - flex: 1, - valueFormatter: (value: any) => { - if (value === null || value === undefined) return "--"; - if (Number.isFinite(Number(value))) { - return Number(value).toFixed(fractionDigits); + const cols: GridColDef[] = []; + + deviceIds.forEach((id) => { + const deviceName = deviceLabels?.[id] ?? id; + + // 为每个设备的每种数据类型创建列 + const suffixes = [ + { key: "clean", name: "清洗值" }, + { key: "raw", name: "监测值" }, + { key: "sim", name: "模拟值" }, + { key: "scheme_sim", name: "策略模拟值" }, + ]; + + suffixes.forEach(({ key, name }) => { + const fieldKey = `${id}_${key}`; + // 检查是否有该字段的数据 + const hasData = dataset.some( + (item) => item[fieldKey] !== null && item[fieldKey] !== undefined + ); + + if (hasData) { + cols.push({ + field: fieldKey, + headerName: `${deviceName} (${name})`, + minWidth: 140, + flex: 1, + valueFormatter: (value: any) => { + if (value === null || value === undefined) return "--"; + if (Number.isFinite(Number(value))) { + return Number(value).toFixed(fractionDigits); + } + return String(value); + }, + }); } - return String(value); - }, - })); + }); + }); + + return cols; })(); return [...base, ...dynamic]; - }, [deviceIds, deviceLabels, fractionDigits, selectedSource]); + }, [deviceIds, deviceLabels, fractionDigits, dataset]); const rows = useMemo( () => @@ -597,48 +623,46 @@ const SCADADataPanel: React.FC = ({ const getSeries = () => { return deviceIds.flatMap((id, index) => { const series = []; - ["clean", "monitored", "sim", "scheme_sim"].forEach( - (suffix, sIndex) => { - const key = `${id}_${suffix}`; - const hasData = dataset.some( - (item) => item[key] !== null && item[key] !== undefined - ); - if (hasData) { - const displayName = - suffix === "clean" - ? "清洗值" - : suffix === "monitored" - ? "监测值" - : suffix === "sim" - ? "模拟" - : "策略模拟"; + ["clean", "raw", "sim", "scheme_sim"].forEach((suffix, sIndex) => { + const key = `${id}_${suffix}`; + const hasData = dataset.some( + (item) => item[key] !== null && item[key] !== undefined + ); + if (hasData) { + const displayName = + suffix === "clean" + ? "清洗值" + : suffix === "raw" + ? "监测值" + : suffix === "sim" + ? "模拟" + : "策略模拟"; - series.push({ - name: `${deviceLabels?.[id] ?? id} (${displayName})`, - type: "line", - symbol: "none", - sampling: "lttb", - itemStyle: { - color: colors[(index * 4 + sIndex) % colors.length], - }, - data: dataset.map((item) => item[key]), - areaStyle: { - color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ - { - offset: 0, - color: colors[(index * 4 + sIndex) % colors.length], - }, - { - offset: 1, - color: "rgba(255, 255, 255, 0)", - }, - ]), - opacity: 0.3, - }, - }); - } + series.push({ + name: `${deviceLabels?.[id] ?? id} (${displayName})`, + type: "line", + symbol: "none", + sampling: "lttb", + itemStyle: { + color: colors[(index * 4 + sIndex) % colors.length], + }, + data: dataset.map((item) => item[key]), + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: colors[(index * 4 + sIndex) % colors.length], + }, + { + offset: 1, + color: "rgba(255, 255, 255, 0)", + }, + ]), + opacity: 0.3, + }, + }); } - ); + }); // 如果没有任何数据,则使用fallback if (series.length === 0) { series.push({ @@ -955,7 +979,7 @@ const SCADADataPanel: React.FC = ({ color="warning.main" sx={{ mt: 1, display: "block" }} > - 未选择任何设备,无法获取数据。 + 请选择一个要素以查看其历史数据。 )} {error && ( diff --git a/src/app/OlMap/Controls/Toolbar.tsx b/src/app/OlMap/Controls/Toolbar.tsx index a0354ec..69facd2 100644 --- a/src/app/OlMap/Controls/Toolbar.tsx +++ b/src/app/OlMap/Controls/Toolbar.tsx @@ -43,6 +43,7 @@ const Toolbar: React.FC = ({ hiddenButtons, queryType }) => { const [showHistoryPanel, setShowHistoryPanel] = useState(false); const [highlightLayer, setHighlightLayer] = useState | null>(null); + const [featureId, setFeatureId] = useState(""); // 样式状态管理 - 在 Toolbar 中管理,带有默认样式 const [layerStyleStates, setLayerStyleStates] = useState([ @@ -186,14 +187,30 @@ const Toolbar: React.FC = ({ hiddenButtons, queryType }) => { ); // 添加矢量属性查询事件监听器 useEffect(() => { - if (!activeTools.includes("info") || !map) return; - map.on("click", handleMapClickSelectFeatures); + if (!map) return; + // 监听 info 或 history 工具激活时添加 + if (activeTools.includes("info") || activeTools.includes("history")) { + map.on("click", handleMapClickSelectFeatures); - return () => { - map.un("click", handleMapClickSelectFeatures); - }; + return () => { + map.un("click", handleMapClickSelectFeatures); + }; + } }, [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) => { // 样式工具的特殊处理 - 只有再次点击时才会取消激活和关闭 @@ -652,7 +669,14 @@ const Toolbar: React.FC = ({ hiddenButtons, queryType }) => { setLayerStyleStates={setLayerStyleStates} /> )} - {showHistoryPanel && } + {showHistoryPanel && ( + + )} {/* 图例显示 */} {activeLegendConfigs.length > 0 && ( diff --git a/src/components/olmap/SCADADataPanel.tsx b/src/components/olmap/SCADADataPanel.tsx index 5440f75..7985ab9 100644 --- a/src/components/olmap/SCADADataPanel.tsx +++ b/src/components/olmap/SCADADataPanel.tsx @@ -648,24 +648,50 @@ const SCADADataPanel: React.FC = ({ })); } } else { - return deviceIds.map((id) => ({ - field: id, - headerName: deviceLabels?.[id] ?? id, - minWidth: 140, - flex: 1, - valueFormatter: (value: any) => { - if (value === null || value === undefined) return "--"; - if (Number.isFinite(Number(value))) { - return Number(value).toFixed(fractionDigits); + // 非清洗模式:显示所有设备的所有有数据的列 + const cols: GridColDef[] = []; + + deviceIds.forEach((id) => { + const deviceName = deviceLabels?.[id] ?? id; + + // 为每个设备的每种数据类型创建列 + const suffixes = [ + { key: 'raw', name: '原始值' }, + { key: 'clean', name: '清洗值' }, + { key: 'sim', name: '模拟值' } + ]; + + suffixes.forEach(({ key, name }) => { + const fieldKey = `${id}_${key}`; + // 检查是否有该字段的数据 + const hasData = dataset.some( + (item) => item[fieldKey] !== null && item[fieldKey] !== undefined + ); + + if (hasData) { + cols.push({ + field: fieldKey, + headerName: `${deviceName} (${name})`, + minWidth: 140, + flex: 1, + valueFormatter: (value: any) => { + if (value === null || value === undefined) return "--"; + if (Number.isFinite(Number(value))) { + return Number(value).toFixed(fractionDigits); + } + return String(value); + }, + }); } - return String(value); - }, - })); + }); + }); + + return cols; } })(); return [...base, ...dynamic]; - }, [deviceIds, deviceLabels, fractionDigits, showCleaning, selectedSource]); + }, [deviceIds, deviceLabels, fractionDigits, showCleaning, selectedSource, dataset]); const rows = useMemo( () =>