Files
digital-pilates/services/medications.ts
richarjiang 84abfa2506 feat(medication): 重构AI分析为结构化展示并支持喝水提醒个性化配置
- 将药品AI分析从Markdown流式输出重构为结构化数据展示(V2)
- 新增适合人群、不适合人群、主要成分、副作用等分类卡片展示
- 优化AI分析UI布局,采用卡片式设计提升可读性
- 新增药品跳过功能,支持用户标记本次用药为已跳过
- 修复喝水提醒逻辑,支持用户开关控制和自定义时间段配置
- 优化个人资料编辑页面键盘适配,避免输入框被遮挡
- 统一API响应码处理,兼容200和0两种成功状态码
- 更新版本号至1.0.28

BREAKING CHANGE: 药品AI分析接口从流式Markdown输出改为结构化JSON格式,旧版本分析结果将不再显示
2025-11-20 10:10:53 +08:00

347 lines
8.1 KiB
TypeScript

/**
* 药物管理 API 服务
*/
import type {
DailyMedicationStats,
Medication,
MedicationAiAnalysisV2,
MedicationForm,
MedicationRecord,
MedicationStatus,
RepeatPattern,
} from '@/types/medication';
import { api, postTextStream, type TextStreamCallbacks } from './api';
// ==================== DTO 类型定义 ====================
/**
* 创建药物 DTO
*/
export interface CreateMedicationDto {
name: string;
photoUrl?: string | null;
form: MedicationForm;
dosageValue: number;
dosageUnit: string;
timesPerDay: number;
medicationTimes: string[];
startDate: string;
endDate?: string | null;
repeatPattern?: RepeatPattern;
note?: string;
}
/**
* 更新药物 DTO
*/
export interface UpdateMedicationDto extends Partial<CreateMedicationDto> {
id: string;
isActive?: boolean;
}
/**
* 创建服药记录 DTO
*/
export interface CreateMedicationRecordDto {
medicationId: string;
scheduledTime: string;
actualTime?: string;
status: MedicationStatus;
note?: string;
}
/**
* 更新服药记录 DTO
*/
export interface UpdateMedicationRecordDto {
id: string;
actualTime?: string;
status?: MedicationStatus;
note?: string;
}
/**
* 获取药物列表参数
*/
export interface GetMedicationsParams {
isActive?: boolean;
startDate?: string;
endDate?: string;
}
/**
* 获取服药记录参数
*/
export interface GetMedicationRecordsParams {
date?: string;
medicationId?: string;
startDate?: string;
endDate?: string;
}
// ==================== API 函数 ====================
/**
* 获取药物列表
* @param params 查询参数
* @returns 药物列表
*/
export const getMedications = async (
params?: GetMedicationsParams
): Promise<Medication[]> => {
const queryParams = new URLSearchParams();
if (params?.startDate) {
queryParams.append('startDate', params.startDate);
}
if (params?.endDate) {
queryParams.append('endDate', params.endDate);
}
const query = queryParams.toString();
const path = query ? `/medications?${query}` : '/medications';
const response = await api.get<{ rows: Medication[]; total: number }>(path);
// 处理不同的响应格式
if (Array.isArray(response)) {
return response;
} else if (response && typeof response === 'object' && 'rows' in response) {
return response.rows;
} else {
return [];
}
};
/**
* 根据 ID 获取单个药物
* @param id 药物 ID
* @returns 药物详情
*/
export const getMedicationById = async (id: string): Promise<Medication> => {
return api.get<Medication>(`/medications/${id}`);
};
/**
* 创建新药物
* @param dto 创建药物数据
* @returns 创建的药物
*/
export const createMedication = async (
dto: CreateMedicationDto
): Promise<Medication> => {
return api.post<Medication>('/medications', dto);
};
/**
* 更新药物信息
* @param dto 更新药物数据
* @returns 更新后的药物
*/
export const updateMedication = async (
dto: UpdateMedicationDto
): Promise<Medication> => {
const { id, ...data } = dto;
return api.put<Medication>(`/medications/${id}`, data);
};
/**
* 删除药物
* @param id 药物 ID
*/
export const deleteMedication = async (id: string): Promise<void> => {
return api.delete<void>(`/medications/${id}`);
};
/**
* 停用药物
* @param id 药物 ID
* @returns 更新后的药物
*/
export const deactivateMedication = async (id: string): Promise<Medication> => {
return api.post<Medication>(`/medications/${id}/deactivate`, {});
};
/**
* 激活药物(暂不支持,需要通过更新接口实现)
* @param id 药物 ID
* @returns 更新后的药物
*/
export const activateMedication = async (id: string): Promise<Medication> => {
return api.put<Medication>(`/medications/${id}`, { isActive: true });
};
// ==================== 服药记录相关 ====================
/**
* 获取服药记录列表
* @param params 查询参数
* @returns 服药记录列表
*/
export const getMedicationRecords = async (
params: GetMedicationRecordsParams
): Promise<MedicationRecord[]> => {
const queryParams = new URLSearchParams();
if (params.date) {
queryParams.append('date', params.date);
}
if (params.medicationId) {
queryParams.append('medicationId', params.medicationId);
}
if (params.startDate) {
queryParams.append('startDate', params.startDate);
}
if (params.endDate) {
queryParams.append('endDate', params.endDate);
}
const query = queryParams.toString();
const path = query ? `/medication-records?${query}` : '/medication-records';
return api.get<MedicationRecord[]>(path);
};
/**
* 获取今日服药记录
* @returns 今日服药记录列表
*/
export const getTodayMedicationRecords = async (): Promise<MedicationRecord[]> => {
return api.get<MedicationRecord[]>('/medication-records/today');
};
/**
* 创建服药记录
* @param dto 创建服药记录数据
* @returns 创建的服药记录
*/
export const createMedicationRecord = async (
dto: CreateMedicationRecordDto
): Promise<MedicationRecord> => {
return api.post<MedicationRecord>('/medication-records', dto);
};
/**
* 更新服药记录
* @param dto 更新服药记录数据
* @returns 更新后的服药记录
*/
export const updateMedicationRecord = async (
dto: UpdateMedicationRecordDto
): Promise<MedicationRecord> => {
const { id, ...data } = dto;
return api.put<MedicationRecord>(`/medication-records/${id}`, data);
};
/**
* 删除服药记录
* @param id 服药记录 ID
*/
export const deleteMedicationRecord = async (id: string): Promise<void> => {
return api.delete<void>(`/medication-records/${id}`);
};
/**
* 标记药物为已服用
* @param recordId 服药记录 ID
* @param actualTime 实际服药时间(可选,默认为当前时间)
* @returns 更新后的服药记录
*/
export const takeMedication = async (
recordId: string,
actualTime?: string
): Promise<MedicationRecord> => {
return api.post<MedicationRecord>(`/medication-records/${recordId}/take`, {
actualTime: actualTime || new Date().toISOString(),
});
};
/**
* 标记药物为已跳过
* @param recordId 服药记录 ID
* @param note 跳过原因(可选)
* @returns 更新后的服药记录
*/
export const skipMedication = async (
recordId: string,
note?: string
): Promise<MedicationRecord> => {
return api.post<MedicationRecord>(`/medication-records/${recordId}/skip`, {
note,
});
};
// ==================== 统计相关 ====================
/**
* 获取指定日期的服药统计
* @param date 日期 'YYYY-MM-DD'
* @returns 每日服药统计
*/
export const getDailyStats = async (
date: string
): Promise<DailyMedicationStats> => {
return api.get<DailyMedicationStats>(`/medication-stats/daily?date=${date}`);
};
/**
* 获取日期范围内的服药统计
* @param startDate 开始日期
* @param endDate 结束日期
* @returns 统计数据列表
*/
export const getStatsRange = async (
startDate: string,
endDate: string
): Promise<DailyMedicationStats[]> => {
return api.get<DailyMedicationStats[]>(
`/medication-stats/range?startDate=${startDate}&endDate=${endDate}`
);
};
/**
* 获取总体统计
* @returns 总体统计数据
*/
export const getOverallStats = async (): Promise<{
totalMedications: number;
totalRecords: number;
completionRate: number;
streak: number;
}> => {
return api.get(`/medication-stats/overall`);
};
// ==================== AI 分析相关 ====================
/**
* 流式获取药品 AI 分析
* @param medicationId 药品 ID
* @param callbacks 流式回调
* @returns 包含 abort 方法的对象,用于取消请求
*/
export async function analyzeMedicationStream(
medicationId: string,
callbacks: TextStreamCallbacks
) {
return postTextStream(
`/api/medications/${medicationId}/ai-analysis`,
{},
callbacks,
{ timeoutMs: 120000 }
);
}
/**
* 获取药品 AI 分析 V2 结构化报告
* @param medicationId 药品 ID
* @returns 结构化 AI 分析结果
*/
export async function analyzeMedicationV2(
medicationId: string
): Promise<MedicationAiAnalysisV2> {
return api.post<MedicationAiAnalysisV2>(
`/api/medications/${medicationId}/ai-analysis/v2`,
{}
);
}