- 替换所有文件中的品牌名称引用,包括App.vue、pages.json、uni.scss等 - 更新全局样式、导航栏标题和组件中的文案显示 - 修改分享功能中的应用名称显示 - 调整聊天页面的输入栏键盘适配逻辑 - 在多个页面组件中添加事件发射声明以支持交互功能
321 lines
8.7 KiB
JavaScript
321 lines
8.7 KiB
JavaScript
/* 碰盏日记 - 工具函数 */
|
||
|
||
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: '#F2F5F9', 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: '#9BA8BA',
|
||
selectedColor: '#E09B2D',
|
||
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' : '#16233C',
|
||
textSecondary: isDark ? '#C0C0D2' : '#5D6B82',
|
||
textTertiary: isDark ? '#9494AC' : '#9BA8BA',
|
||
amber: isDark ? '#E8A838' : '#E09B2D',
|
||
bgBase: isDark ? '#0B0B14' : '#F2F5F9',
|
||
bgCard: isDark ? '#1A1A28' : '#FFFFFF',
|
||
cardGradStart: isDark ? '#1A1510' : '#F8FAFD',
|
||
cardGradEnd: isDark ? '#0D0B08' : '#EFF3F9',
|
||
borderSubtle: isDark ? 'rgba(232,168,56,0.15)' : 'rgba(224,155,45,0.18)',
|
||
bgPlaceholder: isDark ? 'rgba(255,255,255,0.03)' : 'rgba(22,40,70,0.04)',
|
||
emojiBg: isDark ? 'rgba(232,168,56,0.08)' : 'rgba(224,155,45,0.10)'
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查成就解锁
|
||
*/
|
||
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 }
|
||
})
|
||
}
|