From bf067aa8ebb45ecda1e3d73140ee5f150fd40265 Mon Sep 17 00:00:00 2001 From: JIANG Date: Wed, 5 Nov 2025 15:33:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=95=B0=E6=8D=AE=E6=B8=85?= =?UTF-8?q?=E6=B4=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/olmap/SCADADataPanel.tsx | 149 ++++++++++++++++++++--- src/components/olmap/SCADADeviceList.tsx | 78 +++++++++++- 2 files changed, 205 insertions(+), 22 deletions(-) diff --git a/src/components/olmap/SCADADataPanel.tsx b/src/components/olmap/SCADADataPanel.tsx index 468463c..b8e1ae1 100644 --- a/src/components/olmap/SCADADataPanel.tsx +++ b/src/components/olmap/SCADADataPanel.tsx @@ -16,7 +16,6 @@ import { Drawer, } from "@mui/material"; import { - Close, Refresh, ShowChart, TableChart, @@ -31,13 +30,20 @@ import utc from "dayjs/plugin/utc"; import timezone from "dayjs/plugin/timezone"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers"; -import clsx from "clsx"; -import config from "@/config/config"; +import config, { NETWORK_NAME } from "@/config/config"; import { GeoJSON } from "ol/format"; +import { useGetIdentity } from "@refinedev/core"; +import { useNotification } from "@refinedev/core"; +import axios from "axios"; dayjs.extend(utc); dayjs.extend(timezone); +type IUser = { + id: number; + name: string; +}; + export interface TimeSeriesPoint { /** ISO8601 时间戳 */ timestamp: string; @@ -85,8 +91,8 @@ const fetchFromBackend = async ( const endtime = dayjs(range.to).format("YYYY-MM-DD HH:mm:ss"); // 清洗数据接口 const cleaningSCADAUrl = `${config.backendUrl}/querycleaningscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; - - const originSCADAUrl = `${config.backendUrl}/queryscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; + // 原始数据 + const rawSCADAUrl = `${config.backendUrl}/queryscadadatabydeviceidandtimerange/?ids=${ids}&starttime=${starttime}&endtime=${endtime}`; try { const response = await fetch(cleaningSCADAUrl); @@ -94,7 +100,20 @@ const fetchFromBackend = async ( throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); - return transformBackendData(data, deviceIds); + const transformedData = transformBackendData(data, deviceIds); + + // 如果清洗数据接口返回空结果,使用原始数据接口 + if (transformedData.length === 0) { + console.log("[SCADADataPanel] 清洗数据接口无结果,使用原始数据接口"); + const rawResponse = await fetch(rawSCADAUrl); + if (!rawResponse.ok) { + throw new Error(`HTTP error! status: ${rawResponse.status}`); + } + const originData = await rawResponse.json(); + return transformBackendData(originData, deviceIds); + } + + return transformedData; } catch (error) { console.error("[SCADADataPanel] 从后端获取数据失败:", error); throw error; @@ -232,11 +251,11 @@ const emptyStateMessages: Record< > = { chart: { title: "暂无时序数据", - subtitle: "请选择设备并点击刷新来获取曲线", + subtitle: "请切换时间段来获取曲线", }, table: { title: "暂无表格数据", - subtitle: "请选择设备并点击刷新来获取记录", + subtitle: "请切换时间段来获取记录", }, }; @@ -249,6 +268,9 @@ const SCADADataPanel: React.FC = ({ showCleaning = false, onCleanData, }) => { + const { open } = useNotification(); + const { data: user } = useGetIdentity(); + const [from, setFrom] = useState(() => dayjs().subtract(1, "day")); const [to, setTo] = useState(() => dayjs()); const [activeTab, setActiveTab] = useState(defaultTab); @@ -257,6 +279,7 @@ const SCADADataPanel: React.FC = ({ const [error, setError] = useState(null); const [isExpanded, setIsExpanded] = useState(true); const [deviceLabels, setDeviceLabels] = useState>({}); + const [isCleaning, setIsCleaning] = useState(false); // 获取 SCADA 设备信息,生成 deviceLabels useEffect(() => { @@ -330,6 +353,84 @@ const SCADADataPanel: React.FC = ({ [deviceIds, fetchTimeSeriesData, hasDevices, normalizedRange] ); + // 处理数据清洗 + const handleCleanData = useCallback(async () => { + if (!hasDevices) { + open?.({ + type: "error", + message: "请先选择设备", + }); + return; + } + + if (!user || !user.name) { + open?.({ + type: "error", + message: "用户信息无效,请重新登录", + }); + return; + } + + setIsCleaning(true); + + 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 response = await axios.post( + `${config.backendUrl}/scadadevicedatacleaning/`, + null, + { + params: { + network: NETWORK_NAME, + ids_list: deviceIds.join(","), // 修改:将数组转为逗号分隔字符串 + start_time: startTime, + end_time: endTime, + user_name: user.name, + }, + } + ); + + console.log("[SCADADataPanel] 清洗响应:", response.data); + + // 处理成功响应 + if (response.data === "success" || response.data?.success === true) { + open?.({ + type: "success", + message: "数据清洗成功", + description: `已完成 ${deviceIds.length} 个设备的数据清洗`, + }); + + // 清洗完成后自动刷新数据 + await handleFetch("after-cleaning"); + + // 如果父组件提供了回调,也调用它 + onCleanData?.(); + } else { + throw new Error(response.data?.message || "清洗失败"); + } + } catch (err: any) { + console.error("[SCADADataPanel] 数据清洗失败:", err); + open?.({ + type: "error", + message: "数据清洗失败", + description: err.response?.data?.message || err.message || "未知错误", + }); + } finally { + setIsCleaning(false); + } + }, [ + deviceIds, + hasDevices, + normalizedRange, + user, + open, + onCleanData, + handleFetch, + ]); + // 设备变化时自动查询 useEffect(() => { if (hasDevices) { @@ -337,7 +438,7 @@ const SCADADataPanel: React.FC = ({ } else { setTimeSeries([]); } - }, [deviceIds.join(",")]); // 移除 hasDevices,因为它由 deviceIds 决定,避免潜在的依赖循环 + }, [deviceIds.join(",")]); const columns: GridColDef[] = useMemo(() => { const base: GridColDef[] = [ @@ -389,7 +490,7 @@ const SCADADataPanel: React.FC = ({ justifyContent: "center", py: 8, color: "text.secondary", - height: 420, + height: "100%", }} > @@ -421,10 +522,10 @@ const SCADADataPanel: React.FC = ({ ]; return ( - + = ({ legend: { direction: "row", position: { horizontal: "middle", vertical: "bottom" }, - padding: { top: 20, bottom: 10, left: 0, right: 0 }, + padding: { bottom: 2, left: 0, right: 0 }, itemMarkWidth: 16, itemMarkHeight: 3, markGap: 8, @@ -542,7 +643,7 @@ const SCADADataPanel: React.FC = ({ pageSizeOptions={[25, 50, 100]} sx={{ border: "none", - height: "420px", + height: "100%", "& .MuiDataGrid-cell": { borderColor: "#f0f0f0", }, @@ -732,11 +833,21 @@ const SCADADataPanel: React.FC = ({ variant="outlined" size="small" color="secondary" - startIcon={} - disabled={!hasDevices || loadingState === "loading"} - onClick={onCleanData} + startIcon={ + isCleaning ? ( + + ) : ( + + ) + } + disabled={ + !hasDevices || + loadingState === "loading" || + isCleaning + } + onClick={handleCleanData} > - 清洗 + {isCleaning ? "清洗中..." : "清洗"} @@ -744,7 +855,7 @@ const SCADADataPanel: React.FC = ({