Files
CA/frontend/src/stores/index.ts

278 lines
8.2 KiB
TypeScript
Raw Normal View History

import { create } from 'zustand';
import axios from 'axios';
import type { GridRisk, GridDetail, Alert, Stats, ForecastDay } from '@/types';
import { riskApi, alertApi, gridApi, caseApi } from '@/services/api';
function isCancelError(e: unknown): boolean {
return axios.isCancel(e) || (e as Error)?.message === 'canceled';
}
interface RiskState {
grids: GridRisk[];
selectedGrid: GridDetail | null;
selectedGridId: string | null;
alerts: Alert[];
stats: Stats | null;
forecastDay: ForecastDay;
isLoading: boolean;
error: string | null;
showFullscreen: boolean;
setForecastDay: (day: ForecastDay) => void;
setSelectedGridId: (id: string | null) => void;
setShowFullscreen: (show: boolean) => void;
fetchRiskMap: () => Promise<void>;
fetchGridDetail: (gridId: string) => Promise<void>;
fetchAlerts: () => Promise<void>;
fetchStats: () => Promise<void>;
clearError: () => void;
}
export const useRiskStore = create<RiskState>((set, get) => ({
grids: [],
selectedGrid: null,
selectedGridId: null,
alerts: [],
stats: null,
forecastDay: 0,
isLoading: false,
error: null,
showFullscreen: false,
setForecastDay: (day) => {
set({ forecastDay: day });
get().fetchRiskMap();
},
setSelectedGridId: (id) => {
set({ selectedGridId: id });
if (id) get().fetchGridDetail(id);
else set({ selectedGrid: null });
},
setShowFullscreen: (show) => set({ showFullscreen: show }),
clearError: () => set({ error: null }),
fetchRiskMap: async () => {
set({ isLoading: true, error: null });
try {
const { forecastDay } = get();
const data = forecastDay === 0
? await riskApi.getCurrentRiskMap()
: await riskApi.getForecast(forecastDay);
set({ grids: data.grids || [], isLoading: false });
} catch (e) {
if (isCancelError(e)) return;
set({ error: (e as Error).message || '加载风险地图失败', isLoading: false });
}
},
fetchGridDetail: async (gridId) => {
set({ isLoading: true, error: null });
try {
const data = await riskApi.getGridDetail(gridId);
set({ selectedGrid: data.grid, isLoading: false });
} catch (e) {
if (isCancelError(e)) return;
set({ error: (e as Error).message || '加载网格详情失败', isLoading: false });
}
},
fetchAlerts: async () => {
try {
const data = await alertApi.getAlerts({ min_risk: 0.6 });
set({ alerts: data.alerts || [] });
} catch (e) {
if (isCancelError(e)) return;
set({ alerts: [], error: (e as Error).message || '加载预警数据失败' });
}
},
fetchStats: async () => {
try {
const data = await riskApi.getStats();
set({ stats: data });
} catch (e) {
if (isCancelError(e)) return;
set({ error: (e as Error).message || '加载统计数据失败' });
}
},
}));
export { useAnalysisStore } from './analysisStore';
export { useDrilldownStore } from './drilldownStore';
export { useDiseaseStore } from './diseaseStore';
interface TimelineState {
currentDate: string;
startDate: string;
endDate: string;
isPlaying: boolean;
playbackSpeed: number;
setCurrentDate: (date: string) => void;
setDateRange: (start: string, end: string) => void;
setPlaying: (playing: boolean) => void;
setPlaybackSpeed: (speed: number) => void;
goToNextDay: () => void;
goToPrevDay: () => void;
}
export const useTimelineStore = create<TimelineState>((set, get) => ({
currentDate: new Date().toISOString().split('T')[0],
startDate: '2022-12-01',
endDate: '2024-12-30',
isPlaying: false,
playbackSpeed: 1,
setCurrentDate: (date) => set({ currentDate: date }),
setDateRange: (start, end) => set({ startDate: start, endDate: end }),
setPlaying: (playing) => set({ isPlaying: playing }),
setPlaybackSpeed: (speed) => set({ playbackSpeed: speed }),
goToNextDay: () => {
const { currentDate, endDate } = get();
const next = new Date(currentDate);
next.setDate(next.getDate() + 1);
if (next.toISOString().split('T')[0] <= endDate) {
set({ currentDate: next.toISOString().split('T')[0] });
}
},
goToPrevDay: () => {
const { currentDate, startDate } = get();
const prev = new Date(currentDate);
prev.setDate(prev.getDate() - 1);
if (prev.toISOString().split('T')[0] >= startDate) {
set({ currentDate: prev.toISOString().split('T')[0] });
}
},
}));
interface GridFeature {
grid_id: string;
latitude: number;
longitude: number;
district: string;
AQI: number;
PM25: number;
PM10: number;
total_cases: number;
}
interface MonitoringState {
gridFeatures: GridFeature[];
aggregatedData: Array<{ date: string; district: string; total_cases: number; avg_AQI: number }>;
districtCases: Array<{ district: string; total: number; outpatient: number; inpatient: number }>;
isLoading: boolean;
error: string | null;
fetchGridFeatures: (date: string) => Promise<void>;
fetchAggregatedData: (startDate: string, endDate: string, district?: string) => Promise<void>;
fetchDistrictCases: (diagnosis?: string, startDate?: string, endDate?: string) => Promise<void>;
clearError: () => void;
}
export const useMonitoringStore = create<MonitoringState>((set) => ({
gridFeatures: [],
aggregatedData: [],
districtCases: [],
isLoading: false,
error: null,
clearError: () => set({ error: null }),
fetchGridFeatures: async (date) => {
set({ isLoading: true, error: null });
try {
const data = await gridApi.getGridsGeoJSON(date);
const features: GridFeature[] = (data.features || []).map((f: any) => ({
grid_id: f.properties.grid_id,
latitude: f.properties.latitude,
longitude: f.properties.longitude,
district: f.properties.district,
AQI: f.properties.AQI || 0,
PM25: f.properties.PM25 || 0,
PM10: f.properties.PM10 || 0,
total_cases: f.properties.total_cases || 0,
}));
set({ gridFeatures: features, isLoading: false });
} catch (e) {
if (isCancelError(e)) return;
set({ error: (e as Error).message || '加载网格数据失败', isLoading: false });
}
},
fetchAggregatedData: async (startDate, endDate, district) => {
set({ isLoading: true, error: null });
try {
const data = await gridApi.getHistoricalAggregated(startDate, endDate, 'daily', district);
set({ aggregatedData: data.aggregations || [], isLoading: false });
} catch (e) {
if (isCancelError(e)) return;
set({ error: (e as Error).message || '加载聚合数据失败', isLoading: false });
}
},
fetchDistrictCases: async (diagnosis?: string, startDate?: string, endDate?: string) => {
set({ isLoading: true, error: null });
try {
const params: Record<string, string> = {};
if (diagnosis) params.diagnosis = diagnosis;
if (startDate) params.start_date = startDate;
if (endDate) params.end_date = endDate;
const data = await caseApi.getDistricts(Object.keys(params).length > 0 ? params : undefined);
const districts = Array.isArray(data) ? data : (data as any).districts || [];
set({ districtCases: districts, isLoading: false });
} catch (e) {
if (isCancelError(e)) return;
set({ error: (e as Error).message || '加载区县病例数据失败', isLoading: false });
}
},
}));
interface PredictionState {
predictions: GridPrediction[];
predictionDays: number;
isLoading: boolean;
error: string | null;
fetchPredictions: (date: string, days: number, district?: string) => Promise<void>;
clearError: () => void;
}
interface GridPrediction {
grid_id: string;
latitude: number;
longitude: number;
risk_1day: number;
risk_3day: number;
risk_7day: number;
risk_level: string;
}
export const usePredictionStore = create<PredictionState>((set) => ({
predictions: [],
predictionDays: 7,
isLoading: false,
error: null,
clearError: () => set({ error: null }),
fetchPredictions: async (date, days, district) => {
set({ isLoading: true, error: null });
try {
const data = await gridApi.getMultiDayPrediction(date, days, district);
set({ predictions: data.predictions || [], isLoading: false });
} catch (e) {
if (isCancelError(e)) return;
set({ error: (e as Error).message || '加载预测数据失败', isLoading: false });
}
},
}));