diff --git a/src/components/olmap/FlushingAnalysis/SchemeQuery.tsx b/src/components/olmap/FlushingAnalysis/SchemeQuery.tsx index d0e016c..1defbd6 100644 --- a/src/components/olmap/FlushingAnalysis/SchemeQuery.tsx +++ b/src/components/olmap/FlushingAnalysis/SchemeQuery.tsx @@ -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 = ({ {/* 阀门列表 */} - 参与阀门及开度: + 参与阀门及设置: - {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]) => ( + + handleLocateValves([id])} + className="text-xs h-6 bg-gray-50 cursor-pointer hover:bg-orange-50 hover:border-orange-200" + /> + + )) + ) : scheme.schemeDetail?.valve_opening && Object.entries(scheme.schemeDetail.valve_opening).length > 0 ? ( Object.entries(scheme.schemeDetail.valve_opening).map(([id, k]) => ( ; + valve_opening?: Record | null; + valve_control?: Record< + string, + { + status?: "OPEN" | "CLOSED" | "ACTIVE"; + setting?: string | number; + k?: number; + } + > | null; drainage_node_ID: string; flushing_flow: number; duration: number; diff --git a/src/components/olmap/core/Controls/Toolbar.tsx b/src/components/olmap/core/Controls/Toolbar.tsx index b6f9a0f..0f803a3 100644 --- a/src/components/olmap/core/Controls/Toolbar.tsx +++ b/src/components/olmap/core/Controls/Toolbar.tsx @@ -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 | null; + valve_opening?: Record | null; +}; + const isValveLayer = (layerId: string | undefined) => layerId === "geo_valves_mat" || layerId === "geo_valves"; @@ -369,6 +382,8 @@ const Toolbar: React.FC = ({ const [isValvePropertiesLoading, setIsValvePropertiesLoading] = useState(false); const [isValveSettingSaving, setIsValveSettingSaving] = useState(false); + const [activeSchemeDetail, setActiveSchemeDetail] = + useState(null); const selectedFeature = highlightFeatures[0]; const selectedFeatureLayer = selectedFeature @@ -384,9 +399,57 @@ const Toolbar: React.FC = ({ 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 = ({ 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 = ({ return () => { cancelled = true; }; - }, [networkName, open, selectedValveId]); + }, [isSimulationDataActive, networkName, open, selectedValveId]); const handleValveStatusSave = useCallback( async (nextStatus: string) => { @@ -611,7 +674,14 @@ const Toolbar: React.FC = ({ // 添加 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 = ({ 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 = ({ 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 = ({ } } catch (error) { console.error("Error querying computed properties:", error); - setComputedProperties({}); + 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 = ({ ), [ selectedFeature, - computedProperties, + displayedComputedProperties, + isSimulationDataActive, canEditNetwork, selectedValveId, valveStatus, diff --git a/src/components/olmap/core/Controls/toolbarFeatureHelpers.test.ts b/src/components/olmap/core/Controls/toolbarFeatureHelpers.test.ts new file mode 100644 index 0000000..802f5e7 --- /dev/null +++ b/src/components/olmap/core/Controls/toolbarFeatureHelpers.test.ts @@ -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, +) => + ({ + 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" }), + ]), + ); + }); +}); diff --git a/src/components/olmap/core/Controls/toolbarFeatureHelpers.ts b/src/components/olmap/core/Controls/toolbarFeatureHelpers.ts index 6c88018..7e30988 100644 --- a/src/components/olmap/core/Controls/toolbarFeatureHelpers.ts +++ b/src/components/olmap/core/Controls/toolbarFeatureHelpers.ts @@ -58,6 +58,7 @@ export type ValveStatusPropertyOptions = { value: string | null; loading?: boolean; saving?: boolean; + disabled?: boolean; onSave: (value: string) => Promise; }; @@ -66,10 +67,73 @@ export type ValveSettingPropertyOptions = { vType: string | null; loading?: boolean; saving?: boolean; + disabled?: boolean; status?: string | null; onSave: (value: string) => Promise; }; +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) => {