feat(app): 初始化喝酒了么小程序基础架构
- 添加项目配置文件(.editorconfig, .gitignore) - 创建App.vue主应用文件,包含启动时Mock数据初始化 - 添加index.html和main.js应用入口文件 - 配置manifest.json应用基本信息和权限设置 - 设置pages.json页面路由和全局样式 - 添加uni.promisify.adaptor.js适配器文件 - 创建uni.scss设计令牌系统,定义琥珀夜光主题色彩 - 新增constants.js常量定义,包含酒类分类和品牌数据
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user