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; fetchGridDetail: (gridId: string) => Promise; fetchAlerts: () => Promise; fetchStats: () => Promise; clearError: () => void; } export const useRiskStore = create((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((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; fetchAggregatedData: (startDate: string, endDate: string, district?: string) => Promise; fetchDistrictCases: (diagnosis?: string) => Promise; clearError: () => void; } export const useMonitoringStore = create((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) => { set({ isLoading: true, error: null }); try { const data = await caseApi.getDistricts(diagnosis ? { diagnosis } : 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; 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((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 }); } }, }));