From 60181dba54ce1334d6c06ba6b7d85157ba4cda07 Mon Sep 17 00:00:00 2001 From: Huarch Date: Mon, 27 Apr 2026 11:56:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=B1=9E=E6=80=A7=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF=E4=B8=BA=E5=8F=AF=E6=8B=96=E5=8A=A8=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=B7=A5=E5=85=B7=E6=A0=8F=E6=BF=80=E6=B4=BB=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/_refine_context.tsx | 2 +- src/components/olmap/SCADA/SCADADataPanel.tsx | 2 +- .../olmap/core/Controls/HistoryDataPanel.tsx | 133 ++++--- .../olmap/core/Controls/PropertyPanel.tsx | 341 +++++++++--------- .../olmap/core/Controls/Toolbar.tsx | 29 +- 5 files changed, 268 insertions(+), 239 deletions(-) diff --git a/src/app/_refine_context.tsx b/src/app/_refine_context.tsx index 9eece2b..e78741d 100644 --- a/src/app/_refine_context.tsx +++ b/src/app/_refine_context.tsx @@ -169,7 +169,7 @@ const App = (props: React.PropsWithChildren) => { { name: "Hydraulic Simulation", meta: { - icon: , + // icon: , label: "事件模拟", }, }, diff --git a/src/components/olmap/SCADA/SCADADataPanel.tsx b/src/components/olmap/SCADA/SCADADataPanel.tsx index 3b6544c..1477e55 100644 --- a/src/components/olmap/SCADA/SCADADataPanel.tsx +++ b/src/components/olmap/SCADA/SCADADataPanel.tsx @@ -986,7 +986,7 @@ const SCADADataPanel: React.FC = ({ setIsExpanded(true)} - sx={{ zIndex: 1300 }} + sx={{ zIndex: 1290 }} > diff --git a/src/components/olmap/core/Controls/HistoryDataPanel.tsx b/src/components/olmap/core/Controls/HistoryDataPanel.tsx index ae21ab5..340f41c 100644 --- a/src/components/olmap/core/Controls/HistoryDataPanel.tsx +++ b/src/components/olmap/core/Controls/HistoryDataPanel.tsx @@ -129,14 +129,17 @@ const fetchFromBackend = async ( "raw" ); } else if (type === "scheme") { - // 查询策略模拟值、清洗值和监测值 - const [cleanedRes, rawRes, schemeSimRes] = await Promise.all([ + // 查询策略模拟值、实时模拟值、清洗值和监测值 + const [cleanedRes, rawRes, simulationRes, schemeSimRes] = await Promise.all([ apiFetch(cleanedDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), apiFetch(rawDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), + apiFetch(simulationDataUrl) + .then((r) => (r.ok ? r.json() : null)) + .catch(() => null), apiFetch(schemeSimulationDataUrl) .then((r) => (r.ok ? r.json() : null)) .catch(() => null), @@ -146,40 +149,18 @@ const fetchFromBackend = async ( // 如果清洗数据有值,则不显示原始监测值 const rawData = cleanedData.length > 0 ? [] : transformBackendData(rawRes, featureIds); + const simulationData = transformBackendData(simulationRes, featureIds); const schemeSimData = transformBackendData(schemeSimRes, featureIds); - // 合并三组数据 - const timeMap = new Map>(); - - [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, {}); - } - const values = timeMap.get(point.timestamp)!; - featureIds.forEach((deviceId) => { - const value = point.values[deviceId]; - if (value !== undefined) { - values[`${deviceId}_${suffix}`] = value; - } - }); - }); - }); - - const result = Array.from(timeMap.entries()).map( - ([timestamp, values]) => ({ - timestamp, - values, - }) + return mergeMultipleTimeSeriesData( + [ + { data: cleanedData, suffix: "clean" }, + { data: rawData, suffix: "raw" }, + { data: simulationData, suffix: "sim" }, + { data: schemeSimData, suffix: "scheme_sim" }, + ], + featureIds ); - - result.sort( - (a, b) => - new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() - ); - - return result; } else { // realtime: 查询模拟值、清洗值和监测值 const [cleanedRes, rawRes, simulationRes] = await Promise.all([ @@ -336,6 +317,42 @@ const mergeTimeSeriesData = ( return result; }; +const mergeMultipleTimeSeriesData = ( + datasets: Array<{ + data: TimeSeriesPoint[]; + suffix: string; + }>, + deviceIds: string[] +): TimeSeriesPoint[] => { + const timeMap = new Map>(); + + datasets.forEach(({ data, suffix }) => { + 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; + } + }); + }); + }); + + 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 formatTimestamp = (timestamp: string) => dayjs(timestamp).tz("Asia/Shanghai").format("YYYY-MM-DD HH:mm"); @@ -537,7 +554,7 @@ const SCADADataPanel: React.FC = ({ const suffixes = [ { key: "clean", name: "清洗值" }, { key: "raw", name: "监测值" }, - { key: "sim", name: "模拟值" }, + { key: "sim", name: "实时模拟值" }, { key: "scheme_sim", name: "方案模拟值" }, ]; @@ -643,32 +660,46 @@ const SCADADataPanel: React.FC = ({ : suffix === "raw" ? "监测值" : suffix === "sim" - ? "模拟" + ? "实时模拟" : "方案模拟"; series.push({ name: `${id} (${displayName})`, type: "line", - symbol: "none", + symbol: + suffix === "clean" + ? "circle" + : suffix === "raw" + ? "diamond" + : "none", + symbolSize: suffix === "clean" || suffix === "raw" ? 7 : 0, + showSymbol: suffix === "clean" || suffix === "raw", sampling: "lttb", - connectNulls: true, + connectNulls: suffix !== "clean" && suffix !== "raw", 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, - }, + lineStyle: + suffix === "clean" || suffix === "raw" + ? { width: 0 } + : undefined, + areaStyle: + suffix === "clean" || suffix === "raw" + ? undefined + : { + 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, + }, }); } }); @@ -840,7 +871,7 @@ const SCADADataPanel: React.FC = ({ border: "none", display: "flex", flexDirection: "column", - zIndex: 1300, + zIndex: 1290, backgroundColor: "white", overflow: "hidden", "&:hover": { diff --git a/src/components/olmap/core/Controls/PropertyPanel.tsx b/src/components/olmap/core/Controls/PropertyPanel.tsx index 8aa07e7..2cd4d89 100644 --- a/src/components/olmap/core/Controls/PropertyPanel.tsx +++ b/src/components/olmap/core/Controls/PropertyPanel.tsx @@ -1,4 +1,7 @@ -import React from "react"; +"use client"; + +import React, { useRef } from "react"; +import Draggable from "react-draggable"; interface BaseProperty { label: string; @@ -28,6 +31,8 @@ const PropertyPanel: React.FC = ({ type = "未知类型", properties = [], }) => { + const draggableRef = useRef(null); + const formatValue = (property: BaseProperty) => { if (property.formatter) { return property.formatter(property.value); @@ -50,162 +55,16 @@ const PropertyPanel: React.FC = ({ : 0; return ( -
- {/* 头部 */} -
-
- - - -

属性面板

-
-
- - {/* 内容区域 */} -
- {!id ? ( -
+ +
+ {/* 头部 */} +
+
- - -

暂无属性信息

-

请选择一个要素以查看其属性

-
- ) : ( -
- {/* ID 属性 */} -
-
- - ID - - - {id} - -
-
- - {/* 类型属性 */} -
-
- - 类型 - - - {type} - -
-
- - {/* 其他属性(包含二级表格) */} - {properties.map((property, index) => { - // 二级表格 - if ("type" in property && property.type === "table") { - return ( -
-
- - {property.label} - -
-
- - - - {property.columns.map((col, ci) => ( - - ))} - - - - {property.rows.map((row, ri) => ( - - {row.map((cell, cci) => ( - - ))} - - ))} - -
- {col} -
- {cell} -
-
-
- ); - } - - // 普通属性 - const base = property as BaseProperty; - const isImportant = isImportantKeys.includes(base.label); - return ( -
-
- - {base.label} - - - {formatValue(base)} - -
-
- ); - })} -
- )} -
- - {/* 底部统计区域 */} -
-
- - = ({ strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} - d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" + d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> - 共 {totalProps} 个属性 - - {id && ( - - - 已选中 - +

属性面板

+
+
+ + {/* 内容区域 */} +
+ {!id ? ( +
+ + + +

暂无属性信息

+

请选择一个要素以查看其属性

+
+ ) : ( +
+ {/* ID 属性 */} +
+
+ + ID + + + {id} + +
+
+ + {/* 类型属性 */} +
+
+ + 类型 + + + {type} + +
+
+ + {/* 其他属性(包含二级表格) */} + {properties.map((property, index) => { + // 二级表格 + if ("type" in property && property.type === "table") { + return ( +
+
+ + {property.label} + +
+
+ + + + {property.columns.map((col, ci) => ( + + ))} + + + + {property.rows.map((row, ri) => ( + + {row.map((cell, cci) => ( + + ))} + + ))} + +
+ {col} +
+ {cell} +
+
+
+ ); + } + + // 普通属性 + const base = property as BaseProperty; + const isImportant = isImportantKeys.includes(base.label); + return ( +
+
+ + {base.label} + + + {formatValue(base)} + +
+
+ ); + })} +
)} + +
+ + {/* 底部统计区域 */} +
+
+ + + + + 共 {totalProps} 个属性 + + {id && ( + + + 已选中 + + )} +
-
+ ); }; diff --git a/src/components/olmap/core/Controls/Toolbar.tsx b/src/components/olmap/core/Controls/Toolbar.tsx index 1e7d2f8..3466c77 100644 --- a/src/components/olmap/core/Controls/Toolbar.tsx +++ b/src/components/olmap/core/Controls/Toolbar.tsx @@ -402,15 +402,8 @@ const Toolbar: React.FC = ({ deactivateTool(tool); setActiveTools((prev) => prev.filter((t) => t !== tool)); } else { - // 如果当前工具未激活,先关闭所有其他工具,然后激活当前工具 - // 关闭所有面板(但保持样式编辑器状态) - closeAllPanelsExceptStyle(); - - // 取消激活所有非样式工具 - setActiveTools((prev) => { - const styleActive = prev.includes("style"); - return styleActive ? ["style", tool] : [tool]; - }); + // 如果当前工具未激活,保留其他已打开工具,仅新增当前工具 + setActiveTools((prev) => [...prev, tool]); // 激活当前工具并打开对应面板 activateTool(tool); @@ -422,14 +415,18 @@ const Toolbar: React.FC = ({ switch (tool) { case "info": setShowPropertyPanel(false); - setHighlightFeatures([]); + if (!activeTools.includes("history")) { + setHighlightFeatures([]); + } break; case "draw": setShowDrawPanel(false); break; case "history": setShowHistoryPanel(false); - setHighlightFeatures([]); + if (!activeTools.includes("info")) { + setHighlightFeatures([]); + } setChatPanelFeatureInfos(null); setChatPanelTimeRange(null); break; @@ -452,16 +449,6 @@ const Toolbar: React.FC = ({ } }; - // 关闭所有面板(除了样式编辑器) - const closeAllPanelsExceptStyle = () => { - setShowPropertyPanel(false); - setHighlightFeatures([]); - setShowDrawPanel(false); - setShowHistoryPanel(false); - setChatPanelFeatureInfos(null); - setChatPanelTimeRange(null); - // 样式编辑器保持其当前状态,不自动关闭 - }; const [computedProperties, setComputedProperties] = useState< Record >({});