Compare commits

...
12 Commits
Author SHA1 Message Date
jiang 6623f4f7fb merge: sync Server storage cleanup contract
Merge PR #2 after API contract, lint, test, and production build verification.
2026-08-18 18:35:23 +08:00
jiang d2ce1687f2 chore(api): sync storage cleanup contract 2026-08-18 18:29:09 +08:00
jiang dca2817320 merge: integrate agent-mvp into master
Merge PR #1 after frontend, cross-repository contract, and container gates passed.
2026-08-18 17:56:45 +08:00
jiang 41bf7d71e3 chore(api): sync backend contract mirror 2026-08-18 17:56:11 +08:00
jiang 5bea86dbf6 fix(deps): update XML parser and nanoid 2026-08-18 16:50:46 +08:00
jiang 78a470e89e merge(frontend): integrate master into agent-mvp 2026-08-18 16:41:52 +08:00
jiang 6d505ca461 fix(flushing): 收紧空阀门区域高度
Generic Container CI/CD / test-build-publish (push) Successful in 1m1s
Frontend CI/CD v2 / build-test-publish-and-deploy (push) Successful in 1m1s
2026-08-17 19:44:11 +08:00
jiang f34d81c933 fix(flushing): 对齐选择项空状态
Generic Container CI/CD / test-build-publish (push) Successful in 1m1s
Frontend CI/CD v2 / build-test-publish-and-deploy (push) Successful in 1m1s
2026-08-17 19:36:33 +08:00
jiang 5053ddcd1f fix(map): 修正属性查询数据源与模拟属性
Generic Container CI/CD / test-build-publish (push) Successful in 1m4s
Frontend CI/CD v2 / build-test-publish-and-deploy (push) Successful in 1m4s
2026-08-17 19:31:54 +08:00
jiang 4a978de905 ci(frontend): inject public build configuration 2026-08-11 10:44:56 +08:00
TJWater CI f5bcf28f7e fix(map): preserve hook order while data loads
Generic Container CI/CD / test-build-publish (push) Failing after 1m50s
Frontend CI/CD v2 / build-test-publish-and-deploy (push) Failing after 1m50s
2026-08-11 10:32:18 +08:00
TJWater CI ab45c8da8e ci: add frontend v2 deployment workflow
Generic Container CI/CD / test-build-publish (push) Failing after 1m13s
Frontend CI/CD v2 / build-test-publish-and-deploy (push) Failing after 1m14s
2026-08-11 09:51:13 +08:00
15 changed files with 627 additions and 888 deletions
+4
View File
@@ -4,6 +4,10 @@
"agent": { "agent": {
"file": "agent-v1.openapi.json", "file": "agent-v1.openapi.json",
"sha256": "94bd8914597c56b6429160e8c556993ac0617ad079de2980a4b6cb9fdf89c039" "sha256": "94bd8914597c56b6429160e8c556993ac0617ad079de2980a4b6cb9fdf89c039"
},
"server": {
"file": "server-v1.openapi.json",
"sha256": "ac9b6fac185dfd999f1791cba51eb482df17a427b361963250aafa5fb1a276b4"
} }
} }
} }
+5 -410
View File
@@ -1859,7 +1859,7 @@
"title": "PressureRegulationRest", "title": "PressureRegulationRest",
"type": "object" "type": "object"
}, },
"PressureSensorPlacement": { "PressureSensorPlacementRest": {
"properties": { "properties": {
"min_diameter": { "min_diameter": {
"default": 0, "default": 0,
@@ -1867,11 +1867,6 @@
"title": "Min Diameter", "title": "Min Diameter",
"type": "integer" "type": "integer"
}, },
"name": {
"description": "管网名称(或数据库名称)",
"title": "Name",
"type": "string"
},
"scheme_name": { "scheme_name": {
"description": "方案名称", "description": "方案名称",
"title": "Scheme Name", "title": "Scheme Name",
@@ -1881,20 +1876,13 @@
"description": "传感器数量", "description": "传感器数量",
"title": "Sensor Number", "title": "Sensor Number",
"type": "integer" "type": "integer"
},
"username": {
"description": "用户名",
"title": "Username",
"type": "string"
} }
}, },
"required": [ "required": [
"name",
"scheme_name", "scheme_name",
"sensor_number", "sensor_number"
"username"
], ],
"title": "PressureSensorPlacement", "title": "PressureSensorPlacementRest",
"type": "object" "type": "object"
}, },
"ProblemDetails": { "ProblemDetails": {
@@ -5239,97 +5227,6 @@
] ]
} }
}, },
"/api/v1/all-redis": {
"delete": {
"description": "清空整个Redis数据库的所有缓存",
"operationId": "delete_all_redis",
"parameters": [
{
"in": "header",
"name": "X-Project-Id",
"required": true,
"schema": {
"title": "X-Project-Id",
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Successful Response"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Authentication required"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Insufficient permission"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource not found"
},
"409": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource conflict"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Validation error"
},
"503": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Dependency unavailable"
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
],
"summary": "清除所有缓存",
"tags": [
"Cache"
]
}
},
"/api/v1/all-scada-properties": { "/api/v1/all-scada-properties": {
"get": { "get": {
"description": "获取指定水网中所有SCADA点的属性信息", "description": "获取指定水网中所有SCADA点的属性信息",
@@ -24894,7 +24791,7 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/PressureSensorPlacement", "$ref": "#/components/schemas/PressureSensorPlacementRest",
"description": "传感器放置分析参数" "description": "传感器放置分析参数"
} }
} }
@@ -25134,7 +25031,7 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/PressureSensorPlacement", "$ref": "#/components/schemas/PressureSensorPlacementRest",
"description": "传感器放置分析参数" "description": "传感器放置分析参数"
} }
} }
@@ -29464,308 +29361,6 @@
] ]
} }
}, },
"/api/v1/redis": {
"get": {
"description": "获取Redis中所有的缓存键",
"operationId": "get_redis",
"parameters": [
{
"in": "header",
"name": "X-Project-Id",
"required": true,
"schema": {
"title": "X-Project-Id",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JsonValue"
}
}
},
"description": "Successful Response"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Authentication required"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Insufficient permission"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource not found"
},
"409": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource conflict"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Validation error"
},
"503": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Dependency unavailable"
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
],
"summary": "查询缓存键列表",
"tags": [
"Cache"
]
}
},
"/api/v1/redis-keys": {
"delete": {
"description": "根据模式清除匹配的Redis缓存键",
"operationId": "delete_redis_keys",
"parameters": [
{
"description": "缓存键模式(支持通配符)",
"in": "query",
"name": "keys",
"required": true,
"schema": {
"description": "缓存键模式(支持通配符)",
"title": "Keys",
"type": "string"
}
},
{
"in": "header",
"name": "X-Project-Id",
"required": true,
"schema": {
"title": "X-Project-Id",
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Successful Response"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Authentication required"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Insufficient permission"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource not found"
},
"409": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource conflict"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Validation error"
},
"503": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Dependency unavailable"
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
],
"summary": "清除匹配的缓存键",
"tags": [
"Cache"
]
}
},
"/api/v1/redis-keys/detail": {
"delete": {
"description": "根据键名清除单个Redis缓存",
"operationId": "delete_redis_keys_detail",
"parameters": [
{
"description": "缓存键名",
"in": "query",
"name": "key",
"required": true,
"schema": {
"description": "缓存键名",
"title": "Key",
"type": "string"
}
},
{
"in": "header",
"name": "X-Project-Id",
"required": true,
"schema": {
"title": "X-Project-Id",
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Successful Response"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Authentication required"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Insufficient permission"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource not found"
},
"409": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource conflict"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Validation error"
},
"503": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Dependency unavailable"
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
],
"summary": "清除单个缓存键",
"tags": [
"Cache"
]
}
},
"/api/v1/redos": { "/api/v1/redos": {
"post": { "post": {
"description": "重做网络上被撤销的操作", "description": "重做网络上被撤销的操作",
+22 -9
View File
@@ -5611,6 +5611,18 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@nodable/entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/nodable"
}
],
"license": "MIT"
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -13640,9 +13652,9 @@
} }
}, },
"node_modules/fast-xml-parser": { "node_modules/fast-xml-parser": {
"version": "5.5.9", "version": "5.7.0",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.0.tgz",
"integrity": "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==", "integrity": "sha512-MTcrUoRQ1GSQ9iG3QJzBGquYYYeA7piZaJoIWbPFGbRn6Jj6z7xgoAyi4DrZX4y2ZIQQBF59gc/zmvvejjgoFQ==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -13651,9 +13663,10 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-xml-builder": "^1.1.4", "@nodable/entities": "^2.1.0",
"path-expression-matcher": "^1.2.0", "fast-xml-builder": "^1.1.5",
"strnum": "^2.2.2" "path-expression-matcher": "^1.5.0",
"strnum": "^2.2.3"
}, },
"bin": { "bin": {
"fxparser": "src/cli/cli.js" "fxparser": "src/cli/cli.js"
@@ -18189,9 +18202,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.16", "version": "3.3.18",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
+1 -1
View File
@@ -59,7 +59,7 @@
"zustand": "^5.0.11" "zustand": "^5.0.11"
}, },
"overrides": { "overrides": {
"fast-xml-parser": "5.5.9", "fast-xml-parser": "5.7.0",
"postcss": "8.5.25", "postcss": "8.5.25",
"sharp": "0.35.3" "sharp": "0.35.3"
}, },
@@ -53,6 +53,7 @@ import {
VALVE_STATUS_OPTIONS, VALVE_STATUS_OPTIONS,
validateValveSetting, validateValveSetting,
} from "@components/olmap/core/Controls/valveControl"; } from "@components/olmap/core/Controls/valveControl";
import { getValveListClassName } from "./analysisParametersLayout";
export interface ValveItem { export interface ValveItem {
id: string; id: string;
@@ -551,6 +552,7 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
{!drainageNode && ( {!drainageNode && (
<PanelEmptyState <PanelEmptyState
variant="compact" variant="compact"
horizontalAlign="start"
icon={<WaterDropOutlined />} icon={<WaterDropOutlined />}
title="尚未选择排水节点" title="尚未选择排水节点"
description="点击“选择节点”,然后在地图上指定排水点。" description="点击“选择节点”,然后在地图上指定排水点。"
@@ -581,7 +583,10 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
💡 点击地图上的阀门进行添加 💡 点击地图上的阀门进行添加
</Box> </Box>
)} )}
<Stack spacing={1} className="max-h-50 h-48 overflow-auto"> <Stack
spacing={1}
className={getValveListClassName(valves.length)}
>
{valves.map((valve) => { {valves.map((valve) => {
const settingValidation = const settingValidation =
valve.status === "ACTIVE" valve.status === "ACTIVE"
@@ -651,6 +656,7 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
{valves.length === 0 && ( {valves.length === 0 && (
<PanelEmptyState <PanelEmptyState
variant="compact" variant="compact"
horizontalAlign="start"
icon={<AdjustOutlined />} icon={<AdjustOutlined />}
title="未选择参与阀门" title="未选择参与阀门"
description="无需调整阀门时可跳过,也可点击“选择阀门”添加。" description="无需调整阀门时可跳过,也可点击“选择阀门”添加。"
@@ -44,6 +44,7 @@ import { SchemeRecord, SchemaItem } from "./types";
import { FLOW_DISPLAY_UNIT } from "@utils/units"; import { FLOW_DISPLAY_UNIT } from "@utils/units";
import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName"; import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName";
import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState"; import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState";
import { formatValveControlSummary } from "@components/olmap/core/Controls/toolbarFeatureHelpers";
interface SchemeQueryProps { interface SchemeQueryProps {
schemes?: SchemeRecord[]; schemes?: SchemeRecord[];
@@ -589,10 +590,22 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
{/* 阀门列表 */} {/* 阀门列表 */}
<Box className="col-span-2 pl-2"> <Box className="col-span-2 pl-2">
<Typography variant="caption" className="text-gray-600 block mb-1"> <Typography variant="caption" className="text-gray-600 block mb-1">
参与阀门及开度: 参与阀门及设置:
</Typography> </Typography>
<Box className="flex flex-wrap gap-2"> <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]) => ( Object.entries(scheme.schemeDetail.valve_opening).map(([id, k]) => (
<Tooltip key={id} title="点击定位阀门"> <Tooltip key={id} title="点击定位阀门">
<Chip <Chip
@@ -0,0 +1,13 @@
import { getValveListClassName } from "./analysisParametersLayout";
describe("getValveListClassName", () => {
it("uses compact height when no valves are selected", () => {
expect(getValveListClassName(0)).toBe("min-h-16 overflow-auto");
});
it("restores the fixed scroll region when valves are selected", () => {
expect(getValveListClassName(1)).toBe(
"max-h-50 h-48 overflow-auto",
);
});
});
@@ -0,0 +1,4 @@
export const getValveListClassName = (valveCount: number): string =>
valveCount > 0
? "max-h-50 h-48 overflow-auto"
: "min-h-16 overflow-auto";
@@ -1,5 +1,13 @@
export interface SchemeDetail { 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; drainage_node_ID: string;
flushing_flow: number; flushing_flow: number;
duration: number; duration: number;
@@ -36,11 +36,15 @@ describe("PanelEmptyState", () => {
render( render(
<PanelEmptyState <PanelEmptyState
variant="compact" variant="compact"
horizontalAlign="start"
title="尚未选择阀门" title="尚未选择阀门"
description="点击“选择阀门”,然后在地图上添加。" description="点击“选择阀门”,然后在地图上添加。"
/>, />,
); );
expect(screen.getByText("尚未选择阀门")).toBeInTheDocument(); expect(screen.getByText("尚未选择阀门")).toBeInTheDocument();
expect(screen.getByRole("status")).toHaveStyle({
justifyContent: "flex-start",
});
}); });
}); });
@@ -10,6 +10,7 @@ export interface PanelEmptyStateProps {
title: string; title: string;
description?: string; description?: string;
variant?: "panel" | "compact"; variant?: "panel" | "compact";
horizontalAlign?: "center" | "start";
} }
const PanelEmptyState = ({ const PanelEmptyState = ({
@@ -17,6 +18,7 @@ const PanelEmptyState = ({
title, title,
description, description,
variant = "panel", variant = "panel",
horizontalAlign = "center",
}: PanelEmptyStateProps) => { }: PanelEmptyStateProps) => {
const compact = variant === "compact"; const compact = variant === "compact";
@@ -32,7 +34,8 @@ const PanelEmptyState = ({
display: "flex", display: "flex",
flexDirection: compact ? "row" : { xs: "column", sm: "row" }, flexDirection: compact ? "row" : { xs: "column", sm: "row" },
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent:
horizontalAlign === "start" ? "flex-start" : "center",
gap: compact ? 1.5 : { xs: 1.75, sm: 2.5 }, gap: compact ? 1.5 : { xs: 1.75, sm: 2.5 },
px: compact ? 1.5 : { xs: 2, sm: 4 }, px: compact ? 1.5 : { xs: 2, sm: 4 },
py: compact ? 1.25 : { xs: 4, sm: 5 }, py: compact ? 1.25 : { xs: 4, sm: 5 },
+147 -20
View File
@@ -22,6 +22,8 @@ import { useNotification } from "@refinedev/core";
import ToolbarHistoryPanel from "./ToolbarHistoryPanel"; import ToolbarHistoryPanel from "./ToolbarHistoryPanel";
import { import {
buildFeatureProperties, buildFeatureProperties,
getSimulationElementType,
getValvePropertySource,
} from "./toolbarFeatureHelpers"; } from "./toolbarFeatureHelpers";
import { useToolbarChatActions } from "./useToolbarChatActions"; import { useToolbarChatActions } from "./useToolbarChatActions";
import { useStyleEditor } from "./useStyleEditor"; import { useStyleEditor } from "./useStyleEditor";
@@ -52,6 +54,17 @@ type ValveProperties = {
setting: string | null; 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) => const isValveLayer = (layerId: string | undefined) =>
layerId === "geo_valves_mat" || layerId === "geo_valves"; layerId === "geo_valves_mat" || layerId === "geo_valves";
@@ -369,6 +382,8 @@ const Toolbar: React.FC<ToolbarProps> = ({
const [isValvePropertiesLoading, setIsValvePropertiesLoading] = const [isValvePropertiesLoading, setIsValvePropertiesLoading] =
useState(false); useState(false);
const [isValveSettingSaving, setIsValveSettingSaving] = useState(false); const [isValveSettingSaving, setIsValveSettingSaving] = useState(false);
const [activeSchemeDetail, setActiveSchemeDetail] =
useState<ActiveSchemeDetail | null>(null);
const selectedFeature = highlightFeatures[0]; const selectedFeature = highlightFeatures[0];
const selectedFeatureLayer = selectedFeature const selectedFeatureLayer = selectedFeature
@@ -384,9 +399,57 @@ const Toolbar: React.FC<ToolbarProps> = ({
showPropertyPanel && isValveLayer(selectedFeatureLayer) && selectedFeatureId showPropertyPanel && isValveLayer(selectedFeatureLayer) && selectedFeatureId
? String(selectedFeatureId) ? String(selectedFeatureId)
: null; : null;
const isSimulationDataActive =
getValvePropertySource(queryType, schemeName) === "simulation";
useEffect(() => { 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); setValveStatus(null);
setIsValveStatusLoading(false); setIsValveStatusLoading(false);
return; return;
@@ -435,10 +498,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [networkName, open, selectedValveId]); }, [isSimulationDataActive, networkName, open, selectedValveId]);
useEffect(() => { useEffect(() => {
if (!selectedValveId) { if (!selectedValveId || isSimulationDataActive) {
setValveProperties({ vType: null, setting: null }); setValveProperties({ vType: null, setting: null });
setIsValvePropertiesLoading(false); setIsValvePropertiesLoading(false);
return; return;
@@ -490,7 +553,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [networkName, open, selectedValveId]); }, [isSimulationDataActive, networkName, open, selectedValveId]);
const handleValveStatusSave = useCallback( const handleValveStatusSave = useCallback(
async (nextStatus: string) => { async (nextStatus: string) => {
@@ -611,7 +674,14 @@ const Toolbar: React.FC<ToolbarProps> = ({
// 添加 useEffect 来查询计算属性 // 添加 useEffect 来查询计算属性
useEffect(() => { useEffect(() => {
if (highlightFeatures.length === 0 || !selectedDate || !showPropertyPanel) { const canQuerySimulation =
getValvePropertySource(queryType, schemeName) === "simulation";
if (
highlightFeatures.length === 0 ||
!selectedDate ||
!showPropertyPanel ||
!canQuerySimulation
) {
setComputedProperties({}); setComputedProperties({});
return; return;
} }
@@ -623,11 +693,12 @@ const Toolbar: React.FC<ToolbarProps> = ({
return; return;
} }
let cancelled = false;
setComputedProperties({});
const queryComputedProperties = async () => { const queryComputedProperties = async () => {
try { try {
const properties = highlightFeature?.getProperties?.() || {}; const type = getSimulationElementType(highlightFeature);
const type =
properties.geometry?.getType?.() === "LineString" ? "link" : "node";
// selectedDate 格式化为 YYYY-MM-DD // selectedDate 格式化为 YYYY-MM-DD
let dateObj: Date; let dateObj: Date;
if (selectedDate instanceof Date) { if (selectedDate instanceof Date) {
@@ -639,22 +710,33 @@ const Toolbar: React.FC<ToolbarProps> = ({
dateObj.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0); dateObj.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0);
// 转为 UTC ISO 字符串 // 转为 UTC ISO 字符串
const querytime = dateObj.toISOString(); // 例如 "2025-09-16T16:30:00.000Z" const querytime = dateObj.toISOString(); // 例如 "2025-09-16T16:30:00.000Z"
let response; let response: Response;
if (queryType === "scheme") { if (queryType === "scheme") {
const params = new URLSearchParams({
scheme_type: schemeType ?? "",
scheme_name: schemeName ?? "",
id: String(id),
type,
query_time: querytime,
});
response = await apiFetch( 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?${params.toString()}`,
`${config.BACKEND_URL}/api/v1/timeseries/schemes/simulation-results?scheme_type=${schemeType}&scheme_name=${schemeName}&id=${id}&type=${type}&query_time=${querytime}`,
); );
} else { } else {
const params = new URLSearchParams({
id: String(id),
type,
query_time: querytime,
});
response = await apiFetch( response = await apiFetch(
// `${config.BACKEND_URL}/querysimulationrecordsbyidtime/?id=${id}&querytime=${querytime}&type=${type}` `${config.BACKEND_URL}/api/v1/timeseries/realtime/simulation-results?${params.toString()}`,
`${config.BACKEND_URL}/api/v1/timeseries/realtime/simulation-results?id=${id}&type=${type}&query_time=${querytime}`,
); );
} }
if (!response.ok) { if (!response.ok) {
throw new Error("API request failed"); throw new Error("API request failed");
} }
const data = await response.json(); const data = await response.json();
if (cancelled) return;
if (!data.result || data.result.length === 0) { if (!data.result || data.result.length === 0) {
setComputedProperties({}); setComputedProperties({});
} else { } else {
@@ -663,32 +745,76 @@ const Toolbar: React.FC<ToolbarProps> = ({
} }
} catch (error) { } catch (error) {
console.error("Error querying computed properties:", error); console.error("Error querying computed properties:", error);
setComputedProperties({}); if (!cancelled) {
setComputedProperties({});
open?.({
type: "error",
message: "读取模拟属性失败,请检查方案、时间和数据源。",
});
}
} }
}; };
// 仅当 currentTime 有效时查询 // 仅当 currentTime 有效时查询
if (currentTime !== -1 && queryType) queryComputedProperties(); if (currentTime !== -1 && queryType) void queryComputedProperties();
}, [highlightFeatures, currentTime, selectedDate, queryType, schemeName, schemeType, showPropertyPanel]); 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( const propertyPanelData = useMemo(
() => () =>
buildFeatureProperties( buildFeatureProperties(
selectedFeature, selectedFeature,
computedProperties, displayedComputedProperties,
canEditNetwork && selectedValveId !isSimulationDataActive && selectedValveId
? { ? {
value: valveStatus, value: valveStatus,
loading: isValveStatusLoading, loading: isValveStatusLoading,
saving: isValveStatusSaving, saving: isValveStatusSaving,
disabled: !canEditNetwork,
onSave: handleValveStatusSave, onSave: handleValveStatusSave,
} }
: undefined, : undefined,
canEditNetwork && selectedValveId !isSimulationDataActive && selectedValveId
? { ? {
value: valveProperties.setting, value: valveProperties.setting,
vType: selectedValveType, vType: selectedValveType,
loading: isValvePropertiesLoading, loading: isValvePropertiesLoading,
saving: isValveSettingSaving, saving: isValveSettingSaving,
disabled: !canEditNetwork,
status: valveStatus, status: valveStatus,
onSave: handleValveSettingSave, onSave: handleValveSettingSave,
} }
@@ -696,7 +822,8 @@ const Toolbar: React.FC<ToolbarProps> = ({
), ),
[ [
selectedFeature, selectedFeature,
computedProperties, displayedComputedProperties,
isSimulationDataActive,
canEditNetwork, canEditNetwork,
selectedValveId, selectedValveId,
valveStatus, 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; value: string | null;
loading?: boolean; loading?: boolean;
saving?: boolean; saving?: boolean;
disabled?: boolean;
onSave: (value: string) => Promise<void>; onSave: (value: string) => Promise<void>;
}; };
@@ -66,10 +67,73 @@ export type ValveSettingPropertyOptions = {
vType: string | null; vType: string | null;
loading?: boolean; loading?: boolean;
saving?: boolean; saving?: boolean;
disabled?: boolean;
status?: string | null; status?: string | null;
onSave: (value: string) => Promise<void>; 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 getFeatureHistoryType = (feature: Feature): string | null => {
const layerId = feature.getId()?.toString().split(".")[0] || ""; const layerId = feature.getId()?.toString().split(".")[0] || "";
if (layerId.includes("pipe")) return "pipe"; if (layerId.includes("pipe")) return "pipe";
@@ -127,6 +191,51 @@ export const buildFeatureProperties = (
{ key: "quality", label: "水质", unit: "mg/L" }, { 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") { if (layer === "geo_pipes_mat" || layer === "geo_pipes") {
const result: ToolbarPropertyPanelData = { const result: ToolbarPropertyPanelData = {
id: properties.id, id: properties.id,
@@ -146,30 +255,7 @@ export const buildFeatureProperties = (
], ],
}; };
pipeComputedFields.forEach(({ key, label, unit }) => { appendLinkComputedProperties(result);
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,
});
}
});
return result; return result;
} }
@@ -210,25 +296,13 @@ export const buildFeatureProperties = (
], ],
}; };
nodeComputedFields.forEach(({ key, label, unit }) => { appendNodeComputedProperties(result);
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,
});
}
});
return result; return result;
} }
if (layer === "geo_tanks_mat" || layer === "geo_tanks") { if (layer === "geo_tanks_mat" || layer === "geo_tanks") {
return { const result: ToolbarPropertyPanelData = {
id: properties.id, id: properties.id,
type: "水池", type: "水池",
properties: [ properties: [
@@ -268,24 +342,28 @@ export const buildFeatureProperties = (
}, },
], ],
}; };
appendNodeComputedProperties(result);
return result;
} }
if (layer === "geo_reservoirs_mat" || layer === "geo_reservoirs") { if (layer === "geo_reservoirs_mat" || layer === "geo_reservoirs") {
return { const result: ToolbarPropertyPanelData = {
id: properties.id, id: properties.id,
type: "水库", type: "水库",
properties: [ properties: [
{ {
label: "水头", label: "基础水头",
value: properties.head?.toFixed?.(1), value: properties.head?.toFixed?.(1),
unit: "m", unit: "m",
}, },
], ],
}; };
appendNodeComputedProperties(result);
return result;
} }
if (layer === "geo_pumps_mat" || layer === "geo_pumps") { if (layer === "geo_pumps_mat" || layer === "geo_pumps") {
return { const result: ToolbarPropertyPanelData = {
id: properties.id, id: properties.id,
type: "水泵", type: "水泵",
properties: [ properties: [
@@ -312,16 +390,22 @@ export const buildFeatureProperties = (
}, },
], ],
}; };
appendLinkComputedProperties(result);
return result;
} }
if (layer === "geo_valves_mat" || layer === "geo_valves") { if (layer === "geo_valves_mat" || layer === "geo_valves") {
const valveType = valveSetting?.vType ?? properties.v_type; const valveType = valveSetting?.vType ?? properties.v_type;
const hasSimulationValues =
Object.hasOwn(computedProperties, "status") ||
Object.hasOwn(computedProperties, "setting");
const isValveSettingDisabled = const isValveSettingDisabled =
valveSetting?.loading || valveSetting?.loading ||
valveSetting?.disabled ||
valveSetting?.status === "OPEN" || valveSetting?.status === "OPEN" ||
valveSetting?.status === "CLOSED"; valveSetting?.status === "CLOSED";
return { const result: ToolbarPropertyPanelData = {
id: properties.id, id: properties.id,
type: "阀门", type: "阀门",
properties: [ properties: [
@@ -340,7 +424,43 @@ export const buildFeatureProperties = (
label: "局部损失", label: "局部损失",
value: properties.minor_loss?.toFixed?.(2), 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, type: "select" as const,
@@ -348,13 +468,13 @@ export const buildFeatureProperties = (
value: valveStatus.value ?? "", value: valveStatus.value ?? "",
options: VALVE_STATUS_OPTIONS, options: VALVE_STATUS_OPTIONS,
placeholder: valveStatus.loading ? "加载中" : "未设置", placeholder: valveStatus.loading ? "加载中" : "未设置",
disabled: valveStatus.loading, disabled: valveStatus.loading || valveStatus.disabled,
saving: valveStatus.saving, saving: valveStatus.saving,
onSave: valveStatus.onSave, onSave: valveStatus.onSave,
}, },
] ]
: []), : []),
...(valveSetting ...(!hasSimulationValues && valveSetting
? [ ? [
{ {
type: "text" as const, type: "text" as const,
@@ -373,6 +493,8 @@ export const buildFeatureProperties = (
: []), : []),
], ],
}; };
appendLinkComputedProperties(result, ["setting", "status"]);
return result;
} }
const getTransmissionFrequency = (transmissionFrequency: string) => { const getTransmissionFrequency = (transmissionFrequency: string) => {
+4 -398
View File
@@ -305,26 +305,6 @@ export interface paths {
patch?: never; patch?: never;
trace?: never; trace?: never;
}; };
"/api/v1/all-redis": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
/**
* 清除所有缓存
* @description 清空整个Redis数据库的所有缓存
*/
delete: operations["delete_all_redis"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/all-scada-properties": { "/api/v1/all-scada-properties": {
parameters: { parameters: {
query?: never; query?: never;
@@ -3836,66 +3816,6 @@ export interface paths {
patch?: never; patch?: never;
trace?: never; trace?: never;
}; };
"/api/v1/redis": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* 查询缓存键列表
* @description 获取Redis中所有的缓存键
*/
get: operations["get_redis"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/redis-keys": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
/**
* 清除匹配的缓存键
* @description 根据模式清除匹配的Redis缓存键
*/
delete: operations["delete_redis_keys"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/redis-keys/detail": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
/**
* 清除单个缓存键
* @description 根据键名清除单个Redis缓存
*/
delete: operations["delete_redis_keys_detail"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/redos": { "/api/v1/redos": {
parameters: { parameters: {
query?: never; query?: never;
@@ -7810,19 +7730,14 @@ export interface components {
*/ */
tank_init_level?: Record<string, never> | null; tank_init_level?: Record<string, never> | null;
}; };
/** PressureSensorPlacement */ /** PressureSensorPlacementRest */
PressureSensorPlacement: { PressureSensorPlacementRest: {
/** /**
* Min Diameter * Min Diameter
* @description 最小管径限制 * @description 最小管径限制
* @default 0 * @default 0
*/ */
min_diameter: number; min_diameter: number;
/**
* Name
* @description 管网名称(或数据库名称)
*/
name: string;
/** /**
* Scheme Name * Scheme Name
* @description 方案名称 * @description 方案名称
@@ -7833,11 +7748,6 @@ export interface components {
* @description 传感器数量 * @description 传感器数量
*/ */
sensor_number: number; sensor_number: number;
/**
* Username
* @description 用户名
*/
username: string;
}; };
/** /**
* ProblemDetails * ProblemDetails
@@ -10041,80 +9951,6 @@ export interface operations {
}; };
}; };
}; };
delete_all_redis: {
parameters: {
query?: never;
header: {
"X-Project-Id": string;
};
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Authentication required */
401: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Insufficient permission */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Resource not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Resource conflict */
409: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Validation error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Dependency unavailable */
503: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
get_all_scada_properties: { get_all_scada_properties: {
parameters: { parameters: {
query?: { query?: {
@@ -23881,7 +23717,7 @@ export interface operations {
}; };
requestBody: { requestBody: {
content: { content: {
"application/json": components["schemas"]["PressureSensorPlacement"]; "application/json": components["schemas"]["PressureSensorPlacementRest"];
}; };
}; };
responses: { responses: {
@@ -24044,7 +23880,7 @@ export interface operations {
}; };
requestBody: { requestBody: {
content: { content: {
"application/json": components["schemas"]["PressureSensorPlacement"]; "application/json": components["schemas"]["PressureSensorPlacementRest"];
}; };
}; };
responses: { responses: {
@@ -27224,236 +27060,6 @@ export interface operations {
}; };
}; };
}; };
get_redis: {
parameters: {
query?: never;
header: {
"X-Project-Id": string;
};
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["JsonValue"];
};
};
/** @description Authentication required */
401: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Insufficient permission */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Resource not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Resource conflict */
409: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Validation error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Dependency unavailable */
503: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
delete_redis_keys: {
parameters: {
query: {
/** @description 缓存键模式(支持通配符) */
keys: string;
};
header: {
"X-Project-Id": string;
};
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Authentication required */
401: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Insufficient permission */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Resource not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Resource conflict */
409: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Validation error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Dependency unavailable */
503: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
delete_redis_keys_detail: {
parameters: {
query: {
/** @description 缓存键名 */
key: string;
};
header: {
"X-Project-Id": string;
};
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Authentication required */
401: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Insufficient permission */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Resource not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Resource conflict */
409: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Validation error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Dependency unavailable */
503: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
post_redos: { post_redos: {
parameters: { parameters: {
query?: never; query?: never;