feat(map): add valve setting editor
This commit is contained in:
@@ -1,9 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import React, { useRef } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import Draggable from "react-draggable";
|
||||
import { Close } from "@mui/icons-material";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
FormControl,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import type { SelectChangeEvent } from "@mui/material/Select";
|
||||
|
||||
interface BaseProperty {
|
||||
label: string;
|
||||
@@ -20,7 +30,29 @@ interface TableProperty {
|
||||
rows: (string | number)[][]; // 每行的数据
|
||||
}
|
||||
|
||||
type PropertyItem = BaseProperty | TableProperty;
|
||||
interface SelectProperty {
|
||||
type: "select";
|
||||
label: string;
|
||||
value: string;
|
||||
options: { label: string; value: string }[];
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
saving?: boolean;
|
||||
onSave: (value: string) => Promise<void>;
|
||||
}
|
||||
|
||||
interface TextProperty {
|
||||
type: "text";
|
||||
label: string;
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
saving?: boolean;
|
||||
helperText?: string;
|
||||
onSave: (value: string) => Promise<void>;
|
||||
}
|
||||
|
||||
type PropertyItem = BaseProperty | TableProperty | SelectProperty | TextProperty;
|
||||
|
||||
interface PropertyPanelProps {
|
||||
id?: string;
|
||||
@@ -36,6 +68,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||
onClose,
|
||||
}) => {
|
||||
const draggableRef = useRef<HTMLDivElement>(null);
|
||||
const [draftValues, setDraftValues] = useState<Record<string, string>>({});
|
||||
const headerActionSx = {
|
||||
color: "common.white",
|
||||
backgroundColor: "rgba(255,255,255,0.08)",
|
||||
@@ -56,6 +89,26 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||
|
||||
const isImportantKeys = ["ID", "类型", "Name", "面积", "长度"];
|
||||
|
||||
const handleSelectChange = (
|
||||
key: string,
|
||||
event: SelectChangeEvent,
|
||||
) => {
|
||||
setDraftValues((prev) => ({
|
||||
...prev,
|
||||
[key]: event.target.value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleTextChange = (
|
||||
key: string,
|
||||
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
) => {
|
||||
setDraftValues((prev) => ({
|
||||
...prev,
|
||||
[key]: event.target.value,
|
||||
}));
|
||||
};
|
||||
|
||||
// 统计属性数量(表格型按行数计入)
|
||||
const totalProps = id
|
||||
? 2 +
|
||||
@@ -200,6 +253,151 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
if ("type" in property && property.type === "select") {
|
||||
const selected = property as SelectProperty;
|
||||
const draftKey = `${id ?? "unknown"}:${selected.label}`;
|
||||
const draftValue = draftValues[draftKey] ?? selected.value ?? "";
|
||||
const hasChanged = draftValue !== selected.value;
|
||||
const getOptionLabel = (value: string) =>
|
||||
selected.options.find((option) => option.value === value)
|
||||
?.label ?? value;
|
||||
const canSave =
|
||||
hasChanged &&
|
||||
draftValue !== "" &&
|
||||
!selected.disabled &&
|
||||
!selected.saving;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`select-${index}`}
|
||||
className="group rounded-lg p-3 transition-all duration-200 bg-gray-50 hover:bg-gray-100"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="font-medium text-xs uppercase tracking-wide text-gray-600">
|
||||
{selected.label}
|
||||
</span>
|
||||
<div className="flex items-center justify-end gap-2 flex-1">
|
||||
<FormControl size="small" sx={{ minWidth: 118 }}>
|
||||
<Select
|
||||
value={draftValue}
|
||||
displayEmpty
|
||||
disabled={selected.disabled || selected.saving}
|
||||
onChange={(event) =>
|
||||
handleSelectChange(draftKey, event)
|
||||
}
|
||||
renderValue={(value) =>
|
||||
value
|
||||
? getOptionLabel(String(value))
|
||||
: selected.placeholder || "-"
|
||||
}
|
||||
sx={{
|
||||
height: 32,
|
||||
fontSize: 13,
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
>
|
||||
<MenuItem value="" disabled>
|
||||
{selected.placeholder || "未设置"}
|
||||
</MenuItem>
|
||||
{selected.options.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
disabled={!canSave}
|
||||
onClick={() => selected.onSave(draftValue)}
|
||||
sx={{
|
||||
minWidth: 58,
|
||||
height: 32,
|
||||
fontSize: 13,
|
||||
boxShadow: "none",
|
||||
}}
|
||||
>
|
||||
{selected.saving ? (
|
||||
<CircularProgress size={16} color="inherit" />
|
||||
) : (
|
||||
"保存"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if ("type" in property && property.type === "text") {
|
||||
const editable = property as TextProperty;
|
||||
const draftKey = `${id ?? "unknown"}:${editable.label}`;
|
||||
const draftValue = draftValues[draftKey] ?? editable.value ?? "";
|
||||
const hasChanged = draftValue !== editable.value;
|
||||
const canSave =
|
||||
hasChanged &&
|
||||
!editable.disabled &&
|
||||
!editable.saving;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`text-${index}`}
|
||||
className="group rounded-lg p-3 transition-all duration-200 bg-gray-50 hover:bg-gray-100"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<span className="font-medium text-xs uppercase tracking-wide text-gray-600 pt-2">
|
||||
{editable.label}
|
||||
</span>
|
||||
<div className="flex flex-col items-end gap-1 flex-1">
|
||||
<div className="flex items-center justify-end gap-2 w-full">
|
||||
<TextField
|
||||
size="small"
|
||||
value={draftValue}
|
||||
placeholder={editable.placeholder}
|
||||
disabled={editable.disabled || editable.saving}
|
||||
onChange={(event) =>
|
||||
handleTextChange(draftKey, event)
|
||||
}
|
||||
sx={{
|
||||
width: 150,
|
||||
"& .MuiInputBase-input": {
|
||||
height: 15,
|
||||
fontSize: 13,
|
||||
},
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
disabled={!canSave}
|
||||
onClick={() => editable.onSave(draftValue)}
|
||||
sx={{
|
||||
minWidth: 58,
|
||||
height: 32,
|
||||
fontSize: 13,
|
||||
boxShadow: "none",
|
||||
}}
|
||||
>
|
||||
{editable.saving ? (
|
||||
<CircularProgress size={16} color="inherit" />
|
||||
) : (
|
||||
"保存"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
{editable.helperText && (
|
||||
<span className="text-xs text-gray-500 text-right">
|
||||
{editable.helperText}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 普通属性
|
||||
const base = property as BaseProperty;
|
||||
const isImportant = isImportantKeys.includes(base.label);
|
||||
|
||||
@@ -26,7 +26,8 @@ import {
|
||||
import { useToolbarChatActions } from "./useToolbarChatActions";
|
||||
import { useStyleEditor } from "./useStyleEditor";
|
||||
|
||||
import { config } from "@/config/config";
|
||||
import { config, NETWORK_NAME } from "@/config/config";
|
||||
import { useProject } from "@/contexts/ProjectContext";
|
||||
import { apiFetch } from "@/lib/apiFetch";
|
||||
|
||||
// 添加接口定义隐藏按钮的props
|
||||
@@ -37,6 +38,52 @@ interface ToolbarProps {
|
||||
HistoryPanel?: React.FC<any>; // 可选的自定义历史数据面板
|
||||
enableCompare?: boolean;
|
||||
}
|
||||
|
||||
type LinkStatus = "OPEN" | "CLOSED" | "ACTIVE";
|
||||
type ValveProperties = {
|
||||
vType: string | null;
|
||||
setting: string | null;
|
||||
};
|
||||
|
||||
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,
|
||||
@@ -46,6 +93,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
}) => {
|
||||
const map = useMap();
|
||||
const data = useData();
|
||||
const project = useProject();
|
||||
const { open } = useNotification();
|
||||
const [activeTools, setActiveTools] = useState<string[]>([]);
|
||||
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
||||
@@ -58,6 +106,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
const currentTime = data?.currentTime;
|
||||
const selectedDate = data?.selectedDate;
|
||||
const schemeName = data?.schemeName;
|
||||
const networkName = project?.networkName || NETWORK_NAME;
|
||||
const isCompareMode = data?.isCompareMode ?? false;
|
||||
const toggleCompareMode = data?.toggleCompareMode;
|
||||
const canToggleCompare = Boolean(
|
||||
@@ -325,6 +374,261 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
const [computedProperties, setComputedProperties] = useState<
|
||||
Record<string, any>
|
||||
>({});
|
||||
const [valveStatus, setValveStatus] = useState<LinkStatus | null>(null);
|
||||
const [isValveStatusLoading, setIsValveStatusLoading] = useState(false);
|
||||
const [isValveStatusSaving, setIsValveStatusSaving] = useState(false);
|
||||
const [valveProperties, setValveProperties] = useState<ValveProperties>({
|
||||
vType: null,
|
||||
setting: null,
|
||||
});
|
||||
const [isValvePropertiesLoading, setIsValvePropertiesLoading] =
|
||||
useState(false);
|
||||
const [isValveSettingSaving, setIsValveSettingSaving] = useState(false);
|
||||
|
||||
const selectedFeature = highlightFeatures[0];
|
||||
const selectedFeatureLayer = selectedFeature
|
||||
?.getId()
|
||||
?.toString()
|
||||
.split(".")[0];
|
||||
const selectedFeatureId = selectedFeature?.getProperties?.().id;
|
||||
const selectedFeatureValveType = selectedFeature?.getProperties?.().v_type;
|
||||
const selectedValveType =
|
||||
valveProperties.vType ??
|
||||
(selectedFeatureValveType ? String(selectedFeatureValveType) : null);
|
||||
const selectedValveId =
|
||||
showPropertyPanel && isValveLayer(selectedFeatureLayer) && selectedFeatureId
|
||||
? String(selectedFeatureId)
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedValveId) {
|
||||
setValveStatus(null);
|
||||
setIsValveStatusLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
const queryValveStatus = async () => {
|
||||
setIsValveStatusLoading(true);
|
||||
setValveStatus(null);
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
network: networkName,
|
||||
link: selectedValveId,
|
||||
});
|
||||
const response = await apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/getstatus/?${params.toString()}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`getstatus failed: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!cancelled) {
|
||||
setValveStatus(isLinkStatus(data?.status) ? data.status : null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error querying valve status:", error);
|
||||
if (!cancelled) {
|
||||
setValveStatus(null);
|
||||
open?.({
|
||||
type: "error",
|
||||
message: "读取阀门开关状态失败。",
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setIsValveStatusLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
queryValveStatus();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [networkName, open, selectedValveId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedValveId) {
|
||||
setValveProperties({ vType: null, setting: null });
|
||||
setIsValvePropertiesLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
const queryValveProperties = async () => {
|
||||
setIsValvePropertiesLoading(true);
|
||||
setValveProperties({ vType: null, setting: null });
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
network: networkName,
|
||||
valve: selectedValveId,
|
||||
});
|
||||
const response = await apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/getvalveproperties/?${params.toString()}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`getvalveproperties failed: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!cancelled) {
|
||||
setValveProperties({
|
||||
vType: data?.v_type ? String(data.v_type) : null,
|
||||
setting: normalizeValveSetting(data?.setting),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error querying valve properties:", error);
|
||||
if (!cancelled) {
|
||||
setValveProperties({ vType: null, setting: null });
|
||||
open?.({
|
||||
type: "error",
|
||||
message: "读取阀门设置值失败。",
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setIsValvePropertiesLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
queryValveProperties();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [networkName, open, selectedValveId]);
|
||||
|
||||
const handleValveStatusSave = useCallback(
|
||||
async (nextStatus: string) => {
|
||||
if (!selectedValveId || !isLinkStatus(nextStatus)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsValveStatusSaving(true);
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
network: networkName,
|
||||
link: selectedValveId,
|
||||
});
|
||||
const response = await apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/setstatus/?${params.toString()}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ status: nextStatus }),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`setstatus failed: ${response.status}`);
|
||||
}
|
||||
|
||||
setValveStatus(nextStatus);
|
||||
open?.({
|
||||
type: "success",
|
||||
message: "阀门开关状态已更新。",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating valve status:", error);
|
||||
open?.({
|
||||
type: "error",
|
||||
message: "阀门开关状态更新失败。",
|
||||
});
|
||||
} finally {
|
||||
setIsValveStatusSaving(false);
|
||||
}
|
||||
},
|
||||
[networkName, open, selectedValveId],
|
||||
);
|
||||
|
||||
const handleValveSettingSave = useCallback(
|
||||
async (nextSetting: string) => {
|
||||
if (!selectedValveId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (valveStatus === "OPEN" || valveStatus === "CLOSED") {
|
||||
open?.({
|
||||
type: "error",
|
||||
message: "开启或关闭状态下不能编辑阀门设置值。",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const validationMessage = validateValveSetting(
|
||||
selectedValveType,
|
||||
nextSetting,
|
||||
);
|
||||
if (validationMessage) {
|
||||
open?.({
|
||||
type: "error",
|
||||
message: validationMessage,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedSetting = nextSetting.trim();
|
||||
setIsValveSettingSaving(true);
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
network: networkName,
|
||||
valve: selectedValveId,
|
||||
});
|
||||
const response = await apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/setvalveproperties/?${params.toString()}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ setting: trimmedSetting }),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`setvalveproperties failed: ${response.status}`);
|
||||
}
|
||||
|
||||
setValveProperties((prev) => ({
|
||||
...prev,
|
||||
setting: trimmedSetting,
|
||||
}));
|
||||
open?.({
|
||||
type: "success",
|
||||
message: "阀门设置值已更新。",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating valve setting:", error);
|
||||
open?.({
|
||||
type: "error",
|
||||
message: "阀门设置值更新失败。",
|
||||
});
|
||||
} finally {
|
||||
setIsValveSettingSaving(false);
|
||||
}
|
||||
},
|
||||
[
|
||||
networkName,
|
||||
open,
|
||||
selectedValveId,
|
||||
selectedValveType,
|
||||
valveStatus,
|
||||
],
|
||||
);
|
||||
|
||||
// 添加 useEffect 来查询计算属性
|
||||
useEffect(() => {
|
||||
if (highlightFeatures.length === 0 || !selectedDate || !showPropertyPanel) {
|
||||
@@ -387,8 +691,43 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
}, [highlightFeatures, currentTime, selectedDate, queryType, schemeName, schemeType, showPropertyPanel]);
|
||||
|
||||
const propertyPanelData = useMemo(
|
||||
() => buildFeatureProperties(highlightFeatures[0], computedProperties),
|
||||
[highlightFeatures, computedProperties],
|
||||
() =>
|
||||
buildFeatureProperties(
|
||||
selectedFeature,
|
||||
computedProperties,
|
||||
selectedValveId
|
||||
? {
|
||||
value: valveStatus,
|
||||
loading: isValveStatusLoading,
|
||||
saving: isValveStatusSaving,
|
||||
onSave: handleValveStatusSave,
|
||||
}
|
||||
: undefined,
|
||||
selectedValveId
|
||||
? {
|
||||
value: valveProperties.setting,
|
||||
vType: selectedValveType,
|
||||
loading: isValvePropertiesLoading,
|
||||
saving: isValveSettingSaving,
|
||||
status: valveStatus,
|
||||
onSave: handleValveSettingSave,
|
||||
}
|
||||
: undefined,
|
||||
),
|
||||
[
|
||||
selectedFeature,
|
||||
computedProperties,
|
||||
selectedValveId,
|
||||
valveStatus,
|
||||
valveProperties,
|
||||
selectedValveType,
|
||||
isValveStatusLoading,
|
||||
isValveStatusSaving,
|
||||
isValvePropertiesLoading,
|
||||
isValveSettingSaving,
|
||||
handleValveStatusSave,
|
||||
handleValveSettingSave,
|
||||
],
|
||||
);
|
||||
|
||||
if (!data) {
|
||||
|
||||
@@ -16,7 +16,33 @@ type ToolbarTableProperty = {
|
||||
rows: (string | number)[][];
|
||||
};
|
||||
|
||||
export type ToolbarPropertyItem = ToolbarBaseProperty | ToolbarTableProperty;
|
||||
type ToolbarSelectProperty = {
|
||||
type: "select";
|
||||
label: string;
|
||||
value: string;
|
||||
options: { label: string; value: string }[];
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
saving?: boolean;
|
||||
onSave: (value: string) => Promise<void>;
|
||||
};
|
||||
|
||||
type ToolbarTextProperty = {
|
||||
type: "text";
|
||||
label: string;
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
saving?: boolean;
|
||||
helperText?: string;
|
||||
onSave: (value: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export type ToolbarPropertyItem =
|
||||
| ToolbarBaseProperty
|
||||
| ToolbarTableProperty
|
||||
| ToolbarSelectProperty
|
||||
| ToolbarTextProperty;
|
||||
|
||||
export type ToolbarPropertyPanelData = {
|
||||
id?: string;
|
||||
@@ -24,6 +50,46 @@ export type ToolbarPropertyPanelData = {
|
||||
properties?: ToolbarPropertyItem[];
|
||||
};
|
||||
|
||||
export type ValveStatusPropertyOptions = {
|
||||
value: string | null;
|
||||
loading?: boolean;
|
||||
saving?: boolean;
|
||||
onSave: (value: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export type ValveSettingPropertyOptions = {
|
||||
value: string | null;
|
||||
vType: string | null;
|
||||
loading?: boolean;
|
||||
saving?: boolean;
|
||||
status?: string | null;
|
||||
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";
|
||||
@@ -56,6 +122,8 @@ export const inferHistoryFeatureInfos = (
|
||||
export const buildFeatureProperties = (
|
||||
highlightFeature: Feature | undefined,
|
||||
computedProperties: Record<string, any>,
|
||||
valveStatus?: ValveStatusPropertyOptions,
|
||||
valveSetting?: ValveSettingPropertyOptions,
|
||||
): ToolbarPropertyPanelData => {
|
||||
if (!highlightFeature) return {};
|
||||
|
||||
@@ -267,6 +335,12 @@ export const buildFeatureProperties = (
|
||||
}
|
||||
|
||||
if (layer === "geo_valves_mat" || layer === "geo_valves") {
|
||||
const valveType = valveSetting?.vType ?? properties.v_type;
|
||||
const isValveSettingDisabled =
|
||||
valveSetting?.loading ||
|
||||
valveSetting?.status === "OPEN" ||
|
||||
valveSetting?.status === "CLOSED";
|
||||
|
||||
return {
|
||||
id: properties.id,
|
||||
type: "阀门",
|
||||
@@ -280,12 +354,47 @@ export const buildFeatureProperties = (
|
||||
},
|
||||
{
|
||||
label: "阀门类型",
|
||||
value: properties.v_type,
|
||||
value: valveType,
|
||||
},
|
||||
{
|
||||
label: "局部损失",
|
||||
value: properties.minor_loss?.toFixed?.(2),
|
||||
},
|
||||
...(valveStatus
|
||||
? [
|
||||
{
|
||||
type: "select" as const,
|
||||
label: "开关状态",
|
||||
value: valveStatus.value ?? "",
|
||||
options: [
|
||||
{ label: "开启", value: "OPEN" },
|
||||
{ label: "关闭", value: "CLOSED" },
|
||||
{ label: "激活", value: "ACTIVE" },
|
||||
],
|
||||
placeholder: valveStatus.loading ? "加载中" : "未设置",
|
||||
disabled: valveStatus.loading,
|
||||
saving: valveStatus.saving,
|
||||
onSave: valveStatus.onSave,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(valveSetting
|
||||
? [
|
||||
{
|
||||
type: "text" as const,
|
||||
label: "阀门设置值",
|
||||
value: valveSetting.value ?? "",
|
||||
placeholder: valveSetting.loading ? "加载中" : "未设置",
|
||||
disabled: isValveSettingDisabled,
|
||||
saving: valveSetting.saving,
|
||||
helperText: getValveSettingHelperText(
|
||||
valveType,
|
||||
valveSetting.status,
|
||||
),
|
||||
onSave: valveSetting.onSave,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user