统一流量单位为 m³/h,优化相关组件

This commit is contained in:
JIANG
2026-03-07 17:21:01 +08:00
parent 6b68b7d081
commit ddb02cc688
10 changed files with 61 additions and 35 deletions
+3 -2
View File
@@ -21,6 +21,7 @@ import { useNotification } from "@refinedev/core";
import { config } from "@/config/config"; import { config } from "@/config/config";
import { apiFetch } from "@/lib/apiFetch"; import { apiFetch } from "@/lib/apiFetch";
import { FLOW_DISPLAY_UNIT } from "@components/olmap/DMALeakDetection/utils";
// 添加接口定义隐藏按钮的props // 添加接口定义隐藏按钮的props
interface ToolbarProps { interface ToolbarProps {
@@ -427,7 +428,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
const properties = highlightFeature.getProperties(); const properties = highlightFeature.getProperties();
// 计算属性字段,增加 key 字段 // 计算属性字段,增加 key 字段
const pipeComputedFields = [ const pipeComputedFields = [
{ key: "flow", label: "流量", unit: "m³/h" }, { key: "flow", label: "流量", unit: `${FLOW_DISPLAY_UNIT}` },
{ key: "friction", label: "摩阻", unit: "" }, { key: "friction", label: "摩阻", unit: "" },
{ key: "headloss", label: "水头损失", unit: "m" }, { key: "headloss", label: "水头损失", unit: "m" },
{ key: "unit_headloss", label: "单位水头损失", unit: "m/km" }, { key: "unit_headloss", label: "单位水头损失", unit: "m/km" },
@@ -438,7 +439,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
{ key: "velocity", label: "流速", unit: "m/s" }, { key: "velocity", label: "流速", unit: "m/s" },
]; ];
const nodeComputedFields = [ const nodeComputedFields = [
{ key: "actual_demand", label: "实际需水量", unit: "m³/h" }, { key: "actual_demand", label: "实际需水量", unit: `${FLOW_DISPLAY_UNIT}` },
{ key: "total_head", label: "水头", unit: "m" }, { key: "total_head", label: "水头", unit: "m" },
{ key: "pressure", label: "压力", unit: "m" }, { key: "pressure", label: "压力", unit: "m" },
{ key: "quality", label: "水质", unit: "mg/L" }, { key: "quality", label: "水质", unit: "mg/L" },
@@ -24,7 +24,7 @@ import dayjs, { Dayjs } from "dayjs";
import "dayjs/locale/zh-cn"; import "dayjs/locale/zh-cn";
import { api } from "@/lib/api"; import { api } from "@/lib/api";
import { NETWORK_NAME, config } from "@config/config"; import { NETWORK_NAME, config } from "@config/config";
import { DMA_FLOW_DISPLAY_UNIT, toM3s } from "../DMALeakDetection/utils"; import { FLOW_DISPLAY_UNIT, toM3s } from "../DMALeakDetection/utils";
import { BurstLocationResult } from "./types"; import { BurstLocationResult } from "./types";
interface Props { interface Props {
@@ -180,7 +180,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
network: NETWORK_NAME, network: NETWORK_NAME,
data_source: dataSource, data_source: dataSource,
scheme_name: schemeName.trim() || undefined, scheme_name: schemeName.trim() || undefined,
burst_leakage: toM3s(burstLeakage, DMA_FLOW_DISPLAY_UNIT), burst_leakage: toM3s(burstLeakage, FLOW_DISPLAY_UNIT),
min_dpressure: minDpressure, min_dpressure: minDpressure,
basic_pressure: basicPressure, basic_pressure: basicPressure,
scada_burst_start: burstStartTime.toISOString(), scada_burst_start: burstStartTime.toISOString(),
@@ -325,7 +325,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
<Box className="flex flex-col gap-2"> <Box className="flex flex-col gap-2">
<Typography variant="subtitle2" className="mb-1 font-medium"> <Typography variant="subtitle2" className="mb-1 font-medium">
({DMA_FLOW_DISPLAY_UNIT}) ({FLOW_DISPLAY_UNIT})
</Typography> </Typography>
<TextField <TextField
type="number" type="number"
@@ -6,6 +6,7 @@ import {
Typography, Typography,
Chip, Chip,
IconButton, IconButton,
Tooltip,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
@@ -28,7 +29,7 @@ import VectorSource from "ol/source/Vector";
import { Stroke, Style, Circle, Fill } from "ol/style"; import { Stroke, Style, Circle, Fill } from "ol/style";
import { bbox, featureCollection } from "@turf/turf"; import { bbox, featureCollection } from "@turf/turf";
import { BurstCandidate, BurstLocationResult } from "./types"; import { BurstCandidate, BurstLocationResult } from "./types";
import { DMA_FLOW_DISPLAY_UNIT } from "../DMALeakDetection/utils"; import { FLOW_DISPLAY_UNIT } from "../DMALeakDetection/utils";
interface Props { interface Props {
result: BurstLocationResult | null; result: BurstLocationResult | null;
@@ -127,6 +128,14 @@ const LocationResults: React.FC<Props> = ({ result }) => {
return base; return base;
}, [result]); }, [result]);
const allCandidatePipeIds = useMemo<string[]>(() => {
const ids = candidatePipes.map((item) => item.pipe_id);
if (result?.located_pipe) {
ids.unshift(result.located_pipe);
}
return Array.from(new Set(ids.filter(Boolean)));
}, [candidatePipes, result?.located_pipe]);
useEffect(() => { useEffect(() => {
if (!map) return; if (!map) return;
@@ -263,7 +272,7 @@ const LocationResults: React.FC<Props> = ({ result }) => {
/> />
<MetricCard <MetricCard
label="估计漏损量" label="估计漏损量"
value={`${result.burst_leakage.toFixed(2)} ${DMA_FLOW_DISPLAY_UNIT}`} value={`${result.burst_leakage.toFixed(2)} ${FLOW_DISPLAY_UNIT}`}
tone="orange" tone="orange"
/> />
<MetricCard <MetricCard
@@ -288,6 +297,7 @@ const LocationResults: React.FC<Props> = ({ result }) => {
</Typography> </Typography>
</Box> </Box>
<Box className="flex items-center gap-1">
<Chip <Chip
size="small" size="small"
label={`${candidatePipes.length}`} label={`${candidatePipes.length}`}
@@ -300,6 +310,19 @@ const LocationResults: React.FC<Props> = ({ result }) => {
border: "none", border: "none",
}} }}
/> />
<Tooltip title="定位所有管段">
<span>
<IconButton
size="small"
onClick={() => locatePipes(allCandidatePipeIds)}
disabled={allCandidatePipeIds.length === 0}
className="text-blue-600 hover:bg-blue-50 disabled:text-gray-300"
>
<LocationOnIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
</Box>
</Box> </Box>
<Table size="small"> <Table size="small">
<TableHead> <TableHead>
@@ -28,7 +28,7 @@ import {
BurstLocationSchemeDetail, BurstLocationSchemeDetail,
BurstSchemeRecord, BurstSchemeRecord,
} from "./types"; } from "./types";
import { DMA_FLOW_DISPLAY_UNIT } from "../DMALeakDetection/utils"; import { FLOW_DISPLAY_UNIT } from "../DMALeakDetection/utils";
interface Props { interface Props {
onViewResult: (result: BurstLocationResult) => void; onViewResult: (result: BurstLocationResult) => void;
@@ -302,7 +302,7 @@ const SchemeQuery: React.FC<Props> = ({ onViewResult }) => {
: :
</Typography> </Typography>
<Typography variant="caption" className="font-medium text-gray-900"> <Typography variant="caption" className="font-medium text-gray-900">
{typeof leakage === "number" ? `${leakage} ${DMA_FLOW_DISPLAY_UNIT}` : "-"} {typeof leakage === "number" ? `${leakage} ${FLOW_DISPLAY_UNIT}` : "-"}
</Typography> </Typography>
</Box> </Box>
<Box className="grid grid-cols-[78px_1fr] items-center gap-x-2"> <Box className="grid grid-cols-[78px_1fr] items-center gap-x-2">
@@ -32,6 +32,7 @@ import { toLonLat } from "ol/proj";
import moment from "moment"; import moment from "moment";
import "moment-timezone"; import "moment-timezone";
import { LocationResult } from "./types"; import { LocationResult } from "./types";
import { FLOW_DISPLAY_UNIT } from "../DMALeakDetection/utils";
interface LocationResultsProps { interface LocationResultsProps {
results?: LocationResult[]; results?: LocationResult[];
@@ -306,7 +307,7 @@ const LocationResults: React.FC<LocationResultsProps> = ({
sx={{ fontSize: "0.875rem" }} sx={{ fontSize: "0.875rem" }}
> >
{result.leakage !== null {result.leakage !== null
? `${result.leakage.toFixed(2)} m³/h` ? `${result.leakage.toFixed(2)} ${FLOW_DISPLAY_UNIT}`
: "N/A"} : "N/A"}
</Typography> </Typography>
</Box> </Box>
@@ -20,7 +20,7 @@ import { useNotification } from "@refinedev/core";
import { api } from "@/lib/api"; import { api } from "@/lib/api";
import { NETWORK_NAME, config } from "@config/config"; import { NETWORK_NAME, config } from "@config/config";
import { LeakageResultDetail } from "./types"; import { LeakageResultDetail } from "./types";
import { DMA_FLOW_DISPLAY_UNIT, toM3s } from "./utils"; import { FLOW_DISPLAY_UNIT, toM3s } from "./utils";
interface Props { interface Props {
onResult: (result: LeakageResultDetail) => void; onResult: (result: LeakageResultDetail) => void;
@@ -68,9 +68,9 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
scada_end: endTime.toISOString(), scada_end: endTime.toISOString(),
pop_size: popSize, pop_size: popSize,
max_gen: maxGen, max_gen: maxGen,
q_sum: toM3s(qSum, DMA_FLOW_DISPLAY_UNIT), q_sum: toM3s(qSum, FLOW_DISPLAY_UNIT),
q_sum_unit: "m3/s", q_sum_unit: "m3/s",
output_flow_unit: DMA_FLOW_DISPLAY_UNIT, output_flow_unit: FLOW_DISPLAY_UNIT,
}, },
); );
onResult(response.data as LeakageResultDetail); onResult(response.data as LeakageResultDetail);
@@ -172,7 +172,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
<Box className="flex flex-col gap-2"> <Box className="flex flex-col gap-2">
<Typography variant="subtitle2" className="mb-1 font-medium"> <Typography variant="subtitle2" className="mb-1 font-medium">
({DMA_FLOW_DISPLAY_UNIT}) ({FLOW_DISPLAY_UNIT})
</Typography> </Typography>
<TextField <TextField
type="number" type="number"
@@ -13,7 +13,7 @@ import {
} from "@mui/material"; } from "@mui/material";
import { FormatListBulleted } from "@mui/icons-material"; import { FormatListBulleted } from "@mui/icons-material";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { DMA_FLOW_DISPLAY_UNIT, getAreaColor, toM3h } from "./utils"; import { FLOW_DISPLAY_UNIT, getAreaColor, toM3h } from "./utils";
import { LeakageResultDetail } from "./types"; import { LeakageResultDetail } from "./types";
interface Props { interface Props {
@@ -131,7 +131,7 @@ const RecognitionResults: React.FC<Props> = ({ result }) => {
); );
const qSumM3h = toM3h(Number(val), unit); const qSumM3h = toM3h(Number(val), unit);
return Number.isFinite(qSumM3h) return Number.isFinite(qSumM3h)
? `${qSumM3h.toFixed(3)} ${DMA_FLOW_DISPLAY_UNIT}` ? `${qSumM3h.toFixed(3)} ${FLOW_DISPLAY_UNIT}`
: "-"; : "-";
})()} })()}
</Typography> </Typography>
@@ -167,7 +167,7 @@ const RecognitionResults: React.FC<Props> = ({ result }) => {
?.max_leakage; ?.max_leakage;
const maxLeakageM3h = toM3h(Number(maxL), "m3/s"); const maxLeakageM3h = toM3h(Number(maxL), "m3/s");
return Number.isFinite(maxLeakageM3h) return Number.isFinite(maxLeakageM3h)
? `${maxLeakageM3h.toFixed(3)} ${DMA_FLOW_DISPLAY_UNIT}` ? `${maxLeakageM3h.toFixed(3)} ${FLOW_DISPLAY_UNIT}`
: "-"; : "-";
})()} })()}
</Typography> </Typography>
@@ -215,7 +215,7 @@ const RecognitionResults: React.FC<Props> = ({ result }) => {
align="right" align="right"
sx={{ fontWeight: 600, color: "#64748b", py: 1.5, pr: 3 }} sx={{ fontWeight: 600, color: "#64748b", py: 1.5, pr: 3 }}
> >
({DMA_FLOW_DISPLAY_UNIT}) ({FLOW_DISPLAY_UNIT})
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
@@ -24,7 +24,7 @@ import { useNotification } from "@refinedev/core";
import { api } from "@/lib/api"; import { api } from "@/lib/api";
import { NETWORK_NAME, config } from "@config/config"; import { NETWORK_NAME, config } from "@config/config";
import { LeakageResultDetail, LeakageSchemeRecord } from "./types"; import { LeakageResultDetail, LeakageSchemeRecord } from "./types";
import { DMA_FLOW_DISPLAY_UNIT, toM3h } from "./utils"; import { FLOW_DISPLAY_UNIT, toM3h } from "./utils";
interface Props { interface Props {
onViewResult: (result: LeakageResultDetail) => void; onViewResult: (result: LeakageResultDetail) => void;
@@ -210,7 +210,7 @@ const SchemeQuery: React.FC<Props> = ({ onViewResult }) => {
const value = Number((scheme.scheme_detail as any)?.result_summary?.max_leakage); const value = Number((scheme.scheme_detail as any)?.result_summary?.max_leakage);
const maxLeakageM3h = toM3h(value, "m3/s"); const maxLeakageM3h = toM3h(value, "m3/s");
return Number.isFinite(maxLeakageM3h) return Number.isFinite(maxLeakageM3h)
? `${maxLeakageM3h.toFixed(3)} ${DMA_FLOW_DISPLAY_UNIT}` ? `${maxLeakageM3h.toFixed(3)} ${FLOW_DISPLAY_UNIT}`
: "-"; : "-";
})()} })()}
</Typography> </Typography>
@@ -227,7 +227,7 @@ const SchemeQuery: React.FC<Props> = ({ onViewResult }) => {
); );
const qSumM3h = toM3h(value, unit); const qSumM3h = toM3h(value, unit);
return Number.isFinite(qSumM3h) return Number.isFinite(qSumM3h)
? `${qSumM3h.toFixed(3)} ${DMA_FLOW_DISPLAY_UNIT}` ? `${qSumM3h.toFixed(3)} ${FLOW_DISPLAY_UNIT}`
: "-"; : "-";
})()} })()}
</Typography> </Typography>
@@ -11,7 +11,7 @@ export const AREA_COLORS = [
"#be123c", "#be123c",
]; ];
export const DMA_FLOW_DISPLAY_UNIT = "m³/h"; export const FLOW_DISPLAY_UNIT = "m³/h";
const M3H_FACTOR = 3600; const M3H_FACTOR = 3600;
export const getAreaColor = (areaId: string | number | undefined) => { export const getAreaColor = (areaId: string | number | undefined) => {
@@ -40,6 +40,7 @@ import Feature, { FeatureLike } from "ol/Feature";
import { bbox, featureCollection } from "@turf/turf"; import { bbox, featureCollection } from "@turf/turf";
import Timeline from "@app/OlMap/Controls/Timeline"; import Timeline from "@app/OlMap/Controls/Timeline";
import { SchemeRecord, SchemaItem } from "./types"; import { SchemeRecord, SchemaItem } from "./types";
import { FLOW_DISPLAY_UNIT } from "../DMALeakDetection/utils";
interface SchemeQueryProps { interface SchemeQueryProps {
schemes?: SchemeRecord[]; schemes?: SchemeRecord[];
@@ -496,7 +497,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
: :
</Typography> </Typography>
<Typography variant="caption" className="font-medium text-gray-900"> <Typography variant="caption" className="font-medium text-gray-900">
{scheme.schemeDetail?.flushing_flow ?? "-"} m³/h {scheme.schemeDetail?.flushing_flow ?? "-"} {FLOW_DISPLAY_UNIT}
</Typography> </Typography>
</Box> </Box>