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
@@ -24,21 +24,57 @@ import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import { Style, Stroke, Fill, Circle as CircleStyle, Icon } from "ol/style";
import Feature from "ol/Feature";
import { handleMapClickSelectFeatures as mapClickSelectFeatures } from "@/utils/mapQueryService";
import {
handleMapClickSelectFeatures as mapClickSelectFeatures,
queryFeaturesByIds,
} from "@/utils/mapQueryService";
import { useControllableObjectState } from "@components/olmap/core/useControllableState";
const AnalysisParameters: React.FC = () => {
export interface ContaminantAnalysisParametersState {
schemeName: string;
startTime: Dayjs | null;
sourceNode: string;
concentration: number;
duration: number;
pattern: string;
}
export const createContaminantAnalysisParametersState =
(): ContaminantAnalysisParametersState => ({
schemeName: "WQ_" + new Date().getTime(),
startTime: dayjs(new Date()),
sourceNode: "",
concentration: 100,
duration: 3600,
pattern: "",
});
interface AnalysisParametersProps {
state?: ContaminantAnalysisParametersState;
onStateChange?: (state: ContaminantAnalysisParametersState) => void;
}
const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
state,
onStateChange,
}) => {
const map = useMap();
const { open } = useNotification();
const network = NETWORK_NAME;
const [schemeName, setSchemeName] = useState<string>(
"WQ_" + new Date().getTime(),
const [parametersState, , setFormField] = useControllableObjectState(
state,
onStateChange,
createContaminantAnalysisParametersState(),
);
const [startTime, setStartTime] = useState<Dayjs | null>(dayjs(new Date()));
const [sourceNode, setSourceNode] = useState<string>("");
const [concentration, setConcentration] = useState<number>(100);
const [duration, setDuration] = useState<number>(3600);
const [pattern, setPattern] = useState<string>("");
const {
schemeName,
startTime,
sourceNode,
concentration,
duration,
pattern,
} = parametersState;
const [isSelecting, setIsSelecting] = useState<boolean>(false);
const [submitting, setSubmitting] = useState<boolean>(false);
@@ -77,12 +113,12 @@ const AnalysisParameters: React.FC = () => {
const id = feature.getProperties().id;
if (!id) return;
setSourceNode(id);
setFormField("sourceNode", id);
setHighlightFeature(feature);
setIsSelecting(false);
map.un("click", handleMapClickSelectFeatures);
},
[map, open],
[map, open, setFormField],
);
useEffect(() => {
@@ -144,6 +180,17 @@ const AnalysisParameters: React.FC = () => {
}
}, [highlightFeature, highlightLayer]);
useEffect(() => {
if (!sourceNode) {
setHighlightFeature(null);
return;
}
if (highlightFeature) return;
queryFeaturesByIds([sourceNode], "geo_junctions_mat").then((features) => {
setHighlightFeature(features[0] ?? null);
});
}, [highlightFeature, sourceNode]);
const handleStartSelection = () => {
if (!map) return;
setIsSelecting(true);
@@ -157,7 +204,7 @@ const AnalysisParameters: React.FC = () => {
};
const handleClearSource = () => {
setSourceNode("");
setFormField("sourceNode", "");
setHighlightFeature(null);
};
@@ -176,7 +223,7 @@ const AnalysisParameters: React.FC = () => {
: "";
try {
if (!pattern) {
setPattern("CONSTANT");
setFormField("pattern", "CONSTANT");
console.log("默认设置 pattern 为 CONSTANT");
}
const params = {
@@ -310,7 +357,7 @@ const AnalysisParameters: React.FC = () => {
<DateTimePicker
value={startTime}
onChange={(value) =>
value && dayjs.isDayjs(value) && setStartTime(value)
value && dayjs.isDayjs(value) && setFormField("startTime", value)
}
format="YYYY-MM-DD HH:mm"
slotProps={{
@@ -335,7 +382,7 @@ const AnalysisParameters: React.FC = () => {
fullWidth
size="small"
value={schemeName}
onChange={(e) => setSchemeName(e.target.value)}
onChange={(e) => setFormField("schemeName", e.target.value)}
placeholder="输入方案名称"
/>
</Box>
@@ -349,7 +396,9 @@ const AnalysisParameters: React.FC = () => {
size="small"
type="number"
value={concentration}
onChange={(e) => setConcentration(parseFloat(e.target.value) || 0)}
onChange={(e) =>
setFormField("concentration", parseFloat(e.target.value) || 0)
}
placeholder="输入浓度"
/>
</Box>
@@ -363,7 +412,9 @@ const AnalysisParameters: React.FC = () => {
size="small"
type="number"
value={duration}
onChange={(e) => setDuration(parseInt(e.target.value, 10) || 0)}
onChange={(e) =>
setFormField("duration", parseInt(e.target.value, 10) || 0)
}
placeholder="输入持续时长"
/>
</Box>
@@ -376,7 +427,7 @@ const AnalysisParameters: React.FC = () => {
fullWidth
size="small"
value={pattern}
onChange={(e) => setPattern(e.target.value)}
onChange={(e) => setFormField("pattern", e.target.value)}
placeholder="可选,输入 pattern 名称,默认为 CONSTANT"
/>
</Box>
@@ -30,6 +30,7 @@ import moment from "moment";
import { useNotification } from "@refinedev/core";
import { config, NETWORK_NAME } from "@config/config";
import { queryFeaturesByIds } from "@/utils/mapQueryService";
import { useControllableObjectState } from "@components/olmap/core/useControllableState";
import { useData, useMap } from "@components/olmap/core/MapComponent";
import { GeoJSON } from "ol/format";
import VectorLayer from "ol/layer/Vector";
@@ -45,32 +46,60 @@ interface SchemeQueryProps {
onSchemesChange?: (schemes: ContaminantSchemeRecord[]) => void;
onViewResults?: () => void;
network?: string;
state?: ContaminantSchemeQueryState;
onStateChange?: (state: ContaminantSchemeQueryState) => void;
}
const SCHEME_TYPE = "contaminant_analysis";
export interface ContaminantSchemeQueryState {
queryAll: boolean;
queryDate: Dayjs | null;
showTimeline: boolean;
selectedDate: Date | undefined;
timeRange: { start: Date; end: Date } | undefined;
expandedId: number | null;
}
export const createContaminantSchemeQueryState =
(): ContaminantSchemeQueryState => ({
queryAll: true,
queryDate: dayjs(new Date()),
showTimeline: false,
selectedDate: undefined,
timeRange: undefined,
expandedId: null,
});
const SchemeQuery: React.FC<SchemeQueryProps> = ({
schemes: externalSchemes,
onSchemesChange,
onViewResults,
network = NETWORK_NAME,
state,
onStateChange,
}) => {
const [queryAll, setQueryAll] = useState<boolean>(true);
const [queryDate, setQueryDate] = useState<Dayjs | null>(dayjs(new Date()));
const [queryState, setQueryState, setQueryField] = useControllableObjectState(
state,
onStateChange,
createContaminantSchemeQueryState(),
);
const {
queryAll,
queryDate,
showTimeline,
selectedDate,
timeRange,
expandedId,
} = queryState;
const [highlightLayer, setHighlightLayer] =
useState<VectorLayer<VectorSource> | null>(null);
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
const [showTimeline, setShowTimeline] = useState(false);
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
const [timeRange, setTimeRange] = useState<
{ start: Date; end: Date } | undefined
>();
const [internalSchemes, setInternalSchemes] = useState<
ContaminantSchemeRecord[]
>([]);
const [loading, setLoading] = useState<boolean>(false);
const [expandedId, setExpandedId] = useState<number | null>(null);
const [mapContainer, setMapContainer] = useState<HTMLElement | null>(null);
const { open } = useNotification();
@@ -256,7 +285,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
const scheme = filteredSchemes.find((s) => s.id === id);
if (!scheme) return;
setShowTimeline(true);
setQueryState((previous) => ({ ...previous, showTimeline: true }));
const schemeDate = scheme.startTime
? new Date(scheme.startTime)
: undefined;
@@ -265,8 +294,12 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
const end = new Date(
start.getTime() + scheme.schemeDetail.duration * 1000,
);
setSelectedDate(schemeDate);
setTimeRange({ start, end });
setQueryState((previous) => ({
...previous,
showTimeline: true,
selectedDate: schemeDate,
timeRange: { start, end },
}));
}
setSchemeName?.(scheme.schemeName);
if (scheme.schemeDetail?.source) {
@@ -296,7 +329,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
control={
<Checkbox
checked={queryAll}
onChange={(e) => setQueryAll(e.target.checked)}
onChange={(e) => setQueryField("queryAll", e.target.checked)}
size="small"
/>
}
@@ -310,7 +343,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
<DatePicker
value={queryDate}
onChange={(value) =>
value && dayjs.isDayjs(value) && setQueryDate(value)
value && dayjs.isDayjs(value) && setQueryField("queryDate", value)
}
format="YYYY-MM-DD"
disabled={queryAll}
@@ -422,7 +455,8 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
<IconButton
size="small"
onClick={() =>
setExpandedId(
setQueryField(
"expandedId",
expandedId === scheme.id ? null : scheme.id,
)
}
@@ -17,8 +17,14 @@ import {
Search as SearchIcon,
MyLocation as MyLocationIcon,
} from "@mui/icons-material";
import ContaminantAnalysisParameters from "./AnalysisParameters";
import ContaminantSchemeQuery from "./SchemeQuery";
import ContaminantAnalysisParameters, {
createContaminantAnalysisParametersState,
type ContaminantAnalysisParametersState,
} from "./AnalysisParameters";
import ContaminantSchemeQuery, {
createContaminantSchemeQueryState,
type ContaminantSchemeQueryState,
} from "./SchemeQuery";
import { useData } from "@components/olmap/core/MapComponent";
import { ContaminantSchemeRecord } from "./types";
@@ -34,6 +40,14 @@ const WaterQualityPanel: React.FC<WaterQualityPanelProps> = ({
const [internalOpen, setInternalOpen] = useState(true);
const [currentTab, setCurrentTab] = useState(0);
const [schemes, setSchemes] = useState<ContaminantSchemeRecord[]>([]);
const [analysisState, setAnalysisState] =
useState<ContaminantAnalysisParametersState>(
createContaminantAnalysisParametersState,
);
const [queryState, setQueryState] =
useState<ContaminantSchemeQueryState>(
createContaminantSchemeQueryState,
);
const data = useData();
@@ -170,7 +184,10 @@ const WaterQualityPanel: React.FC<WaterQualityPanelProps> = ({
{/* Tab 内容 */}
<TabPanel value={currentTab} index={0}>
<ContaminantAnalysisParameters />
<ContaminantAnalysisParameters
state={analysisState}
onStateChange={setAnalysisState}
/>
</TabPanel>
<TabPanel value={currentTab} index={1}>
@@ -178,6 +195,8 @@ const WaterQualityPanel: React.FC<WaterQualityPanelProps> = ({
schemes={schemes}
onSchemesChange={setSchemes}
onViewResults={() => setCurrentTab(2)}
state={queryState}
onStateChange={setQueryState}
/>
</TabPanel>
</Box>