"use client"; import React, { useCallback, useMemo, useState } from "react"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import RefreshIcon from "@mui/icons-material/Refresh"; import { Box, Button, CircularProgress, Collapse, FormControl, MenuItem, Select, TextField, Typography, IconButton, } from "@mui/material"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { zhCN as pickerZhCN } from "@mui/x-date-pickers/locales"; import { useNotification } from "@refinedev/core"; import dayjs, { Dayjs } from "dayjs"; import "dayjs/locale/zh-cn"; import { api } from "@/lib/api"; import { NETWORK_NAME, config } from "@config/config"; import { FLOW_DISPLAY_UNIT, toM3s } from "@utils/units"; import { BurstLocationResult } from "./types"; interface Props { onResult: (result: BurstLocationResult) => void; } interface SchemeItem { scheme_id: number; scheme_name: string; scheme_type: string; create_time: string; scheme_start_time: string; scheme_detail?: { modify_total_duration: number; }; } type DataSource = "monitoring" | "simulation"; const AnalysisParameters: React.FC = ({ onResult }) => { const { open } = useNotification(); const [schemeName, setSchemeName] = useState(`Burst_Locate_${Date.now()}`); const [dataSource, setDataSource] = useState("monitoring"); const [schemes, setSchemes] = useState([]); const [selectedSchemeId, setSelectedSchemeId] = useState(""); const [schemeLoading, setSchemeLoading] = useState(false); const [burstLeakage, setBurstLeakage] = useState(1440); const [enableFlow, setEnableFlow] = useState(false); const [burstStartTime, setBurstStartTime] = useState( dayjs().subtract(20, "minute"), ); const [burstEndTime, setBurstEndTime] = useState( dayjs().subtract(5, "minute"), ); const [minDpressure, setMinDpressure] = useState(2); const [basicPressure, setBasicPressure] = useState(10); const [advancedOpen, setAdvancedOpen] = useState(false); const [running, setRunning] = useState(false); const isSimulationMode = dataSource === "simulation"; const applySchemeTimeRange = useCallback((scheme: SchemeItem) => { const start = dayjs(scheme.scheme_start_time); const durationSeconds = scheme.scheme_detail?.modify_total_duration ?? 3600; const end = start.add(durationSeconds, "second"); setBurstStartTime(start); setBurstEndTime(end); }, []); const fetchSchemes = useCallback( async ({ force = false, notify = false }: { force?: boolean; notify?: boolean } = {}) => { if (schemeLoading || (!force && schemes.length > 0)) return; setSchemeLoading(true); try { const response = await api.get(`${config.BACKEND_URL}/api/v1/getallschemes/`, { params: { network: NETWORK_NAME }, }); const burstSchemes = (response.data as SchemeItem[]).filter( (scheme) => scheme.scheme_type === "burst_analysis", ); setSchemes(burstSchemes); if (selectedSchemeId) { const matchedScheme = burstSchemes.find( (scheme) => scheme.scheme_id === selectedSchemeId, ); if (matchedScheme) { applySchemeTimeRange(matchedScheme); } else { setSelectedSchemeId(""); } } if (notify) { open?.({ type: "success", message: "方案列表已刷新", description: `当前可选爆管分析方案 ${burstSchemes.length} 个`, }); } } catch (error: any) { open?.({ type: "error", message: "刷新方案失败", description: error?.response?.data?.detail ?? error?.message ?? "无法获取爆管分析方案列表", }); } finally { setSchemeLoading(false); } }, [applySchemeTimeRange, open, schemeLoading, schemes.length, selectedSchemeId], ); const handleDataSourceChange = (value: DataSource) => { setDataSource(value); if (value === "simulation") { void fetchSchemes(); } }; const handleSchemeSelect = (schemeId: number) => { setSelectedSchemeId(schemeId); const scheme = schemes.find((item) => item.scheme_id === schemeId); if (scheme) { applySchemeTimeRange(scheme); } }; const isValid = useMemo(() => { if (!Number.isFinite(burstLeakage) || burstLeakage <= 0) return false; if (!burstStartTime || !burstEndTime) { return false; } if (dataSource === "simulation" && !selectedSchemeId) { return false; } return burstStartTime.isBefore(burstEndTime); }, [ burstLeakage, burstStartTime, burstEndTime, dataSource, selectedSchemeId, ]); const handleRun = async () => { if (!isValid || !burstStartTime || !burstEndTime) { open?.({ type: "error", message: "请完善参数并确认时间范围合法" }); return; } setRunning(true); open?.({ key: "burst-location-analysis-progress", type: "progress", message: "方案提交分析中", undoableTimeout: 3, }); try { const selectedScheme = dataSource === "simulation" ? schemes.find((item) => item.scheme_id === selectedSchemeId) : undefined; const response = await api.post( `${config.BACKEND_URL}/api/v1/burst-location/locate/`, { network: NETWORK_NAME, data_source: dataSource, scheme_name: schemeName.trim() || undefined, burst_leakage: toM3s(burstLeakage, FLOW_DISPLAY_UNIT), min_dpressure: minDpressure, basic_pressure: basicPressure, scada_burst_start: burstStartTime.toISOString(), scada_burst_end: burstEndTime.toISOString(), use_scada_flow: enableFlow || undefined, simulation_scheme_name: selectedScheme?.scheme_name, simulation_scheme_type: selectedScheme?.scheme_type, }, ); onResult(response.data as BurstLocationResult); open?.({ key: "burst-location-analysis-success", type: "success", message: "爆管定位成功", description: `定位到管段: ${(response.data as BurstLocationResult).located_pipe}`, }); } catch (error: any) { open?.({ key: "burst-location-analysis-error", type: "error", message: "提交分析失败", description: error?.response?.data?.detail ?? error?.message ?? "请求失败", }); } finally { setRunning(false); } }; return ( 方案名称 setSchemeName(e.target.value)} placeholder="请输入方案名称" fullWidth size="small" /> SCADA 数据来源 {isSimulationMode && ( 选择爆管分析方案 void fetchSchemes({ force: true, notify: true })} disabled={schemeLoading} aria-label="刷新爆管分析方案" sx={{ border: "1px solid", borderColor: "divider", borderRadius: 1, }} > {schemeLoading ? ( ) : ( )} )} 爆管开始时间 爆管结束时间 爆管漏损流量 ({FLOW_DISPLAY_UNIT}) { const value = Number(e.target.value); setBurstLeakage(Number.isNaN(value) ? 1440 : Math.max(0, value)); }} fullWidth inputProps={{ min: 0, step: 10 }} /> setAdvancedOpen((prev) => !prev)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") setAdvancedOpen((prev) => !prev); }} sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", px: 1.25, py: 0.75, cursor: "pointer", backgroundColor: "transparent", "&:hover": { backgroundColor: "action.hover" }, }} > 高级选项 流量校核 setMinDpressure(Number(e.target.value))} /> setBasicPressure(Number(e.target.value))} /> ); }; export default AnalysisParameters;