116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
/**
|
||
* 卡路里计算工具
|
||
* 基于标准 MET (Metabolic Equivalent of Task) 公式
|
||
*/
|
||
|
||
export interface FoodItem {
|
||
name: string
|
||
emoji: string
|
||
calories: number
|
||
unit: string
|
||
}
|
||
|
||
export interface FoodComparison {
|
||
food: FoodItem
|
||
ratio: number
|
||
message: string
|
||
}
|
||
|
||
export interface CalorieCalculationConfig {
|
||
totalReps: number
|
||
bpm: number
|
||
userWeight?: number // 默认 60kg
|
||
}
|
||
|
||
// 食物卡路里数据库
|
||
export const FOOD_CALORIES: Record<string, FoodItem> = {
|
||
apple: { name: '苹果', emoji: '🍎', calories: 52, unit: '个(100g)' },
|
||
egg: { name: '鸡蛋', emoji: '🥚', calories: 70, unit: '个' },
|
||
chocolate: { name: '巧克力', emoji: '🍫', calories: 54, unit: '块(10g)' },
|
||
banana: { name: '香蕉', emoji: '🍌', calories: 89, unit: '根' },
|
||
rice: { name: '米饭', emoji: '🍚', calories: 116, unit: '碗(100g)' },
|
||
}
|
||
|
||
/**
|
||
* 计算卡路里消耗
|
||
* 公式:卡路里 = MET × 体重(kg) × 时长(小时)
|
||
*
|
||
* @param config 计算配置
|
||
* @returns 卡路里消耗(kcal,保留一位小数)
|
||
*/
|
||
export function calculateCalories(config: CalorieCalculationConfig): number {
|
||
const MET = 3.5 // 握力环训练的标准 MET 值
|
||
const weight = config.userWeight || 60 // 默认 60kg
|
||
|
||
// 边界情况:无效输入
|
||
if (config.totalReps <= 0 || config.bpm <= 0) {
|
||
return 0
|
||
}
|
||
|
||
const secondsPerRep = 60 / config.bpm
|
||
const activeTimeHours = (config.totalReps * secondsPerRep) / 3600
|
||
|
||
const calories = MET * weight * activeTimeHours
|
||
return Math.round(calories * 10) / 10 // 保留一位小数
|
||
}
|
||
|
||
/**
|
||
* 自动匹配最接近的食物对比
|
||
*
|
||
* @param burnedCalories 消耗的卡路里
|
||
* @returns 食物对比信息
|
||
*/
|
||
export function getFoodComparison(burnedCalories: number): FoodComparison {
|
||
const foods = Object.values(FOOD_CALORIES)
|
||
|
||
// 边界情况:食物数据库为空
|
||
if (foods.length === 0) {
|
||
throw new Error('食物数据库为空')
|
||
}
|
||
|
||
// 边界情况:卡路里为 0 或负数
|
||
if (burnedCalories <= 0) {
|
||
return {
|
||
food: foods[0],
|
||
ratio: 0,
|
||
message: `暂无消耗`,
|
||
}
|
||
}
|
||
|
||
// 找到卡路里最接近的食物
|
||
const closest = foods.reduce((prev, curr) => {
|
||
const prevDiff = Math.abs(prev.calories - burnedCalories)
|
||
const currDiff = Math.abs(curr.calories - burnedCalories)
|
||
return currDiff < prevDiff ? curr : prev
|
||
})
|
||
|
||
const ratio = burnedCalories / closest.calories
|
||
const ratioRounded = Math.round(ratio * 100) / 100 // 保留两位小数
|
||
|
||
// 格式化显示:ratio < 1 时显示分数形式更直观
|
||
let displayText: string
|
||
if (ratioRounded < 1) {
|
||
displayText = `相当于 ${ratioRounded.toFixed(2)} 个${closest.name} ${closest.emoji}`
|
||
} else {
|
||
displayText = `相当于 ${ratioRounded.toFixed(2)} ${closest.unit}${closest.name} ${closest.emoji}`
|
||
}
|
||
|
||
return {
|
||
food: closest,
|
||
ratio: ratioRounded,
|
||
message: displayText,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 格式化时长(秒 → 分:秒)
|
||
*
|
||
* @param seconds 秒数
|
||
* @returns 格式化字符串(如 "3:45")
|
||
*/
|
||
export function formatDuration(seconds: number): string {
|
||
const mins = Math.floor(seconds / 60)
|
||
const secs = seconds % 60
|
||
return `${mins}:${secs.toString().padStart(2, '0')}`
|
||
}
|