feat: 支持饮水记录卡片

This commit is contained in:
richarjiang
2025-09-02 15:50:35 +08:00
parent ed694f6142
commit 85a3c742df
16 changed files with 2066 additions and 56 deletions

View File

@@ -9,8 +9,8 @@ export async function setAuthToken(token: string | null): Promise<void> {
inMemoryToken = token;
}
export function getAuthToken(): string | null {
return inMemoryToken;
export function getAuthToken(): Promise<string | null> {
return AsyncStorage.getItem(STORAGE_KEYS.authToken);
}
export type ApiRequestOptions = {
@@ -31,7 +31,7 @@ async function doFetch<T>(path: string, options: ApiRequestOptions = {}): Promis
...(options.headers || {}),
};
const token = getAuthToken();
const token = await getAuthToken();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
@@ -77,14 +77,6 @@ export const STORAGE_KEYS = {
privacyAgreed: '@privacy_agreed',
} as const;
export async function loadPersistedToken(): Promise<string | null> {
try {
const t = await AsyncStorage.getItem(STORAGE_KEYS.authToken);
return t || null;
} catch {
return null;
}
}
// 流式文本 POST基于 XMLHttpRequest支持增量 onChunk 回调与取消
export type TextStreamCallbacks = {
@@ -99,9 +91,9 @@ export type TextStreamOptions = {
signal?: AbortSignal;
};
export function postTextStream(path: string, body: any, callbacks: TextStreamCallbacks, options: TextStreamOptions = {}) {
export async function postTextStream(path: string, body: any, callbacks: TextStreamCallbacks, options: TextStreamOptions = {}) {
const url = buildApiUrl(path);
const token = getAuthToken();
const token = await getAuthToken();
// 生成请求ID用于追踪和取消
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
@@ -128,11 +120,6 @@ export function postTextStream(path: string, body: any, callbacks: TextStreamCal
resolved = true;
};
// 日志:请求开始
try {
console.log('[AI_CHAT][stream] start', { url, hasToken: !!token, body });
} catch { }
xhr.open('POST', url, true);
// 设置超时(可选)
if (typeof options.timeoutMs === 'number') {

View File

@@ -100,8 +100,7 @@ export class NotificationService {
this.setupNotificationListeners();
// 检查已存在的通知
const existingNotifications = await this.getAllScheduledNotifications();
console.log('已存在的通知数量:', existingNotifications.length);
await this.getAllScheduledNotifications();
this.isInitialized = true;
console.log('推送通知服务初始化成功');

167
services/waterRecords.ts Normal file
View File

@@ -0,0 +1,167 @@
import { api } from './api';
// 喝水记录类型
export interface WaterRecord {
id: string;
userId?: string;
amount: number; // 喝水量(毫升)
source?: 'Manual' | 'Auto'; // 记录来源
note?: string; // 备注
recordedAt: string; // 记录时间 ISO格式
createdAt: string; // 创建时间 ISO格式
updatedAt: string; // 更新时间 ISO格式
}
export enum WaterRecordSource {
Manual = 'manual',
Auto = 'auto',
Other = 'other',
}
// 创建喝水记录请求
export interface CreateWaterRecordDto {
amount: number; // 喝水量(毫升)
recordedAt?: string; // 记录时间,默认为当前时间
source?: WaterRecordSource; // 记录来源,默认为 'manual'
}
// 更新喝水记录请求
export interface UpdateWaterRecordDto {
id: string;
amount?: number; // 喝水量(毫升)
recordedAt?: string; // 记录时间
source?: 'Manual' | 'Auto'; // 记录来源
note?: string; // 备注
}
// 删除喝水记录请求
export interface DeleteWaterRecordDto {
id: string;
}
// 今日喝水统计
export interface TodayWaterStats {
date: string; // 统计日期
totalAmount: number; // 当日总喝水量
dailyGoal: number; // 每日目标
completionRate: number; // 完成率(百分比)
recordCount: number; // 记录次数
records?: WaterRecord[]; // 当日所有记录(可选)
}
// 更新喝水目标请求
export interface UpdateWaterGoalDto {
dailyWaterGoal: number; // 每日喝水目标(毫升)
}
// 创建喝水记录
export async function createWaterRecord(dto: CreateWaterRecordDto): Promise<WaterRecord> {
return await api.post('/water-records', dto);
}
// 获取喝水记录列表
export async function getWaterRecords(params?: {
startDate?: string; // 开始日期 (YYYY-MM-DD)
endDate?: string; // 结束日期 (YYYY-MM-DD)
page?: number; // 页码默认1
limit?: number; // 每页数量默认20
date?: string; // 指定日期格式YYYY-MM-DD (向后兼容)
}): Promise<{
records: WaterRecord[];
total: number;
page: number;
limit: number;
hasMore: boolean;
}> {
const queryParams = new URLSearchParams();
// 处理日期范围查询
if (params?.startDate) queryParams.append('startDate', params.startDate);
if (params?.endDate) queryParams.append('endDate', params.endDate);
// 处理单日期查询(向后兼容)
if (params?.date) queryParams.append('startDate', params.date);
if (params?.date) queryParams.append('endDate', params.date);
// 处理分页
const page = params?.page || 1;
const limit = params?.limit || 20;
queryParams.append('page', page.toString());
queryParams.append('limit', limit.toString());
const path = `/water-records${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
const response = await api.get<{
records: WaterRecord[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}>(path);
const pagination = response.pagination || { page, limit, total: 0, totalPages: 0 };
return {
records: response.records || [],
total: pagination.total,
page: pagination.page,
limit: pagination.limit,
hasMore: pagination.page < pagination.totalPages
};
}
// 更新喝水记录
export async function updateWaterRecord(dto: UpdateWaterRecordDto): Promise<WaterRecord> {
const { id, ...updateData } = dto;
return await api.put(`/water-records/${id}`, updateData);
}
// 删除喝水记录
export async function deleteWaterRecord(id: string): Promise<boolean> {
return await api.delete(`/water-records/${id}`);
}
// 更新喝水目标
export async function updateWaterGoal(dto: UpdateWaterGoalDto): Promise<{ dailyWaterGoal: number }> {
return await api.put('/water-records/goal/daily', dto);
}
// 获取今日喝水统计
export async function getTodayWaterStats(): Promise<TodayWaterStats> {
return await api.get('/water-records/stats');
}
// 获取指定日期的喝水统计
export async function getWaterStatsByDate(date: string): Promise<TodayWaterStats> {
return await api.get(`/water-records/stats?date=${date}`);
}
// 按小时分组获取喝水记录(用于图表显示)
export function groupWaterRecordsByHour(records: WaterRecord[]): { hour: number; amount: number }[] {
const hourlyData: { hour: number; amount: number }[] = Array.from({ length: 24 }, (_, i) => ({
hour: i,
amount: 0,
}));
records.forEach(record => {
// 优先使用 recordedAt如果没有则使用 createdAt
const dateTime = record.recordedAt || record.createdAt;
const hour = new Date(dateTime).getHours();
if (hour >= 0 && hour < 24) {
hourlyData[hour].amount += record.amount;
}
});
return hourlyData;
}
// 获取指定日期的总喝水量
export function getTotalWaterAmount(records: WaterRecord[]): number {
return records.reduce((total, record) => total + record.amount, 0);
}
// 计算喝水目标完成率
export function calculateCompletionRate(totalAmount: number, dailyGoal: number): number {
if (dailyGoal <= 0) return 0;
return Math.min(totalAmount / dailyGoal, 1);
}