feat(app): 初始化喝酒了么小程序基础架构
- 添加项目配置文件(.editorconfig, .gitignore) - 创建App.vue主应用文件,包含启动时Mock数据初始化 - 添加index.html和main.js应用入口文件 - 配置manifest.json应用基本信息和权限设置 - 设置pages.json页面路由和全局样式 - 添加uni.promisify.adaptor.js适配器文件 - 创建uni.scss设计令牌系统,定义琥珀夜光主题色彩 - 新增constants.js常量定义,包含酒类分类和品牌数据
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
/* 喝酒了么 - 常量定义 */
|
||||
|
||||
// 酒类分类
|
||||
export const DRINK_CATEGORIES = [
|
||||
{ id: 'baijiu', name: '白酒', emoji: '🥃', defaultDegree: 52 },
|
||||
{ id: 'beer', name: '啤酒', emoji: '🍺', defaultDegree: 5 },
|
||||
{ id: 'wine', name: '红酒', emoji: '🍷', defaultDegree: 13 },
|
||||
{ id: 'whisky', name: '洋酒', emoji: '🥂', defaultDegree: 40 },
|
||||
{ id: 'huangjiu', name: '黄酒', emoji: '🍶', defaultDegree: 15 },
|
||||
{ id: 'sake', name: '清酒', emoji: '🍶', defaultDegree: 15 },
|
||||
{ id: 'fruit', name: '果酒', emoji: '🍹', defaultDegree: 8 },
|
||||
{ id: 'cocktail', name: '调酒', emoji: '🍸', defaultDegree: 15 },
|
||||
{ id: 'other', name: '其他', emoji: '🫗', defaultDegree: 10 }
|
||||
]
|
||||
|
||||
// 饮用量单位
|
||||
export const DRINK_UNITS = [
|
||||
{ id: 'ml', name: 'ml', toMl: 1 },
|
||||
{ id: 'liang', name: '两', toMl: 50 },
|
||||
{ id: 'bottle', name: '瓶', toMl: 500 },
|
||||
{ id: 'cup', name: '杯', toMl: 150 },
|
||||
{ id: 'can', name: '听', toMl: 330 },
|
||||
{ id: 'shot', name: 'shot', toMl: 30 }
|
||||
]
|
||||
|
||||
// 预设品牌库 (各酒类常见品牌)
|
||||
export const BRANDS = {
|
||||
baijiu: [
|
||||
{ name: '茅台', product: '飞天茅台53度', degree: 53 },
|
||||
{ name: '五粮液', product: '普五52度', degree: 52 },
|
||||
{ name: '泸州老窖', product: '国窖1573', degree: 52 },
|
||||
{ name: '洋河', product: '梦之蓝M6', degree: 52 },
|
||||
{ name: '汾酒', product: '青花汾酒20', degree: 53 },
|
||||
{ name: '郎酒', product: '红花郎', degree: 53 },
|
||||
{ name: '剑南春', product: '水晶剑南春', degree: 52 },
|
||||
{ name: '牛栏山', product: '陈酿42度', degree: 42 },
|
||||
{ name: '红星', product: '二锅头56度', degree: 56 },
|
||||
{ name: '江小白', product: '表达瓶', degree: 40 }
|
||||
],
|
||||
beer: [
|
||||
{ name: '青岛啤酒', product: '经典', degree: 4.3 },
|
||||
{ name: '雪花', product: '勇闯天涯', degree: 3.3 },
|
||||
{ name: '百威', product: '经典', degree: 5.0 },
|
||||
{ name: '喜力', product: '星银', degree: 5.0 },
|
||||
{ name: '科罗娜', product: 'Extra', degree: 4.5 },
|
||||
{ name: '嘉士伯', product: '特醇', degree: 5.0 },
|
||||
{ name: '燕京', product: 'U8', degree: 2.5 },
|
||||
{ name: '哈尔滨', product: '冰纯', degree: 4.5 },
|
||||
{ name: '精酿', product: '自选', degree: 6.0 },
|
||||
{ name: '朝日', product: '超爽', degree: 5.0 }
|
||||
],
|
||||
wine: [
|
||||
{ name: '拉菲', product: '传奇', degree: 13 },
|
||||
{ name: '奔富', product: 'Bin 389', degree: 14.5 },
|
||||
{ name: '长城', product: '五星赤霞珠', degree: 13 },
|
||||
{ name: '张裕', product: '解百纳', degree: 12.5 },
|
||||
{ name: '杰卡斯', product: '西拉', degree: 14 },
|
||||
{ name: '黄尾袋鼠', product: '西拉', degree: 13.5 },
|
||||
{ name: '蒙特斯', product: '紫天使', degree: 14.5 },
|
||||
{ name: '云雾之湾', product: '长相思', degree: 13 }
|
||||
],
|
||||
whisky: [
|
||||
{ name: '麦卡伦', product: '12年雪莉桶', degree: 40 },
|
||||
{ name: '格兰菲迪', product: '12年', degree: 40 },
|
||||
{ name: '杰克丹尼', product: 'No.7', degree: 40 },
|
||||
{ name: '芝华士', product: '12年', degree: 40 },
|
||||
{ name: '尊尼获加', product: '黑牌', degree: 40 },
|
||||
{ name: '山崎', product: '12年', degree: 43 },
|
||||
{ name: '百龄坛', product: '12年', degree: 40 },
|
||||
{ name: '人头马', product: 'XO', degree: 40 }
|
||||
],
|
||||
huangjiu: [
|
||||
{ name: '古越龙山', product: '花雕五年', degree: 15 },
|
||||
{ name: '会稽山', product: '纯正五年', degree: 15 },
|
||||
{ name: '塔牌', product: '手工冬酿', degree: 14 },
|
||||
{ name: '女儿红', product: '三年陈', degree: 15 }
|
||||
],
|
||||
sake: [
|
||||
{ name: '獺祭', product: '纯米大吟酿45', degree: 15 },
|
||||
{ name: '久保田', product: '纯米吟酿', degree: 15 },
|
||||
{ name: '松竹梅', product: '上撰', degree: 15 }
|
||||
],
|
||||
fruit: [
|
||||
{ name: '梅乃宿', product: '柚子酒', degree: 8 },
|
||||
{ name: 'RIO', product: '微醺', degree: 3 },
|
||||
{ name: '冰锐', product: '朗姆预调', degree: 4.5 },
|
||||
{ name: '梅见', product: '青梅酒', degree: 12 }
|
||||
],
|
||||
cocktail: [
|
||||
{ name: '自制', product: 'Mojito', degree: 15 },
|
||||
{ name: '自制', product: 'Gin Tonic', degree: 12 },
|
||||
{ name: '自制', product: 'Old Fashioned', degree: 30 }
|
||||
],
|
||||
other: []
|
||||
}
|
||||
|
||||
// 配餐分类
|
||||
export const FOOD_CATEGORIES = [
|
||||
{ id: 'hotpot', name: '火锅', emoji: '🍲' },
|
||||
{ id: 'bbq', name: '烧烤', emoji: '🍢' },
|
||||
{ id: 'stirfry', name: '炒菜', emoji: '🥘' },
|
||||
{ id: 'seafood', name: '海鲜', emoji: '🦐' },
|
||||
{ id: 'japanese', name: '日料', emoji: '🍣' },
|
||||
{ id: 'western', name: '西餐', emoji: '🥩' },
|
||||
{ id: 'snack', name: '小吃卤味', emoji: '🥜' },
|
||||
{ id: 'junk', name: '零食', emoji: '🍿' },
|
||||
{ id: 'none', name: '无配餐', emoji: '🚫' }
|
||||
]
|
||||
|
||||
// 饮用感受
|
||||
export const FEELINGS = [
|
||||
{ id: 'tipsy', name: '微醺', emoji: '😊', desc: '刚刚好的快乐' },
|
||||
{ id: 'buzzed', name: '到位', emoji: '😌', desc: '今夜刚刚好' },
|
||||
{ id: 'drunk', name: '醉了', emoji: '😵', desc: '有点上头了' },
|
||||
{ id: 'blackout', name: '断片', emoji: '🫠', desc: '什么都不记得了' }
|
||||
]
|
||||
|
||||
// 酒言酒语
|
||||
export const DRINK_QUOTES = [
|
||||
'人生得意须尽欢,莫使金樽空对月',
|
||||
'微醺是最好的状态',
|
||||
'今朝有酒今朝醉,明日愁来明日愁',
|
||||
'对酒当歌,人生几何',
|
||||
'举杯邀明月,对影成三人',
|
||||
'酒逢知己千杯少',
|
||||
'晚来天欲雪,能饮一杯无',
|
||||
'醉后不知天在水,满船清梦压星河',
|
||||
'白日放歌须纵酒,青春作伴好还乡',
|
||||
'且将新火试新茶,诗酒趁年华',
|
||||
'一壶浊酒喜相逢,古今多少事,都付笑谈中',
|
||||
'何以解忧?唯有杜康',
|
||||
'把酒祝东风,且共从容',
|
||||
'明月几时有,把酒问青天',
|
||||
'劝君更尽一杯酒,西出阳关无故人',
|
||||
'葡萄美酒夜光杯,欲饮琵琶马上催',
|
||||
'借问酒家何处有,牧童遥指杏花村',
|
||||
'花间一壶酒,独酌无相亲',
|
||||
'金樽清酒斗十千,玉盘珍羞直万钱',
|
||||
'东篱把酒黄昏后,有暗香盈袖',
|
||||
'今夜不设防,快乐至上',
|
||||
'干了这杯,烦恼全忘',
|
||||
'好友配好酒,人间值得',
|
||||
'生活有点苦,需要加点酒',
|
||||
'酒杯太浅,敬不到来日方长',
|
||||
'世间万物,唯有美食与酒不可辜负',
|
||||
'把酒言欢,不醉不归',
|
||||
'夜色温柔,酒也温柔',
|
||||
'小酌怡情,大饮伤身,今日刚好',
|
||||
'酒是粮食精,越喝越年轻'
|
||||
]
|
||||
|
||||
// 成就定义
|
||||
export const ACHIEVEMENTS = [
|
||||
{
|
||||
id: 'first_checkin',
|
||||
name: '初来乍到',
|
||||
desc: '完成第一次打卡,开启你的饮酒记录之旅',
|
||||
icon: '🏅',
|
||||
condition: { type: 'total_records', value: 1 }
|
||||
},
|
||||
{
|
||||
id: 'hundred_cups',
|
||||
name: '百杯不醉',
|
||||
desc: '累计饮用100标准杯',
|
||||
icon: '🏆',
|
||||
condition: { type: 'total_standard_cups', value: 100 }
|
||||
},
|
||||
{
|
||||
id: 'streak_30',
|
||||
name: '签到王者',
|
||||
desc: '连续打卡30天,自律的记录者',
|
||||
icon: '👑',
|
||||
condition: { type: 'streak_days', value: 30 }
|
||||
},
|
||||
{
|
||||
id: 'baijiu_master',
|
||||
name: '白酒达人',
|
||||
desc: '白酒累计打卡20次',
|
||||
icon: '🥃',
|
||||
condition: { type: 'category_records', category: 'baijiu', value: 20 }
|
||||
},
|
||||
{
|
||||
id: 'tasting_master',
|
||||
name: '品鉴师',
|
||||
desc: '尝试过5种以上不同类型的酒',
|
||||
icon: '🌈',
|
||||
condition: { type: 'category_variety', value: 5 }
|
||||
}
|
||||
]
|
||||
|
||||
// 可见范围选项
|
||||
export const VISIBILITY_OPTIONS = [
|
||||
{ id: 'public', name: '公开', desc: '所有人都能看到' },
|
||||
{ id: 'friends', name: '仅好友', desc: '只有好友可见' },
|
||||
{ id: 'private', name: '仅自己', desc: '只有自己能看' }
|
||||
]
|
||||
|
||||
// 打卡模式
|
||||
export const CHECKIN_MODES = [
|
||||
{ id: 'drank', name: '今日喝了', color: '$amber' },
|
||||
{ id: 'abstain', name: '今日未喝', color: '$mint' }
|
||||
]
|
||||
@@ -0,0 +1,150 @@
|
||||
/* 喝酒了么 - Mock 数据 */
|
||||
import { formatDate, calcStandardCups, unitToMl } from './utils'
|
||||
|
||||
// 模拟用户数据
|
||||
export const MOCK_USER = {
|
||||
id: 'user_001',
|
||||
nickname: '酒友小王',
|
||||
avatar: '/static/logo.png',
|
||||
preferences: {
|
||||
favoriteCategories: ['baijiu', 'beer'],
|
||||
frequency: 'often'
|
||||
},
|
||||
joinedAt: '2026-01-15'
|
||||
}
|
||||
|
||||
// 生成过去一个月的模拟打卡记录
|
||||
export function generateMockRecords() {
|
||||
const records = []
|
||||
const today = new Date()
|
||||
|
||||
// 预设一些打卡日
|
||||
const drinkDays = [0, 1, 3, 5, 6, 8, 10, 12, 13, 15, 17, 19, 20, 22, 24, 25, 27, 29]
|
||||
const abstainDays = [2, 4, 7, 9, 11, 14, 16, 18, 21, 23, 26, 28]
|
||||
|
||||
const drinkTemplates = [
|
||||
{
|
||||
drinks: [{ category: 'baijiu', brand: '茅台', product: '飞天茅台53度', amount: 100, unit: 'ml', degree: 53 }],
|
||||
food: { category: 'hotpot', name: '重庆老火锅' },
|
||||
feeling: 'tipsy'
|
||||
},
|
||||
{
|
||||
drinks: [
|
||||
{ category: 'beer', brand: '青岛啤酒', product: '经典', amount: 2, unit: 'bottle', degree: 4.3 },
|
||||
],
|
||||
food: { category: 'bbq', name: '烤串配毛豆' },
|
||||
feeling: 'buzzed'
|
||||
},
|
||||
{
|
||||
drinks: [{ category: 'wine', brand: '奔富', product: 'Bin 389', amount: 2, unit: 'cup', degree: 14.5 }],
|
||||
food: { category: 'western', name: '牛排' },
|
||||
feeling: 'tipsy'
|
||||
},
|
||||
{
|
||||
drinks: [
|
||||
{ category: 'baijiu', brand: '五粮液', product: '普五52度', amount: 150, unit: 'ml', degree: 52 },
|
||||
{ category: 'beer', brand: '雪花', product: '勇闯天涯', amount: 1, unit: 'bottle', degree: 3.3 }
|
||||
],
|
||||
food: { category: 'stirfry', name: '水煮鱼+回锅肉' },
|
||||
feeling: 'drunk'
|
||||
},
|
||||
{
|
||||
drinks: [{ category: 'whisky', brand: '麦卡伦', product: '12年雪莉桶', amount: 3, unit: 'shot', degree: 40 }],
|
||||
food: { category: 'snack', name: '坚果拼盘' },
|
||||
feeling: 'tipsy'
|
||||
},
|
||||
{
|
||||
drinks: [{ category: 'beer', brand: '百威', product: '经典', amount: 3, unit: 'bottle', degree: 5.0 }],
|
||||
food: { category: 'seafood', name: '小龙虾' },
|
||||
feeling: 'buzzed'
|
||||
},
|
||||
{
|
||||
drinks: [{ category: 'fruit', brand: '梅见', product: '青梅酒', amount: 200, unit: 'ml', degree: 12 }],
|
||||
food: { category: 'japanese', name: '刺身拼盘' },
|
||||
feeling: 'tipsy'
|
||||
}
|
||||
]
|
||||
|
||||
drinkDays.forEach(daysAgo => {
|
||||
const date = new Date(today)
|
||||
date.setDate(date.getDate() - daysAgo)
|
||||
const dateStr = formatDate(date, 'YYYY-MM-DD')
|
||||
|
||||
const template = drinkTemplates[daysAgo % drinkTemplates.length]
|
||||
const standardCupsTotal = template.drinks.reduce((sum, d) => {
|
||||
const ml = unitToMl(d.amount, d.unit)
|
||||
return sum + calcStandardCups(ml, d.degree)
|
||||
}, 0)
|
||||
|
||||
records.push({
|
||||
id: `record_${daysAgo}`,
|
||||
date: dateStr,
|
||||
mode: 'drank',
|
||||
drinks: template.drinks.map(d => ({ ...d, categoryId: d.category })),
|
||||
food: template.food,
|
||||
feeling: template.feeling,
|
||||
photos: [],
|
||||
visibility: 'friends',
|
||||
standardCupsTotal: Math.round(standardCupsTotal * 100) / 100,
|
||||
createdAt: date.toISOString()
|
||||
})
|
||||
})
|
||||
|
||||
abstainDays.forEach(daysAgo => {
|
||||
const date = new Date(today)
|
||||
date.setDate(date.getDate() - daysAgo)
|
||||
const dateStr = formatDate(date, 'YYYY-MM-DD')
|
||||
|
||||
records.push({
|
||||
id: `record_abs_${daysAgo}`,
|
||||
date: dateStr,
|
||||
mode: 'abstain',
|
||||
drinks: [],
|
||||
food: null,
|
||||
feeling: null,
|
||||
photos: [],
|
||||
visibility: 'private',
|
||||
standardCupsTotal: 0,
|
||||
createdAt: date.toISOString()
|
||||
})
|
||||
})
|
||||
|
||||
return records.sort((a, b) => new Date(b.date) - new Date(a.date))
|
||||
}
|
||||
|
||||
// 初始化并缓存Mock数据
|
||||
export function initMockData() {
|
||||
const records = generateMockRecords()
|
||||
uni.setStorageSync('drink_records', JSON.stringify(records))
|
||||
uni.setStorageSync('user_info', JSON.stringify(MOCK_USER))
|
||||
uni.setStorageSync('is_first_launch', 'true')
|
||||
return records
|
||||
}
|
||||
|
||||
// 获取记录
|
||||
export function getRecords() {
|
||||
const data = uni.getStorageSync('drink_records')
|
||||
if (data) {
|
||||
try { return JSON.parse(data) } catch (e) { return [] }
|
||||
}
|
||||
return initMockData()
|
||||
}
|
||||
|
||||
// 添加记录
|
||||
export function addRecord(record) {
|
||||
const records = getRecords()
|
||||
record.id = `record_${Date.now()}`
|
||||
record.createdAt = new Date().toISOString()
|
||||
records.unshift(record)
|
||||
uni.setStorageSync('drink_records', JSON.stringify(records))
|
||||
return record
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
export function getUserInfo() {
|
||||
const data = uni.getStorageSync('user_info')
|
||||
if (data) {
|
||||
try { return JSON.parse(data) } catch (e) { return MOCK_USER }
|
||||
}
|
||||
return MOCK_USER
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
/* 喝酒了么 - 工具函数 */
|
||||
|
||||
/**
|
||||
* 标准杯换算引擎
|
||||
* 核心公式: 标准杯 = (饮用量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 }
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user