fix(analysis): preserve tab panel state

This commit is contained in:
2026-07-09 13:59:01 +08:00
parent 701c5a949d
commit 694f7629ee
24 changed files with 1119 additions and 281 deletions
@@ -25,14 +25,17 @@ import dayjs, { Dayjs } from "dayjs";
import "dayjs/locale/zh-cn";
import { api } from "@/lib/api";
import { NETWORK_NAME, config } from "@config/config";
import { useControllableObjectState } from "@components/olmap/core/useControllableState";
import { FLOW_DISPLAY_UNIT, toM3s } from "@utils/units";
import { BurstLocationResult } from "./types";
interface Props {
onResult: (result: BurstLocationResult) => void;
state?: BurstLocationAnalysisParametersState;
onStateChange?: (state: BurstLocationAnalysisParametersState) => void;
}
interface SchemeItem {
export interface SchemeItem {
scheme_id: number;
scheme_name: string;
scheme_type: string;
@@ -45,24 +48,60 @@ interface SchemeItem {
type DataSource = "monitoring" | "simulation";
const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
export interface BurstLocationAnalysisParametersState {
schemeName: string;
dataSource: DataSource;
schemes: SchemeItem[];
selectedSchemeId: number | "";
burstLeakage: number;
enableFlow: boolean;
burstStartTime: Dayjs | null;
burstEndTime: Dayjs | null;
minDpressure: number;
basicPressure: number;
advancedOpen: boolean;
}
export const createBurstLocationAnalysisParametersState =
(): BurstLocationAnalysisParametersState => ({
schemeName: `Burst_Locate_${Date.now()}`,
dataSource: "monitoring",
schemes: [],
selectedSchemeId: "",
burstLeakage: 1440,
enableFlow: false,
burstStartTime: dayjs().subtract(20, "minute"),
burstEndTime: dayjs().subtract(5, "minute"),
minDpressure: 2,
basicPressure: 10,
advancedOpen: false,
});
const AnalysisParameters: React.FC<Props> = ({
onResult,
state,
onStateChange,
}) => {
const { open } = useNotification();
const [schemeName, setSchemeName] = useState(`Burst_Locate_${Date.now()}`);
const [dataSource, setDataSource] = useState<DataSource>("monitoring");
const [schemes, setSchemes] = useState<SchemeItem[]>([]);
const [selectedSchemeId, setSelectedSchemeId] = useState<number | "">("");
const [parametersState, setParametersState, setFormField] = useControllableObjectState(
state,
onStateChange,
createBurstLocationAnalysisParametersState(),
);
const {
schemeName,
dataSource,
schemes,
selectedSchemeId,
burstLeakage,
enableFlow,
burstStartTime,
burstEndTime,
minDpressure,
basicPressure,
advancedOpen,
} = parametersState;
const [schemeLoading, setSchemeLoading] = useState(false);
const [burstLeakage, setBurstLeakage] = useState<number>(1440);
const [enableFlow, setEnableFlow] = useState(false);
const [burstStartTime, setBurstStartTime] = useState<Dayjs | null>(
dayjs().subtract(20, "minute"),
);
const [burstEndTime, setBurstEndTime] = useState<Dayjs | null>(
dayjs().subtract(5, "minute"),
);
const [minDpressure, setMinDpressure] = useState<number>(2);
const [basicPressure, setBasicPressure] = useState<number>(10);
const [advancedOpen, setAdvancedOpen] = useState(false);
const [running, setRunning] = useState(false);
const isSimulationMode = dataSource === "simulation";
@@ -71,9 +110,12 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
const durationSeconds = scheme.scheme_detail?.modify_total_duration ?? 3600;
const end = start.add(durationSeconds, "second");
setBurstStartTime(start);
setBurstEndTime(end);
}, []);
setParametersState((previous) => ({
...previous,
burstStartTime: start,
burstEndTime: end,
}));
}, [setParametersState]);
const fetchSchemes = useCallback(
async ({ force = false, notify = false }: { force?: boolean; notify?: boolean } = {}) => {
@@ -88,7 +130,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
(scheme) => scheme.scheme_type === "burst_analysis",
);
setSchemes(burstSchemes);
setFormField("schemes", burstSchemes);
if (selectedSchemeId) {
const matchedScheme = burstSchemes.find(
@@ -97,7 +139,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
if (matchedScheme) {
applySchemeTimeRange(matchedScheme);
} else {
setSelectedSchemeId("");
setFormField("selectedSchemeId", "");
}
}
@@ -119,18 +161,18 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
setSchemeLoading(false);
}
},
[applySchemeTimeRange, open, schemeLoading, schemes.length, selectedSchemeId],
[applySchemeTimeRange, open, schemeLoading, schemes.length, selectedSchemeId, setFormField],
);
const handleDataSourceChange = (value: DataSource) => {
setDataSource(value);
setFormField("dataSource", value);
if (value === "simulation") {
void fetchSchemes();
}
};
const handleSchemeSelect = (schemeId: number) => {
setSelectedSchemeId(schemeId);
setFormField("selectedSchemeId", schemeId);
const scheme = schemes.find((item) => item.scheme_id === schemeId);
if (scheme) {
applySchemeTimeRange(scheme);
@@ -220,7 +262,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
</Typography>
<TextField
value={schemeName}
onChange={(e) => setSchemeName(e.target.value)}
onChange={(e) => setFormField("schemeName", e.target.value)}
placeholder="请输入方案名称"
fullWidth
size="small"
@@ -321,7 +363,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
</Typography>
<DateTimePicker
value={burstStartTime}
onChange={setBurstStartTime}
onChange={(value) => setFormField("burstStartTime", value)}
maxDateTime={burstEndTime ?? undefined}
disabled={isSimulationMode}
format="YYYY-MM-DD HH:mm"
@@ -334,7 +376,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
</Typography>
<DateTimePicker
value={burstEndTime}
onChange={setBurstEndTime}
onChange={(value) => setFormField("burstEndTime", value)}
minDateTime={burstStartTime ?? undefined}
disabled={isSimulationMode}
format="YYYY-MM-DD HH:mm"
@@ -354,7 +396,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
value={burstLeakage}
onChange={(e) => {
const value = Number(e.target.value);
setBurstLeakage(Number.isNaN(value) ? 1440 : Math.max(0, value));
setFormField("burstLeakage", Number.isNaN(value) ? 1440 : Math.max(0, value));
}}
fullWidth
inputProps={{ min: 0, step: 10 }}
@@ -370,9 +412,9 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
<Box
role="button"
tabIndex={0}
onClick={() => setAdvancedOpen((prev) => !prev)}
onClick={() => setFormField("advancedOpen", !advancedOpen)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") setAdvancedOpen((prev) => !prev);
if (e.key === "Enter" || e.key === " ") setFormField("advancedOpen", !advancedOpen);
}}
sx={{
display: "flex",
@@ -412,7 +454,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
<FormControl fullWidth size="small">
<Select
value={enableFlow ? "enabled" : "disabled"}
onChange={(e) => setEnableFlow(e.target.value === "enabled")}
onChange={(e) => setFormField("enableFlow", e.target.value === "enabled")}
>
<MenuItem value="disabled"></MenuItem>
<MenuItem value="enabled">使</MenuItem>
@@ -425,14 +467,14 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
label="最小压降 (m)"
size="small"
value={minDpressure}
onChange={(e) => setMinDpressure(Number(e.target.value))}
onChange={(e) => setFormField("minDpressure", Number(e.target.value))}
/>
<TextField
type="number"
label="基础压力 (m)"
size="small"
value={basicPressure}
onChange={(e) => setBasicPressure(Number(e.target.value))}
onChange={(e) => setFormField("basicPressure", Number(e.target.value))}
/>
</Box>
</Box>