Files
hejiu/common/utils.js
T
cg 9f5c69e1e5 feat(app): 项目重命名为干杯日记并优化UI设计
- 将应用名称从"喝了么"更改为"干杯日记"
- 更新所有相关文件中的品牌标识和描述信息
- 升级HaveADrink依赖包从1.0.3到1.0.4版本
- 在pages.json中新增分享个人资料页面配置
- 重构DrinkCard组件UI,增加装饰背景、酒类图标支持、饮酒感受徽章等功能
- 优化API错误处理逻辑,增加静默模式支持
- 修改白酒类别图标为 urn emoji,黄酒图标为 tea emoji
- 添加分享功能按钮并优化页面返回交互体验
- 引入新的工具函数用于获取酒类图标和路径判断
- 更新主题混入、常量定义等基础配置文件
- 调整全局样式和设计令牌以匹配新品牌形象
2026-07-20 22:12:27 +08:00

321 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 干杯日记 - 工具函数 */
import { DRINK_CATEGORIES } from './constants'
/**
* 获取酒类图标:优先返回自定义图片路径,否则返回 emoji
*/
export function getCatIcon(catId) {
const cat = DRINK_CATEGORIES.find(c => c.id === catId)
if (!cat) return '🥃'
return cat.icon || cat.emoji
}
export function isIconPath(val) {
return typeof val === 'string' && val.charAt(0) === '/'
}
/**
* 标准杯换算引擎
* 核心公式: 标准杯 = (饮用量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
}
}
/**
* 主题系统:日夜自动切换
* 白天:6:00-16:00 → light
* 夜间:16:00-6:00 → dark
*/
export function getCurrentTheme() {
const hour = new Date().getHours()
return (hour >= 6 && hour < 16) ? 'light' : 'dark'
}
export function applyTheme(theme) {
// 同步存储当前主题
uni.setStorageSync('current_theme', theme)
// 应用导航栏和TabBar主题
applyNavBarTheme(theme)
applyTabBarTheme(theme)
}
export function applyNavBarTheme(theme) {
const colors = {
dark: { backgroundColor: '#0B0B14', frontColor: '#ffffff' },
light: { backgroundColor: '#FAF7F2', frontColor: '#000000' }
}
const c = colors[theme] || colors.dark
try {
uni.setNavigationBarColor({
frontColor: c.frontColor,
backgroundColor: c.backgroundColor,
animation: { duration: 300, timingFunc: 'easeInOut' }
})
} catch (e) {}
}
export function applyTabBarTheme(theme) {
// 仅在 tabBar 页面上调用 setTabBarStyle,避免报错
const tabBarPages = ['pages/index/index', 'pages/profile/profile']
const pages = getCurrentPages()
if (!pages.length) return
const currentPath = pages[pages.length - 1].route
if (!tabBarPages.includes(currentPath)) return
const styles = {
dark: {
color: '#9494AC',
selectedColor: '#E8A838',
backgroundColor: '#151520',
borderStyle: 'black'
},
light: {
color: '#B5AEA0',
selectedColor: '#D49530',
backgroundColor: '#FFFFFF',
borderStyle: 'white'
}
}
const s = styles[theme] || styles.dark
try {
uni.setTabBarStyle({
color: s.color,
selectedColor: s.selectedColor,
backgroundColor: s.backgroundColor,
borderStyle: s.borderStyle
})
} catch (e) {}
}
export function getThemeColors(theme) {
const isDark = theme !== 'light'
return {
textPrimary: isDark ? '#FFFFFF' : '#2C2416',
textSecondary: isDark ? '#C0C0D2' : '#8C8474',
textTertiary: isDark ? '#9494AC' : '#B5AEA0',
amber: isDark ? '#E8A838' : '#D49530',
bgBase: isDark ? '#0B0B14' : '#FAF7F2',
bgCard: isDark ? '#1A1A28' : '#FFFFFF',
cardGradStart: isDark ? '#1A1510' : '#F5F0E8',
cardGradEnd: isDark ? '#0D0B08' : '#EDE8DF',
borderSubtle: isDark ? 'rgba(232,168,56,0.15)' : 'rgba(212,149,48,0.2)',
bgPlaceholder: isDark ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.04)',
emojiBg: isDark ? 'rgba(232,168,56,0.08)' : 'rgba(212,149,48,0.1)'
}
}
/**
* 检查成就解锁
*/
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 }
})
}