Files
hejiu/pages/index/index.vue
T
cg d335ec290a refactor(app): 重构应用主题和页面结构
- 移除动态主题切换功能,改为全局统一深色主题
- 实现自定义TabBar组件替换原生tabBar,解决iOS闪白问题
- 创建main容器页面统一管理三个tab页面的生命周期和状态
- 将所有页面的onShow/onHide等生命周期方法迁移到子组件内部
- 更新页面路径从index改为main,并调整路由跳转逻辑
- 移除theme-mixin和相关主题工具函数
- 统一页面背景色设置,优化用户体验一致性
2026-09-23 20:56:14 +08:00

986 lines
27 KiB
Vue
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.
<template>
<view 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>
</view>
<!-- 游客登录引导条(非阻断,可继续浏览;登录由用户自行选择) -->
<view v-if="isGuest" class="guest-banner" @click="goLogin">
<view class="guest-banner-info">
<text class="guest-banner-title">登录后开启完整记录</text>
<text class="guest-banner-desc">云端同步 · 成就徽章 · 酒友互动</text>
</view>
<text class="guest-banner-btn">去登录</text>
</view>
<!-- 核心CTA - 首页最醒目位置 -->
<view class="hero-cta" @click="goRecord">
<view class="hero-cta-bg"></view>
<view class="hero-cta-content">
<text class="hero-cta-emoji">🍻</text>
<view class="hero-cta-text">
<text class="hero-cta-title">开始记录</text>
<text class="hero-cta-sub">记录今晚的酒局</text>
</view>
<text class="hero-cta-arrow">›</text>
</view>
</view>
<!-- 统计卡片 -->
<view class="stats-row">
<view class="stat-card card">
<text class="stat-value text-num text-amber">{{ stats.monthDrinkCount }}</text>
<text class="stat-label">本月饮酒</text>
</view>
<view class="stat-card card">
<text class="stat-value text-num">{{ stats.monthCups }}</text>
<text class="stat-label">标准杯</text>
</view>
<view class="stat-card card">
<text class="stat-value text-num text-amber">{{ stats.drankStreak }}</text>
<text class="stat-label">连喝天数</text>
</view>
<view class="stat-card card">
<text class="stat-value text-num text-mint">{{ stats.abstainStreak }}</text>
<text class="stat-label">戒酒天数</text>
</view>
</view>
<!-- 饮酒日历 -->
<view class="calendar-section card">
<DrinkCalendar
:year="calendarYear"
:month="calendarMonth"
:records="records"
@change-month="onMonthChange"
@select-date="onDateSelect"
/>
</view>
<!-- 本周摘要 -->
<view class="week-summary card">
<view class="week-header flex-between">
<text class="text-h3">本周摘要</text>
<text class="text-caption">{{ weekRange }}</text>
</view>
<view class="week-stats flex">
<view class="week-stat">
<text class="week-stat-value text-num">{{ stats.weekDrinkCount }}</text>
<text class="week-stat-label">次饮酒</text>
</view>
<view class="week-divider"></view>
<view class="week-stat">
<text class="week-stat-value text-num text-amber">{{ stats.weekCups }}</text>
<text class="week-stat-label">标准杯</text>
</view>
</view>
<view class="health-ref" v-if="stats.weekCups > 0">
<view class="health-bar-fill" :style="{ width: healthPercent + '%' }"></view>
<text class="health-text">
{{ stats.weekCups > 14 ? '本周超标了,注意休息' : '本周控制得不错' }}
</text>
</view>
</view>
<!-- 底部健康提示 -->
<view class="bottom-health-tip">
<text>过量饮酒有害健康 · 请理性记录</text>
</view>
<!-- 日期记录弹窗 -->
<view v-if="showDayDetail" class="day-overlay" @click="showDayDetail = false">
<view class="day-detail card-elevated" @click.stop>
<view class="day-detail-handle"></view>
<view class="day-detail-header flex-between">
<text class="day-detail-date text-h3">{{ selectedDate }}</text>
<text class="day-close btn-text" @click="showDayDetail = false">关闭</text>
</view>
<view v-if="selectedRecord">
<view v-if="selectedRecord.mode === 'drank'" class="day-record-info">
<view class="day-drinks">
<view v-for="(drink, i) in selectedRecord.drinks" :key="i" class="day-drink-item">
<view class="day-drink-row">
<view class="day-drink-name">
<image v-if="isIconPath(getCatIcon(drink.category))" :src="getCatIcon(drink.category)" class="day-drink-icon" mode="aspectFit"></image>
<text v-else>{{ getCatEmoji(drink.category) }}</text>
<text>{{ drink.brand }} {{ drink.product }}</text>
</view>
<text v-if="drink.customAmount != null" class="day-drink-amount text-num">{{ drink.customAmount }}{{ getUnitLabel(drink.customUnit) }}</text>
</view>
</view>
</view>
<view class="day-meta">
<view class="day-meta-chip day-feeling-chip">
<text class="day-feeling-text">{{ getFeelingName(selectedRecord.feeling) }}</text>
</view>
<view class="day-meta-chip day-cups-chip">
<text class="day-cups-num text-num">{{ selectedRecord.standardCupsTotal }}</text>
<text class="day-cups-unit">标准杯</text>
</view>
</view>
<view class="day-action-row">
<button class="btn-ghost day-action-btn" @click="editRecord">修改</button>
<button class="btn-ghost day-action-btn" @click="toggleToAbstain">改为未饮酒</button>
</view>
<button class="btn-danger day-delete-btn" @click="deleteRecord">删除记录</button>
</view>
<view v-else class="day-abstain">
<text class="day-abstain-emoji">🚫</text>
<text class="text-caption">今日未饮酒,好样的</text>
<button class="btn-ghost day-toggle-btn" @click="toggleToDrank">改为饮酒</button>
<button class="btn-danger day-delete-btn" @click="deleteRecord">删除记录</button>
</view>
</view>
<view v-else class="day-empty">
<text class="text-caption">当天无记录</text>
<button v-if="isSelectedToday" class="btn-primary day-record-btn" @click="goRecordToday">记录饮酒</button>
<button v-else class="btn-ghost day-record-btn" @click="backfillDate">补打卡饮酒</button>
<button class="btn-ghost day-abstain-btn" @click="markAbstain">未饮酒</button>
</view>
</view>
</view>
</view>
</template>
<script>
import DrinkCalendar from '../../components/DrinkCalendar.vue'
import client, { isLoggedIn, requireLogin } from '../../common/api'
import { GetCalendarReq, DeleteRecordReq, CreateRecordReq, GetRecordDetailReq } from 'HaveADrink'
import { getGreeting, formatDate, getWeekStart, getCatIcon, isIconPath, calcLocalStreak } from '../../common/utils'
import { FEELINGS, DRINK_CATEGORIES, DRINK_UNITS } from '../../common/constants'
import { savePendingInvite, handlePendingInvite } from '../../common/invite'
export default {
components: { DrinkCalendar },
data() {
const now = new Date()
const sysInfo = uni.getSystemInfoSync()
return {
statusBarHeight: sysInfo.statusBarHeight || 25,
calendarYear: now.getFullYear(),
calendarMonth: now.getMonth() + 1,
records: [],
stats: {},
greeting: '',
userName: '',
showDayDetail: false,
selectedDate: '',
selectedRecord: null
}
},
computed: {
weekRange() {
const weekStart = getWeekStart()
const weekEnd = new Date(weekStart)
weekEnd.setDate(weekEnd.getDate() + 6)
return `${formatDate(weekStart, 'MM/DD')} - ${formatDate(weekEnd, 'MM/DD')}`
},
healthPercent() {
// 建议每周不超过14标准杯
return Math.min((this.stats.weekCups / 14) * 100, 100)
},
isSelectedToday() {
return this.selectedDate === formatDate(new Date(), 'YYYY-MM-DD')
}
},
methods: {
// === 子组件生命周期(由 main.vue 容器通过 ref 调用)===
pageLoad(options) {
// 分享邀请落地:携带 inviteCode 时暂存邀请,已登录则立即接受邀请
if (options && options.inviteCode) {
savePendingInvite(options.inviteCode, options.inviterNick)
if (uni.getStorageSync('is_logged_in') === 'true') {
handlePendingInvite()
}
}
},
pageShow() {
// 首次启动跳转引导页由 App.vue onLaunch 统一处理,
// 这里不再重复 reLaunch,避免两个跳转并发导致 reLaunch:fail timeout
const isFirst = uni.getStorageSync('is_first_launch')
if (isFirst === 'true') {
return
}
// 游客可直接浏览首页:不发起鉴权请求,展示空数据;
// 仅在用户主动点击记录等功能时才引导登录(微信审核要求:先浏览后登录)
this.isGuest = !isLoggedIn()
if (this.isGuest) {
this.initGuestView()
return
}
this.loadData()
},
getShareAppMessage() {
return {
title: '碰盏日记 - 让每一杯酒都有迹可循',
path: '/pages/main/main?tab=0'
}
},
getShareTimeline() {
return {
title: '碰盏日记 - 让每一杯酒都有迹可循',
query: 'tab=0'
}
},
// 游客浏览视图:问候语与零数据,页面结构可完整浏览,不发起任何鉴权请求
initGuestView() {
this.greeting = getGreeting()
this.userName = '酒友'
this.records = []
this.stats = {
weekDrinkCount: 0,
weekCups: 0,
monthDrinkCount: 0,
monthCups: 0,
streak: 0,
streakType: 'drank',
drankStreak: 0,
abstainStreak: 0
}
},
// 游客主动点击"去登录"
goLogin() {
uni.navigateTo({ url: '/pages/login/login' })
},
async loadData() {
this.greeting = getGreeting()
// 并行加载用户信息、统计概览、记录列表
try {
const [userResp, statsResp, recordsResp] = await Promise.allSettled([
client.GetUserProfile({}),
client.GetStatsOverview({}),
client.GetCalendar(new GetCalendarReq(this.calendarYear, this.calendarMonth))
])
// 用户信息
if (userResp.status === 'fulfilled' && userResp.value) {
const user = userResp.value
this.userName = user.nickname || '酒友'
} else {
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
}
// 后端 streak 不区分饮酒/戒酒模式,拉取记录本地重算修正
this.refreshLocalStreak()
}
// 日历数据
if (recordsResp.status === 'fulfilled' && recordsResp.value) {
const calData = recordsResp.value.data || recordsResp.value
const days = calData.days || []
this.records = days.filter(d => d && d.hasRecord).map(d => ({
id: d.id || '',
date: d.date,
mode: String(d.mode),
standardCupsTotal: d.standardCupsTotal || 0,
drinks: (d.drinks || []).map(dr => ({
...dr,
category: String(dr.category),
customAmount: dr.customAmount,
customUnit: dr.customUnit ? String(dr.customUnit) : ''
})),
food: d.food ? { category: String(d.food.category), name: d.food.name || '' } : null,
feeling: d.feeling ? String(d.feeling) : null,
quote: d.quote || '',
visibility: d.visibility ? String(d.visibility) : 'friends'
}))
}
} catch (e) {
console.warn('加载首页数据失败:', e)
this.userName = '酒友'
}
},
onMonthChange({ year, month }) {
this.calendarYear = year
this.calendarMonth = month
// 切换月份时重新加载记录
this.loadRecordsForMonth(year, month)
},
/** 后端 streak 不区分饮酒/戒酒模式,拉取全部记录后本地重算连续天数 */
async refreshLocalStreak() {
try {
const resp = await client.GetRecords({ page: 1, pageSize: 100 })
const payload = resp.data || resp
const list = payload.list || []
if (!list.length) return
const { streak, streakType, drankStreak, abstainStreak } = calcLocalStreak(list)
this.stats.streak = streak
this.stats.streakType = streakType
this.stats.drankStreak = drankStreak
this.stats.abstainStreak = abstainStreak
} catch (e) {
console.warn('本地计算连续天数失败:', e)
}
},
async loadRecordsForMonth(year, month) {
try {
const resp = await client.GetCalendar(new GetCalendarReq(year, month))
const calData = resp.data || resp
const days = calData.days || []
this.records = days.filter(d => d && d.hasRecord).map(d => ({
id: d.id || '',
date: d.date,
mode: String(d.mode),
standardCupsTotal: d.standardCupsTotal || 0,
drinks: (d.drinks || []).map(dr => ({
...dr,
category: String(dr.category),
customAmount: dr.customAmount,
customUnit: dr.customUnit ? String(dr.customUnit) : ''
})),
food: d.food ? { category: String(d.food.category), name: d.food.name || '' } : null,
feeling: d.feeling ? String(d.feeling) : null,
quote: d.quote || '',
visibility: d.visibility ? String(d.visibility) : 'friends'
}))
} catch (e) {
console.warn('加载月度记录失败:', e)
}
},
async onDateSelect(dateStr) {
this.selectedDate = dateStr
const localRecord = this.records.find(r => r.date === dateStr) || null
// 如果有记录且有id,拉取完整详情(确保拿到 customAmount/customUnit 等全量字段)
if (localRecord && localRecord.id && localRecord.mode === 'drank') {
this.selectedRecord = localRecord // 先用本地数据展示
this.showDayDetail = true
try {
const resp = await client.GetRecordDetail(new GetRecordDetailReq(localRecord.id))
const rec = resp.data || resp
if (rec) {
this.selectedRecord = {
...localRecord,
...rec,
mode: String(rec.mode || localRecord.mode),
drinks: (rec.drinks || localRecord.drinks || []).map(d => ({
...d,
category: String(d.category),
customUnit: d.customUnit ? String(d.customUnit) : ''
})),
feeling: rec.feeling ? String(rec.feeling) : localRecord.feeling,
visibility: rec.visibility ? String(rec.visibility) : localRecord.visibility
}
}
} catch (e) {
console.warn('获取记录详情失败,使用本地数据:', e)
}
} else {
this.selectedRecord = localRecord
this.showDayDetail = true
}
},
getFeelingName(feelingId) {
const f = FEELINGS.find(f => f.id === feelingId)
return f ? `${f.emoji} ${f.name}` : ''
},
getCatEmoji(catId) {
const cat = DRINK_CATEGORIES.find(c => c.id === catId)
return cat ? cat.emoji : '🥃'
},
getCatIcon,
isIconPath,
getUnitLabel(unitId) {
if (!unitId) return ''
const u = DRINK_UNITS.find(u => u.id === unitId)
return u ? u.name : unitId
},
backfillDate() {
this.showDayDetail = false
uni.navigateTo({
url: `/pages/record/record?date=${this.selectedDate}&mode=backfill`
})
},
// 修改记录(跳转到记录页编辑)
editRecord() {
this.showDayDetail = false
uni.navigateTo({
url: `/pages/record/record?date=${this.selectedDate}&mode=edit`
})
},
// 改为未饮酒
toggleToAbstain() {
uni.showModal({
title: '确认修改',
content: '确定将这天改为未饮酒吗?饮酒记录将被清除。',
success: async (res) => {
if (!res.confirm) return
const recordId = this.selectedRecord && this.selectedRecord.id
try {
if (recordId) {
await client.DeleteRecord(new DeleteRecordReq(recordId))
}
uni.showToast({ title: '已修改', icon: 'success' })
this.showDayDetail = false
this.loadData()
} catch (e) {
console.warn('修改为未饮酒失败:', e)
uni.showToast({ title: '修改失败,请重试', icon: 'none' })
}
}
})
},
// 改为饮酒(跳转到补录页)
toggleToDrank() {
this.showDayDetail = false
uni.navigateTo({
url: `/pages/record/record?date=${this.selectedDate}&mode=backfill`
})
},
// 删除记录
deleteRecord() {
uni.showModal({
title: '确认删除',
content: `确定删除 ${this.selectedDate} 的记录吗?删除后不可恢复。`,
confirmColor: '#e85d3a',
success: async (res) => {
if (!res.confirm) return
const recordId = this.selectedRecord && this.selectedRecord.id
try {
if (recordId) {
await client.DeleteRecord(new DeleteRecordReq(recordId))
}
this.showDayDetail = false
uni.showToast({ title: '已删除', icon: 'success' })
// 重新加载统计和日历数据
this.loadData()
} catch (e) {
console.warn('删除记录失败:', e)
uni.showToast({ title: '删除失败,请重试', icon: 'none' })
}
}
})
},
goRecord() {
if (!requireLogin()) return
uni.navigateTo({ url: '/pages/record/record' })
},
goRecordToday() {
if (!requireLogin()) return
this.showDayDetail = false
uni.navigateTo({ url: '/pages/record/record' })
},
// 标记为未饮酒(直接创建 abstain 记录)
async markAbstain() {
if (!requireLogin()) return
const recordData = {
date: this.selectedDate,
mode: 'abstain',
drinks: [],
food: null,
feeling: null,
photos: [],
visibility: 'friends',
quote: ''
}
try {
uni.showLoading({ title: '保存中...' })
await client.CreateRecord(recordData)
uni.hideLoading()
this.showDayDetail = false
uni.showToast({ title: '已记录未饮酒', icon: 'success' })
this.loadData()
} catch (e) {
uni.hideLoading()
console.warn('标记未饮酒失败:', e)
uni.showToast({ title: (e && e.msg) || '保存失败,请重试', icon: 'none' })
}
},
}
}
</script>
<style lang="scss" scoped>
.home {
padding: $sp-md;
padding-top: 0;
}
.greeting-bar {
display: flex;
align-items: flex-start;
padding: $sp-md 0 $sp-sm;
}
.greeting-text {
display: block;
font-size:42rpx;
font-weight: $fw-bold;
color: $text-primary;
}
.greeting-sub {
display: block;
font-size: $fs-xs;
color: $text-secondary;
margin-top: 4rpx;
}
/* 游客登录引导条(非阻断) */
.guest-banner {
display: flex;
align-items: center;
justify-content: space-between;
padding: $sp-md $sp-lg;
margin-bottom: $sp-md;
border-radius: $radius-lg;
background: rgba(232, 168, 56, 0.08);
border: 2rpx solid rgba(232, 168, 56, 0.25);
&:active {
background: rgba(232, 168, 56, 0.14);
}
}
.guest-banner-info {
display: flex;
flex-direction: column;
gap: 4rpx;
}
.guest-banner-title {
font-size: $fs-sm;
font-weight: $fw-bold;
color: $amber;
}
.guest-banner-desc {
font-size: $fs-xs;
color: $text-tertiary;
}
.guest-banner-btn {
font-size: $fs-sm;
font-weight: $fw-bold;
color: $text-on-amber;
background: linear-gradient(135deg, $amber-light, $amber-deep);
padding: $sp-xs $sp-lg;
border-radius: $radius-full;
flex-shrink: 0;
}
.calendar-section {
margin-bottom: $sp-md;
padding: $sp-md;
}
.stats-row {
display: flex;
gap: $sp-xs;
margin-bottom: $sp-md;
}
.stat-card {
flex: 1;
text-align: center;
padding: $sp-md $sp-xs;
}
.stat-value {
display: block;
font-size: $fs-xl;
color: $text-primary;
margin-bottom: 4rpx;
}
.stat-label {
font-size: $fs-xs;
color: $text-secondary;
}
.week-summary {
padding: $sp-md;
}
.week-header {
margin-bottom: $sp-sm;
}
.week-stats {
gap: 0;
}
.week-stat {
flex: 1;
text-align: center;
}
.week-stat-value {
display: block;
font-size: $fs-xl;
font-weight: $fw-bold;
color: $text-primary;
margin-bottom: 4rpx;
}
.week-stat-label {
font-size: $fs-sm;
color: $text-secondary;
}
.week-divider {
width: 2rpx;
background: var(--divider-color, rgba(255, 255, 255, 0.08));
margin: $sp-xs 0;
}
.health-ref {
margin-top: $sp-md;
position: relative;
height: 12rpx;
background: $bg-elevated;
border-radius: $radius-full;
overflow: hidden;
}
.health-bar-fill {
height: 100%;
border-radius: $radius-full;
background: linear-gradient(90deg, $mint, $amber);
transition: width $duration-slow $ease-out;
}
.health-text {
display: block;
font-size: $fs-xs;
color: $text-tertiary;
margin-top: $sp-xs;
}
/* 核心CTA - 首页英雄区域 */
.hero-cta {
position: relative;
margin-bottom: $sp-md;
border-radius: $radius-lg;
overflow: hidden;
padding: $sp-md $sp-lg;
cursor: pointer;
&:active {
transform: scale(0.98);
}
}
.hero-cta-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, $amber-light, $amber, $amber-deep);
z-index: 0;
&::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, var(--btn-shimmer, rgba(255, 255, 255, 0.15)), transparent);
animation: shimmer 4s infinite;
}
}
@keyframes shimmer {
0% { left: -100%; }
100% { left: 100%; }
}
.hero-cta-content {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: $sp-md;
}
.hero-cta-emoji {
font-size: 56rpx;
}
.hero-cta-text {
flex: 1;
}
.hero-cta-title {
display: block;
font-size: $fs-xl;
font-weight: $fw-black;
color: $text-on-amber;
letter-spacing: -1rpx;
}
.hero-cta-sub {
display: block;
font-size: $fs-xs;
color: rgba(11, 11, 20, 0.6);
margin-top: 4rpx;
}
.hero-cta-arrow {
font-size: 44rpx;
color: $text-on-amber;
opacity: 0.5;
font-weight: $fw-bold;
}
.bottom-health-tip {
text-align: center;
padding: $sp-sm 0;
font-size: $fs-xs;
color: $text-tertiary;
opacity: 0.4;
}
/* 日期弹窗 */
.day-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--overlay-mask, rgba(0, 0, 0, 0.6));
display: flex;
align-items: flex-end;
z-index: 100;
padding: $sp-lg;
animation: overlayIn 0.2s ease-out;
}
@keyframes overlayIn {
from { opacity: 0; }
to { opacity: 1; }
}
.day-detail {
position: relative;
width: 100%;
padding: $sp-md $sp-xl $sp-xl;
color: $text-primary;
border-radius: $radius-xl;
/* 背景层次:底部卡片色 + 顶部琥珀光晕 */
background:
radial-gradient(120% 60% at 50% 0%, var(--amber-glow, rgba(232, 168, 56, 0.15)) 0%, transparent 60%),
linear-gradient(180deg, var(--bg-elevated, #28283E) 0%, var(--bg-card, #1A1A28) 55%);
border: 2rpx solid var(--border-faint, rgba(255, 255, 255, 0.08));
box-shadow: var(--shadow-lg);
animation: sheetIn 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes sheetIn {
from { opacity: 0; transform: translateY(48rpx); }
to { opacity: 1; transform: translateY(0); }
}
/* 顶部拖拽把手 */
.day-detail-handle {
width: 64rpx;
height: 8rpx;
border-radius: $radius-full;
background: var(--border-active, rgba(255, 255, 255, 0.12));
margin: 0 auto $sp-md;
}
.day-detail-header {
margin-bottom: $sp-lg;
}
.day-detail-date {
position: relative;
padding-left: $sp-md;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 6rpx;
height: 70%;
border-radius: $radius-full;
background: linear-gradient(180deg, var(--amber-light, #F5C563), var(--amber-deep, #C47F17));
}
}
.day-close {
padding: $sp-xs $sp-sm;
}
.day-drinks {
background: var(--bg-subtle, rgba(255, 255, 255, 0.03));
border-radius: $radius-md;
padding: $sp-xs $sp-md;
}
.day-drink-item {
padding: $sp-md 0;
font-size: $fs-base;
color: $text-primary;
border-bottom: 2rpx solid var(--border-micro, rgba(255, 255, 255, 0.05));
&:last-child {
border-bottom: none;
}
}
.day-drink-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.day-drink-name {
display: flex;
align-items: center;
gap: $sp-xs;
flex: 1;
min-width: 0;
}
.day-drink-icon {
width: 36rpx;
height: 36rpx;
flex-shrink: 0;
}
.day-drink-amount {
font-size: $fs-sm;
color: $text-secondary;
white-space: nowrap;
margin-left: $sp-sm;
}
.day-meta {
display: flex;
gap: $sp-sm;
margin-top: $sp-lg;
}
.day-meta-chip {
display: flex;
align-items: center;
gap: $sp-xs;
padding: $sp-sm $sp-md;
border-radius: $radius-full;
}
.day-feeling-chip {
background: var(--bg-hover, rgba(255, 255, 255, 0.08));
}
.day-feeling-text {
font-size: $fs-sm;
color: $text-secondary;
}
.day-cups-chip {
background: var(--amber-glow, rgba(232, 168, 56, 0.15));
border: 2rpx solid rgba(232, 168, 56, 0.25);
}
.day-cups-num {
font-size: $fs-lg;
font-weight: $fw-bold;
color: $amber;
}
.day-cups-unit {
font-size: $fs-xs;
color: $amber;
}
.day-abstain {
text-align: center;
padding: $sp-xl 0;
}
.day-abstain-emoji {
display: block;
font-size: $fs-hero;
margin-bottom: $sp-sm;
}
.day-empty {
text-align: center;
padding: $sp-xl 0;
.btn-ghost, .day-record-btn {
margin-top: $sp-lg;
width: 100%;
}
.day-abstain-btn {
color: $mint;
border-color: rgba(94, 198, 160, 0.35);
background: rgba(94, 198, 160, 0.08);
}
}
.day-action-row {
display: flex;
gap: $sp-sm;
margin-top: $sp-lg;
}
/* 修改:琥珀主操作 */
.day-action-btn {
flex: 1;
font-size: $fs-sm;
&:first-child {
color: $text-on-amber;
background: linear-gradient(135deg, var(--amber-light, #F5C563), var(--amber-deep, #C47F17));
border: none;
box-shadow: var(--shadow-amber);
}
/* 改为未饮酒:薄荷淡底 */
&:last-child {
color: $mint;
background: rgba(94, 198, 160, 0.08);
border: 2rpx solid rgba(94, 198, 160, 0.30);
}
}
.day-delete-btn {
margin-top: $sp-md;
width: 100%;
font-size: $fs-sm;
color: #E85D3A;
background: rgba(232, 93, 58, 0.06);
border: 2rpx solid rgba(232, 93, 58, 0.30);
&:active {
background: rgba(232, 93, 58, 0.14);
}
}
.day-toggle-btn {
margin-top: $sp-lg;
width: 100%;
font-size: $fs-sm;
color: $text-secondary;
border-color: var(--border-active, rgba(255, 255, 255, 0.12));
}
</style>