feat: Enhance onboarding and profile pages with improved navigation and UI updates

Updated the onboarding page to include a navigation area with a step indicator and adjusted the progress bar styling. In the profile page, simplified the user section, added options for clearing cache and copying feedback email, and improved layout consistency. Enhanced overall visual design with a light green gradient theme.
This commit is contained in:
nepiedg
2026-03-04 12:38:46 +08:00
parent ebca42c34e
commit 17daf254cd
2 changed files with 105 additions and 139 deletions
+50 -9
View File
@@ -1,7 +1,12 @@
<template>
<view class="page">
<view class="progress-bar">
<view class="progress-fill" :style="{ width: progressWidth }"></view>
<view class="nav-area" :style="{ paddingTop: navBarHeight + 'px' }">
<view class="nav-row">
<text class="step-indicator">{{ step }} / {{ totalSteps }}</text>
</view>
<view class="progress-bar">
<view class="progress-fill" :style="{ width: progressWidth }"></view>
</view>
</view>
<view class="content">
@@ -105,6 +110,7 @@ import { useLogin } from '@/hooks/useLogin'
const profileStore = useProfileStore()
const { waitForLogin } = useLogin()
const navBarHeight = ref(0)
const step = ref(1)
const totalSteps = 5
@@ -192,6 +198,14 @@ async function nextStep() {
}
onMounted(async () => {
const sys = uni.getSystemInfoSync()
const statusBarH = sys.statusBarHeight || 0
try {
const menuBtn = uni.getMenuButtonBoundingClientRect()
navBarHeight.value = menuBtn.bottom + (menuBtn.top - statusBarH)
} catch (e) {
navBarHeight.value = statusBarH + 44
}
await waitForLogin()
})
</script>
@@ -204,20 +218,48 @@ onMounted(async () => {
flex-direction: column;
}
.nav-area {
padding-left: 32rpx;
padding-right: 32rpx;
background: linear-gradient(to bottom, #D1FAE5, #E9FDF2);
}
.nav-row {
display: flex;
justify-content: center;
align-items: center;
padding: 12rpx 0;
}
.step-indicator {
font-size: 24rpx;
font-weight: 600;
color: #059669;
background-color: rgba(255, 255, 255, 0.7);
padding: 6rpx 24rpx;
border-radius: 999rpx;
}
.progress-bar {
height: 8rpx;
background-color: #E5E7EB;
height: 6rpx;
background-color: rgba(255, 255, 255, 0.5);
border-radius: 999rpx;
margin-top: 8rpx;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #10B981, #34D399);
border-radius: 999rpx;
transition: width 0.3s ease;
}
.content {
flex: 1;
padding: 64rpx 48rpx;
padding: 0 48rpx;
display: flex;
flex-direction: column;
justify-content: center;
}
.step {
@@ -227,11 +269,11 @@ onMounted(async () => {
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(20rpx);
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateX(0);
transform: translateY(0);
}
}
@@ -248,7 +290,7 @@ onMounted(async () => {
font-size: 28rpx;
color: #6B7280;
display: block;
margin-bottom: 64rpx;
margin-bottom: 56rpx;
}
.input-group {
@@ -377,7 +419,6 @@ onMounted(async () => {
gap: 24rpx;
padding: 32rpx 48rpx;
padding-bottom: 64rpx;
background-color: #FFFFFF;
}
.btn-primary {
+55 -130
View File
@@ -1,34 +1,17 @@
<template>
<view class="page">
<view class="user-section">
<view class="avatar-wrapper">
<image class="avatar" :src="userAvatar" mode="aspectFill"></image>
<view class="avatar-edit">📷</view>
</view>
<image class="avatar" :src="userAvatar" mode="aspectFill"></image>
<text class="user-name">{{ userName }}</text>
<view class="goal-badge">
<text>目标{{ goalDate }} 戒烟</text>
<text class="goal-icon">🎯</text>
</view>
<text class="streak-text">已连续戒烟 {{ streakDays }} 🔥</text>
</view>
<view class="section">
<text class="section-title">我的进程</text>
<view class="menu-list">
<view class="menu-item" @tap="goPage('goal')">
<view class="menu-icon menu-icon-green">🎯</view>
<view class="menu-item" @tap="goOnboarding">
<view class="menu-icon menu-icon-green">📝</view>
<view class="menu-content">
<text class="menu-label">目标设定</text>
<text class="menu-desc">调整每日限额与戒烟日期</text>
</view>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @tap="goPage('ai-plan')">
<view class="menu-icon menu-icon-blue">🤖</view>
<view class="menu-content">
<text class="menu-label">AI 计划调整</text>
<text class="menu-desc">个性化辅导风格</text>
<text class="menu-label">重新填写问卷</text>
<text class="menu-desc">修改吸烟基线与个人信息</text>
</view>
<text class="menu-arrow"></text>
</view>
@@ -36,40 +19,18 @@
</view>
<view class="section">
<text class="section-title">偏好设置</text>
<view class="menu-list">
<view class="menu-item" @tap="goPage('notification')">
<view class="menu-icon menu-icon-orange">🔔</view>
<view class="menu-item" @tap="clearCache">
<view class="menu-icon menu-icon-gray">🗑</view>
<view class="menu-content">
<text class="menu-label">通知设置</text>
<text class="menu-label">清除缓存</text>
</view>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @tap="goPage('vip')">
<view class="menu-icon menu-icon-yellow">💎</view>
<view class="menu-item" @tap="copyInfo">
<view class="menu-icon menu-icon-gray">💬</view>
<view class="menu-content">
<text class="menu-label">解锁会员</text>
<view class="pro-badge">PRO</view>
</view>
<text class="menu-arrow"></text>
</view>
</view>
</view>
<view class="section">
<text class="section-title">通用</text>
<view class="menu-list">
<view class="menu-item" @tap="goPage('settings')">
<view class="menu-icon menu-icon-gray"></view>
<view class="menu-content">
<text class="menu-label">基础设置</text>
</view>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @tap="goPage('privacy')">
<view class="menu-icon menu-icon-gray">🔒</view>
<view class="menu-content">
<text class="menu-label">隐私与数据</text>
<text class="menu-label">意见反馈</text>
</view>
<text class="menu-arrow"></text>
</view>
@@ -85,20 +46,44 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { computed, onMounted } from 'vue'
import { useUserStore } from '@/stores/user'
import { useLogin } from '@/hooks/useLogin'
const userStore = useUserStore()
const { waitForLogin } = useLogin()
const userName = computed(() => userStore.user?.nickname || 'Alex Doe')
const userName = computed(() => userStore.user?.nickname || '戒烟用户')
const userAvatar = computed(() => userStore.user?.avatar_url || '/static/images/default-avatar.png')
const goalDate = ref('12月1日')
const streakDays = ref(12)
function goPage(page) {
uni.showToast({ title: '功能开发中', icon: 'none' })
function goOnboarding() {
uni.navigateTo({ url: '/pages/onboarding/index' })
}
function clearCache() {
uni.showModal({
title: '清除缓存',
content: '将清除本地缓存数据,不会影响云端记录',
success: (res) => {
if (res.confirm) {
try {
uni.clearStorageSync()
uni.showToast({ title: '缓存已清除', icon: 'success' })
} catch (e) {
uni.showToast({ title: '清除失败', icon: 'none' })
}
}
}
})
}
function copyInfo() {
uni.setClipboardData({
data: '806669289@qq.com',
success: () => {
uni.showToast({ title: '反馈邮箱已复制', icon: 'success' })
}
})
}
function logout() {
@@ -124,7 +109,7 @@ onMounted(async () => {
min-height: 100vh;
background: linear-gradient(to bottom, #D1FAE5 0%, #F0FDF4 45%, #FFFFFF 100%);
padding: 32rpx;
padding-bottom: 120rpx;
padding-bottom: 160rpx;
box-sizing: border-box;
}
@@ -132,70 +117,25 @@ onMounted(async () => {
display: flex;
flex-direction: column;
align-items: center;
padding: 48rpx 0;
}
.avatar-wrapper {
position: relative;
margin-bottom: 24rpx;
padding: 48rpx 0 40rpx;
}
.avatar {
width: 160rpx;
height: 160rpx;
width: 140rpx;
height: 140rpx;
border-radius: 50%;
border: 6rpx solid #10B981;
background-color: #F3F4F6;
}
.avatar-edit {
position: absolute;
right: 0;
bottom: 0;
width: 48rpx;
height: 48rpx;
background-color: #10B981;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
border: 4rpx solid #A7F3D0;
background-color: #ECFDF5;
margin-bottom: 20rpx;
}
.user-name {
font-size: 40rpx;
font-size: 38rpx;
font-weight: 700;
color: #111827;
margin-bottom: 16rpx;
}
.goal-badge {
display: flex;
align-items: center;
gap: 8rpx;
background-color: #EF4444;
color: #FFFFFF;
padding: 12rpx 24rpx;
border-radius: 32rpx;
font-size: 24rpx;
margin-bottom: 12rpx;
}
.goal-icon { font-size: 24rpx; }
.streak-text {
font-size: 26rpx;
color: #6B7280;
}
.section { margin-bottom: 32rpx; }
.section-title {
font-size: 26rpx;
color: #6B7280;
margin-bottom: 16rpx;
display: block;
}
.section { margin-bottom: 24rpx; }
.menu-list {
display: flex;
@@ -209,7 +149,7 @@ onMounted(async () => {
gap: 24rpx;
background-color: #FFFFFF;
border-radius: 24rpx;
padding: 24rpx;
padding: 28rpx 24rpx;
border: 2rpx solid #ECFDF3;
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.08);
}
@@ -225,9 +165,6 @@ onMounted(async () => {
}
.menu-icon-green { background-color: #DCFCE7; }
.menu-icon-blue { background-color: #DBEAFE; }
.menu-icon-orange { background-color: #FFEDD5; }
.menu-icon-yellow { background-color: #FEF3C7; }
.menu-icon-gray { background-color: #F3F4F6; }
.menu-content {
@@ -247,18 +184,6 @@ onMounted(async () => {
color: #6B7280;
}
.pro-badge {
display: inline-block;
background-color: #10B981;
color: #FFFFFF;
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 8rpx;
font-weight: 600;
margin-top: 4rpx;
width: fit-content;
}
.menu-arrow {
font-size: 36rpx;
color: #9CA3AF;
@@ -266,8 +191,8 @@ onMounted(async () => {
.logout-btn {
text-align: center;
padding: 24rpx;
margin-top: 32rpx;
padding: 28rpx;
margin-top: 40rpx;
background-color: #FFFFFF;
border-radius: 24rpx;
border: 2rpx solid #FEE2E2;
@@ -281,8 +206,8 @@ onMounted(async () => {
.version {
display: block;
text-align: center;
font-size: 24rpx;
font-size: 22rpx;
color: #9CA3AF;
margin-top: 24rpx;
margin-top: 32rpx;
}
</style>