fix(api): align frontend with project-scoped backend

Use project-scoped endpoints and run IDs for current project, SCADA metadata, historical time series, migrated DMA results, and sensor placement runs.

The regressions recurred because tests mocked pre-interceptor and new-only payload shapes, while history queries relied on implicit global scheme state and unbounded request fan-out. Cover post-interceptor pages, migrated result_rows, explicit run_id routing, multi-element requests, request limits, and cancellation.
This commit is contained in:
2026-09-01 14:37:51 +08:00
parent 0dad61ff1f
commit 29b8babd68
28 changed files with 1512 additions and 3494 deletions
@@ -17,7 +17,7 @@ import {
ChevronRight,
FormatListBulleted,
} from "@mui/icons-material";
import { useMap } from "@components/olmap/core/MapComponent";
import { useData, useMap } from "@components/olmap/core/MapComponent";
import StyleLegend from "@components/olmap/core/Controls/StyleLegend";
import AnalysisParameters, {
createDMALeakAnalysisParametersState,
@@ -50,6 +50,7 @@ const DMA_AREA_INDEX_PROPERTY = "dma_area_index";
const DMALeakDetectionPanel: React.FC = () => {
const map = useMap();
const setSchemeRunId = useData()?.setSchemeRunId;
const [open, setOpen] = useState(true);
const [tab, setTab] = useState(0);
const [result, setResult] = useState<LeakageResultDetail | null>(null);
@@ -79,15 +80,23 @@ const DMALeakDetectionPanel: React.FC = () => {
[activeAreas.length],
);
const handleAnalysisResult = useCallback((res: LeakageResultDetail) => {
setResult(res);
}, []);
const handleAnalysisResult = useCallback(
(res: LeakageResultDetail) => {
setSchemeRunId?.("");
setResult(res);
},
[setSchemeRunId],
);
const handleViewResult = useCallback((res: LeakageResultDetail) => {
setResult(res);
setLoadedResult(res);
setTab(2);
}, []);
const handleViewResult = useCallback(
(res: LeakageResultDetail, runId: string) => {
setSchemeRunId?.(runId);
setResult(res);
setLoadedResult(res);
setTab(2);
},
[setSchemeRunId],
);
useEffect(() => {
if (!map) return;
@@ -27,13 +27,17 @@ import {
listAnalysisSchemes,
} from "@/lib/analysisRuns";
import { useControllableObjectState } from "@components/olmap/core/useControllableState";
import { LeakageResultDetail, LeakageSchemeRecord } from "./types";
import {
LeakageResultDetail,
LeakageSchemeRecord,
normalizeLeakageRows,
} from "./types";
import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units";
import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName";
import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState";
interface Props {
onViewResult: (result: LeakageResultDetail) => void;
onViewResult: (result: LeakageResultDetail, runId: string) => void;
schemes?: LeakageSchemeRecord[];
onSchemesChange?: (schemes: LeakageSchemeRecord[]) => void;
state?: DMALeakSchemeQueryState;
@@ -131,14 +135,18 @@ const SchemeQuery: React.FC<Props> = ({
if (!result) {
throw new Error("方案详情缺少漏损识别结果");
}
onViewResult({
...result,
scheme_name: scheme.scheme_name,
scheme_detail: scheme.scheme_detail,
scheme_start_time: scheme.scheme_start_time,
create_time: scheme.create_time,
username: scheme.username,
} as LeakageResultDetail);
onViewResult(
{
...result,
rows: normalizeLeakageRows(result),
scheme_name: scheme.scheme_name,
scheme_detail: scheme.scheme_detail,
scheme_start_time: scheme.scheme_start_time,
create_time: scheme.create_time,
username: scheme.username,
} as LeakageResultDetail,
runId,
);
} catch (error: any) {
open?.({
type: "error",
@@ -0,0 +1,27 @@
import { normalizeLeakageRows } from "./types";
const migratedRow = {
Area: "DMA-01",
LeakageRatioRaw: 0.12,
LeakageRatio: 0.1,
LeakageFlow_m3_per_s: 0.02,
};
describe("normalizeLeakageRows", () => {
it("restores migrated DMA results stored under result_rows", () => {
expect(normalizeLeakageRows({ result_rows: [migratedRow] })).toEqual([
migratedRow,
]);
});
it("prefers the canonical rows field when both shapes are present", () => {
const canonicalRow = { ...migratedRow, Area: "DMA-02" };
expect(
normalizeLeakageRows({
rows: [canonicalRow],
result_rows: [migratedRow],
}),
).toEqual([canonicalRow]);
});
});
@@ -5,6 +5,18 @@ export interface LeakageRow {
LeakageFlow_m3_per_s: number;
}
export const normalizeLeakageRows = (
payload: Record<string, unknown>,
): LeakageRow[] => {
if (Array.isArray(payload.rows)) {
return payload.rows as LeakageRow[];
}
return Array.isArray(payload.result_rows)
? (payload.result_rows as LeakageRow[])
: [];
};
export interface LeakageSchemeRecord {
scheme_id: string;
scheme_name: string;