/* 喝酒了么 - 工具函数 */ /** * 标准杯换算引擎 * 核心公式: 标准杯 = (饮用量ml × 酒精度数% × 0.8) / 10 * 1标准杯 = 10g纯酒精 */ export function calcStandardCups(amountMl, degreePercent) { if (!amountMl || !degreePercent) return 0 return Math.round((amountMl * (degreePercent / 100) * 0.8 / 10) * 100) / 100 } /** * 单位换算为ml */ const UNIT_TO_ML = { ml: 1, liang: 50, // 1两 = 50ml bottle: 500, // 1瓶 = 500ml (啤酒默认) cup: 150, // 1杯 = 150ml can: 330, // 1听 = 330ml shot: 30 // 1shot = 30ml } export function unitToMl(amount, unit) { const factor = UNIT_TO_ML[unit] || 1 return amount * factor } /** * 计算一组饮品的总标准杯 */ export function calcTotalStandardCups(drinks) { return drinks.reduce((sum, d) => { const ml = unitToMl(d.amount, d.unit) return sum + calcStandardCups(ml, d.degree) }, 0) } /** * 日期格式化 */ export function formatDate(date, format = 'YYYY-MM-DD') { const d = new Date(date) const year = d.getFullYear() const month = String(d.getMonth() + 1).padStart(2, '0') const day = String(d.getDate()).padStart(2, '0') const hours = String(d.getHours()).padStart(2, '0') const minutes = String(d.getMinutes()).padStart(2, '0') return format .replace('YYYY', year) .replace('MM', month) .replace('DD', day) .replace('HH', hours) .replace('mm', minutes) } /** * 获取指定月份的日历数据 * @param {number} year * @param {number} month 1-12 * @returns {Array} 日历数组,包含前置空白、日期、后置空白 */ export function getCalendarDays(year, month) { const firstDay = new Date(year, month - 1, 1) const lastDay = new Date(year, month, 0) const daysInMonth = lastDay.getDate() // 周一为一周起始 (0=周一, 6=周日) let startWeekday = firstDay.getDay() - 1 if (startWeekday < 0) startWeekday = 6 const days = [] // 前置空白 for (let i = 0; i < startWeekday; i++) { days.push({ day: null, date: null }) } // 日期 for (let i = 1; i <= daysInMonth; i++) { const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(i).padStart(2, '0')}` days.push({ day: i, date: dateStr, isToday: dateStr === formatDate(new Date(), 'YYYY-MM-DD') }) } return days } /** * 获取本周一的日期 */ export function getWeekStart(date = new Date()) { const d = new Date(date) const day = d.getDay() const diff = d.getDate() - day + (day === 0 ? -6 : 1) return new Date(d.setDate(diff)) } /** * 获取时间问候语 */ export function getGreeting() { const hour = new Date().getHours() if (hour < 6) return '夜深了' if (hour < 12) return '早上好' if (hour < 14) return '中午好' if (hour < 18) return '下午好' if (hour < 22) return '晚上好' return '夜深了' } /** * 随机获取一条酒言酒语 */ export function getRandomQuote(quotes) { return quotes[Math.floor(Math.random() * quotes.length)] } /** * 统计数据计算 */ export function calcStats(records) { const now = new Date() const today = formatDate(now, 'YYYY-MM-DD') // 本周统计 const weekStart = getWeekStart(now) const weekRecords = records.filter(r => new Date(r.date) >= weekStart) const weekDrinkCount = weekRecords.filter(r => r.mode === 'drank').length const weekCups = weekRecords.reduce((sum, r) => sum + (r.standardCupsTotal || 0), 0) // 本月统计 const monthStr = formatDate(now, 'YYYY-MM') const monthRecords = records.filter(r => r.date && r.date.startsWith(monthStr)) const monthDrinkCount = monthRecords.filter(r => r.mode === 'drank').length const monthCups = monthRecords.reduce((sum, r) => sum + (r.standardCupsTotal || 0), 0) // 连续天数 let streak = 0 let streakType = 'drank' const d = new Date() // 检查今天是否有记录 const todayRecord = records.find(r => r.date === today) if (todayRecord) { streakType = todayRecord.mode streak = 1 // 向前追溯 for (let i = 1; i < 365; i++) { const prevDate = new Date(d) prevDate.setDate(prevDate.getDate() - i) const prevStr = formatDate(prevDate, 'YYYY-MM-DD') const prev = records.find(r => r.date === prevStr) if (prev && prev.mode === streakType) { streak++ } else { break } } } // 总打卡天数 const totalDays = new Set(records.filter(r => r.mode === 'drank').map(r => r.date)).size const totalRecords = records.filter(r => r.mode === 'drank').length const totalCups = records.reduce((sum, r) => sum + (r.standardCupsTotal || 0), 0) // 最爱酒类 const categoryCount = {} records.forEach(r => { if (r.drinks) { r.drinks.forEach(d => { categoryCount[d.category] = (categoryCount[d.category] || 0) + 1 }) } }) const favCategory = Object.entries(categoryCount).sort((a, b) => b[1] - a[1])[0] return { weekDrinkCount, weekCups: Math.round(weekCups * 10) / 10, monthDrinkCount, monthCups: Math.round(monthCups * 10) / 10, streak, streakType, totalDays, totalRecords, totalCups: Math.round(totalCups * 10) / 10, favCategory: favCategory ? favCategory[0] : null, categoryVariety: Object.keys(categoryCount).length } } /** * 检查成就解锁 */ export function checkAchievements(stats, achievements) { return achievements.map(a => { let unlocked = false const { type, value, category } = a.condition switch (type) { case 'total_records': unlocked = stats.totalRecords >= value break case 'total_standard_cups': unlocked = stats.totalCups >= value break case 'streak_days': unlocked = stats.streak >= value break case 'category_variety': unlocked = stats.categoryVariety >= value break case 'category_records': // 需要更细粒度的数据,这里简化处理 unlocked = false break } return { ...a, unlocked } }) }