Files
hejiu/pages/profile/profile.vue
T
cg 9f5c69e1e5 feat(app): 项目重命名为干杯日记并优化UI设计
- 将应用名称从"喝了么"更改为"干杯日记"
- 更新所有相关文件中的品牌标识和描述信息
- 升级HaveADrink依赖包从1.0.3到1.0.4版本
- 在pages.json中新增分享个人资料页面配置
- 重构DrinkCard组件UI,增加装饰背景、酒类图标支持、饮酒感受徽章等功能
- 优化API错误处理逻辑,增加静默模式支持
- 修改白酒类别图标为 urn emoji,黄酒图标为 tea emoji
- 添加分享功能按钮并优化页面返回交互体验
- 引入新的工具函数用于获取酒类图标和路径判断
- 更新主题混入、常量定义等基础配置文件
- 调整全局样式和设计令牌以匹配新品牌形象
2026-07-20 22:12:27 +08:00

966 lines
24 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="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>
</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>
<text class="hero-name">{{ user.nickname || '酒友' }}</text>
<view class="hero-level-tag">
<text class="hero-level-text">{{ drinkLevel }}</text>
</view>
<text class="hero-joined">{{ joinedDate }} 加入</text>
</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="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">
<text 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="actions-section">
<!-- 酒友圈 -->
<view class="action-card" @click="goCircle">
<view class="action-icon-wrap action-icon-circle">
<text class="action-icon">🍻</text>
</view>
<view class="action-text">
<view class="action-title-row">
<text class="action-title">酒友圈</text>
<view class="action-new-tag">NEW</view>
</view>
<text class="action-desc">看看酒友们今晚都在喝什么</text>
</view>
<text class="action-arrow"></text>
</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'">
<text 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 } 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
const map = { baijiu: '⚱️', beer: '🍺', wine: '🍷', whisky: '🥂', huangjiu: '🫖', sake: '🍶', fruit: '🍹', cocktail: '🍸' }
return map[fav] || '🍻'
},
personaTitle() {
const fav = this.stats.favCategory
const map = { baijiu: '白酒之魂', beer: '啤酒达人', wine: '红酒雅士', whisky: '威士忌行家', huangjiu: '黄酒知音', sake: '清酒爱好者', fruit: '果酒小清新', cocktail: '调酒师' }
return map[fav] || '多元品鉴师'
},
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: {
// 构建分享名片的 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.showToast({ title: '酒友圈即将上线,敬请期待 🍻', icon: 'none', duration: 2000 })
},
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 80rpx;
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-content {
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
padding-top: $sp-lg;
}
.avatar-ring {
width: 168rpx; height: 168rpx;
border-radius: 50%;
padding: 4rpx;
background: linear-gradient(160deg, rgba(232,168,56,0.7), rgba(232,168,56,0.2), rgba(139,133,184,0.3));
margin-bottom: $sp-xl;
box-shadow: 0 0 48rpx rgba(232,168,56,0.15);
}
.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: 72rpx;
opacity: 0.5;
}
.hero-name {
font-size: $fs-2xl;
font-weight: $fw-black;
color: $text-primary;
margin-bottom: $sp-md;
letter-spacing: -1rpx;
}
.hero-level-tag {
padding: 8rpx 24rpx;
background: rgba(232,168,56,0.08);
border: 1rpx solid rgba(232,168,56,0.18);
border-radius: $radius-full;
margin-bottom: $sp-md;
}
.hero-level-text {
font-size: $fs-xs;
color: $amber;
font-weight: $fw-medium;
}
.hero-joined {
font-size: $fs-xs;
color: $text-tertiary;
opacity: 0.7;
}
/* === 悬浮数据卡片 === */
.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-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-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-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-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>