diff --git a/src/app/(main)/hydraulic-simulation/dma-leak-detection/loading.tsx b/src/app/(main)/hydraulic-simulation/dma-leak-detection/loading.tsx
new file mode 100644
index 0000000..2c57921
--- /dev/null
+++ b/src/app/(main)/hydraulic-simulation/dma-leak-detection/loading.tsx
@@ -0,0 +1,5 @@
+import { MapSkeleton } from "@components/loading/MapSkeleton";
+
+export default function Loading() {
+ return ;
+}
diff --git a/src/app/(main)/hydraulic-simulation/dma-leak-detection/page.tsx b/src/app/(main)/hydraulic-simulation/dma-leak-detection/page.tsx
new file mode 100644
index 0000000..fc90a12
--- /dev/null
+++ b/src/app/(main)/hydraulic-simulation/dma-leak-detection/page.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import MapComponent from "@app/OlMap/MapComponent";
+import MapToolbar from "@app/OlMap/Controls/Toolbar";
+import DMALeakDetectionPanel from "@/components/olmap/DMALeakDetection/DMALeakDetectionPanel";
+
+export default function Home() {
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/OlMap/Controls/StyleLegend.tsx b/src/app/OlMap/Controls/StyleLegend.tsx
index e374e84..a1bffe8 100644
--- a/src/app/OlMap/Controls/StyleLegend.tsx
+++ b/src/app/OlMap/Controls/StyleLegend.tsx
@@ -10,6 +10,9 @@ interface LegendStyleConfig {
type: string; // 图例类型
dimensions: number[]; // 尺寸大小
breaks: number[]; // 分段值
+ labels?: string[]; // 可选标签(用于离散分类)
+ columns?: number;
+ itemsPerColumn?: number;
}
// 图例组件
// 该组件用于显示图层样式的图例,包含属性名称、颜色、尺寸和分段值等信息
@@ -24,6 +27,9 @@ const StyleLegend: React.FC = ({
type, // 图例类型
dimensions,
breaks,
+ labels,
+ columns = 1,
+ itemsPerColumn,
}) => {
return (
= ({
{layerName} - {property}
- {[...Array(breaks.length)].map((_, index) => {
- const color = colors[index]; // 默认颜色为黑色
- const dimension = dimensions[index]; // 默认尺寸为16
+ 0
+ ? undefined
+ : `repeat(${Math.max(1, columns)}, minmax(0, 1fr))`,
+ gridTemplateRows:
+ itemsPerColumn && itemsPerColumn > 0
+ ? `repeat(${itemsPerColumn}, minmax(0, auto))`
+ : undefined,
+ gridAutoFlow:
+ itemsPerColumn && itemsPerColumn > 0 ? "column" : undefined,
+ columnGap: 1.5,
+ rowGap: 0.5,
+ }}
+ >
+ {[...Array(breaks.length)].map((_, index) => {
+ const color = colors[index]; // 默认颜色为黑色
+ const dimension = dimensions[index]; // 默认尺寸为16
// // 处理第一个区间(小于 breaks[0])
// if (index === 0) {
@@ -66,37 +89,39 @@ const StyleLegend: React.FC = ({
// }
// 处理中间区间(breaks[index] - breaks[index + 1])
- if (index + 1 < breaks.length) {
- const prevValue = breaks[index];
- const currentValue = breaks[index + 1];
- return (
-
-
-
- {prevValue?.toFixed(1)} - {currentValue?.toFixed(1)}
-
-
- );
- }
+ if (index + 1 < breaks.length) {
+ const prevValue = breaks[index];
+ const currentValue = breaks[index + 1];
+ return (
+
+
+
+ {labels?.[index] ??
+ `${prevValue?.toFixed(1)} - ${currentValue?.toFixed(1)}`}
+
+
+ );
+ }
- return null;
- })}
+ return null;
+ })}
+
);
};
diff --git a/src/app/_refine_context.tsx b/src/app/_refine_context.tsx
index e88653d..282ea2a 100644
--- a/src/app/_refine_context.tsx
+++ b/src/app/_refine_context.tsx
@@ -180,6 +180,15 @@ const App = (props: React.PropsWithChildren) => {
label: "爆管分析定位",
},
},
+ {
+ name: "DMA漏损识别",
+ list: "/hydraulic-simulation/dma-leak-detection",
+ meta: {
+ parent: "Hydraulic Simulation",
+ icon: ,
+ label: "DMA漏损识别",
+ },
+ },
{
name: "水质模拟",
list: "/hydraulic-simulation/water-quality-simulation",
diff --git a/src/components/olmap/ContaminantSimulation/ResultsPanel.tsx b/src/components/olmap/ContaminantSimulation/ResultsPanel.tsx
deleted file mode 100644
index 92aae8e..0000000
--- a/src/components/olmap/ContaminantSimulation/ResultsPanel.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-"use client";
-
-import React from "react";
-import { Box, Typography } from "@mui/material";
-
-interface ResultsPanelProps {
- schemeName?: string;
-}
-
-const ResultsPanel: React.FC = ({ schemeName }) => {
- return (
-
-
-
- 水质模拟结果
-
-
- 请在下方时间轴查看各时刻的水质分布。
-
- {schemeName && (
-
- 当前方案:{schemeName}
-
- )}
-
-
- );
-};
-
-export default ResultsPanel;
diff --git a/src/components/olmap/ContaminantSimulation/WaterQualityPanel.tsx b/src/components/olmap/ContaminantSimulation/WaterQualityPanel.tsx
index 9450e9f..43c7023 100644
--- a/src/components/olmap/ContaminantSimulation/WaterQualityPanel.tsx
+++ b/src/components/olmap/ContaminantSimulation/WaterQualityPanel.tsx
@@ -19,7 +19,6 @@ import {
} from "@mui/icons-material";
import ContaminantAnalysisParameters from "./AnalysisParameters";
import ContaminantSchemeQuery from "./SchemeQuery";
-import ContaminantResultsPanel from "./ResultsPanel";
import { useData } from "@app/OlMap/MapComponent";
interface WaterQualityPanelProps {
@@ -175,10 +174,6 @@ const WaterQualityPanel: React.FC = ({
setCurrentTab(2)} />
-
-
-
-
>
diff --git a/src/components/olmap/DMALeakDetection/AnalysisParameters.tsx b/src/components/olmap/DMALeakDetection/AnalysisParameters.tsx
new file mode 100644
index 0000000..a19bd44
--- /dev/null
+++ b/src/components/olmap/DMALeakDetection/AnalysisParameters.tsx
@@ -0,0 +1,260 @@
+"use client";
+
+import React, { useMemo, useState } from "react";
+import {
+ Alert,
+ Box,
+ Button,
+ Collapse,
+ TextField,
+ Typography,
+} from "@mui/material";
+import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
+import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker";
+import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
+import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
+import { zhCN as pickerZhCN } from "@mui/x-date-pickers/locales";
+import dayjs, { Dayjs } from "dayjs";
+import "dayjs/locale/zh-cn";
+import { useNotification } from "@refinedev/core";
+import { api } from "@/lib/api";
+import { NETWORK_NAME, config } from "@config/config";
+import { LeakageResultDetail } from "./types";
+
+interface Props {
+ onResult: (result: LeakageResultDetail) => void;
+}
+
+const AnalysisParameters: React.FC = ({ onResult }) => {
+ const { open } = useNotification();
+ const [schemeName, setSchemeName] = useState(`DMA_Leak_${Date.now()}`);
+ const [dmaCount, setDmaCount] = useState(5);
+ const [startTime, setStartTime] = useState(
+ dayjs().subtract(2, "hour"),
+ );
+ const [endTime, setEndTime] = useState(dayjs());
+ const [popSize, setPopSize] = useState(50);
+ const [maxGen, setMaxGen] = useState(100);
+ const [qSum, setQSum] = useState(0.4);
+ const [advancedOpen, setAdvancedOpen] = useState(false);
+ const [running, setRunning] = useState(false);
+
+ const isValid = useMemo(() => {
+ if (!schemeName.trim() || !startTime || !endTime) return false;
+ return startTime.isBefore(endTime) && qSum >= 0.1;
+ }, [schemeName, startTime, endTime, qSum]);
+
+ const handleRun = async () => {
+ if (!isValid || !startTime || !endTime) {
+ open?.({ type: "error", message: "请完善参数并确认时间范围合法" });
+ return;
+ }
+ setRunning(true);
+ open?.({
+ key: "dma-leak-analysis",
+ type: "progress",
+ message: "方案提交分析中",
+ undoableTimeout: 3,
+ });
+ try {
+ const response = await api.post(
+ `${config.BACKEND_URL}/api/v1/leakage/identify/`,
+ {
+ network: NETWORK_NAME,
+ scheme_name: schemeName.trim(),
+ dma_count: dmaCount,
+ scada_start: startTime.toISOString(),
+ scada_end: endTime.toISOString(),
+ pop_size: popSize,
+ max_gen: maxGen,
+ q_sum: qSum,
+ q_sum_unit: "m3/s",
+ output_flow_unit: "m3/s",
+ },
+ );
+ onResult(response.data as LeakageResultDetail);
+ open?.({
+ key: "dma-leak-analysis",
+ type: "success",
+ message: "方案分析成功",
+ description: "DMA漏损识别完成,请在方案查询中查看结果。",
+ });
+ } catch (error: any) {
+ open?.({
+ key: "dma-leak-analysis",
+ type: "error",
+ message: "提交分析失败",
+ description: error?.response?.data?.detail ?? "请求失败",
+ });
+ } finally {
+ setRunning(false);
+ }
+ };
+
+ return (
+
+
+
+ 漏损识别耗时较长(DMA 数量越多越慢),建议先用较小 DMA 数量试跑。
+
+
+
+
+ 方案名称
+
+ setSchemeName(e.target.value)}
+ placeholder="请输入方案名称"
+ fullWidth
+ size="small"
+ />
+
+
+
+
+ DMA 数量
+
+ {
+ const value = Number.parseInt(e.target.value, 10);
+ setDmaCount(Number.isNaN(value) ? 5 : Math.max(3, value));
+ }}
+ fullWidth
+ size="small"
+ inputProps={{ min: 3, step: 1 }}
+ />
+
+
+
+
+
+ SCADA 开始时间
+
+
+
+
+
+ SCADA 结束时间
+
+
+
+
+
+
+
+ 总漏损流量 (m3/s)
+
+ {
+ const value = Number(e.target.value);
+ setQSum(Number.isNaN(value) ? 0.4 : Math.max(0.1, value));
+ }}
+ inputProps={{ min: 0.1, step: 0.1 }}
+ />
+
+ 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" },
+ }}
+ >
+
+ 高级选项
+
+
+
+
+
+
+ setPopSize(Number(e.target.value))}
+ />
+ setMaxGen(Number(e.target.value))}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default AnalysisParameters;
diff --git a/src/components/olmap/DMALeakDetection/DMALeakDetectionPanel.tsx b/src/components/olmap/DMALeakDetection/DMALeakDetectionPanel.tsx
new file mode 100644
index 0000000..6c1e3de
--- /dev/null
+++ b/src/components/olmap/DMALeakDetection/DMALeakDetectionPanel.tsx
@@ -0,0 +1,432 @@
+"use client";
+
+import React, { useCallback, useEffect, useMemo, useState } from "react";
+import {
+ Box,
+ Drawer,
+ Tabs,
+ Tab,
+ Typography,
+ IconButton,
+ Tooltip,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+ Chip,
+} from "@mui/material";
+import {
+ Analytics as AnalyticsIcon,
+ Search as SearchIcon,
+ ChevronLeft,
+ ChevronRight,
+ FormatListBulleted,
+} from "@mui/icons-material";
+import dayjs from "dayjs";
+import { Circle as CircleStyle, Fill, Stroke, Style } from "ol/style";
+import VectorLayer from "ol/layer/Vector";
+import VectorSource from "ol/source/Vector";
+import Feature from "ol/Feature";
+import { queryFeaturesByIds } from "@/utils/mapQueryService";
+import { useMap } from "@app/OlMap/MapComponent";
+import StyleLegend from "@app/OlMap/Controls/StyleLegend";
+import AnalysisParameters from "./AnalysisParameters";
+import SchemeQuery from "./SchemeQuery";
+import { LeakageResultDetail } from "./types";
+
+const TabPanel = ({
+ value,
+ index,
+ children,
+}: {
+ value: number;
+ index: number;
+ children: React.ReactNode;
+}) => (
+
+ {value === index ? {children} : null}
+
+);
+
+const AREA_COLORS = [
+ "#2563eb",
+ "#7c3aed",
+ "#0891b2",
+ "#16a34a",
+ "#ca8a04",
+ "#dc2626",
+ "#ea580c",
+ "#0f766e",
+ "#4338ca",
+ "#be123c",
+];
+
+const getAreaColor = (areaId: string | number | undefined) => {
+ const text = String(areaId ?? "");
+ let hash = 0;
+ for (let i = 0; i < text.length; i += 1) {
+ hash = (hash * 31 + text.charCodeAt(i)) >>> 0;
+ }
+ return AREA_COLORS[hash % AREA_COLORS.length];
+};
+
+const DMALeakDetectionPanel: React.FC = () => {
+ const map = useMap();
+ const [open, setOpen] = useState(true);
+ const [tab, setTab] = useState(0);
+ const [result, setResult] = useState(null);
+ const [loadedResult, setLoadedResult] = useState(null);
+ const [nodeLayer, setNodeLayer] = useState | null>(null);
+
+ const sortedRows = useMemo(() => {
+ if (!result?.rows) return [];
+ return [...result.rows].sort(
+ (a, b) => b.LeakageFlow_m3_per_s - a.LeakageFlow_m3_per_s,
+ );
+ }, [result]);
+ const drawerWidth = 450;
+ const panelTitle = "DMA漏损识别";
+ const activeAreas = loadedResult?.areas ?? [];
+ const legendColors = useMemo(
+ () => activeAreas.map((area) => getAreaColor(area.area_id)),
+ [activeAreas],
+ );
+ const legendLabels = useMemo(
+ () => activeAreas.map((area) => `区域 ${area.area_id}`),
+ [activeAreas],
+ );
+ const legendBreaks = useMemo(
+ () => Array.from({ length: activeAreas.length + 1 }, (_, i) => i + 1),
+ [activeAreas.length],
+ );
+
+ useEffect(() => {
+ if (!map) return;
+ const layer = new VectorLayer({
+ source: new VectorSource(),
+ maxZoom: 24,
+ minZoom: 12,
+ properties: {
+ name: "DMA漏损节点着色",
+ value: "dma_leak_nodes",
+ },
+ style: (feature) => {
+ const areaId = feature.get("__areaId");
+ return new Style({
+ image: new CircleStyle({
+ radius: 4.5,
+ fill: new Fill({ color: getAreaColor(areaId) }),
+ stroke: new Stroke({ color: "#ffffff", width: 1.2 }),
+ }),
+ });
+ },
+ });
+ map.addLayer(layer);
+ setNodeLayer(layer);
+ return () => {
+ map.removeLayer(layer);
+ };
+ }, [map]);
+
+ useEffect(() => {
+ if (!nodeLayer) return;
+ const source = nodeLayer.getSource();
+ if (!source) return;
+ source.clear();
+ if (!loadedResult) return;
+
+ const nodeAreaMap = loadedResult.node_area_map || {};
+ const nodeIds = Object.keys(nodeAreaMap);
+ if (nodeIds.length === 0) return;
+
+ queryFeaturesByIds(nodeIds, "geo_junctions_mat").then((features) => {
+ if (!features?.length) return;
+ features.forEach((feature) => {
+ const nodeId = String(feature.get("id") ?? "");
+ feature.set("__areaId", nodeAreaMap[nodeId] ?? "");
+ });
+ source.addFeatures(features as Feature[]);
+ });
+ }, [loadedResult, nodeLayer]);
+
+ const handleAnalysisResult = useCallback((res: LeakageResultDetail) => {
+ setResult(res);
+ }, []);
+
+ const handleViewResult = useCallback((res: LeakageResultDetail) => {
+ setResult(res);
+ setLoadedResult(res);
+ setTab(2);
+ }, []);
+
+ return (
+ <>
+ {!open && (
+ setOpen(true)}
+ sx={{ zIndex: 1300 }}
+ >
+
+
+
+ {panelTitle}
+
+
+
+
+ )}
+
+
+
+
+
+
+ {panelTitle}
+
+
+
+ setOpen(false)}
+ sx={{ color: "primary.contrastText" }}
+ >
+
+
+
+
+
+ setTab(v)}
+ variant="fullWidth"
+ sx={{
+ minHeight: 48,
+ "& .MuiTab-root": {
+ minHeight: 48,
+ textTransform: "none",
+ fontSize: "0.875rem",
+ fontWeight: 500,
+ transition: "all 0.2s",
+ },
+ "& .Mui-selected": {
+ color: "#257DD4",
+ },
+ "& .MuiTabs-indicator": {
+ backgroundColor: "#257DD4",
+ },
+ }}
+ >
+ } iconPosition="start" label="识别参数" />
+ } iconPosition="start" label="方案查询" />
+ } iconPosition="start" label="识别结果" />
+
+
+
+
+
+
+
+
+
+ {!result || !sortedRows.length ? (
+
+
+
+
+ 暂无识别结果
+
+ 请先加载方案或执行识别分析
+
+
+ ) : (
+
+ {/* 方案详情卡片 */}
+
+
+
+
+
+ {result.scheme_name || "漏损识别结果"}
+
+
+ {result.username && (
+
+ )}
+
+
+
+ {/* 方案时间 */}
+
+
+ 方案时间
+
+
+ {dayjs(result.scheme_start_time || result.create_time).format("MM-DD HH:mm")}
+
+
+
+ {/* 总漏损流量 */}
+
+
+ 总漏损流量
+
+
+ {(() => {
+ const val = (result.scheme_detail as any)?.algorithm_params?.q_sum;
+ const unit = (result.scheme_detail as any)?.algorithm_params?.q_sum_unit || "m3/s";
+ return val !== undefined ? `${Number(val).toFixed(3)} ${unit}` : "-";
+ })()}
+
+
+
+ {/* 分区数量 */}
+
+
+ 分区数量
+
+
+ {(result.scheme_detail as any)?.result_summary?.area_count ?? result.areas?.length ?? 0} 个
+
+
+
+ {/* 最大漏损 */}
+
+
+ 最大漏损
+
+
+ {(() => {
+ const maxL = (result.scheme_detail as any)?.result_summary?.max_leakage;
+ return maxL !== undefined ? `${Number(maxL).toFixed(3)} m3/s` : "-";
+ })()}
+
+
+
+
+
+ {/* 漏损列表 */}
+
+
+
+
+
+ 区域漏损列表
+
+
+
+
+
+
+
+ 区域
+ 漏损量占比 (%)
+ 漏损量 (m3/s)
+
+
+
+ {sortedRows.map((row) => (
+
+
+
+
+
+ {row.Area}
+
+
+
+ {(row.LeakageRatio * 100).toFixed(3)}
+ {row.LeakageFlow_m3_per_s.toFixed(3)}
+
+ ))}
+
+
+
+
+ )}
+
+
+
+
+ {loadedResult && activeAreas.length > 0 && (
+
+
+
+ )}
+ >
+ );
+};
+
+export default DMALeakDetectionPanel;
diff --git a/src/components/olmap/DMALeakDetection/SchemeQuery.tsx b/src/components/olmap/DMALeakDetection/SchemeQuery.tsx
new file mode 100644
index 0000000..e7c6f91
--- /dev/null
+++ b/src/components/olmap/DMALeakDetection/SchemeQuery.tsx
@@ -0,0 +1,259 @@
+"use client";
+
+import React, { useState } from "react";
+import {
+ Box,
+ Button,
+ Card,
+ CardContent,
+ Chip,
+ Collapse,
+ FormControlLabel,
+ Checkbox,
+ IconButton,
+ Tooltip,
+ Typography,
+} from "@mui/material";
+import { Info as InfoIcon } from "@mui/icons-material";
+import { DatePicker } from "@mui/x-date-pickers/DatePicker";
+import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
+import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
+import "dayjs/locale/zh-cn";
+import dayjs, { Dayjs } from "dayjs";
+import { useNotification } from "@refinedev/core";
+import { api } from "@/lib/api";
+import { NETWORK_NAME, config } from "@config/config";
+import { LeakageResultDetail, LeakageSchemeRecord } from "./types";
+
+interface Props {
+ onViewResult: (result: LeakageResultDetail) => void;
+}
+
+const SchemeQuery: React.FC = ({ onViewResult }) => {
+ const { open } = useNotification();
+ const [queryAll, setQueryAll] = useState(true);
+ const [queryDate, setQueryDate] = useState(dayjs());
+ const [schemes, setSchemes] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [expandedId, setExpandedId] = useState(null);
+
+ const handleQuery = async () => {
+ setLoading(true);
+ try {
+ const params: Record = { network: NETWORK_NAME };
+ if (!queryAll && queryDate) {
+ params.query_date = queryDate.startOf("day").toISOString();
+ }
+ const response = await api.get(`${config.BACKEND_URL}/api/v1/leakage/schemes/`, {
+ params,
+ });
+ setSchemes(response.data);
+ } catch (error: any) {
+ open?.({
+ type: "error",
+ message: "查询失败",
+ description: error?.response?.data?.detail ?? "无法获取方案列表",
+ });
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleViewSchemeResult = async (schemeName: string) => {
+ try {
+ const response = await api.get(
+ `${config.BACKEND_URL}/api/v1/leakage/schemes/${encodeURIComponent(schemeName)}`,
+ { params: { network: NETWORK_NAME } },
+ );
+ onViewResult(response.data as LeakageResultDetail);
+ } catch (error: any) {
+ open?.({
+ type: "error",
+ message: "查看详情失败",
+ description: error?.response?.data?.detail ?? "无法获取方案详情",
+ });
+ }
+ };
+
+ return (
+
+
+
+
+ setQueryAll(e.target.checked)}
+ />
+ }
+ label={查询全部}
+ className="m-0"
+ />
+
+
+
+
+
+
+
+
+ {schemes.length === 0 ? (
+
+
+
+
+ 总共 0 条
+
+ No data
+
+
+ ) : (
+
+
+ 共 {schemes.length} 条记录
+
+ {schemes.map((scheme) => (
+
+
+
+
+
+
+ {scheme.scheme_name}
+
+
+
+
+ ID: {scheme.scheme_id} · 日期: {dayjs(scheme.create_time).format("MM-DD")}
+
+
+
+
+ setExpandedId(expandedId === scheme.scheme_id ? null : scheme.scheme_id)}
+ color="primary"
+ className="p-1"
+ >
+
+
+
+
+
+
+
+
+
+
+ 分区数量:
+
+
+ {String((scheme.scheme_detail as any)?.result_summary?.area_count ?? "-")}
+
+
+
+
+ 用户:
+
+
+ {scheme.username || "-"}
+
+
+
+
+ 最大漏损:
+
+
+ {(() => {
+ const value = Number((scheme.scheme_detail as any)?.result_summary?.max_leakage);
+ return Number.isFinite(value) ? `${value.toFixed(3)} m3/s` : "-";
+ })()}
+
+
+
+
+ 总漏损流量:
+
+
+ {(() => {
+ const value = Number((scheme.scheme_detail as any)?.algorithm_params?.q_sum);
+ const unit = String((scheme.scheme_detail as any)?.algorithm_params?.q_sum_unit || "m3/s");
+ return Number.isFinite(value) ? `${value.toFixed(3)} ${unit}` : "-";
+ })()}
+
+
+
+
+ 方案时间:
+
+
+ {dayjs(scheme.scheme_start_time || scheme.create_time).format("YYYY-MM-DD HH:mm")}
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+ );
+};
+
+export default SchemeQuery;
diff --git a/src/components/olmap/DMALeakDetection/types.ts b/src/components/olmap/DMALeakDetection/types.ts
new file mode 100644
index 0000000..b7bf649
--- /dev/null
+++ b/src/components/olmap/DMALeakDetection/types.ts
@@ -0,0 +1,53 @@
+export interface LeakageRow {
+ Area: string;
+ LeakageRatioRaw: number;
+ LeakageRatio: number;
+ LeakageFlow_m3_per_s: number;
+}
+
+export interface LeakageSchemeRecord {
+ scheme_id: number;
+ scheme_name: string;
+ scheme_type: string;
+ username: string;
+ create_time: string;
+ scheme_start_time: string;
+ scheme_detail: Record;
+}
+
+export interface LeakageResultDetail {
+ scheme_name?: string;
+ network?: string;
+ sensor_nodes: string[];
+ rows: LeakageRow[];
+ node_area_map: Record;
+ areas: Array<{
+ area_id: string;
+ node_count: number;
+ node_ids: string[];
+ sensor_nodes: string[];
+ }>;
+ drawing_payload: {
+ type: "FeatureCollection";
+ features: Array>;
+ };
+ node_visual_payload?: {
+ type: "FeatureCollection";
+ features: Array>;
+ };
+ scheme_detail?: {
+ algorithm_params?: {
+ q_sum?: number;
+ q_sum_unit?: string;
+ [key: string]: unknown;
+ };
+ result_summary?: {
+ area_count?: number;
+ max_leakage?: number;
+ };
+ [key: string]: unknown;
+ };
+ scheme_start_time?: string;
+ create_time?: string;
+ username?: string;
+}