Files
digital-pilates/store/healthSlice.ts

440 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import * as healthProfileApi from '@/services/healthProfile';
import { AppDispatch, RootState } from './index';
import {
HistoryData,
HistoryItemDetail,
MedicalRecordItem,
MedicalRecordsData,
MedicalRecordType,
} from '@/services/healthProfile';
// 健康数据类型定义
export interface FitnessRingsData {
activeCalories: number;
activeCaloriesGoal: number;
exerciseMinutes: number;
exerciseMinutesGoal: number;
standHours: number;
standHoursGoal: number;
}
export interface HealthData {
activeCalories: number | null;
heartRate: number | null;
activeEnergyBurned: number;
activeCaloriesGoal: number;
exerciseMinutes: number;
exerciseMinutesGoal: number;
standHours: number;
standHoursGoal: number;
}
// 健康史数据类型定义
export interface HistoryItemDetail {
id: string;
name: string;
date?: string; // ISO Date string
isRecommendation?: boolean;
}
export interface HistoryData {
[key: string]: {
hasHistory: boolean | null;
items: HistoryItemDetail[];
};
}
export interface HealthState {
// 按日期存储的历史数据
dataByDate: Record<string, HealthData>;
// 健康史数据
historyData: HistoryData;
// 就医资料数据
medicalRecords: MedicalRecordsData;
// 加载状态
loading: boolean;
error: string | null;
// 最后更新时间
lastUpdateTime: string | null;
}
// 初始状态
const initialState: HealthState = {
dataByDate: {},
historyData: {
allergy: { hasHistory: null, items: [] },
disease: { hasHistory: null, items: [] },
surgery: { hasHistory: null, items: [] },
familyDisease: { hasHistory: null, items: [] },
},
medicalRecords: {
records: [],
prescriptions: [],
},
loading: false,
error: null,
lastUpdateTime: null,
};
const healthSlice = createSlice({
name: 'health',
initialState,
reducers: {
// 设置加载状态
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload;
},
// 设置错误信息
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
},
// 设置完整的健康数据
setHealthData: (state, action: PayloadAction<{
date: string;
data: HealthData;
}>) => {
const { date, data } = action.payload;
// 存储到历史数据
state.dataByDate[date] = data;
state.lastUpdateTime = new Date().toISOString();
},
// 清除特定日期的数据
clearHealthDataForDate: (state, action: PayloadAction<string>) => {
const date = action.payload;
delete state.dataByDate[date];
},
// 清除所有健康数据
clearAllHealthData: (state) => {
state.dataByDate = {};
state.error = null;
state.lastUpdateTime = null;
},
// 更新健康史数据(本地更新,用于乐观更新或离线模式)
updateHistoryData: (state, action: PayloadAction<{
type: string;
data: {
hasHistory: boolean | null;
items: HistoryItemDetail[];
};
}>) => {
const { type, data } = action.payload;
state.historyData[type] = data;
state.lastUpdateTime = new Date().toISOString();
},
// 设置完整的健康史数据(从服务端同步)
setHistoryData: (state, action: PayloadAction<HistoryData>) => {
state.historyData = action.payload;
state.lastUpdateTime = new Date().toISOString();
},
// 清除健康史数据
clearHistoryData: (state) => {
state.historyData = {
allergy: { hasHistory: null, items: [] },
disease: { hasHistory: null, items: [] },
surgery: { hasHistory: null, items: [] },
familyDisease: { hasHistory: null, items: [] },
};
},
// 更新就医资料列表
setMedicalRecords: (state, action: PayloadAction<MedicalRecordsData>) => {
state.medicalRecords = action.payload;
state.lastUpdateTime = new Date().toISOString();
},
// 添加就医资料项
addMedicalRecordItem: (state, action: PayloadAction<MedicalRecordItem>) => {
const item = action.payload;
if (item.type === 'medical_record') {
state.medicalRecords.records.unshift(item);
} else {
state.medicalRecords.prescriptions.unshift(item);
}
state.lastUpdateTime = new Date().toISOString();
},
// 删除就医资料项
removeMedicalRecordItem: (state, action: PayloadAction<{ id: string; type: MedicalRecordType }>) => {
const { id, type } = action.payload;
if (type === 'medical_record') {
state.medicalRecords.records = state.medicalRecords.records.filter(item => item.id !== id);
} else {
state.medicalRecords.prescriptions = state.medicalRecords.prescriptions.filter(item => item.id !== id);
}
state.lastUpdateTime = new Date().toISOString();
},
},
extraReducers: (builder) => {
builder
// 获取健康史
.addCase(fetchHealthHistory.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchHealthHistory.fulfilled, (state, action) => {
state.loading = false;
// 转换服务端数据格式到本地格式
const serverData = action.payload;
const categories = ['allergy', 'disease', 'surgery', 'familyDisease'] as const;
categories.forEach(category => {
if (serverData[category]) {
state.historyData[category] = {
hasHistory: serverData[category].hasHistory,
items: serverData[category].items.map(item => ({
id: item.id,
name: item.name,
date: item.diagnosisDate,
isRecommendation: item.isRecommendation,
})),
};
}
});
state.lastUpdateTime = new Date().toISOString();
})
.addCase(fetchHealthHistory.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
})
// 保存健康史分类
.addCase(saveHealthHistoryCategory.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(saveHealthHistoryCategory.fulfilled, (state, action) => {
state.loading = false;
const { category, data } = action.payload;
// 更新对应分类的数据
state.historyData[category] = {
hasHistory: data.hasHistory,
items: data.items.map(item => ({
id: item.id,
name: item.name,
date: item.diagnosisDate,
isRecommendation: item.isRecommendation,
})),
};
state.lastUpdateTime = new Date().toISOString();
})
.addCase(saveHealthHistoryCategory.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
})
// 获取健康史进度
.addCase(fetchHealthHistoryProgress.rejected, (state, action) => {
state.error = action.payload as string;
})
// 获取就医资料
.addCase(fetchMedicalRecords.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchMedicalRecords.fulfilled, (state, action) => {
state.loading = false;
state.medicalRecords = action.payload;
state.lastUpdateTime = new Date().toISOString();
})
.addCase(fetchMedicalRecords.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
})
// 添加就医资料
.addCase(addNewMedicalRecord.fulfilled, (state, action) => {
const item = action.payload;
if (item.type === 'medical_record') {
state.medicalRecords.records.unshift(item);
} else {
state.medicalRecords.prescriptions.unshift(item);
}
state.lastUpdateTime = new Date().toISOString();
})
// 删除就医资料
.addCase(deleteMedicalRecordItem.fulfilled, (state, action) => {
const { id, type } = action.payload;
if (type === 'medical_record') {
state.medicalRecords.records = state.medicalRecords.records.filter(item => item.id !== id);
} else {
state.medicalRecords.prescriptions = state.medicalRecords.prescriptions.filter(item => item.id !== id);
}
state.lastUpdateTime = new Date().toISOString();
});
},
});
// Action creators
export const {
setLoading,
setError,
setHealthData,
clearHealthDataForDate,
clearAllHealthData,
updateHistoryData,
setHistoryData,
clearHistoryData,
setMedicalRecords,
addMedicalRecordItem,
removeMedicalRecordItem,
} = healthSlice.actions;
// Thunk action to fetch and set health data for a specific date
export const fetchHealthDataForDate = (date: Date) => {
return async (dispatch: AppDispatch, getState: () => RootState) => {
try {
dispatch(setLoading(true));
dispatch(setError(null));
// 这里可以添加实际的 API 调用逻辑
// 目前我们假设数据已经通过其他方式获取
dispatch(setLoading(false));
} catch (error) {
dispatch(setError(error instanceof Error ? error.message : '获取健康数据失败'));
dispatch(setLoading(false));
}
};
};
// ==================== 健康史 API Thunks ====================
/**
* 从服务端获取完整健康史数据
*/
export const fetchHealthHistory = createAsyncThunk(
'health/fetchHistory',
async (_, { rejectWithValue }) => {
try {
const data = await healthProfileApi.getHealthHistory();
return data;
} catch (err: any) {
return rejectWithValue(err?.message ?? '获取健康史失败');
}
}
);
/**
* 保存健康史分类到服务端
*/
export const saveHealthHistoryCategory = createAsyncThunk(
'health/saveHistoryCategory',
async (
{
category,
data,
}: {
category: healthProfileApi.HealthHistoryCategory;
data: healthProfileApi.UpdateHealthHistoryRequest;
},
{ rejectWithValue }
) => {
try {
const result = await healthProfileApi.updateHealthHistory(category, data);
return { category, data: result };
} catch (err: any) {
return rejectWithValue(err?.message ?? '保存健康史失败');
}
}
);
/**
* 获取健康史完成进度
*/
export const fetchHealthHistoryProgress = createAsyncThunk(
'health/fetchHistoryProgress',
async (_, { rejectWithValue }) => {
try {
const data = await healthProfileApi.getHealthHistoryProgress();
return data;
} catch (err: any) {
return rejectWithValue(err?.message ?? '获取健康史进度失败');
}
}
);
// ==================== 就医资料 API Thunks ====================
/**
* 获取就医资料
*/
export const fetchMedicalRecords = createAsyncThunk(
'health/fetchMedicalRecords',
async (_, { rejectWithValue }) => {
try {
const data = await healthProfileApi.getMedicalRecords();
return data;
} catch (err: any) {
return rejectWithValue(err?.message ?? '获取就医资料失败');
}
}
);
/**
* 添加就医资料
*/
export const addNewMedicalRecord = createAsyncThunk(
'health/addMedicalRecord',
async (data: Omit<MedicalRecordItem, 'id' | 'createdAt' | 'updatedAt'>, { rejectWithValue }) => {
try {
const result = await healthProfileApi.addMedicalRecord(data);
return result;
} catch (err: any) {
return rejectWithValue(err?.message ?? '添加就医资料失败');
}
}
);
/**
* 删除就医资料
*/
export const deleteMedicalRecordItem = createAsyncThunk(
'health/deleteMedicalRecord',
async ({ id, type }: { id: string; type: MedicalRecordType }, { rejectWithValue }) => {
try {
await healthProfileApi.deleteMedicalRecord(id);
return { id, type };
} catch (err: any) {
return rejectWithValue(err?.message ?? '删除就医资料失败');
}
}
);
// Selectors
export const selectHealthDataByDate = (date: string) => (state: RootState) => state.health.dataByDate[date];
export const selectHealthLoading = (state: RootState) => state.health.loading;
export const selectHealthError = (state: RootState) => state.health.error;
export const selectLastUpdateTime = (state: RootState) => state.health.lastUpdateTime;
export const selectHistoryData = (state: RootState) => state.health.historyData;
export const selectMedicalRecords = (state: RootState) => state.health.medicalRecords;
// 计算健康史完成度的 selector
export const selectHealthHistoryProgress = (state: RootState) => {
const historyData = state.health.historyData;
const categories = ['allergy', 'disease', 'surgery', 'familyDisease'];
let answeredCount = 0;
categories.forEach(category => {
const data = historyData[category];
// 只要回答了是否有历史hasHistory !== null就算已完成
if (data && data.hasHistory !== null) {
answeredCount++;
}
});
return Math.round((answeredCount / categories.length) * 100);
};
export default healthSlice.reducer;