import { Repository } from 'typeorm'; import { AppDataSource } from '../config/database'; import { WeightRecord } from '../entities/WeightRecord'; import { WeightGoal, GoalStatus } from '../entities/WeightGoal'; export class StatsService { private weightRecordRepository: Repository; private weightGoalRepository: Repository; constructor() { this.weightRecordRepository = AppDataSource.getRepository(WeightRecord); this.weightGoalRepository = AppDataSource.getRepository(WeightGoal); } /** * 获取用户统计数据 */ async getStats(userId: string, from?: string, to?: string): Promise { // 获取记录 const queryBuilder = this.weightRecordRepository .createQueryBuilder('wr') .where('wr.userId = :userId', { userId }) .andWhere('wr.deletedAt IS NULL'); if (from) { queryBuilder.andWhere('wr.recordDate >= :from', { from }); } if (to) { queryBuilder.andWhere('wr.recordDate <= :to', { to }); } const records = await queryBuilder .orderBy('wr.recordDate', 'ASC') .getMany(); if (records.length === 0) { return { currentWeight: null, weightChange: null, daysTracked: 0, avgWeeklyChange: null, bodyFatChange: null, goalProgress: null, goalETA: null }; } // 基础统计 const firstRecord = records[0]; const latestRecord = records[records.length - 1]; const currentWeight = Number(latestRecord.weight); const initialWeight = Number(firstRecord.weight); const weightChange = Number((currentWeight - initialWeight).toFixed(2)); const bodyFatChange = Number((Number(latestRecord.bodyFat) - Number(firstRecord.bodyFat)).toFixed(2)); // 天数统计 const firstDate = new Date(firstRecord.recordDate); const latestDate = new Date(latestRecord.recordDate); const daysTracked = Math.ceil((latestDate.getTime() - firstDate.getTime()) / (1000 * 60 * 60 * 24)) + 1; // 平均每周变化 const weeksTracked = daysTracked / 7; const avgWeeklyChange = weeksTracked > 0 ? Number((weightChange / weeksTracked).toFixed(2)) : 0; // 获取活跃目标 const goal = await this.weightGoalRepository.findOne({ where: { userId, status: GoalStatus.ACTIVE } }); let goalProgress: number | null = null; let goalETA: string | null = null; if (goal) { const totalWeightDiff = Math.abs(Number(goal.targetWeight) - Number(goal.startWeight)); const currentWeightDiff = Math.abs(Number(goal.targetWeight) - currentWeight); goalProgress = totalWeightDiff > 0 ? Number((((totalWeightDiff - currentWeightDiff) / totalWeightDiff) * 100).toFixed(1)) : 100; // 计算ETA if (avgWeeklyChange !== 0 && currentWeightDiff > 0) { const weeksNeeded = currentWeightDiff / Math.abs(avgWeeklyChange); const etaDate = new Date(); etaDate.setDate(etaDate.getDate() + Math.ceil(weeksNeeded * 7)); goalETA = etaDate.toISOString().split('T')[0]; } } return { currentWeight, weightChange, daysTracked, avgWeeklyChange, bodyFatChange, goalProgress, goalETA }; } }