feat(medications): 实现完整的用药管理功能
添加了药物管理的核心功能,包括: - 药物列表展示和状态管理 - 添加新药物的完整流程 - 服药记录的创建和状态更新 - 药物管理界面,支持激活/停用操作 - Redux状态管理和API服务层 - 相关类型定义和辅助函数 主要文件: - app/(tabs)/medications.tsx - 主界面,集成Redux数据 - app/medications/add-medication.tsx - 添加药物流程 - app/medications/manage-medications.tsx - 药物管理界面 - store/medicationsSlice.ts - Redux状态管理 - services/medications.ts - API服务层 - types/medication.ts - 类型定义
This commit is contained in:
724
store/medicationsSlice.ts
Normal file
724
store/medicationsSlice.ts
Normal file
@@ -0,0 +1,724 @@
|
||||
/**
|
||||
* 药物管理 Redux Slice
|
||||
*/
|
||||
|
||||
import * as medicationsApi from '@/services/medications';
|
||||
import type {
|
||||
DailyMedicationStats,
|
||||
Medication,
|
||||
MedicationRecord,
|
||||
MedicationStatus,
|
||||
} from '@/types/medication';
|
||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import dayjs from 'dayjs';
|
||||
import type { RootState } from './index';
|
||||
|
||||
// ==================== 状态接口 ====================
|
||||
|
||||
interface MedicationsState {
|
||||
// 药物列表
|
||||
medications: Medication[];
|
||||
// 激活的药物列表(快速访问)
|
||||
activeMedications: Medication[];
|
||||
|
||||
// 按日期存储的服药记录 { 'YYYY-MM-DD': MedicationRecord[] }
|
||||
medicationRecords: Record<string, MedicationRecord[]>;
|
||||
|
||||
// 每日统计 { 'YYYY-MM-DD': DailyMedicationStats }
|
||||
dailyStats: Record<string, DailyMedicationStats>;
|
||||
|
||||
// 总体统计
|
||||
overallStats: {
|
||||
totalMedications: number;
|
||||
totalRecords: number;
|
||||
completionRate: number;
|
||||
streak: number;
|
||||
} | null;
|
||||
|
||||
// 当前选中的日期
|
||||
selectedDate: string;
|
||||
|
||||
// 加载状态
|
||||
loading: {
|
||||
medications: boolean;
|
||||
records: boolean;
|
||||
stats: boolean;
|
||||
create: boolean;
|
||||
update: boolean;
|
||||
delete: boolean;
|
||||
takeMedication: boolean;
|
||||
};
|
||||
|
||||
// 错误信息
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// ==================== 初始状态 ====================
|
||||
|
||||
const initialState: MedicationsState = {
|
||||
medications: [],
|
||||
activeMedications: [],
|
||||
medicationRecords: {},
|
||||
dailyStats: {},
|
||||
overallStats: null,
|
||||
selectedDate: dayjs().format('YYYY-MM-DD'),
|
||||
loading: {
|
||||
medications: false,
|
||||
records: false,
|
||||
stats: false,
|
||||
create: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
takeMedication: false,
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
||||
// ==================== 异步 Thunks ====================
|
||||
|
||||
/**
|
||||
* 获取药物列表
|
||||
*/
|
||||
export const fetchMedications = createAsyncThunk(
|
||||
'medications/fetchMedications',
|
||||
async (params?: medicationsApi.GetMedicationsParams) => {
|
||||
return await medicationsApi.getMedications(params);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取指定日期的服药记录
|
||||
*/
|
||||
export const fetchMedicationRecords = createAsyncThunk(
|
||||
'medications/fetchMedicationRecords',
|
||||
async (params: { date: string }) => {
|
||||
const records = await medicationsApi.getMedicationRecords(params);
|
||||
return { date: params.date, records };
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取今日服药记录
|
||||
*/
|
||||
export const fetchTodayMedicationRecords = createAsyncThunk(
|
||||
'medications/fetchTodayMedicationRecords',
|
||||
async () => {
|
||||
const records = await medicationsApi.getTodayMedicationRecords();
|
||||
const today = dayjs().format('YYYY-MM-DD');
|
||||
return { date: today, records };
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取日期范围内的服药记录
|
||||
*/
|
||||
export const fetchMedicationRecordsByDateRange = createAsyncThunk(
|
||||
'medications/fetchMedicationRecordsByDateRange',
|
||||
async (params: { startDate: string; endDate: string }) => {
|
||||
const records = await medicationsApi.getMedicationRecords(params);
|
||||
return { params, records };
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取每日统计
|
||||
*/
|
||||
export const fetchDailyStats = createAsyncThunk(
|
||||
'medications/fetchDailyStats',
|
||||
async (date: string) => {
|
||||
const stats = await medicationsApi.getDailyStats(date);
|
||||
return { date, stats };
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取总体统计
|
||||
*/
|
||||
export const fetchOverallStats = createAsyncThunk(
|
||||
'medications/fetchOverallStats',
|
||||
async () => {
|
||||
return await medicationsApi.getOverallStats();
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 创建新药物
|
||||
*/
|
||||
export const createMedicationAction = createAsyncThunk(
|
||||
'medications/createMedication',
|
||||
async (dto: medicationsApi.CreateMedicationDto) => {
|
||||
return await medicationsApi.createMedication(dto);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 更新药物信息
|
||||
*/
|
||||
export const updateMedicationAction = createAsyncThunk(
|
||||
'medications/updateMedication',
|
||||
async (dto: medicationsApi.UpdateMedicationDto) => {
|
||||
return await medicationsApi.updateMedication(dto);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 删除药物
|
||||
*/
|
||||
export const deleteMedicationAction = createAsyncThunk(
|
||||
'medications/deleteMedication',
|
||||
async (id: string) => {
|
||||
await medicationsApi.deleteMedication(id);
|
||||
return id;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 停用药物
|
||||
*/
|
||||
export const deactivateMedicationAction = createAsyncThunk(
|
||||
'medications/deactivateMedication',
|
||||
async (id: string) => {
|
||||
return await medicationsApi.deactivateMedication(id);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 服用药物
|
||||
*/
|
||||
export const takeMedicationAction = createAsyncThunk(
|
||||
'medications/takeMedication',
|
||||
async (params: { recordId: string; actualTime?: string }) => {
|
||||
return await medicationsApi.takeMedication(params.recordId, params.actualTime);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 跳过药物
|
||||
*/
|
||||
export const skipMedicationAction = createAsyncThunk(
|
||||
'medications/skipMedication',
|
||||
async (params: { recordId: string; note?: string }) => {
|
||||
return await medicationsApi.skipMedication(params.recordId, params.note);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 更新服药记录
|
||||
*/
|
||||
export const updateMedicationRecordAction = createAsyncThunk(
|
||||
'medications/updateMedicationRecord',
|
||||
async (dto: medicationsApi.UpdateMedicationRecordDto) => {
|
||||
return await medicationsApi.updateMedicationRecord(dto);
|
||||
}
|
||||
);
|
||||
|
||||
// ==================== Slice ====================
|
||||
|
||||
const medicationsSlice = createSlice({
|
||||
name: 'medications',
|
||||
initialState,
|
||||
reducers: {
|
||||
/**
|
||||
* 设置选中的日期
|
||||
*/
|
||||
setSelectedDate: (state, action: PayloadAction<string>) => {
|
||||
state.selectedDate = action.payload;
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除错误信息
|
||||
*/
|
||||
clearError: (state) => {
|
||||
state.error = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除所有药物数据
|
||||
*/
|
||||
clearMedicationsData: (state) => {
|
||||
state.medications = [];
|
||||
state.activeMedications = [];
|
||||
state.medicationRecords = {};
|
||||
state.dailyStats = {};
|
||||
state.overallStats = null;
|
||||
state.error = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除服药记录
|
||||
*/
|
||||
clearMedicationRecords: (state) => {
|
||||
state.medicationRecords = {};
|
||||
state.dailyStats = {};
|
||||
},
|
||||
|
||||
/**
|
||||
* 本地更新记录状态(用于乐观更新)
|
||||
*/
|
||||
updateRecordStatusLocally: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
recordId: string;
|
||||
status: MedicationStatus;
|
||||
date: string;
|
||||
actualTime?: string;
|
||||
}>
|
||||
) => {
|
||||
const { recordId, status, date, actualTime } = action.payload;
|
||||
const records = state.medicationRecords[date];
|
||||
if (records) {
|
||||
const record = records.find((r) => r.id === recordId);
|
||||
if (record) {
|
||||
record.status = status;
|
||||
if (actualTime) {
|
||||
record.actualTime = actualTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新统计数据
|
||||
const stats = state.dailyStats[date];
|
||||
if (stats) {
|
||||
if (status === 'taken') {
|
||||
stats.taken += 1;
|
||||
stats.upcoming = Math.max(0, stats.upcoming - 1);
|
||||
} else if (status === 'missed') {
|
||||
stats.missed += 1;
|
||||
stats.upcoming = Math.max(0, stats.upcoming - 1);
|
||||
} else if (status === 'skipped') {
|
||||
stats.upcoming = Math.max(0, stats.upcoming - 1);
|
||||
}
|
||||
stats.completionRate = stats.totalScheduled > 0
|
||||
? (stats.taken / stats.totalScheduled) * 100
|
||||
: 0;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 添加本地服药记录(用于离线场景)
|
||||
*/
|
||||
addLocalMedicationRecord: (state, action: PayloadAction<MedicationRecord>) => {
|
||||
const record = action.payload;
|
||||
const date = dayjs(record.scheduledTime).format('YYYY-MM-DD');
|
||||
|
||||
if (!state.medicationRecords[date]) {
|
||||
state.medicationRecords[date] = [];
|
||||
}
|
||||
|
||||
// 检查是否已存在相同ID的记录
|
||||
const existingIndex = state.medicationRecords[date].findIndex(
|
||||
(r) => r.id === record.id
|
||||
);
|
||||
if (existingIndex >= 0) {
|
||||
state.medicationRecords[date][existingIndex] = record;
|
||||
} else {
|
||||
state.medicationRecords[date].push(record);
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// ==================== fetchMedications ====================
|
||||
builder
|
||||
.addCase(fetchMedications.pending, (state) => {
|
||||
state.loading.medications = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchMedications.fulfilled, (state, action) => {
|
||||
console.log('action', action);
|
||||
|
||||
state.loading.medications = false;
|
||||
state.medications = action.payload;
|
||||
state.activeMedications = action.payload.filter((m) => m.isActive);
|
||||
})
|
||||
.addCase(fetchMedications.rejected, (state, action) => {
|
||||
state.loading.medications = false;
|
||||
state.error = action.error.message || '获取药物列表失败';
|
||||
});
|
||||
|
||||
// ==================== fetchMedicationRecords ====================
|
||||
builder
|
||||
.addCase(fetchMedicationRecords.pending, (state) => {
|
||||
state.loading.records = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchMedicationRecords.fulfilled, (state, action) => {
|
||||
state.loading.records = false;
|
||||
const { date, records } = action.payload;
|
||||
state.medicationRecords[date] = records;
|
||||
})
|
||||
.addCase(fetchMedicationRecords.rejected, (state, action) => {
|
||||
state.loading.records = false;
|
||||
state.error = action.error.message || '获取服药记录失败';
|
||||
});
|
||||
|
||||
// ==================== fetchTodayMedicationRecords ====================
|
||||
builder
|
||||
.addCase(fetchTodayMedicationRecords.pending, (state) => {
|
||||
state.loading.records = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchTodayMedicationRecords.fulfilled, (state, action) => {
|
||||
state.loading.records = false;
|
||||
const { date, records } = action.payload;
|
||||
state.medicationRecords[date] = records;
|
||||
})
|
||||
.addCase(fetchTodayMedicationRecords.rejected, (state, action) => {
|
||||
state.loading.records = false;
|
||||
state.error = action.error.message || '获取今日服药记录失败';
|
||||
});
|
||||
|
||||
// ==================== fetchMedicationRecordsByDateRange ====================
|
||||
builder
|
||||
.addCase(fetchMedicationRecordsByDateRange.pending, (state) => {
|
||||
state.loading.records = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchMedicationRecordsByDateRange.fulfilled, (state, action) => {
|
||||
state.loading.records = false;
|
||||
const { records } = action.payload;
|
||||
|
||||
// 按日期分组存储记录
|
||||
records.forEach((record) => {
|
||||
const date = dayjs(record.scheduledTime).format('YYYY-MM-DD');
|
||||
if (!state.medicationRecords[date]) {
|
||||
state.medicationRecords[date] = [];
|
||||
}
|
||||
|
||||
// 检查是否已存在相同ID的记录
|
||||
const existingIndex = state.medicationRecords[date].findIndex(
|
||||
(r) => r.id === record.id
|
||||
);
|
||||
if (existingIndex >= 0) {
|
||||
state.medicationRecords[date][existingIndex] = record;
|
||||
} else {
|
||||
state.medicationRecords[date].push(record);
|
||||
}
|
||||
});
|
||||
})
|
||||
.addCase(fetchMedicationRecordsByDateRange.rejected, (state, action) => {
|
||||
state.loading.records = false;
|
||||
state.error = action.error.message || '获取服药记录失败';
|
||||
});
|
||||
|
||||
// ==================== fetchDailyStats ====================
|
||||
builder
|
||||
.addCase(fetchDailyStats.pending, (state) => {
|
||||
state.loading.stats = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchDailyStats.fulfilled, (state, action) => {
|
||||
state.loading.stats = false;
|
||||
const { date, stats } = action.payload;
|
||||
state.dailyStats[date] = stats;
|
||||
})
|
||||
.addCase(fetchDailyStats.rejected, (state, action) => {
|
||||
state.loading.stats = false;
|
||||
state.error = action.error.message || '获取统计数据失败';
|
||||
});
|
||||
|
||||
// ==================== fetchOverallStats ====================
|
||||
builder
|
||||
.addCase(fetchOverallStats.pending, (state) => {
|
||||
state.loading.stats = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchOverallStats.fulfilled, (state, action) => {
|
||||
state.loading.stats = false;
|
||||
state.overallStats = action.payload;
|
||||
})
|
||||
.addCase(fetchOverallStats.rejected, (state, action) => {
|
||||
state.loading.stats = false;
|
||||
state.error = action.error.message || '获取总体统计失败';
|
||||
});
|
||||
|
||||
// ==================== createMedication ====================
|
||||
builder
|
||||
.addCase(createMedicationAction.pending, (state) => {
|
||||
state.loading.create = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(createMedicationAction.fulfilled, (state, action) => {
|
||||
state.loading.create = false;
|
||||
const newMedication = action.payload;
|
||||
state.medications.push(newMedication);
|
||||
if (newMedication.isActive) {
|
||||
state.activeMedications.push(newMedication);
|
||||
}
|
||||
})
|
||||
.addCase(createMedicationAction.rejected, (state, action) => {
|
||||
state.loading.create = false;
|
||||
state.error = action.error.message || '创建药物失败';
|
||||
});
|
||||
|
||||
// ==================== updateMedication ====================
|
||||
builder
|
||||
.addCase(updateMedicationAction.pending, (state) => {
|
||||
state.loading.update = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(updateMedicationAction.fulfilled, (state, action) => {
|
||||
state.loading.update = false;
|
||||
const updated = action.payload;
|
||||
const index = state.medications.findIndex((m) => m.id === updated.id);
|
||||
if (index >= 0) {
|
||||
// 只有当 isActive 状态改变时才更新 activeMedications
|
||||
const wasActive = state.medications[index].isActive;
|
||||
const isActiveNow = updated.isActive;
|
||||
|
||||
// 更新药品信息
|
||||
state.medications[index] = updated;
|
||||
|
||||
// 优化:只有当 isActive 状态改变时才重新计算 activeMedications
|
||||
if (wasActive !== isActiveNow) {
|
||||
if (isActiveNow) {
|
||||
// 激活药品:添加到 activeMedications(如果不在其中)
|
||||
if (!state.activeMedications.some(m => m.id === updated.id)) {
|
||||
state.activeMedications.push(updated);
|
||||
}
|
||||
} else {
|
||||
// 停用药品:从 activeMedications 中移除
|
||||
state.activeMedications = state.activeMedications.filter(
|
||||
(m) => m.id !== updated.id
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// isActive 状态未改变,只需更新 activeMedications 中的对应项
|
||||
const activeIndex = state.activeMedications.findIndex((m) => m.id === updated.id);
|
||||
if (activeIndex >= 0) {
|
||||
state.activeMedications[activeIndex] = updated;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.addCase(updateMedicationAction.rejected, (state, action) => {
|
||||
state.loading.update = false;
|
||||
state.error = action.error.message || '更新药物失败';
|
||||
});
|
||||
|
||||
// ==================== deleteMedication ====================
|
||||
builder
|
||||
.addCase(deleteMedicationAction.pending, (state) => {
|
||||
state.loading.delete = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(deleteMedicationAction.fulfilled, (state, action) => {
|
||||
state.loading.delete = false;
|
||||
const deletedId = action.payload;
|
||||
state.medications = state.medications.filter((m) => m.id !== deletedId);
|
||||
state.activeMedications = state.activeMedications.filter(
|
||||
(m) => m.id !== deletedId
|
||||
);
|
||||
})
|
||||
.addCase(deleteMedicationAction.rejected, (state, action) => {
|
||||
state.loading.delete = false;
|
||||
state.error = action.error.message || '删除药物失败';
|
||||
});
|
||||
|
||||
// ==================== deactivateMedication ====================
|
||||
builder
|
||||
.addCase(deactivateMedicationAction.pending, (state) => {
|
||||
state.loading.update = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(deactivateMedicationAction.fulfilled, (state, action) => {
|
||||
state.loading.update = false;
|
||||
const updated = action.payload;
|
||||
const index = state.medications.findIndex((m) => m.id === updated.id);
|
||||
if (index >= 0) {
|
||||
state.medications[index] = updated;
|
||||
}
|
||||
// 从激活列表中移除
|
||||
state.activeMedications = state.activeMedications.filter(
|
||||
(m) => m.id !== updated.id
|
||||
);
|
||||
})
|
||||
.addCase(deactivateMedicationAction.rejected, (state, action) => {
|
||||
state.loading.update = false;
|
||||
state.error = action.error.message || '停用药物失败';
|
||||
});
|
||||
|
||||
// ==================== takeMedication ====================
|
||||
builder
|
||||
.addCase(takeMedicationAction.pending, (state) => {
|
||||
state.loading.takeMedication = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(takeMedicationAction.fulfilled, (state, action) => {
|
||||
state.loading.takeMedication = false;
|
||||
const updated = action.payload;
|
||||
const date = dayjs(updated.scheduledTime).format('YYYY-MM-DD');
|
||||
const records = state.medicationRecords[date];
|
||||
if (records) {
|
||||
const index = records.findIndex((r) => r.id === updated.id);
|
||||
if (index >= 0) {
|
||||
records[index] = updated;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新统计数据
|
||||
const stats = state.dailyStats[date];
|
||||
if (stats) {
|
||||
stats.taken += 1;
|
||||
stats.upcoming = Math.max(0, stats.upcoming - 1);
|
||||
stats.completionRate = stats.totalScheduled > 0
|
||||
? (stats.taken / stats.totalScheduled) * 100
|
||||
: 0;
|
||||
}
|
||||
})
|
||||
.addCase(takeMedicationAction.rejected, (state, action) => {
|
||||
state.loading.takeMedication = false;
|
||||
state.error = action.error.message || '服药操作失败';
|
||||
});
|
||||
|
||||
// ==================== skipMedication ====================
|
||||
builder
|
||||
.addCase(skipMedicationAction.pending, (state) => {
|
||||
state.loading.takeMedication = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(skipMedicationAction.fulfilled, (state, action) => {
|
||||
state.loading.takeMedication = false;
|
||||
const updated = action.payload;
|
||||
const date = dayjs(updated.scheduledTime).format('YYYY-MM-DD');
|
||||
const records = state.medicationRecords[date];
|
||||
if (records) {
|
||||
const index = records.findIndex((r) => r.id === updated.id);
|
||||
if (index >= 0) {
|
||||
records[index] = updated;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新统计数据
|
||||
const stats = state.dailyStats[date];
|
||||
if (stats) {
|
||||
stats.upcoming = Math.max(0, stats.upcoming - 1);
|
||||
}
|
||||
})
|
||||
.addCase(skipMedicationAction.rejected, (state, action) => {
|
||||
state.loading.takeMedication = false;
|
||||
state.error = action.error.message || '跳过操作失败';
|
||||
});
|
||||
|
||||
// ==================== updateMedicationRecord ====================
|
||||
builder
|
||||
.addCase(updateMedicationRecordAction.pending, (state) => {
|
||||
state.loading.update = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(updateMedicationRecordAction.fulfilled, (state, action) => {
|
||||
state.loading.update = false;
|
||||
const updated = action.payload;
|
||||
const date = dayjs(updated.scheduledTime).format('YYYY-MM-DD');
|
||||
const records = state.medicationRecords[date];
|
||||
if (records) {
|
||||
const index = records.findIndex((r) => r.id === updated.id);
|
||||
if (index >= 0) {
|
||||
records[index] = updated;
|
||||
}
|
||||
}
|
||||
})
|
||||
.addCase(updateMedicationRecordAction.rejected, (state, action) => {
|
||||
state.loading.update = false;
|
||||
state.error = action.error.message || '更新服药记录失败';
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// ==================== Actions ====================
|
||||
|
||||
export const {
|
||||
setSelectedDate,
|
||||
clearError,
|
||||
clearMedicationsData,
|
||||
clearMedicationRecords,
|
||||
updateRecordStatusLocally,
|
||||
addLocalMedicationRecord,
|
||||
} = medicationsSlice.actions;
|
||||
|
||||
// ==================== Selectors ====================
|
||||
|
||||
export const selectMedicationsState = (state: RootState) => state.medications;
|
||||
export const selectMedications = (state: RootState) => state.medications.medications;
|
||||
export const selectActiveMedications = (state: RootState) =>
|
||||
state.medications.activeMedications;
|
||||
export const selectSelectedDate = (state: RootState) => state.medications.selectedDate;
|
||||
export const selectMedicationsLoading = (state: RootState) => state.medications.loading;
|
||||
export const selectMedicationsError = (state: RootState) => state.medications.error;
|
||||
export const selectOverallStats = (state: RootState) => state.medications.overallStats;
|
||||
|
||||
/**
|
||||
* 获取指定日期的服药记录
|
||||
*/
|
||||
export const selectMedicationRecordsByDate = (date: string) => (state: RootState) => {
|
||||
return state.medications.medicationRecords[date] || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前选中日期的服药记录
|
||||
*/
|
||||
export const selectSelectedDateMedicationRecords = (state: RootState) => {
|
||||
const selectedDate = state.medications.selectedDate;
|
||||
return state.medications.medicationRecords[selectedDate] || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取指定日期的统计数据
|
||||
*/
|
||||
export const selectDailyStatsByDate = (date: string) => (state: RootState) => {
|
||||
return state.medications.dailyStats[date];
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前选中日期的统计数据
|
||||
*/
|
||||
export const selectSelectedDateStats = (state: RootState) => {
|
||||
const selectedDate = state.medications.selectedDate;
|
||||
return state.medications.dailyStats[selectedDate];
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取指定日期的展示项列表(用于UI渲染)
|
||||
* 将药物记录和药物信息合并为展示项
|
||||
*/
|
||||
export const selectMedicationDisplayItemsByDate = (date: string) => (state: RootState) => {
|
||||
const records = state.medications.medicationRecords[date] || [];
|
||||
const medications = state.medications.medications;
|
||||
|
||||
// 创建药物ID到药物的映射
|
||||
const medicationMap = new Map<string, Medication>();
|
||||
medications.forEach((med) => medicationMap.set(med.id, med));
|
||||
|
||||
// 转换为展示项
|
||||
return records
|
||||
.map((record) => {
|
||||
const medication = record.medication || medicationMap.get(record.medicationId);
|
||||
if (!medication) return null;
|
||||
|
||||
// 格式化剂量
|
||||
const dosage = `${medication.dosageValue} ${medication.dosageUnit}`;
|
||||
|
||||
// 提取并格式化为当地时间(HH:mm格式)
|
||||
// 服务端返回的是UTC时间,需要转换为用户本地时间显示
|
||||
const localTime = dayjs(record.scheduledTime).format('HH:mm');
|
||||
const scheduledTime = localTime || '00:00';
|
||||
|
||||
// 频率描述
|
||||
const frequency = medication.repeatPattern === 'daily' ? '每日' : '自定义';
|
||||
|
||||
return {
|
||||
id: record.id,
|
||||
name: medication.name,
|
||||
dosage,
|
||||
scheduledTime,
|
||||
frequency,
|
||||
status: record.status,
|
||||
recordId: record.id,
|
||||
medicationId: medication.id,
|
||||
} as import('@/types/medication').MedicationDisplayItem;
|
||||
})
|
||||
.filter((item): item is import('@/types/medication').MedicationDisplayItem => item !== null);
|
||||
};
|
||||
|
||||
// ==================== Export ====================
|
||||
|
||||
export default medicationsSlice.reducer;
|
||||
Reference in New Issue
Block a user