Files
hejiu/pages/profile/profile.vue
T
cg f945ccf4c7 feat(app): 初始化喝酒了么小程序基础架构
- 添加项目配置文件(.editorconfig, .gitignore)
- 创建App.vue主应用文件,包含启动时Mock数据初始化
- 添加index.html和main.js应用入口文件
- 配置manifest.json应用基本信息和权限设置
- 设置pages.json页面路由和全局样式
- 添加uni.promisify.adaptor.js适配器文件
- 创建uni.scss设计令牌系统,定义琥珀夜光主题色彩
- 新增constants.js常量定义,包含酒类分类和品牌数据
2026-07-13 22:15:16 +08:00

434 lines
9.8 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 profile-page safe-bottom">
<!-- 用户头部 -->
<view class="profile-header">
<view class="profile-glow"></view>
<image class="profile-avatar" :src="user.avatar" mode="aspectFill"></image>
<text class="profile-name">{{ user.nickname }}</text>
<text class="profile-joined">加入于 {{ user.joinedAt || '2026' }}</text>
</view>
<!-- 饮酒档案统计 -->
<view class="stats-section">
<text class="section-title">饮酒档案</text>
<view class="stats-grid">
<view class="profile-stat card">
<text class="profile-stat-value text-num">{{ stats.totalDays }}</text>
<text class="profile-stat-label">打卡天数</text>
</view>
<view class="profile-stat card">
<text class="profile-stat-value text-num">{{ stats.totalRecords }}</text>
<text class="profile-stat-label">饮酒次数</text>
</view>
<view class="profile-stat card">
<text class="profile-stat-value text-num text-amber">{{ stats.totalCups }}</text>
<text class="profile-stat-label">标准杯</text>
</view>
<view class="profile-stat card">
<text class="profile-stat-value text-num">{{ favCategoryName }}</text>
<text class="profile-stat-label">最爱酒类</text>
</view>
</view>
</view>
<!-- 连续打卡 -->
<view class="streak-card card">
<view class="flex-between">
<view>
<text class="streak-label">{{ stats.streakType === 'drank' ? '连续饮酒' : '连续戒酒' }}</text>
<text class="streak-value text-num" :class="stats.streakType === 'drank' ? 'text-amber' : 'text-mint'">
{{ stats.streak }}
</text>
</view>
<text class="streak-emoji">{{ stats.streakType === 'drank' ? '🔥' : '💪' }}</text>
</view>
</view>
<!-- 成就墙 -->
<view class="achievements-section">
<text class="section-title">成就徽章</text>
<view class="achievements-grid">
<AchievementBadge
v-for="a in achievementList"
:key="a.id"
:achievement="a"
@click="showAchievementDetail(a)"
/>
</view>
</view>
<!-- 设置入口 -->
<view class="settings-section card">
<view class="settings-item" @click="showComingSoon('偏好设置')">
<text class="settings-icon">⚙️</text>
<text class="settings-name">饮酒偏好设置</text>
<text class="settings-arrow"></text>
</view>
<view class="divider"></view>
<view class="settings-item" @click="showComingSoon('隐私设置')">
<text class="settings-icon">🔒</text>
<text class="settings-name">隐私设置</text>
<text class="settings-arrow"></text>
</view>
<view class="divider"></view>
<view class="settings-item" @click="showComingSoon('关于')">
<text class="settings-icon"></text>
<text class="settings-name">关于喝酒了么</text>
<text class="settings-arrow"></text>
</view>
</view>
<!-- 底部TabBar -->
<view class="tab-bar">
<view class="tab-item" @click="goHome">
<text class="tab-icon">🏠</text>
<text class="tab-label">首页</text>
</view>
<view class="tab-center" @click="goRecord">
<view class="tab-fab">
<text class="tab-fab-icon">+</text>
</view>
</view>
<view class="tab-item tab-active" @click="goProfile">
<text class="tab-icon">👤</text>
<text class="tab-label">我的</text>
</view>
</view>
<!-- 成就详情弹窗 -->
<view v-if="showDetail" class="detail-overlay" @click="showDetail = false">
<view class="detail-card card-elevated" @click.stop>
<text class="detail-emoji">{{ detailAchievement.icon }}</text>
<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="btn-secondary" style="width: 100%; margin-top: 24rpx;" @click="showDetail = false">关闭</button>
</view>
</view>
</view>
</template>
<script>
import AchievementBadge from '../../components/AchievementBadge.vue'
import { getRecords, getUserInfo } from '../../common/mock-data'
import { calcStats, checkAchievements } from '../../common/utils'
import { ACHIEVEMENTS, DRINK_CATEGORIES } from '../../common/constants'
export default {
components: { AchievementBadge },
data() {
return {
user: {},
stats: {},
achievementList: [],
showDetail: false,
detailAchievement: {}
}
},
computed: {
favCategoryName() {
if (!this.stats.favCategory) return '-'
const cat = DRINK_CATEGORIES.find(c => c.id === this.stats.favCategory)
return cat ? cat.name : '-'
}
},
onShow() {
this.loadData()
},
methods: {
loadData() {
this.user = getUserInfo()
const records = getRecords()
this.stats = calcStats(records)
this.achievementList = checkAchievements(this.stats, ACHIEVEMENTS)
},
showAchievementDetail(a) {
this.detailAchievement = a
this.showDetail = true
},
showComingSoon(name) {
uni.showToast({ title: `${name}即将上线`, icon: 'none' })
},
goHome() {
uni.reLaunch({ url: '/pages/index/index' })
},
goRecord() {
uni.navigateTo({ url: '/pages/record/record' })
},
goProfile() {
// 已在个人页面
}
}
}
</script>
<style lang="scss" scoped>
.profile-page {
padding: $sp-lg;
padding-bottom: calc(180rpx + env(safe-area-inset-bottom));
}
/* 头部 */
.profile-header {
display: flex;
flex-direction: column;
align-items: center;
padding: $sp-2xl 0 $sp-xl;
position: relative;
}
.profile-glow {
position: absolute;
width: 300rpx;
height: 300rpx;
background: radial-gradient(circle, rgba(232,168,56,0.12) 0%, transparent 70%);
border-radius: 50%;
top: 0;
filter: blur(30rpx);
}
.profile-avatar {
width: 160rpx;
height: 160rpx;
border-radius: $radius-full;
border: 4rpx solid $amber;
margin-bottom: $sp-lg;
position: relative;
z-index: 2;
}
.profile-name {
font-size: $fs-xl;
font-weight: $fw-bold;
margin-bottom: $sp-xs;
}
.profile-joined {
font-size: $fs-sm;
color: $text-secondary;
}
/* 统计 */
.stats-section {
margin-bottom: $sp-lg;
}
.section-title {
display: block;
font-size: $fs-lg;
font-weight: $fw-bold;
margin-bottom: $sp-lg;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: $sp-md;
}
.profile-stat {
text-align: center;
padding: $sp-lg;
}
.profile-stat-value {
display: block;
font-size: $fs-2xl;
font-weight: $fw-bold;
margin-bottom: $sp-xs;
}
.profile-stat-label {
font-size: $fs-xs;
color: $text-secondary;
}
/* 连续打卡 */
.streak-card {
margin-bottom: $sp-xl;
padding: $sp-xl;
}
.streak-label {
display: block;
font-size: $fs-sm;
color: $text-secondary;
margin-bottom: $sp-sm;
}
.streak-value {
display: block;
font-size: $fs-3xl;
font-weight: $fw-black;
}
.streak-emoji {
font-size: $fs-hero;
}
/* 成就 */
.achievements-section {
margin-bottom: $sp-xl;
}
.achievements-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: $sp-md;
}
/* 设置 */
.settings-section {
margin-bottom: $sp-xl;
}
.settings-item {
display: flex;
align-items: center;
gap: $sp-md;
padding: $sp-lg 0;
&:active {
opacity: 0.7;
}
}
.settings-icon {
font-size: $fs-xl;
}
.settings-name {
flex: 1;
font-size: $fs-base;
}
.settings-arrow {
font-size: $fs-xl;
color: $text-tertiary;
}
/* TabBar */
.tab-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background: $bg-card;
display: flex;
align-items: center;
justify-content: space-around;
padding-bottom: env(safe-area-inset-bottom);
border-top: 2rpx solid rgba(255,255,255,0.04);
z-index: 50;
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-xs;
}
.tab-icon {
font-size: $fs-xl;
opacity: 0.5;
}
.tab-label {
font-size: $fs-xs;
color: $text-tertiary;
}
.tab-active {
.tab-icon { opacity: 1; }
.tab-label { color: $amber; }
}
.tab-center {
position: relative;
top: -20rpx;
}
.tab-fab {
width: 100rpx;
height: 100rpx;
background: linear-gradient(135deg, $amber-light, $amber-deep);
border-radius: $radius-full;
display: flex;
align-items: center;
justify-content: center;
box-shadow: $shadow-amber;
}
.tab-fab-icon {
font-size: $fs-2xl;
font-weight: $fw-bold;
color: $text-on-amber;
}
/* 成就详情弹窗 */
.detail-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: 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: 600rpx;
text-align: center;
padding: $sp-2xl $sp-xl;
}
.detail-emoji {
font-size: 120rpx;
display: block;
margin-bottom: $sp-lg;
}
.detail-name {
display: block;
font-size: $fs-xl;
font-weight: $fw-bold;
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;
}
}
</style>