feat(flushing): 添加阀门状态与设置值控制
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import React, { useState, useEffect, useCallback, useRef } from "react";
|
||||
import {
|
||||
Box,
|
||||
TextField,
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
Typography,
|
||||
IconButton,
|
||||
Stack,
|
||||
Alert,
|
||||
Divider,
|
||||
MenuItem,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
AdjustOutlined,
|
||||
@@ -33,14 +33,25 @@ import {
|
||||
import Feature, { FeatureLike } from "ol/Feature";
|
||||
import { useNotification } from "@refinedev/core";
|
||||
import { api } from "@/lib/api";
|
||||
import { config, NETWORK_NAME } from "@/config/config";
|
||||
import { config } from "@/config/config";
|
||||
import { useControllableObjectState } from "@components/olmap/core/useControllableState";
|
||||
import { useSessionRecoveryDraft } from "@/lib/sessionRecoveryDraft";
|
||||
import PanelEmptyState from "@components/olmap/common/PanelEmptyState";
|
||||
import {
|
||||
type LinkStatus,
|
||||
getValveSettingHelperText,
|
||||
isLinkStatus,
|
||||
normalizeValveSetting,
|
||||
VALVE_STATUS_OPTIONS,
|
||||
validateValveSetting,
|
||||
} from "@components/olmap/core/Controls/valveControl";
|
||||
|
||||
export interface ValveItem {
|
||||
id: string;
|
||||
k: number;
|
||||
setting?: string | null;
|
||||
vType?: string | null;
|
||||
status?: LinkStatus | null;
|
||||
detailsLoaded?: boolean;
|
||||
}
|
||||
|
||||
export interface FlushingAnalysisParametersState {
|
||||
@@ -92,6 +103,7 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
|
||||
|
||||
const [selectionMode, setSelectionMode] = useState<'none' | 'valve' | 'drainage'>('none');
|
||||
const [analyzing, setAnalyzing] = useState<boolean>(false);
|
||||
const valveSettingRequests = useRef(new Set<string>());
|
||||
|
||||
const [highlightLayer, setHighlightLayer] = useState<VectorLayer<VectorSource> | null>(null);
|
||||
|
||||
@@ -119,7 +131,7 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
|
||||
if (!feature) return;
|
||||
|
||||
const layer = feature.getId()?.toString().split(".")[0];
|
||||
const featureId = feature.getProperties().id;
|
||||
const featureId = String(feature.getProperties().id);
|
||||
|
||||
if (selectionMode === 'valve') {
|
||||
if (layer !== 'geo_valves') {
|
||||
@@ -139,7 +151,16 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
|
||||
return prev;
|
||||
}
|
||||
setValveFeatures((features) => [...features, feature]);
|
||||
return [...prev, { id: featureId, k: 1.0 }];
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
id: featureId,
|
||||
setting: undefined,
|
||||
vType: undefined,
|
||||
status: undefined,
|
||||
detailsLoaded: false,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
} else if (selectionMode === 'drainage') {
|
||||
@@ -253,6 +274,75 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
|
||||
});
|
||||
}, [valveFeatures.length, valves]);
|
||||
|
||||
useEffect(() => {
|
||||
valves.forEach((valve) => {
|
||||
if (valve.detailsLoaded || valveSettingRequests.current.has(valve.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
valveSettingRequests.current.add(valve.id);
|
||||
void Promise.allSettled([
|
||||
api.get(`${config.BACKEND_URL}/api/v1/valves/properties`, {
|
||||
params: { valve: valve.id },
|
||||
}),
|
||||
api.get(`${config.BACKEND_URL}/api/v1/status`, {
|
||||
params: { link: valve.id },
|
||||
}),
|
||||
])
|
||||
.then(([propertiesResult, statusResult]) => {
|
||||
const properties =
|
||||
propertiesResult.status === "fulfilled"
|
||||
? propertiesResult.value.data
|
||||
: null;
|
||||
const rawSetting = properties?.setting;
|
||||
const rawStatus =
|
||||
statusResult.status === "fulfilled"
|
||||
? statusResult.value.data?.status
|
||||
: null;
|
||||
const status = isLinkStatus(rawStatus) ? rawStatus : null;
|
||||
|
||||
setValves((previous) =>
|
||||
previous.map((item) =>
|
||||
item.id === valve.id
|
||||
? {
|
||||
...item,
|
||||
setting: normalizeValveSetting(rawSetting),
|
||||
vType: properties?.v_type
|
||||
? String(properties.v_type)
|
||||
: null,
|
||||
status,
|
||||
detailsLoaded: true,
|
||||
}
|
||||
: item,
|
||||
),
|
||||
);
|
||||
|
||||
if (
|
||||
propertiesResult.status === "rejected" ||
|
||||
statusResult.status === "rejected"
|
||||
) {
|
||||
console.error("读取阀门属性失败", {
|
||||
propertiesError:
|
||||
propertiesResult.status === "rejected"
|
||||
? propertiesResult.reason
|
||||
: undefined,
|
||||
statusError:
|
||||
statusResult.status === "rejected"
|
||||
? statusResult.reason
|
||||
: undefined,
|
||||
});
|
||||
open?.({
|
||||
type: "error",
|
||||
message: `阀门 ${valve.id} 的部分属性读取失败`,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
valveSettingRequests.current.delete(valve.id);
|
||||
});
|
||||
});
|
||||
}, [open, setValves, valves]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!drainageNode) {
|
||||
setDrainageFeature(null);
|
||||
@@ -292,9 +382,21 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const handleValveKChange = (id: string, k: string) => {
|
||||
const numK = parseFloat(k);
|
||||
setValves(prev => prev.map(v => v.id === id ? { ...v, k: isNaN(numK) ? 0 : numK } : v));
|
||||
const handleValveStatusChange = (id: string, status: string) => {
|
||||
if (!isLinkStatus(status)) return;
|
||||
setValves((previous) =>
|
||||
previous.map((valve) =>
|
||||
valve.id === id ? { ...valve, status } : valve,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const handleValveSettingChange = (id: string, setting: string) => {
|
||||
setValves((previous) =>
|
||||
previous.map((valve) =>
|
||||
valve.id === id ? { ...valve, setting } : valve,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const handleAnalyze = async () => {
|
||||
@@ -307,6 +409,33 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (valves.some((valve) => !isLinkStatus(valve.status))) {
|
||||
open?.({
|
||||
type: "error",
|
||||
message: "阀门开关状态未设置",
|
||||
description: "请为所有参与阀门选择开启、关闭或激活状态",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const invalidActiveValve = valves.find(
|
||||
(valve) =>
|
||||
valve.status === "ACTIVE" &&
|
||||
validateValveSetting(valve.vType, valve.setting ?? ""),
|
||||
);
|
||||
if (invalidActiveValve) {
|
||||
open?.({
|
||||
type: "error",
|
||||
message: `阀门 ${invalidActiveValve.id} 的设置值无效`,
|
||||
description:
|
||||
validateValveSetting(
|
||||
invalidActiveValve.vType,
|
||||
invalidActiveValve.setting ?? "",
|
||||
) ?? undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setAnalyzing(true);
|
||||
|
||||
try {
|
||||
@@ -315,8 +444,13 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
|
||||
const params = {
|
||||
scheme_name: schemeName,
|
||||
start_time: formattedTime,
|
||||
valves: valves.map(v => v.id),
|
||||
valves_k: valves.map(v => v.k),
|
||||
...(valves.length > 0 && {
|
||||
valves: valves.map(v => v.id),
|
||||
valve_statuses: valves.map((v) => v.status),
|
||||
valve_settings: valves.map((v) =>
|
||||
v.status === "ACTIVE" ? (v.setting ?? "").trim() : "",
|
||||
),
|
||||
}),
|
||||
drainage_node_id: drainageNode,
|
||||
flush_flow: flushFlow,
|
||||
duration: duration
|
||||
@@ -355,62 +489,11 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
|
||||
|
||||
return (
|
||||
<Box className="flex flex-col h-full gap-4 pb-4">
|
||||
{/* 1. Valve Selection */}
|
||||
{/* 1. Drainage Node Selection */}
|
||||
<Box>
|
||||
<Box className="flex items-center justify-between mb-2">
|
||||
<Typography variant="subtitle2" className="font-medium">
|
||||
参与阀门
|
||||
</Typography>
|
||||
<Button
|
||||
variant={selectionMode === 'valve' ? "contained" : "outlined"}
|
||||
color={selectionMode === 'valve' ? "error" : "primary"}
|
||||
size="small"
|
||||
onClick={() => toggleSelection('valve')}
|
||||
>
|
||||
{selectionMode === 'valve' ? "停止选择" : "选择阀门"}
|
||||
</Button>
|
||||
</Box>
|
||||
{selectionMode === 'valve' && (
|
||||
<Box className="mb-2 p-2 bg-blue-50 text-xs text-blue-700 rounded">
|
||||
💡 点击地图上的阀门进行添加
|
||||
</Box>
|
||||
)}
|
||||
<Stack spacing={1} className="max-h-50 h-48 overflow-auto">
|
||||
{valves.map((valve) => (
|
||||
<Box key={valve.id} className="flex items-center gap-2 p-2 bg-gray-50 rounded">
|
||||
<Typography className="text-sm flex-1 pl-1">{valve.id}</Typography>
|
||||
<TextField
|
||||
label="开度"
|
||||
size="small"
|
||||
type="number"
|
||||
value={valve.k}
|
||||
onChange={(e) => handleValveKChange(valve.id, e.target.value)}
|
||||
className="w-20"
|
||||
slotProps={{ htmlInput: { step: 0.1, min: 0, max: 1 } }}
|
||||
/>
|
||||
<IconButton size="small" onClick={() => handleRemoveValve(valve.id)}>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
))}
|
||||
{valves.length === 0 && (
|
||||
<PanelEmptyState
|
||||
variant="compact"
|
||||
icon={<AdjustOutlined />}
|
||||
title="尚未选择阀门"
|
||||
description="点击“选择阀门”,然后在地图上添加。"
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* 2. Drainage Node Selection */}
|
||||
<Box>
|
||||
<Box className="flex items-center justify-between mb-2">
|
||||
<Typography variant="subtitle2" className="font-medium">
|
||||
排水节点
|
||||
排水节点(必选)
|
||||
</Typography>
|
||||
<Button
|
||||
variant={selectionMode === 'drainage' ? "contained" : "outlined"}
|
||||
@@ -454,6 +537,106 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* 2. Optional Valve Selection */}
|
||||
<Box>
|
||||
<Box className="flex items-center justify-between mb-2">
|
||||
<Typography variant="subtitle2" className="font-medium">
|
||||
参与阀门(可选)
|
||||
</Typography>
|
||||
<Button
|
||||
variant={selectionMode === 'valve' ? "contained" : "outlined"}
|
||||
color={selectionMode === 'valve' ? "error" : "primary"}
|
||||
size="small"
|
||||
onClick={() => toggleSelection('valve')}
|
||||
>
|
||||
{selectionMode === 'valve' ? "停止选择" : "选择阀门"}
|
||||
</Button>
|
||||
</Box>
|
||||
{selectionMode === 'valve' && (
|
||||
<Box className="mb-2 p-2 bg-blue-50 text-xs text-blue-700 rounded">
|
||||
💡 点击地图上的阀门进行添加
|
||||
</Box>
|
||||
)}
|
||||
<Stack spacing={1} className="max-h-50 h-48 overflow-auto">
|
||||
{valves.map((valve) => {
|
||||
const settingValidation =
|
||||
valve.status === "ACTIVE"
|
||||
? validateValveSetting(valve.vType, valve.setting ?? "")
|
||||
: null;
|
||||
const isSettingDisabled =
|
||||
!valve.detailsLoaded ||
|
||||
valve.status === "OPEN" ||
|
||||
valve.status === "CLOSED";
|
||||
|
||||
return (
|
||||
<Box key={valve.id} className="p-2 bg-gray-50 rounded">
|
||||
<Box className="flex items-center gap-2 mb-2">
|
||||
<Typography className="text-sm min-w-0 flex-1 pl-1">
|
||||
{valve.id}
|
||||
</Typography>
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label={`移除阀门 ${valve.id}`}
|
||||
onClick={() => handleRemoveValve(valve.id)}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box className="grid grid-cols-2 gap-2">
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
size="small"
|
||||
label="开关状态"
|
||||
value={valve.status ?? ""}
|
||||
disabled={!valve.detailsLoaded}
|
||||
onChange={(event) =>
|
||||
handleValveStatusChange(valve.id, event.target.value)
|
||||
}
|
||||
>
|
||||
{VALVE_STATUS_OPTIONS.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField
|
||||
fullWidth
|
||||
size="small"
|
||||
label="阀门设置值"
|
||||
value={valve.setting ?? ""}
|
||||
disabled={isSettingDisabled}
|
||||
error={Boolean(settingValidation)}
|
||||
helperText={
|
||||
valve.detailsLoaded
|
||||
? settingValidation ??
|
||||
getValveSettingHelperText(
|
||||
valve.vType,
|
||||
valve.status,
|
||||
)
|
||||
: "加载中"
|
||||
}
|
||||
onChange={(event) =>
|
||||
handleValveSettingChange(valve.id, event.target.value)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
{valves.length === 0 && (
|
||||
<PanelEmptyState
|
||||
variant="compact"
|
||||
icon={<AdjustOutlined />}
|
||||
title="未选择参与阀门"
|
||||
description="无需调整阀门时可跳过,也可点击“选择阀门”添加。"
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* 3. Parameters */}
|
||||
<Box className="flex flex-col gap-3">
|
||||
<Box>
|
||||
|
||||
@@ -25,6 +25,12 @@ import {
|
||||
} from "./toolbarFeatureHelpers";
|
||||
import { useToolbarChatActions } from "./useToolbarChatActions";
|
||||
import { useStyleEditor } from "./useStyleEditor";
|
||||
import {
|
||||
type LinkStatus,
|
||||
isLinkStatus,
|
||||
normalizeValveSetting,
|
||||
validateValveSetting,
|
||||
} from "./valveControl";
|
||||
|
||||
import { config, NETWORK_NAME } from "@/config/config";
|
||||
import { useProject } from "@/contexts/ProjectContext";
|
||||
@@ -41,7 +47,6 @@ interface ToolbarProps {
|
||||
enableCompare?: boolean;
|
||||
}
|
||||
|
||||
type LinkStatus = "OPEN" | "CLOSED" | "ACTIVE";
|
||||
type ValveProperties = {
|
||||
vType: string | null;
|
||||
setting: string | null;
|
||||
@@ -50,42 +55,6 @@ type ValveProperties = {
|
||||
const isValveLayer = (layerId: string | undefined) =>
|
||||
layerId === "geo_valves_mat" || layerId === "geo_valves";
|
||||
|
||||
const isLinkStatus = (value: unknown): value is LinkStatus =>
|
||||
value === "OPEN" || value === "CLOSED" || value === "ACTIVE";
|
||||
|
||||
const normalizeValveSetting = (value: unknown): string | null => {
|
||||
if (value === undefined || value === null) return null;
|
||||
return String(value);
|
||||
};
|
||||
|
||||
const validateValveSetting = (
|
||||
valveType: string | null,
|
||||
value: string,
|
||||
): string | null => {
|
||||
const normalizedType = valveType?.toUpperCase();
|
||||
const trimmedValue = value.trim();
|
||||
const numericTypes = new Set(["PRV", "PSV", "PBV", "FCV", "TCV"]);
|
||||
|
||||
if (normalizedType === "GPV") {
|
||||
return trimmedValue ? null : "GPV 阀门设置值必须是非空曲线 ID。";
|
||||
}
|
||||
|
||||
if (numericTypes.has(normalizedType ?? "")) {
|
||||
if (!trimmedValue) {
|
||||
return "阀门设置值必须是 0 或正数。";
|
||||
}
|
||||
|
||||
const numericValue = Number(trimmedValue);
|
||||
if (!Number.isFinite(numericValue) || numericValue < 0) {
|
||||
return "阀门设置值必须是有限数字,且大于或等于 0。";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return trimmedValue ? null : "阀门设置值不能为空。";
|
||||
};
|
||||
|
||||
const Toolbar: React.FC<ToolbarProps> = ({
|
||||
hiddenButtons,
|
||||
queryType,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import Feature from "ol/Feature";
|
||||
|
||||
import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units";
|
||||
import {
|
||||
getValveSettingHelperText,
|
||||
VALVE_STATUS_OPTIONS,
|
||||
} from "./valveControl";
|
||||
|
||||
type ToolbarBaseProperty = {
|
||||
label: string;
|
||||
@@ -66,30 +70,6 @@ export type ValveSettingPropertyOptions = {
|
||||
onSave: (value: string) => Promise<void>;
|
||||
};
|
||||
|
||||
const getValveSettingHelperText = (
|
||||
vType: string | null,
|
||||
status?: string | null,
|
||||
) => {
|
||||
if (status === "OPEN" || status === "CLOSED") {
|
||||
return "开启/关闭状态下 EPANET 会忽略阀门设置值";
|
||||
}
|
||||
|
||||
switch (vType?.toUpperCase()) {
|
||||
case "PRV":
|
||||
case "PSV":
|
||||
case "PBV":
|
||||
return "压力设置值,需为 0 或正数";
|
||||
case "FCV":
|
||||
return "流量设置值,需为 0 或正数";
|
||||
case "TCV":
|
||||
return "损失系数,需为 0 或正数";
|
||||
case "GPV":
|
||||
return "水头损失曲线 ID";
|
||||
default:
|
||||
return "阀门类型相关设置值";
|
||||
}
|
||||
};
|
||||
|
||||
const getFeatureHistoryType = (feature: Feature): string | null => {
|
||||
const layerId = feature.getId()?.toString().split(".")[0] || "";
|
||||
if (layerId.includes("pipe")) return "pipe";
|
||||
@@ -366,11 +346,7 @@ export const buildFeatureProperties = (
|
||||
type: "select" as const,
|
||||
label: "开关状态",
|
||||
value: valveStatus.value ?? "",
|
||||
options: [
|
||||
{ label: "开启", value: "OPEN" },
|
||||
{ label: "关闭", value: "CLOSED" },
|
||||
{ label: "激活", value: "ACTIVE" },
|
||||
],
|
||||
options: VALVE_STATUS_OPTIONS,
|
||||
placeholder: valveStatus.loading ? "加载中" : "未设置",
|
||||
disabled: valveStatus.loading,
|
||||
saving: valveStatus.saving,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
getValveSettingHelperText,
|
||||
isLinkStatus,
|
||||
normalizeValveSetting,
|
||||
VALVE_STATUS_OPTIONS,
|
||||
validateValveSetting,
|
||||
} from "./valveControl";
|
||||
|
||||
describe("valveControl", () => {
|
||||
it("provides the status translations shared by valve editors", () => {
|
||||
expect(VALVE_STATUS_OPTIONS).toEqual([
|
||||
{ label: "开启", value: "OPEN" },
|
||||
{ label: "关闭", value: "CLOSED" },
|
||||
{ label: "激活", value: "ACTIVE" },
|
||||
]);
|
||||
expect(isLinkStatus("ACTIVE")).toBe(true);
|
||||
expect(isLinkStatus("UNKNOWN")).toBe(false);
|
||||
});
|
||||
|
||||
it("normalizes setting values without dropping zero", () => {
|
||||
expect(normalizeValveSetting(0)).toBe("0");
|
||||
expect(normalizeValveSetting(null)).toBeNull();
|
||||
});
|
||||
|
||||
it("validates numeric and curve settings by valve type", () => {
|
||||
expect(validateValveSetting("PRV", "2.5")).toBeNull();
|
||||
expect(validateValveSetting("PRV", "-1")).toContain("大于或等于 0");
|
||||
expect(validateValveSetting("GPV", "curve-1")).toBeNull();
|
||||
expect(validateValveSetting("GPV", " ")).toContain("曲线 ID");
|
||||
});
|
||||
|
||||
it("explains that OPEN and CLOSED ignore the setting", () => {
|
||||
expect(getValveSettingHelperText("PRV", "OPEN")).toContain("忽略");
|
||||
expect(getValveSettingHelperText("FCV", "ACTIVE")).toContain("流量");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
export type LinkStatus = "OPEN" | "CLOSED" | "ACTIVE";
|
||||
|
||||
export const VALVE_STATUS_OPTIONS: Array<{
|
||||
label: string;
|
||||
value: LinkStatus;
|
||||
}> = [
|
||||
{ label: "开启", value: "OPEN" },
|
||||
{ label: "关闭", value: "CLOSED" },
|
||||
{ label: "激活", value: "ACTIVE" },
|
||||
];
|
||||
|
||||
export const isLinkStatus = (value: unknown): value is LinkStatus =>
|
||||
value === "OPEN" || value === "CLOSED" || value === "ACTIVE";
|
||||
|
||||
export const normalizeValveSetting = (value: unknown): string | null => {
|
||||
if (value === undefined || value === null) return null;
|
||||
return String(value);
|
||||
};
|
||||
|
||||
export const validateValveSetting = (
|
||||
valveType: string | null | undefined,
|
||||
value: string,
|
||||
): string | null => {
|
||||
const normalizedType = valveType?.toUpperCase();
|
||||
const trimmedValue = value.trim();
|
||||
const numericTypes = new Set(["PRV", "PSV", "PBV", "FCV", "TCV"]);
|
||||
|
||||
if (normalizedType === "GPV") {
|
||||
return trimmedValue ? null : "GPV 阀门设置值必须是非空曲线 ID。";
|
||||
}
|
||||
|
||||
if (numericTypes.has(normalizedType ?? "")) {
|
||||
if (!trimmedValue) {
|
||||
return "阀门设置值必须是 0 或正数。";
|
||||
}
|
||||
|
||||
const numericValue = Number(trimmedValue);
|
||||
if (!Number.isFinite(numericValue) || numericValue < 0) {
|
||||
return "阀门设置值必须是有限数字,且大于或等于 0。";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return trimmedValue ? null : "阀门设置值不能为空。";
|
||||
};
|
||||
|
||||
export const getValveSettingHelperText = (
|
||||
valveType: string | null | undefined,
|
||||
status?: LinkStatus | string | null,
|
||||
) => {
|
||||
if (status === "OPEN" || status === "CLOSED") {
|
||||
return "开启/关闭状态下 EPANET 会忽略阀门设置值";
|
||||
}
|
||||
|
||||
switch (valveType?.toUpperCase()) {
|
||||
case "PRV":
|
||||
case "PSV":
|
||||
case "PBV":
|
||||
return "压力设置值,需为 0 或正数";
|
||||
case "FCV":
|
||||
return "流量设置值,需为 0 或正数";
|
||||
case "TCV":
|
||||
return "损失系数,需为 0 或正数";
|
||||
case "GPV":
|
||||
return "水头损失曲线 ID";
|
||||
default:
|
||||
return "阀门类型相关设置值";
|
||||
}
|
||||
};
|
||||
@@ -1121,7 +1121,7 @@ export interface paths {
|
||||
put?: never;
|
||||
/**
|
||||
* 冲洗分析(高级)
|
||||
* @description 高级版本的冲洗分析,支持同时开启多个阀门进行冲洗,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。
|
||||
* @description 高级版本的冲洗分析,支持按状态和设置值控制多个可选阀门,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。
|
||||
*/
|
||||
post: operations["post_flushing_analyses"];
|
||||
delete?: never;
|
||||
@@ -14004,10 +14004,14 @@ export interface operations {
|
||||
query: {
|
||||
/** @description 冲洗开始时间(ISO 8601格式) */
|
||||
start_time: string;
|
||||
/** @description 要开启的阀门ID列表 */
|
||||
valves: string[];
|
||||
/** @description 对应各阀门的开度列表(0-1) */
|
||||
valves_k: number[];
|
||||
/** @description 参与控制的阀门ID列表(可选) */
|
||||
valves?: string[] | null;
|
||||
/** @description 对应各阀门的开度列表(0-1,可选,与valves同时提供) */
|
||||
valves_k?: number[] | null;
|
||||
/** @description 对应各阀门的开关状态列表(OPEN、CLOSED或ACTIVE,可选) */
|
||||
valve_statuses?: ("OPEN" | "CLOSED" | "ACTIVE")[] | null;
|
||||
/** @description 对应各阀门的设置值列表(ACTIVE状态下必填) */
|
||||
valve_settings?: string[] | null;
|
||||
/** @description 排污节点ID */
|
||||
drainage_node_id: string;
|
||||
/** @description 冲洗流量(L/s),0表示自动计算 */
|
||||
|
||||
Reference in New Issue
Block a user