feat(theme): 实现日夜主题自动切换功能

- 添加日夜主题CSS变量定义,支持琥珀夜光和暖白琥珀两种风格
- 实现主题切换逻辑,根据时间自动切换白天(light)和夜间(dark)主题
- 在App.vue中集成主题初始化和token恢复功能
- 更新全局样式类应用主题颜色变量,包括卡片、文本、边框等
- 创建主题混入(mixin)供各页面使用,确保主题同步更新
- 实现导航栏和TabBar主题动态切换,提升用户体验
- 添加API服务层封装HaveADrink SDK,统一处理认证和请求
- 优化DrinkCard组件显示饮酒感受信息,丰富打卡记录展示
- 更新项目依赖配置,集成新的API客户端库
This commit is contained in:
cg
2026-07-16 22:47:38 +08:00
parent 145976f221
commit 860136bceb
22 changed files with 7051 additions and 155 deletions
+105 -42
View File
@@ -1,15 +1,11 @@
<template>
<view class="page-container home">
<view :class="themeClass" class="page-container home">
<!-- 顶部问候 -->
<view class="greeting-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="greeting-left">
<text class="greeting-text">{{ greeting }}{{ userName }}</text>
<text class="greeting-sub">今晚喝一杯</text>
</view>
<image v-if="userAvatar" class="avatar" :src="userAvatar" mode="aspectFill"></image>
<view v-else class="avatar avatar-placeholder">
<text class="avatar-placeholder-icon">👤</text>
</view>
</view>
<!-- 核心CTA - 首页最醒目位置 -->
@@ -121,11 +117,13 @@
<script>
import DrinkCalendar from '../../components/DrinkCalendar.vue'
import { getRecords, getUserInfo } from '../../common/mock-data'
import { calcStats, getGreeting, formatDate, getWeekStart } from '../../common/utils'
import client from '../../common/api'
import { getGreeting, formatDate, getWeekStart } from '../../common/utils'
import { FEELINGS } from '../../common/constants'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
components: { DrinkCalendar },
data() {
const now = new Date()
@@ -138,7 +136,6 @@ export default {
stats: {},
greeting: '',
userName: '',
userAvatar: '',
showDayDetail: false,
selectedDate: '',
selectedRecord: null
@@ -167,17 +164,100 @@ export default {
this.loadData()
},
methods: {
loadData() {
this.records = getRecords()
const user = getUserInfo()
this.userName = user.nickname || '酒友'
this.userAvatar = user.avatar || ''
async loadData() {
this.greeting = getGreeting()
this.stats = calcStats(this.records)
// 并行加载用户信息、统计概览、记录列表
try {
const [userResp, statsResp, recordsResp] = await Promise.allSettled([
client.GetUserProfile({}),
client.GetStatsOverview({}),
client.GetRecords({ page: 1, pageSize: 100 })
])
// 用户信息
if (userResp.status === 'fulfilled' && userResp.value) {
const user = userResp.value
this.userName = user.nickname || '酒友'
uni.setStorageSync('user_info', JSON.stringify(user))
} else {
// 降级读取本地缓存
try {
const cached = JSON.parse(uni.getStorageSync('user_info') || '{}')
this.userName = cached.nickname || '酒友'
} catch (e) {
this.userName = '酒友'
}
}
// 统计概览
if (statsResp.status === 'fulfilled' && statsResp.value) {
const s = statsResp.value.data || statsResp.value
this.stats = {
weekDrinkCount: s.weekDrinkCount || 0,
weekCups: s.weekCups || 0,
monthDrinkCount: s.monthDrinkCount || 0,
monthCups: s.monthCups || 0,
streak: s.streak || 0,
streakType: String(s.streakType || 'drank'),
totalDays: s.totalDays || 0,
totalRecords: s.totalRecords || 0,
totalCups: s.totalCups || 0,
favCategory: s.favCategory ? String(s.favCategory) : null,
categoryVariety: s.categoryVariety || 0
}
}
// 记录列表
if (recordsResp.status === 'fulfilled' && recordsResp.value) {
const list = recordsResp.value.list || recordsResp.value
this.records = Array.isArray(list) ? list.map(r => ({
...r,
mode: String(r.mode),
feeling: r.feeling ? String(r.feeling) : null,
visibility: r.visibility ? String(r.visibility) : 'private',
drinks: (r.drinks || []).map(d => ({
...d,
category: String(d.category),
unit: String(d.unit)
}))
})) : []
}
} catch (e) {
console.warn('加载首页数据失败:', e)
// 降级:读取本地缓存
try {
const cached = JSON.parse(uni.getStorageSync('user_info') || '{}')
this.userName = cached.nickname || '酒友'
} catch (err) {
this.userName = '酒友'
}
}
},
onMonthChange({ year, month }) {
this.calendarYear = year
this.calendarMonth = month
// 切换月份时重新加载记录
this.loadRecordsForMonth(year, month)
},
async loadRecordsForMonth(year, month) {
try {
const monthStr = `${year}-${String(month).padStart(2, '0')}`
const resp = await client.GetRecords({ page: 1, pageSize: 100, month: monthStr })
const list = resp.list || resp
this.records = Array.isArray(list) ? list.map(r => ({
...r,
mode: String(r.mode),
feeling: r.feeling ? String(r.feeling) : null,
visibility: r.visibility ? String(r.visibility) : 'private',
drinks: (r.drinks || []).map(d => ({
...d,
category: String(d.category),
unit: String(d.unit)
}))
})) : []
} catch (e) {
console.warn('加载月度记录失败:', e)
}
},
onDateSelect(dateStr) {
this.selectedDate = dateStr
@@ -240,8 +320,7 @@ export default {
.greeting-bar {
display: flex;
justify-content: space-between;
align-items: center;
align-items: flex-start;
padding: $sp-md 0 $sp-sm;
}
@@ -249,6 +328,7 @@ export default {
display: block;
font-size:38rpx;
font-weight: $fw-bold;
color: $text-primary;
}
.greeting-sub {
@@ -258,27 +338,6 @@ export default {
margin-top: 4rpx;
}
.avatar {
width: 72rpx;
height: 72rpx;
border-radius: $radius-full;
border: 4rpx solid $amber-glow;
flex-shrink: 0;
}
.avatar-placeholder {
background: $bg-elevated;
display: flex;
align-items: center;
justify-content: center;
border: 4rpx dashed rgba(232,168,56,0.3);
}
.avatar-placeholder-icon {
font-size: 40rpx;
opacity: 0.5;
}
.calendar-section {
margin-bottom: $sp-md;
padding: $sp-md;
@@ -299,6 +358,7 @@ export default {
.stat-value {
display: block;
font-size: $fs-xl;
color: $text-primary;
margin-bottom: 4rpx;
}
@@ -328,6 +388,7 @@ export default {
display: block;
font-size: $fs-xl;
font-weight: $fw-bold;
color: $text-primary;
margin-bottom: 4rpx;
}
@@ -338,7 +399,7 @@ export default {
.week-divider {
width: 2rpx;
background: rgba(255,255,255,0.06);
background: var(--divider-color, rgba(255, 255, 255, 0.08));
margin: $sp-xs 0;
}
@@ -395,7 +456,7 @@ export default {
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.12), transparent);
background: linear-gradient(90deg, transparent, var(--btn-shimmer, rgba(255, 255, 255, 0.15)), transparent);
animation: shimmer 4s infinite;
}
}
@@ -458,7 +519,7 @@ export default {
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.6);
background: var(--overlay-mask, rgba(0, 0, 0, 0.6));
display: flex;
align-items: flex-end;
z-index: 100;
@@ -468,6 +529,7 @@ export default {
.day-detail {
width: 100%;
padding: $sp-xl;
color: $text-primary;
border-radius: $radius-xl $radius-xl 0 0;
}
@@ -478,7 +540,8 @@ export default {
.day-drink-item {
padding: $sp-sm 0;
font-size: $fs-base;
border-bottom: 2rpx solid rgba(255,255,255,0.04);
color: $text-primary;
border-bottom: 2rpx solid var(--border-micro, rgba(255, 255, 255, 0.05));
}
.day-meta {
@@ -510,6 +573,6 @@ export default {
width: 100%;
font-size: $fs-sm;
color: $text-secondary;
border-color: rgba(255,255,255,0.1);
border-color: var(--border-active, rgba(255, 255, 255, 0.12));
}
</style>