- 替换所有文件中的品牌名称引用,包括App.vue、pages.json、uni.scss等 - 更新全局样式、导航栏标题和组件中的文案显示 - 修改分享功能中的应用名称显示 - 调整聊天页面的输入栏键盘适配逻辑 - 在多个页面组件中添加事件发射声明以支持交互功能
1041 lines
26 KiB
Vue
1041 lines
26 KiB
Vue
<template>
|
||
<view :class="themeClass" class="page-container profile-page">
|
||
<!-- 沉浸式头部 -->
|
||
<view class="hero-header" :style="{ paddingTop: headerPaddingTop }">
|
||
<view class="hero-bg">
|
||
<view class="hero-orb hero-orb-1"></view>
|
||
<view class="hero-orb hero-orb-2"></view>
|
||
<text class="hero-deco">🥃</text>
|
||
</view>
|
||
<!-- 右上角分享按钮(避让胶囊) -->
|
||
<button class="hero-share-btn" open-type="share" :style="shareBtnStyle">
|
||
<text class="hero-share-icon">↗</text>
|
||
</button>
|
||
<view class="hero-content">
|
||
<view class="avatar-ring">
|
||
<image v-if="user.avatar" class="hero-avatar" :src="user.avatar" mode="aspectFill"></image>
|
||
<view v-else class="hero-avatar hero-avatar-placeholder">
|
||
<text class="hero-avatar-icon">👤</text>
|
||
</view>
|
||
</view>
|
||
<view class="hero-info">
|
||
<text class="hero-greeting">{{ greeting }}</text>
|
||
<view class="hero-name-row">
|
||
<text class="hero-name">{{ user.nickname || '酒友' }}</text>
|
||
<view class="hero-level-tag">
|
||
<text class="hero-level-text">{{ drinkLevel }}</text>
|
||
</view>
|
||
</view>
|
||
<text class="hero-joined">{{ joinedDate }} 加入 · 碰盏日记</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 悬浮数据卡片 -->
|
||
<view class="stats-float-card">
|
||
<view class="stats-item" @click="noop">
|
||
<text class="stats-num text-num">{{ stats.totalDays }}</text>
|
||
<text class="stats-label">打卡天数</text>
|
||
</view>
|
||
<view class="stats-divider"></view>
|
||
<view class="stats-item" @click="noop">
|
||
<text class="stats-num text-num">{{ stats.totalRecords }}</text>
|
||
<text class="stats-label">饮酒次数</text>
|
||
</view>
|
||
<view class="stats-divider"></view>
|
||
<view class="stats-item" @click="noop">
|
||
<text class="stats-num text-num text-amber">{{ stats.totalCups }}</text>
|
||
<text class="stats-label">标准杯</text>
|
||
</view>
|
||
<view class="stats-divider"></view>
|
||
<view class="stats-item" @click="noop">
|
||
<text class="stats-num stats-num-text">{{ favCategoryName }}</text>
|
||
<text class="stats-label">最爱酒类</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 酒桌小游戏入口 -->
|
||
<view class="actions-section">
|
||
<view class="action-card" @click="goGames">
|
||
<view class="action-icon-wrap action-icon-games">
|
||
<text class="action-icon">🎲</text>
|
||
</view>
|
||
<view class="action-text">
|
||
<view class="action-title-row">
|
||
<text class="action-title">酒桌小游戏</text>
|
||
<view class="action-hot-tag">HOT</view>
|
||
</view>
|
||
<text class="action-desc">数字炸弹·猜拳·骰子·转盘,今晚谁喝?</text>
|
||
</view>
|
||
<text class="action-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 连续打卡卡片 -->
|
||
<view class="streak-section">
|
||
<view class="streak-card" :class="stats.streakType === 'drank' ? 'streak-drank' : 'streak-abstain'">
|
||
<view class="streak-left">
|
||
<view class="streak-badge">
|
||
<text class="streak-badge-emoji">{{ stats.streakType === 'drank' ? '🔥' : '💪' }}</text>
|
||
</view>
|
||
<view class="streak-info">
|
||
<text class="streak-title">{{ stats.streakType === 'drank' ? '连续饮酒' : '连续戒酒' }}</text>
|
||
<text class="streak-sub">{{ stats.streakType === 'drank' ? '保持记录,理性饮酒' : '自律即自由,继续坚持' }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="streak-right">
|
||
<text class="streak-days text-num">{{ stats.streak }}</text>
|
||
<text class="streak-days-unit">天</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 饮酒人格卡片 -->
|
||
<view class="persona-card">
|
||
<view class="persona-glow"></view>
|
||
<view class="persona-content">
|
||
<view class="persona-header">
|
||
<image v-if="isIconPath(personaEmoji)" :src="personaEmoji" class="persona-icon-img" mode="aspectFit"></image>
|
||
<text v-else class="persona-emoji">{{ personaEmoji }}</text>
|
||
<view class="persona-text">
|
||
<text class="persona-title">{{ personaTitle }}</text>
|
||
<text class="persona-desc">{{ personaDesc }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="persona-tags">
|
||
<view class="persona-tag" v-if="favCategoryName !== '-'">
|
||
<text>偏爱{{ favCategoryName }}</text>
|
||
</view>
|
||
<view class="persona-tag" v-if="stats.categoryVariety > 0">
|
||
<text>尝过{{ stats.categoryVariety }}种酒</text>
|
||
</view>
|
||
<view class="persona-tag" v-if="stats.totalCups > 0">
|
||
<text>累计{{ stats.totalCups }}标准杯</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 成就墙 - 横向滚动 -->
|
||
<view class="achievements-section">
|
||
<view class="section-header">
|
||
<text class="section-title">成就徽章</text>
|
||
<text class="section-count text-num">{{ unlockedCount }}/{{ achievementList.length }}</text>
|
||
</view>
|
||
<scroll-view class="achievements-scroll" scroll-x :show-scrollbar="false">
|
||
<view class="achievements-track">
|
||
<view class="achievement-item" v-for="a in achievementList" :key="a.id">
|
||
<AchievementBadge
|
||
:achievement="a"
|
||
@click="showAchievementDetail(a)"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 登出/登录 -->
|
||
<view class="logout-area">
|
||
<button v-if="isGuest" class="btn-ghost logout-btn" @click="goLogin">
|
||
登录账号,同步数据
|
||
</button>
|
||
<button v-else class="logout-text-btn" @click="handleLogout">
|
||
退出登录
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 成就详情弹窗 -->
|
||
<view v-if="showDetail" class="detail-overlay" @click="showDetail = false">
|
||
<view class="detail-card" @click.stop>
|
||
<view class="detail-icon-ring" :class="detailAchievement.unlocked ? 'ring-unlocked' : 'ring-locked'">
|
||
<image v-if="isIconPath(detailAchievement.icon)" :src="detailAchievement.icon" class="detail-icon-img" mode="aspectFit"></image>
|
||
<text v-else class="detail-emoji">{{ detailAchievement.icon }}</text>
|
||
</view>
|
||
<text class="detail-name">{{ detailAchievement.name }}</text>
|
||
<text class="detail-desc">{{ detailAchievement.desc }}</text>
|
||
<view class="detail-status" :class="detailAchievement.unlocked ? 'status-unlocked' : 'status-locked'">
|
||
<text>{{ detailAchievement.unlocked ? '✓ 已解锁' : '🔒 未解锁' }}</text>
|
||
</view>
|
||
<button class="detail-close-btn" @click="showDetail = false">关闭</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import AchievementBadge from '../../components/AchievementBadge.vue'
|
||
import client, { clearAuth } from '../../common/api'
|
||
import { checkAchievements, getCatIcon, isIconPath } from '../../common/utils'
|
||
import { ACHIEVEMENTS, DRINK_CATEGORIES } from '../../common/constants'
|
||
import themeMixin from '../../common/theme-mixin'
|
||
|
||
export default {
|
||
mixins: [themeMixin],
|
||
components: { AchievementBadge },
|
||
data() {
|
||
// 获取胶囊按钮位置,动态计算分享按钮偏移和头部内边距
|
||
const sysInfo = uni.getSystemInfoSync()
|
||
const menuBtn = uni.getMenuButtonBoundingClientRect()
|
||
return {
|
||
user: {},
|
||
stats: {},
|
||
achievementList: [],
|
||
showDetail: false,
|
||
detailAchievement: {},
|
||
isGuest: false,
|
||
// 分享按钮定位:与胶囊垂直居中对齐,右侧留出胶囊宽度
|
||
shareBtnStyle: `top:${menuBtn.top}px;right:${sysInfo.windowWidth - menuBtn.left + 8}px;`,
|
||
// 头部内容从胶囊底部开始,避免遮挡头像
|
||
headerPaddingTop: (menuBtn.bottom + 20) + 'px'
|
||
}
|
||
},
|
||
computed: {
|
||
favCategoryName() {
|
||
if (!this.stats.favCategory) return '-'
|
||
const cat = DRINK_CATEGORIES.find(c => c.id === this.stats.favCategory)
|
||
return cat ? cat.name : '-'
|
||
},
|
||
joinedDate() {
|
||
const raw = this.user.joinedAt
|
||
if (!raw) return '2026'
|
||
// 只取年月日,去掉时间戳
|
||
const str = String(raw)
|
||
const m = str.match(/(\d{4})[-/](\d{2})[-/](\d{2})/)
|
||
if (m) return `${m[1]}.${m[2]}.${m[3]}`
|
||
return str.slice(0, 10)
|
||
},
|
||
unlockedCount() {
|
||
return this.achievementList.filter(a => a.unlocked).length
|
||
},
|
||
drinkLevel() {
|
||
const cups = this.stats.totalCups || 0
|
||
if (cups === 0) return '🌱 新手上路'
|
||
if (cups < 20) return '🍶 小酌新手'
|
||
if (cups < 50) return '🍺 进阶酒友'
|
||
if (cups < 100) return '🥃 资深酒客'
|
||
if (cups < 200) return '🏆 品酒达人'
|
||
return '👑 传奇酒神'
|
||
},
|
||
personaEmoji() {
|
||
const fav = this.stats.favCategory
|
||
if (fav) return getCatIcon(fav)
|
||
return '🍻'
|
||
},
|
||
personaTitle() {
|
||
const fav = this.stats.favCategory
|
||
const map = { baijiu: '白酒之魂', beer: '啤酒达人', wine: '红酒雅士', whisky: '威士忌行家', huangjiu: '黄酒知音', sake: '清酒爱好者', fruit: '果酒小清新', cocktail: '调酒师' }
|
||
return map[fav] || '多元品鉴师'
|
||
},
|
||
greeting() {
|
||
const h = new Date().getHours()
|
||
if (h < 6) return '🌙 夜深了,微醺正好'
|
||
if (h < 11) return '☀️ 早上好,新的一天'
|
||
if (h < 14) return '🌤 中午好,小酌怡情'
|
||
if (h < 18) return '🌇 下午好,来一杯?'
|
||
return '🌙 晚上好,今晚喝点?'
|
||
},
|
||
personaDesc() {
|
||
const days = this.stats.totalDays || 0
|
||
if (days === 0) return '开始记录你的第一杯吧'
|
||
if (days < 7) return '初来乍到,探索中'
|
||
if (days < 30) return '已养成记录习惯'
|
||
if (days < 100) return '资深记录者,每一杯都有故事'
|
||
return '百天老友,酒中见真章'
|
||
}
|
||
},
|
||
onShow() {
|
||
this.checkLoginState()
|
||
this.loadData()
|
||
},
|
||
onShareAppMessage() {
|
||
const name = this.user.nickname || '我'
|
||
return {
|
||
title: `${name}的饮酒名片 - 碰盏日记`,
|
||
path: `/pages/share-profile/share-profile?${this.buildShareQuery()}`
|
||
}
|
||
},
|
||
onShareTimeline() {
|
||
const name = this.user.nickname || '我'
|
||
return {
|
||
title: `${name}的饮酒名片 - 碰盏日记`,
|
||
query: this.buildShareQuery()
|
||
}
|
||
},
|
||
methods: {
|
||
isIconPath,
|
||
// 构建分享名片的 query 参数(携带公开档案数据)
|
||
buildShareQuery() {
|
||
const parts = [`nick=${encodeURIComponent(this.user.nickname || '酒友')}`]
|
||
if (this.user.avatar) parts.push(`avatar=${encodeURIComponent(this.user.avatar)}`)
|
||
parts.push(`days=${this.stats.totalDays || 0}`)
|
||
parts.push(`records=${this.stats.totalRecords || 0}`)
|
||
parts.push(`cups=${this.stats.totalCups || 0}`)
|
||
parts.push(`streak=${this.stats.streak || 0}`)
|
||
parts.push(`st=${this.stats.streakType || 'drank'}`)
|
||
if (this.stats.favCategory) parts.push(`fav=${this.stats.favCategory}`)
|
||
return parts.join('&')
|
||
},
|
||
async loadData() {
|
||
try {
|
||
const [userResp, statsResp, achieveResp] = await Promise.allSettled([
|
||
client.GetUserProfile({}),
|
||
client.GetStatsOverview({}),
|
||
client.GetAchievements({})
|
||
])
|
||
|
||
// 用户信息
|
||
if (userResp.status === 'fulfilled' && userResp.value) {
|
||
this.user = userResp.value.data || userResp.value
|
||
} else {
|
||
try {
|
||
this.user = JSON.parse(uni.getStorageSync('user_info') || '{}')
|
||
} catch (e) { this.user = {} }
|
||
}
|
||
|
||
// 统计概览
|
||
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 (achieveResp.status === 'fulfilled' && achieveResp.value) {
|
||
// SDK 已解包 data.data,成就数组在 achievements 键下
|
||
const payload = achieveResp.value.data || achieveResp.value
|
||
const list = payload.achievements || payload.list || (Array.isArray(payload) ? payload : [])
|
||
if (Array.isArray(list) && list.length > 0) {
|
||
this.achievementList = list.map((a, i) => ({
|
||
id: a.achievement_id || a.id,
|
||
name: a.name,
|
||
desc: a.desc,
|
||
// 后端返回的 icon 是不存在的图片路径,按顺序复用本地 emoji 图标
|
||
icon: this.resolveAchievementIcon(i),
|
||
condition: a.condition || '',
|
||
unlocked: !!a.unlocked,
|
||
progress: a.progress || 0
|
||
}))
|
||
} else {
|
||
// API 返回空,降级本地计算
|
||
this.achievementList = checkAchievements(this.stats, ACHIEVEMENTS)
|
||
}
|
||
} else {
|
||
this.achievementList = checkAchievements(this.stats, ACHIEVEMENTS)
|
||
}
|
||
} catch (e) {
|
||
console.warn('加载个人数据失败:', e)
|
||
try {
|
||
this.user = JSON.parse(uni.getStorageSync('user_info') || '{}')
|
||
} catch (err) { this.user = {} }
|
||
this.achievementList = checkAchievements(this.stats, ACHIEVEMENTS)
|
||
}
|
||
},
|
||
showAchievementDetail(a) {
|
||
this.detailAchievement = a
|
||
this.showDetail = true
|
||
},
|
||
// 后端返回的 icon 是无效图片路径,按索引复用本地成就的 emoji 图标(超出后循环取)
|
||
resolveAchievementIcon(index) {
|
||
if (!ACHIEVEMENTS.length) return '🏅'
|
||
return ACHIEVEMENTS[index % ACHIEVEMENTS.length].icon
|
||
},
|
||
goCircle() {
|
||
uni.switchTab({ url: '/pages/circle/circle' })
|
||
},
|
||
goGames() {
|
||
uni.navigateTo({ url: '/pages/games/games' })
|
||
},
|
||
goHome() {
|
||
uni.switchTab({ url: '/pages/index/index' })
|
||
},
|
||
goRecord() {
|
||
uni.navigateTo({ url: '/pages/record/record' })
|
||
},
|
||
checkLoginState() {
|
||
const isLoggedIn = uni.getStorageSync('is_logged_in')
|
||
const isGuest = uni.getStorageSync('is_guest')
|
||
if (!isLoggedIn || isLoggedIn !== 'true') {
|
||
uni.reLaunch({ url: '/pages/login/login' })
|
||
return
|
||
}
|
||
this.isGuest = isGuest === 'true'
|
||
},
|
||
handleLogout() {
|
||
uni.showModal({
|
||
title: '退出登录',
|
||
content: '退出后本地数据将保留,重新登录后可同步',
|
||
confirmText: '退出',
|
||
confirmColor: '#FF6B6B',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
clearAuth()
|
||
uni.reLaunch({ url: '/pages/login/login' })
|
||
}
|
||
}
|
||
})
|
||
},
|
||
shareTimeline() {
|
||
uni.showModal({
|
||
title: '分享到朋友圈',
|
||
content: '请点击右上角「···」菜单,选择「分享到朋友圈」',
|
||
showCancel: false,
|
||
confirmText: '知道了'
|
||
})
|
||
},
|
||
goLogin() {
|
||
uni.navigateTo({ url: '/pages/login/login' })
|
||
},
|
||
noop() {}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.profile-page {
|
||
padding-bottom: calc(env(safe-area-inset-bottom) + 40rpx);
|
||
}
|
||
|
||
/* === 沉浸式头部 === */
|
||
.hero-header {
|
||
position: relative;
|
||
padding: 0 $sp-lg 64rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.hero-share-btn {
|
||
position: absolute;
|
||
z-index: 10;
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(255,255,255,0.06);
|
||
border: 1rpx solid rgba(255,255,255,0.10);
|
||
border-radius: 50%;
|
||
backdrop-filter: blur(10px);
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
|
||
&:active {
|
||
background: rgba(255,255,255,0.12);
|
||
transform: scale(0.9);
|
||
}
|
||
}
|
||
|
||
.hero-share-icon {
|
||
font-size: $fs-base;
|
||
color: $text-secondary;
|
||
font-weight: $fw-bold;
|
||
}
|
||
|
||
.hero-bg {
|
||
position: absolute;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
background: radial-gradient(ellipse 80% 60% at 50% 20%, rgba(232,168,56,0.08) 0%, transparent 70%);
|
||
}
|
||
|
||
.hero-orb {
|
||
position: absolute;
|
||
border-radius: 50%;
|
||
filter: blur(80rpx);
|
||
}
|
||
|
||
.hero-orb-1 {
|
||
width: 400rpx; height: 400rpx;
|
||
top: -120rpx; right: -100rpx;
|
||
background: rgba(232,168,56,0.07);
|
||
}
|
||
|
||
.hero-orb-2 {
|
||
width: 280rpx; height: 280rpx;
|
||
top: 60rpx; left: -80rpx;
|
||
background: rgba(139,133,184,0.06);
|
||
}
|
||
|
||
.hero-deco {
|
||
position: absolute;
|
||
right: -20rpx;
|
||
bottom: -30rpx;
|
||
font-size: 200rpx;
|
||
opacity: 0.06;
|
||
transform: rotate(-15deg);
|
||
z-index: 1;
|
||
}
|
||
|
||
.hero-content {
|
||
position: relative;
|
||
z-index: 2;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.avatar-ring {
|
||
width: 128rpx; height: 128rpx;
|
||
border-radius: 50%;
|
||
padding: 4rpx;
|
||
flex-shrink: 0;
|
||
background: linear-gradient(160deg, rgba(232,168,56,0.7), rgba(232,168,56,0.2), rgba(139,133,184,0.3));
|
||
box-shadow: 0 0 40rpx rgba(232,168,56,0.12);
|
||
}
|
||
|
||
.hero-avatar {
|
||
width: 100%; height: 100%;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.hero-avatar-placeholder {
|
||
background: $bg-card;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.hero-avatar-icon {
|
||
font-size: 56rpx;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.hero-info {
|
||
flex: 1;
|
||
margin-left: $sp-xl;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.hero-greeting {
|
||
font-size: $fs-xs;
|
||
color: $text-tertiary;
|
||
margin-bottom: 14rpx;
|
||
letter-spacing: 1rpx;
|
||
}
|
||
|
||
.hero-name-row {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 14rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.hero-name {
|
||
font-size: $fs-2xl;
|
||
font-weight: $fw-black;
|
||
color: $text-primary;
|
||
letter-spacing: -1rpx;
|
||
}
|
||
|
||
.hero-level-tag {
|
||
padding: 6rpx 18rpx;
|
||
background: rgba(232,168,56,0.10);
|
||
border: 1rpx solid rgba(232,168,56,0.20);
|
||
border-radius: $radius-full;
|
||
margin-left: 20rpx;
|
||
}
|
||
|
||
.hero-level-text {
|
||
font-size: 20rpx;
|
||
color: $amber;
|
||
font-weight: $fw-medium;
|
||
}
|
||
|
||
.hero-joined {
|
||
font-size: $fs-xs;
|
||
color: $text-tertiary;
|
||
opacity: 0.7;
|
||
line-height: $lh-normal;
|
||
}
|
||
|
||
/* === 悬浮数据卡片 === */
|
||
.stats-float-card {
|
||
display: flex;
|
||
align-items: center;
|
||
margin: -40rpx $sp-lg 0;
|
||
padding: $sp-xl $sp-md;
|
||
background: $bg-card;
|
||
border-radius: $radius-xl;
|
||
box-shadow: $shadow-md;
|
||
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
position: relative;
|
||
z-index: 10;
|
||
}
|
||
|
||
.stats-item {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.stats-num {
|
||
font-size: $fs-xl;
|
||
font-weight: $fw-black;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.stats-num-text {
|
||
font-size: $fs-lg;
|
||
}
|
||
|
||
.stats-label {
|
||
font-size: $fs-xs;
|
||
color: $text-tertiary;
|
||
}
|
||
|
||
.stats-divider {
|
||
width: 2rpx;
|
||
height: 48rpx;
|
||
background: var(--divider-color, rgba(255,255,255,0.08));
|
||
}
|
||
|
||
/* === 连续打卡 === */
|
||
.streak-section {
|
||
padding: $sp-lg $sp-lg 0;
|
||
}
|
||
|
||
.streak-card {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: $sp-xl;
|
||
border-radius: $radius-xl;
|
||
position: relative;
|
||
overflow: hidden;
|
||
|
||
&.streak-drank {
|
||
background: linear-gradient(135deg, rgba(232,168,56,0.12) 0%, rgba(232,168,56,0.04) 100%);
|
||
border: 1rpx solid rgba(232,168,56,0.2);
|
||
}
|
||
|
||
&.streak-abstain {
|
||
background: linear-gradient(135deg, rgba(94,198,160,0.12) 0%, rgba(94,198,160,0.04) 100%);
|
||
border: 1rpx solid rgba(94,198,160,0.2);
|
||
}
|
||
}
|
||
|
||
.streak-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $sp-md;
|
||
}
|
||
|
||
.streak-badge {
|
||
width: 88rpx; height: 88rpx;
|
||
border-radius: 50%;
|
||
background: $bg-card;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: $shadow-sm;
|
||
}
|
||
|
||
.streak-badge-emoji {
|
||
font-size: 44rpx;
|
||
}
|
||
|
||
.streak-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4rpx;
|
||
}
|
||
|
||
.streak-title {
|
||
font-size: $fs-base;
|
||
font-weight: $fw-bold;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.streak-sub {
|
||
font-size: $fs-xs;
|
||
color: $text-tertiary;
|
||
}
|
||
|
||
.streak-right {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 4rpx;
|
||
}
|
||
|
||
.streak-days {
|
||
font-size: $fs-hero;
|
||
font-weight: $fw-black;
|
||
color: $amber;
|
||
line-height: 1;
|
||
|
||
.streak-abstain & {
|
||
color: $mint;
|
||
}
|
||
}
|
||
|
||
.streak-days-unit {
|
||
font-size: $fs-sm;
|
||
color: $text-secondary;
|
||
font-weight: $fw-medium;
|
||
}
|
||
|
||
/* === 饮酒人格卡片 === */
|
||
.persona-card {
|
||
margin: $sp-lg $sp-lg 0;
|
||
border-radius: $radius-xl;
|
||
background: $bg-card;
|
||
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
padding: $sp-xl;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.persona-glow {
|
||
position: absolute;
|
||
width: 240rpx; height: 240rpx;
|
||
top: -80rpx; right: -60rpx;
|
||
background: radial-gradient(circle, rgba(232,168,56,0.10) 0%, transparent 70%);
|
||
border-radius: 50%;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.persona-content {
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
.persona-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $sp-md;
|
||
margin-bottom: $sp-lg;
|
||
}
|
||
|
||
.persona-emoji {
|
||
font-size: 56rpx;
|
||
}
|
||
|
||
.persona-icon-img {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.persona-text {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4rpx;
|
||
}
|
||
|
||
.persona-title {
|
||
font-size: $fs-lg;
|
||
font-weight: $fw-bold;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.persona-desc {
|
||
font-size: $fs-xs;
|
||
color: $text-tertiary;
|
||
}
|
||
|
||
.persona-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: $sp-sm;
|
||
}
|
||
|
||
.persona-tag {
|
||
padding: 8rpx 20rpx;
|
||
background: $bg-elevated;
|
||
border-radius: $radius-full;
|
||
border: 1rpx solid var(--border-micro, rgba(255,255,255,0.05));
|
||
|
||
text {
|
||
font-size: $fs-xs;
|
||
color: $text-secondary;
|
||
}
|
||
}
|
||
|
||
/* === 成就墙 === */
|
||
.achievements-section {
|
||
margin-top: $sp-xl;
|
||
padding: 0 $sp-lg;
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: $sp-lg;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: $fs-lg;
|
||
font-weight: $fw-bold;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.section-count {
|
||
font-size: $fs-sm;
|
||
color: $text-tertiary;
|
||
}
|
||
|
||
.achievements-scroll {
|
||
white-space: nowrap;
|
||
width: 100%;
|
||
}
|
||
|
||
.achievements-track {
|
||
display: inline-flex;
|
||
gap: $sp-md;
|
||
padding-bottom: $sp-sm;
|
||
}
|
||
|
||
.achievement-item {
|
||
display: inline-flex;
|
||
min-width: 180rpx;
|
||
}
|
||
|
||
/* === 操作入口 === */
|
||
.actions-section {
|
||
margin: $sp-xl $sp-lg 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: $sp-sm;
|
||
}
|
||
|
||
.action-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $sp-md;
|
||
padding: $sp-lg;
|
||
background: $bg-card;
|
||
border-radius: $radius-lg;
|
||
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
transition: all $duration-fast $ease-out;
|
||
line-height: 1.4;
|
||
text-align: left;
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
|
||
&:active {
|
||
transform: scale(0.98);
|
||
background: $bg-card-alt;
|
||
}
|
||
}
|
||
|
||
.action-icon-wrap {
|
||
width: 80rpx; height: 80rpx;
|
||
border-radius: $radius-md;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.action-icon-circle {
|
||
background: rgba(139,133,184,0.12);
|
||
}
|
||
|
||
.action-icon-games {
|
||
background: rgba(232,168,56,0.12);
|
||
}
|
||
|
||
.action-icon-timeline {
|
||
background: rgba(94,198,160,0.12);
|
||
}
|
||
|
||
.action-icon {
|
||
font-size: 36rpx;
|
||
}
|
||
|
||
.action-text {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.action-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $sp-sm;
|
||
}
|
||
|
||
.action-title {
|
||
font-size: $fs-base;
|
||
font-weight: $fw-medium;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.action-new-tag {
|
||
font-size: 18rpx;
|
||
font-weight: $fw-bold;
|
||
color: #fff;
|
||
background: linear-gradient(135deg, $amber, $amber-deep);
|
||
padding: 2rpx 10rpx;
|
||
border-radius: $radius-full;
|
||
}
|
||
|
||
.action-hot-tag {
|
||
font-size: 18rpx;
|
||
font-weight: $fw-bold;
|
||
color: #fff;
|
||
background: linear-gradient(135deg, $coral, #e04848);
|
||
padding: 2rpx 10rpx;
|
||
border-radius: $radius-full;
|
||
}
|
||
|
||
.action-desc {
|
||
display: block;
|
||
font-size: $fs-xs;
|
||
color: $text-tertiary;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
.action-arrow {
|
||
font-size: $fs-xl;
|
||
color: $text-tertiary;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
/* === 登出 === */
|
||
.logout-area {
|
||
padding: $sp-2xl $sp-lg $sp-xl;
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.logout-btn {
|
||
min-width: 300rpx;
|
||
}
|
||
|
||
.logout-text-btn {
|
||
background: none;
|
||
border: none;
|
||
color: $text-tertiary;
|
||
font-size: $fs-sm;
|
||
padding: $sp-sm $sp-lg;
|
||
|
||
&:active {
|
||
color: $coral;
|
||
}
|
||
}
|
||
|
||
/* === 成就详情弹窗 === */
|
||
.detail-overlay {
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
background: var(--overlay-modal, rgba(0,0,0,0.7));
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 100;
|
||
padding: $sp-xl;
|
||
}
|
||
|
||
.detail-card {
|
||
width: 100%;
|
||
max-width: 580rpx;
|
||
text-align: center;
|
||
padding: $sp-2xl $sp-xl;
|
||
background: $bg-card;
|
||
border-radius: $radius-xl;
|
||
box-shadow: $shadow-lg;
|
||
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
}
|
||
|
||
.detail-icon-ring {
|
||
width: 140rpx; height: 140rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin: 0 auto $sp-lg;
|
||
|
||
&.ring-unlocked {
|
||
background: rgba(232,168,56,0.12);
|
||
border: 3rpx solid rgba(232,168,56,0.3);
|
||
box-shadow: 0 0 32rpx rgba(232,168,56,0.2);
|
||
}
|
||
|
||
&.ring-locked {
|
||
background: $bg-elevated;
|
||
border: 3rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
}
|
||
}
|
||
|
||
.detail-emoji {
|
||
font-size: 64rpx;
|
||
}
|
||
|
||
.detail-icon-img {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
}
|
||
|
||
.detail-name {
|
||
display: block;
|
||
font-size: $fs-xl;
|
||
font-weight: $fw-bold;
|
||
color: $text-primary;
|
||
margin-bottom: $sp-sm;
|
||
}
|
||
|
||
.detail-desc {
|
||
display: block;
|
||
font-size: $fs-sm;
|
||
color: $text-secondary;
|
||
margin-bottom: $sp-lg;
|
||
line-height: $lh-loose;
|
||
}
|
||
|
||
.detail-status {
|
||
display: inline-flex;
|
||
padding: $sp-sm $sp-lg;
|
||
border-radius: $radius-full;
|
||
font-size: $fs-sm;
|
||
font-weight: $fw-medium;
|
||
|
||
&.status-unlocked {
|
||
background: rgba(94,198,160,0.15);
|
||
color: $mint;
|
||
}
|
||
|
||
&.status-locked {
|
||
background: $bg-elevated;
|
||
color: $text-tertiary;
|
||
}
|
||
}
|
||
|
||
.detail-close-btn {
|
||
width: 100%;
|
||
margin-top: $sp-xl;
|
||
background: $bg-elevated;
|
||
color: $text-primary;
|
||
font-size: $fs-base;
|
||
font-weight: $fw-medium;
|
||
padding: $sp-md 0;
|
||
border-radius: $radius-full;
|
||
border: 1rpx solid var(--border-subtle, rgba(255,255,255,0.10));
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
|
||
&:active {
|
||
background: $bg-card-alt;
|
||
}
|
||
}
|
||
</style>
|