合并 agent-mvp 到 master #1
@@ -44,6 +44,7 @@ import { SchemeRecord, SchemaItem } from "./types";
|
||||
import { FLOW_DISPLAY_UNIT } from "@utils/units";
|
||||
import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName";
|
||||
import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState";
|
||||
import { formatValveControlSummary } from "@components/olmap/core/Controls/toolbarFeatureHelpers";
|
||||
|
||||
interface SchemeQueryProps {
|
||||
schemes?: SchemeRecord[];
|
||||
@@ -589,10 +590,22 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
{/* 阀门列表 */}
|
||||
<Box className="col-span-2 pl-2">
|
||||
<Typography variant="caption" className="text-gray-600 block mb-1">
|
||||
参与阀门及开度:
|
||||
参与阀门及设置:
|
||||
</Typography>
|
||||
<Box className="flex flex-wrap gap-2">
|
||||
{scheme.schemeDetail?.valve_opening && Object.entries(scheme.schemeDetail.valve_opening).length > 0 ? (
|
||||
{scheme.schemeDetail?.valve_control && Object.entries(scheme.schemeDetail.valve_control).length > 0 ? (
|
||||
Object.entries(scheme.schemeDetail.valve_control).map(([id, control]) => (
|
||||
<Tooltip key={id} title="点击定位阀门">
|
||||
<Chip
|
||||
label={formatValveControlSummary(id, control)}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
onClick={() => handleLocateValves([id])}
|
||||
className="text-xs h-6 bg-gray-50 cursor-pointer hover:bg-orange-50 hover:border-orange-200"
|
||||
/>
|
||||
</Tooltip>
|
||||
))
|
||||
) : scheme.schemeDetail?.valve_opening && Object.entries(scheme.schemeDetail.valve_opening).length > 0 ? (
|
||||
Object.entries(scheme.schemeDetail.valve_opening).map(([id, k]) => (
|
||||
<Tooltip key={id} title="点击定位阀门">
|
||||
<Chip
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
export interface SchemeDetail {
|
||||
valve_opening: Record<string, number>;
|
||||
valve_opening?: Record<string, number> | null;
|
||||
valve_control?: Record<
|
||||
string,
|
||||
{
|
||||
status?: "OPEN" | "CLOSED" | "ACTIVE";
|
||||
setting?: string | number;
|
||||
k?: number;
|
||||
}
|
||||
> | null;
|
||||
drainage_node_ID: string;
|
||||
flushing_flow: number;
|
||||
duration: number;
|
||||
|
||||
@@ -22,6 +22,8 @@ import { useNotification } from "@refinedev/core";
|
||||
import ToolbarHistoryPanel from "./ToolbarHistoryPanel";
|
||||
import {
|
||||
buildFeatureProperties,
|
||||
getSimulationElementType,
|
||||
getValvePropertySource,
|
||||
} from "./toolbarFeatureHelpers";
|
||||
import { useToolbarChatActions } from "./useToolbarChatActions";
|
||||
import { useStyleEditor } from "./useStyleEditor";
|
||||
@@ -52,6 +54,17 @@ type ValveProperties = {
|
||||
setting: string | null;
|
||||
};
|
||||
|
||||
type SchemeValveControl = {
|
||||
status?: string;
|
||||
setting?: string | number;
|
||||
k?: number;
|
||||
};
|
||||
|
||||
type ActiveSchemeDetail = {
|
||||
valve_control?: Record<string, SchemeValveControl> | null;
|
||||
valve_opening?: Record<string, number> | null;
|
||||
};
|
||||
|
||||
const isValveLayer = (layerId: string | undefined) =>
|
||||
layerId === "geo_valves_mat" || layerId === "geo_valves";
|
||||
|
||||
@@ -369,6 +382,8 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
const [isValvePropertiesLoading, setIsValvePropertiesLoading] =
|
||||
useState(false);
|
||||
const [isValveSettingSaving, setIsValveSettingSaving] = useState(false);
|
||||
const [activeSchemeDetail, setActiveSchemeDetail] =
|
||||
useState<ActiveSchemeDetail | null>(null);
|
||||
|
||||
const selectedFeature = highlightFeatures[0];
|
||||
const selectedFeatureLayer = selectedFeature
|
||||
@@ -384,9 +399,57 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
showPropertyPanel && isValveLayer(selectedFeatureLayer) && selectedFeatureId
|
||||
? String(selectedFeatureId)
|
||||
: null;
|
||||
const isSimulationDataActive =
|
||||
getValvePropertySource(queryType, schemeName) === "simulation";
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedValveId) {
|
||||
if (
|
||||
queryType !== "scheme" ||
|
||||
schemeType !== "flushing_analysis" ||
|
||||
!schemeName ||
|
||||
!selectedValveId
|
||||
) {
|
||||
setActiveSchemeDetail(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveSchemeDetail(null);
|
||||
let cancelled = false;
|
||||
|
||||
const querySchemeDetail = async () => {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (schemeType) params.set("scheme_type", schemeType);
|
||||
const response = await apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/schemes/${encodeURIComponent(schemeName)}?${params.toString()}`,
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`getschemedetail failed: ${response.status}`);
|
||||
}
|
||||
const payload = await response.json();
|
||||
if (!cancelled) {
|
||||
setActiveSchemeDetail(payload?.scheme_detail ?? null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error querying scheme valve settings:", error);
|
||||
if (!cancelled) {
|
||||
setActiveSchemeDetail(null);
|
||||
open?.({
|
||||
type: "error",
|
||||
message: "读取方案阀门设置失败。",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void querySchemeDetail();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [open, queryType, schemeName, schemeType, selectedValveId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedValveId || isSimulationDataActive) {
|
||||
setValveStatus(null);
|
||||
setIsValveStatusLoading(false);
|
||||
return;
|
||||
@@ -435,10 +498,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [networkName, open, selectedValveId]);
|
||||
}, [isSimulationDataActive, networkName, open, selectedValveId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedValveId) {
|
||||
if (!selectedValveId || isSimulationDataActive) {
|
||||
setValveProperties({ vType: null, setting: null });
|
||||
setIsValvePropertiesLoading(false);
|
||||
return;
|
||||
@@ -490,7 +553,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [networkName, open, selectedValveId]);
|
||||
}, [isSimulationDataActive, networkName, open, selectedValveId]);
|
||||
|
||||
const handleValveStatusSave = useCallback(
|
||||
async (nextStatus: string) => {
|
||||
@@ -611,7 +674,14 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
|
||||
// 添加 useEffect 来查询计算属性
|
||||
useEffect(() => {
|
||||
if (highlightFeatures.length === 0 || !selectedDate || !showPropertyPanel) {
|
||||
const canQuerySimulation =
|
||||
getValvePropertySource(queryType, schemeName) === "simulation";
|
||||
if (
|
||||
highlightFeatures.length === 0 ||
|
||||
!selectedDate ||
|
||||
!showPropertyPanel ||
|
||||
!canQuerySimulation
|
||||
) {
|
||||
setComputedProperties({});
|
||||
return;
|
||||
}
|
||||
@@ -623,11 +693,12 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
setComputedProperties({});
|
||||
|
||||
const queryComputedProperties = async () => {
|
||||
try {
|
||||
const properties = highlightFeature?.getProperties?.() || {};
|
||||
const type =
|
||||
properties.geometry?.getType?.() === "LineString" ? "link" : "node";
|
||||
const type = getSimulationElementType(highlightFeature);
|
||||
// selectedDate 格式化为 YYYY-MM-DD
|
||||
let dateObj: Date;
|
||||
if (selectedDate instanceof Date) {
|
||||
@@ -639,22 +710,33 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
dateObj.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0);
|
||||
// 转为 UTC ISO 字符串
|
||||
const querytime = dateObj.toISOString(); // 例如 "2025-09-16T16:30:00.000Z"
|
||||
let response;
|
||||
let response: Response;
|
||||
if (queryType === "scheme") {
|
||||
const params = new URLSearchParams({
|
||||
scheme_type: schemeType ?? "",
|
||||
scheme_name: schemeName ?? "",
|
||||
id: String(id),
|
||||
type,
|
||||
query_time: querytime,
|
||||
});
|
||||
response = await apiFetch(
|
||||
// `${config.BACKEND_URL}/queryschemesimulationrecordsbyidtime/?scheme_name=${schemeName}&id=${id}&querytime=${querytime}&type=${type}`
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/schemes/simulation-results?scheme_type=${schemeType}&scheme_name=${schemeName}&id=${id}&type=${type}&query_time=${querytime}`,
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/schemes/simulation-results?${params.toString()}`,
|
||||
);
|
||||
} else {
|
||||
const params = new URLSearchParams({
|
||||
id: String(id),
|
||||
type,
|
||||
query_time: querytime,
|
||||
});
|
||||
response = await apiFetch(
|
||||
// `${config.BACKEND_URL}/querysimulationrecordsbyidtime/?id=${id}&querytime=${querytime}&type=${type}`
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/realtime/simulation-results?id=${id}&type=${type}&query_time=${querytime}`,
|
||||
`${config.BACKEND_URL}/api/v1/timeseries/realtime/simulation-results?${params.toString()}`,
|
||||
);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error("API request failed");
|
||||
}
|
||||
const data = await response.json();
|
||||
if (cancelled) return;
|
||||
if (!data.result || data.result.length === 0) {
|
||||
setComputedProperties({});
|
||||
} else {
|
||||
@@ -663,32 +745,76 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error querying computed properties:", error);
|
||||
if (!cancelled) {
|
||||
setComputedProperties({});
|
||||
open?.({
|
||||
type: "error",
|
||||
message: "读取模拟属性失败,请检查方案、时间和数据源。",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
// 仅当 currentTime 有效时查询
|
||||
if (currentTime !== -1 && queryType) queryComputedProperties();
|
||||
}, [highlightFeatures, currentTime, selectedDate, queryType, schemeName, schemeType, showPropertyPanel]);
|
||||
if (currentTime !== -1 && queryType) void queryComputedProperties();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [highlightFeatures, currentTime, open, selectedDate, queryType, schemeName, schemeType, showPropertyPanel]);
|
||||
|
||||
const displayedComputedProperties = useMemo(() => {
|
||||
if (!isSimulationDataActive || !selectedValveId) {
|
||||
return computedProperties;
|
||||
}
|
||||
|
||||
const control = activeSchemeDetail?.valve_control?.[selectedValveId];
|
||||
const legacyOpening = activeSchemeDetail?.valve_opening?.[selectedValveId];
|
||||
return {
|
||||
...computedProperties,
|
||||
...(control?.status !== undefined
|
||||
? { scheme_status: control.status }
|
||||
: {}),
|
||||
...(control
|
||||
? {
|
||||
scheme_setting:
|
||||
control.status === "OPEN" || control.status === "CLOSED"
|
||||
? "不适用"
|
||||
: (control.setting ?? "未设置"),
|
||||
}
|
||||
: {}),
|
||||
...(control?.k !== undefined
|
||||
? { scheme_opening: control.k }
|
||||
: legacyOpening !== undefined
|
||||
? { scheme_opening: legacyOpening }
|
||||
: {}),
|
||||
};
|
||||
}, [
|
||||
activeSchemeDetail,
|
||||
computedProperties,
|
||||
isSimulationDataActive,
|
||||
selectedValveId,
|
||||
]);
|
||||
|
||||
const propertyPanelData = useMemo(
|
||||
() =>
|
||||
buildFeatureProperties(
|
||||
selectedFeature,
|
||||
computedProperties,
|
||||
canEditNetwork && selectedValveId
|
||||
displayedComputedProperties,
|
||||
!isSimulationDataActive && selectedValveId
|
||||
? {
|
||||
value: valveStatus,
|
||||
loading: isValveStatusLoading,
|
||||
saving: isValveStatusSaving,
|
||||
disabled: !canEditNetwork,
|
||||
onSave: handleValveStatusSave,
|
||||
}
|
||||
: undefined,
|
||||
canEditNetwork && selectedValveId
|
||||
!isSimulationDataActive && selectedValveId
|
||||
? {
|
||||
value: valveProperties.setting,
|
||||
vType: selectedValveType,
|
||||
loading: isValvePropertiesLoading,
|
||||
saving: isValveSettingSaving,
|
||||
disabled: !canEditNetwork,
|
||||
status: valveStatus,
|
||||
onSave: handleValveSettingSave,
|
||||
}
|
||||
@@ -696,7 +822,8 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
),
|
||||
[
|
||||
selectedFeature,
|
||||
computedProperties,
|
||||
displayedComputedProperties,
|
||||
isSimulationDataActive,
|
||||
canEditNetwork,
|
||||
selectedValveId,
|
||||
valveStatus,
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
jest.mock("ol/Feature", () => ({
|
||||
__esModule: true,
|
||||
default: class Feature {},
|
||||
}));
|
||||
|
||||
import type Feature from "ol/Feature";
|
||||
|
||||
import {
|
||||
buildFeatureProperties,
|
||||
formatValveControlSummary,
|
||||
getSimulationElementType,
|
||||
getValvePropertySource,
|
||||
} from "./toolbarFeatureHelpers";
|
||||
|
||||
const createValveFeature = () => {
|
||||
const properties = {
|
||||
id: "V1",
|
||||
node1: "J1",
|
||||
node2: "J2",
|
||||
diameter: 200,
|
||||
v_type: "PRV",
|
||||
minor_loss: 0,
|
||||
};
|
||||
return {
|
||||
getId: () => "geo_valves.V1",
|
||||
getProperties: () => properties,
|
||||
} as unknown as Feature;
|
||||
};
|
||||
|
||||
const createFeature = (
|
||||
layer: string,
|
||||
id: string,
|
||||
properties: Record<string, unknown>,
|
||||
) =>
|
||||
({
|
||||
getId: () => `${layer}.${id}`,
|
||||
getProperties: () => ({ id, ...properties }),
|
||||
}) as unknown as Feature;
|
||||
|
||||
describe("buildFeatureProperties valve simulation values", () => {
|
||||
it("shows TimescaleDB status and setting for a simulated valve", () => {
|
||||
const result = buildFeatureProperties(createValveFeature(), {
|
||||
status: 2,
|
||||
setting: 2.5,
|
||||
});
|
||||
|
||||
expect(result.properties).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ label: "模拟开关状态", value: "激活" }),
|
||||
expect.objectContaining({ label: "模拟设置值", value: 2.5 }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the other TimescaleDB link results for a simulated valve", () => {
|
||||
const result = buildFeatureProperties(createValveFeature(), {
|
||||
flow: 10,
|
||||
pressure: 999,
|
||||
status: 1,
|
||||
setting: 2.5,
|
||||
velocity: 0.75,
|
||||
});
|
||||
|
||||
expect(result.properties).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ label: "流量", value: "36.000" }),
|
||||
expect.objectContaining({ label: "流速", value: "0.750" }),
|
||||
]),
|
||||
);
|
||||
expect(result.properties).not.toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ label: "压力" })]),
|
||||
);
|
||||
});
|
||||
|
||||
it("shows the selected scheme control next to TimescaleDB values", () => {
|
||||
const result = buildFeatureProperties(createValveFeature(), {
|
||||
status: 1,
|
||||
setting: 0,
|
||||
scheme_status: "CLOSED",
|
||||
scheme_setting: "不适用",
|
||||
});
|
||||
|
||||
expect(result.properties).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ label: "模拟开关状态", value: "开启" }),
|
||||
expect.objectContaining({ label: "方案设置状态", value: "关闭" }),
|
||||
expect.objectContaining({ label: "方案设置值", value: "不适用" }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps PostgreSQL valve controls when simulation values are absent", () => {
|
||||
const onStatusSave = jest.fn(async () => undefined);
|
||||
const onSettingSave = jest.fn(async () => undefined);
|
||||
const result = buildFeatureProperties(
|
||||
createValveFeature(),
|
||||
{},
|
||||
{ value: "ACTIVE", onSave: onStatusSave },
|
||||
{
|
||||
value: "2.5",
|
||||
vType: "PRV",
|
||||
status: "ACTIVE",
|
||||
onSave: onSettingSave,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.properties).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "select",
|
||||
label: "开关状态",
|
||||
value: "ACTIVE",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: "text",
|
||||
label: "阀门设置值",
|
||||
value: "2.5",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(result.properties).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ label: "模拟开关状态" }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getValvePropertySource", () => {
|
||||
it("uses business PostgreSQL until simulation data is opened", () => {
|
||||
expect(getValvePropertySource(undefined, undefined)).toBe("business");
|
||||
expect(getValvePropertySource("scheme", "")).toBe("business");
|
||||
expect(getValvePropertySource("scheme", "flush_260817_153045123")).toBe(
|
||||
"simulation",
|
||||
);
|
||||
expect(getValvePropertySource("realtime", undefined)).toBe("simulation");
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatValveControlSummary", () => {
|
||||
it.each(["OPEN", "CLOSED"])(
|
||||
"marks the setting as not applicable when status is %s",
|
||||
(status) => {
|
||||
expect(
|
||||
formatValveControlSummary("V1", { status, setting: 2.5 }),
|
||||
).toBe(`V1: ${status === "OPEN" ? "开启" : "关闭"} / 不适用`);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps the setting for an active valve", () => {
|
||||
expect(
|
||||
formatValveControlSummary("V1", { status: "ACTIVE", setting: 2.5 }),
|
||||
).toBe("V1: 激活 / 2.5");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSimulationElementType", () => {
|
||||
it("treats a point-rendered valve as a hydraulic link", () => {
|
||||
expect(getSimulationElementType(createValveFeature())).toBe("link");
|
||||
});
|
||||
|
||||
it("keeps point-rendered pumps on the same hydraulic-link path", () => {
|
||||
const pump = {
|
||||
getId: () => "geo_pumps.P1",
|
||||
getProperties: () => ({
|
||||
id: "P1",
|
||||
geometry: { getType: () => "Point" },
|
||||
}),
|
||||
} as unknown as Feature;
|
||||
|
||||
expect(getSimulationElementType(pump)).toBe("link");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildFeatureProperties simulation values by hydraulic type", () => {
|
||||
it("shows link simulation results for a point-rendered pump", () => {
|
||||
const pump = createFeature("geo_pumps", "P1", {
|
||||
node1: "J1",
|
||||
node2: "J2",
|
||||
geometry: { getType: () => "Point" },
|
||||
});
|
||||
|
||||
const result = buildFeatureProperties(pump, {
|
||||
flow: 10,
|
||||
status: 1,
|
||||
velocity: 0.75,
|
||||
});
|
||||
|
||||
expect(result.properties).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ label: "流量", value: "36.000" }),
|
||||
expect.objectContaining({ label: "状态", value: "1.000" }),
|
||||
expect.objectContaining({ label: "流速", value: "0.750" }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["geo_tanks", "T1", "水池"],
|
||||
["geo_reservoirs", "R1", "水库"],
|
||||
])("shows node simulation results for %s", (layer, id, type) => {
|
||||
const feature = createFeature(layer, id, {
|
||||
geometry: { getType: () => "Point" },
|
||||
});
|
||||
|
||||
const result = buildFeatureProperties(feature, {
|
||||
actual_demand: 5,
|
||||
total_head: 42,
|
||||
pressure: 18,
|
||||
});
|
||||
|
||||
expect(result.type).toBe(type);
|
||||
expect(result.properties).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ label: "实际需水量", value: "18.000" }),
|
||||
expect.objectContaining({ label: "水头", value: "42.000" }),
|
||||
expect.objectContaining({ label: "压力", value: "18.000" }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -58,6 +58,7 @@ export type ValveStatusPropertyOptions = {
|
||||
value: string | null;
|
||||
loading?: boolean;
|
||||
saving?: boolean;
|
||||
disabled?: boolean;
|
||||
onSave: (value: string) => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -66,10 +67,73 @@ export type ValveSettingPropertyOptions = {
|
||||
vType: string | null;
|
||||
loading?: boolean;
|
||||
saving?: boolean;
|
||||
disabled?: boolean;
|
||||
status?: string | null;
|
||||
onSave: (value: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export const getValvePropertySource = (
|
||||
queryType: string | undefined,
|
||||
schemeName: string | undefined,
|
||||
): "business" | "simulation" =>
|
||||
queryType === "realtime" || (queryType === "scheme" && Boolean(schemeName))
|
||||
? "simulation"
|
||||
: "business";
|
||||
|
||||
export const getSimulationElementType = (
|
||||
feature: Feature,
|
||||
): "link" | "node" => {
|
||||
const layerId = feature.getId()?.toString().split(".")[0] ?? "";
|
||||
if (
|
||||
layerId.includes("pipe") ||
|
||||
layerId.includes("pump") ||
|
||||
layerId.includes("valve")
|
||||
) {
|
||||
return "link";
|
||||
}
|
||||
return feature.getProperties().geometry?.getType?.() === "LineString"
|
||||
? "link"
|
||||
: "node";
|
||||
};
|
||||
|
||||
const formatValveStatus = (value: unknown): string => {
|
||||
if (typeof value === "string") {
|
||||
const normalized = value.toUpperCase();
|
||||
const option = VALVE_STATUS_OPTIONS.find(
|
||||
(candidate) => candidate.value === normalized,
|
||||
);
|
||||
if (option) return option.label;
|
||||
}
|
||||
|
||||
const numericStatus = Number(value);
|
||||
if (Number.isFinite(numericStatus)) {
|
||||
if (numericStatus === 0) return "关闭";
|
||||
if (numericStatus === 1) return "开启";
|
||||
if (numericStatus === 2) return "激活";
|
||||
}
|
||||
|
||||
return value === null || value === undefined || value === ""
|
||||
? "未返回"
|
||||
: String(value);
|
||||
};
|
||||
|
||||
const displayValue = (value: unknown): string | number =>
|
||||
typeof value === "string" || typeof value === "number"
|
||||
? value
|
||||
: "未设置";
|
||||
|
||||
export const formatValveControlSummary = (
|
||||
id: string,
|
||||
control: { status?: string; setting?: unknown },
|
||||
): string => {
|
||||
const normalizedStatus = control.status?.toUpperCase();
|
||||
const setting =
|
||||
normalizedStatus === "OPEN" || normalizedStatus === "CLOSED"
|
||||
? "不适用"
|
||||
: displayValue(control.setting);
|
||||
return `${id}: ${formatValveStatus(control.status)} / ${setting}`;
|
||||
};
|
||||
|
||||
const getFeatureHistoryType = (feature: Feature): string | null => {
|
||||
const layerId = feature.getId()?.toString().split(".")[0] || "";
|
||||
if (layerId.includes("pipe")) return "pipe";
|
||||
@@ -127,6 +191,51 @@ export const buildFeatureProperties = (
|
||||
{ key: "quality", label: "水质", unit: "mg/L" },
|
||||
];
|
||||
|
||||
const appendLinkComputedProperties = (
|
||||
result: ToolbarPropertyPanelData,
|
||||
excludedKeys: string[] = [],
|
||||
) => {
|
||||
pipeComputedFields.forEach(({ key, label, unit }) => {
|
||||
if (excludedKeys.includes(key)) return;
|
||||
|
||||
let value = computedProperties[key];
|
||||
if (key === "flow" && value !== undefined) {
|
||||
value = toM3h(value, "lps");
|
||||
}
|
||||
if (
|
||||
key === "unit_headloss" &&
|
||||
value === undefined &&
|
||||
computedProperties.headloss !== undefined &&
|
||||
properties.length
|
||||
) {
|
||||
value = (computedProperties.headloss / properties.length) * 1000;
|
||||
}
|
||||
if (value !== undefined) {
|
||||
result.properties?.push({
|
||||
label,
|
||||
value: typeof value === "number" ? value.toFixed(3) : value,
|
||||
unit,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const appendNodeComputedProperties = (result: ToolbarPropertyPanelData) => {
|
||||
nodeComputedFields.forEach(({ key, label, unit }) => {
|
||||
if (computedProperties[key] === undefined) return;
|
||||
|
||||
let value = computedProperties[key];
|
||||
if (key === "actual_demand") {
|
||||
value = toM3h(value, "lps");
|
||||
}
|
||||
result.properties?.push({
|
||||
label,
|
||||
value: typeof value === "number" ? value.toFixed(3) : value,
|
||||
unit,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (layer === "geo_pipes_mat" || layer === "geo_pipes") {
|
||||
const result: ToolbarPropertyPanelData = {
|
||||
id: properties.id,
|
||||
@@ -146,30 +255,7 @@ export const buildFeatureProperties = (
|
||||
],
|
||||
};
|
||||
|
||||
pipeComputedFields.forEach(({ key, label, unit }) => {
|
||||
let value = computedProperties[key];
|
||||
|
||||
if (key === "flow" && value !== undefined) {
|
||||
value = toM3h(value, "lps");
|
||||
}
|
||||
|
||||
if (
|
||||
key === "unit_headloss" &&
|
||||
value === undefined &&
|
||||
computedProperties.headloss !== undefined &&
|
||||
properties.length
|
||||
) {
|
||||
value = (computedProperties.headloss / properties.length) * 1000;
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
result.properties?.push({
|
||||
label,
|
||||
value: typeof value === "number" ? value.toFixed(3) : value,
|
||||
unit,
|
||||
});
|
||||
}
|
||||
});
|
||||
appendLinkComputedProperties(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -210,25 +296,13 @@ export const buildFeatureProperties = (
|
||||
],
|
||||
};
|
||||
|
||||
nodeComputedFields.forEach(({ key, label, unit }) => {
|
||||
if (computedProperties[key] !== undefined) {
|
||||
let value = computedProperties[key];
|
||||
if (key === "actual_demand") {
|
||||
value = toM3h(value, "lps");
|
||||
}
|
||||
result.properties?.push({
|
||||
label,
|
||||
value: value?.toFixed?.(3) || value,
|
||||
unit,
|
||||
});
|
||||
}
|
||||
});
|
||||
appendNodeComputedProperties(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layer === "geo_tanks_mat" || layer === "geo_tanks") {
|
||||
return {
|
||||
const result: ToolbarPropertyPanelData = {
|
||||
id: properties.id,
|
||||
type: "水池",
|
||||
properties: [
|
||||
@@ -268,24 +342,28 @@ export const buildFeatureProperties = (
|
||||
},
|
||||
],
|
||||
};
|
||||
appendNodeComputedProperties(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layer === "geo_reservoirs_mat" || layer === "geo_reservoirs") {
|
||||
return {
|
||||
const result: ToolbarPropertyPanelData = {
|
||||
id: properties.id,
|
||||
type: "水库",
|
||||
properties: [
|
||||
{
|
||||
label: "水头",
|
||||
label: "基础水头",
|
||||
value: properties.head?.toFixed?.(1),
|
||||
unit: "m",
|
||||
},
|
||||
],
|
||||
};
|
||||
appendNodeComputedProperties(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layer === "geo_pumps_mat" || layer === "geo_pumps") {
|
||||
return {
|
||||
const result: ToolbarPropertyPanelData = {
|
||||
id: properties.id,
|
||||
type: "水泵",
|
||||
properties: [
|
||||
@@ -312,16 +390,22 @@ export const buildFeatureProperties = (
|
||||
},
|
||||
],
|
||||
};
|
||||
appendLinkComputedProperties(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layer === "geo_valves_mat" || layer === "geo_valves") {
|
||||
const valveType = valveSetting?.vType ?? properties.v_type;
|
||||
const hasSimulationValues =
|
||||
Object.hasOwn(computedProperties, "status") ||
|
||||
Object.hasOwn(computedProperties, "setting");
|
||||
const isValveSettingDisabled =
|
||||
valveSetting?.loading ||
|
||||
valveSetting?.disabled ||
|
||||
valveSetting?.status === "OPEN" ||
|
||||
valveSetting?.status === "CLOSED";
|
||||
|
||||
return {
|
||||
const result: ToolbarPropertyPanelData = {
|
||||
id: properties.id,
|
||||
type: "阀门",
|
||||
properties: [
|
||||
@@ -340,7 +424,43 @@ export const buildFeatureProperties = (
|
||||
label: "局部损失",
|
||||
value: properties.minor_loss?.toFixed?.(2),
|
||||
},
|
||||
...(valveStatus
|
||||
...(hasSimulationValues
|
||||
? [
|
||||
{
|
||||
label: "模拟开关状态",
|
||||
value: formatValveStatus(computedProperties.status),
|
||||
},
|
||||
{
|
||||
label: "模拟设置值",
|
||||
value: displayValue(computedProperties.setting),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(Object.hasOwn(computedProperties, "scheme_status")
|
||||
? [
|
||||
{
|
||||
label: "方案设置状态",
|
||||
value: formatValveStatus(computedProperties.scheme_status),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(Object.hasOwn(computedProperties, "scheme_setting")
|
||||
? [
|
||||
{
|
||||
label: "方案设置值",
|
||||
value: displayValue(computedProperties.scheme_setting),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(Object.hasOwn(computedProperties, "scheme_opening")
|
||||
? [
|
||||
{
|
||||
label: "方案开度",
|
||||
value: displayValue(computedProperties.scheme_opening),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!hasSimulationValues && valveStatus
|
||||
? [
|
||||
{
|
||||
type: "select" as const,
|
||||
@@ -348,13 +468,13 @@ export const buildFeatureProperties = (
|
||||
value: valveStatus.value ?? "",
|
||||
options: VALVE_STATUS_OPTIONS,
|
||||
placeholder: valveStatus.loading ? "加载中" : "未设置",
|
||||
disabled: valveStatus.loading,
|
||||
disabled: valveStatus.loading || valveStatus.disabled,
|
||||
saving: valveStatus.saving,
|
||||
onSave: valveStatus.onSave,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(valveSetting
|
||||
...(!hasSimulationValues && valveSetting
|
||||
? [
|
||||
{
|
||||
type: "text" as const,
|
||||
@@ -373,6 +493,8 @@ export const buildFeatureProperties = (
|
||||
: []),
|
||||
],
|
||||
};
|
||||
appendLinkComputedProperties(result, ["setting", "status"]);
|
||||
return result;
|
||||
}
|
||||
|
||||
const getTransmissionFrequency = (transmissionFrequency: string) => {
|
||||
|
||||
Reference in New Issue
Block a user