547 lines
12 KiB
Vue
547 lines
12 KiB
Vue
<template>
|
||
<view class="page">
|
||
<view class="page-bg"></view>
|
||
<view class="nav-placeholder" :style="{ height: navBarHeight + 'px' }"></view>
|
||
|
||
<view class="section">
|
||
<text class="section-label">当前账号</text>
|
||
<view class="user-section card">
|
||
<!-- #ifdef MP-WEIXIN -->
|
||
<button class="avatar-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
|
||
<image class="avatar" :src="userAvatar" mode="aspectFill"></image>
|
||
</button>
|
||
<!-- #endif -->
|
||
<!-- #ifdef H5 -->
|
||
<image class="avatar" :src="userAvatar" mode="aspectFill" @tap="onChooseAvatarH5"></image>
|
||
<!-- #endif -->
|
||
<view class="user-copy">
|
||
<!-- #ifdef MP-WEIXIN -->
|
||
<input
|
||
type="nickname"
|
||
class="nickname-input"
|
||
:value="userStore.user?.nickname || ''"
|
||
placeholder="点击设置昵称"
|
||
@blur="onNicknameChange"
|
||
/>
|
||
<!-- #endif -->
|
||
<!-- #ifdef H5 -->
|
||
<text class="user-name">{{ userName }}</text>
|
||
<!-- #endif -->
|
||
<text class="user-desc">点击头像或昵称可修改</text>
|
||
<view class="user-meta">
|
||
<text class="user-pill">{{ modeText }}</text>
|
||
<text class="user-pill user-pill-muted">{{ achievementTitle }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section">
|
||
<text class="section-label">常用操作</text>
|
||
<view class="menu-list card">
|
||
<view class="menu-item">
|
||
<view class="menu-icon menu-icon-accent">
|
||
<text class="menu-glyph">奖</text>
|
||
</view>
|
||
<view class="menu-content">
|
||
<text class="menu-label">生成成就海报</text>
|
||
<text class="menu-desc">{{ posterDesc }}</text>
|
||
</view>
|
||
<button class="share-btn" @tap.stop="goAchievementPoster">
|
||
生成
|
||
</button>
|
||
</view>
|
||
|
||
<view class="menu-divider"></view>
|
||
|
||
<view class="menu-item" @tap="goSupervisor">
|
||
<view class="menu-icon menu-icon-accent">
|
||
<text class="menu-glyph">督</text>
|
||
</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-divider"></view>
|
||
|
||
<view class="menu-item" @tap="goNSTI">
|
||
<view class="menu-icon menu-icon-nsti">
|
||
<text class="menu-glyph">测</text>
|
||
</view>
|
||
<view class="menu-content">
|
||
<text class="menu-label">赛博尼古丁测试</text>
|
||
<text class="menu-desc">{{ nstiDesc }}</text>
|
||
</view>
|
||
<text class="menu-arrow">›</text>
|
||
</view>
|
||
|
||
<view class="menu-divider"></view>
|
||
|
||
<view class="menu-item" @tap="goOnboarding">
|
||
<view class="menu-icon menu-icon-accent">
|
||
<text class="menu-glyph">问</text>
|
||
</view>
|
||
<view class="menu-content">
|
||
<text class="menu-label">重新填写问卷</text>
|
||
<text class="menu-desc">修改打卡模式、吸烟基线等个人信息</text>
|
||
</view>
|
||
<text class="menu-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section">
|
||
<text class="section-label">更多设置</text>
|
||
<view class="menu-list card">
|
||
<view class="menu-item" @tap="clearCache">
|
||
<view class="menu-icon menu-icon-muted">
|
||
<text class="menu-glyph">清</text>
|
||
</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-divider"></view>
|
||
|
||
<view class="menu-item" @tap="copyInfo">
|
||
<view class="menu-icon menu-icon-muted">
|
||
<text class="menu-glyph">邮</text>
|
||
</view>
|
||
<view class="menu-content">
|
||
<text class="menu-label">意见反馈</text>
|
||
<text class="menu-desc">复制反馈邮箱,发送使用建议或问题</text>
|
||
</view>
|
||
<text class="menu-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<text class="version">版本 1.0.0</text>
|
||
<view class="bottom-safe"></view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, ref, onMounted } from 'vue'
|
||
import { onShareAppMessage, onShow } from '@dcloudio/uni-app'
|
||
import { useProfileStore } from '@/stores/profile'
|
||
import { useUserStore } from '@/stores/user'
|
||
import { useLogin } from '@/hooks/useLogin'
|
||
import { getAchievement } from '@/api/smoke'
|
||
import { updateUserProfile, uploadFile } from '@/api/auth'
|
||
import { getLatestNSTIResult } from '@/utils/nsti'
|
||
|
||
const profileStore = useProfileStore()
|
||
const userStore = useUserStore()
|
||
const { waitForLogin } = useLogin()
|
||
|
||
const navBarHeight = ref(0)
|
||
const latestNSTIResult = ref(null)
|
||
const achievementData = ref(null)
|
||
|
||
const userName = computed(() => userStore.user?.nickname || '戒烟用户')
|
||
const userAvatar = computed(() => userStore.user?.avatar_url || 'https://linghu-wmr.oss-cn-beijing.aliyuncs.com/smt/avatar.png')
|
||
const modeText = computed(() => {
|
||
if (userStore.mode === 'quit') return '戒烟打卡'
|
||
if (userStore.mode === 'record') return '记录抽烟'
|
||
return '未选择'
|
||
})
|
||
const nstiDesc = computed(() => latestNSTIResult.value
|
||
? `你上次测出:${latestNSTIResult.value.name}`
|
||
: '10 道题,测测你是哪一种抽象戒烟人格')
|
||
|
||
const currentAchievement = computed(() => achievementData.value?.current || null)
|
||
const achievementTitle = computed(() => currentAchievement.value?.name || '戒烟新星')
|
||
const achievementTheme = computed(() => achievementData.value?.theme_name || '少抽成就')
|
||
const achievementScore = computed(() => Number(achievementData.value?.score) || 0)
|
||
const posterDesc = computed(() => {
|
||
if (!achievementData.value) return '生成你的等级称号、少抽进度和节省成果'
|
||
return `${achievementTheme.value} · ${achievementTitle.value} · 积分 ${achievementScore.value}`
|
||
})
|
||
|
||
function setupNavBar() {
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
const statusBarH = systemInfo.statusBarHeight || 0
|
||
try {
|
||
const menuBtn = uni.getMenuButtonBoundingClientRect()
|
||
navBarHeight.value = menuBtn.bottom + (menuBtn.top - statusBarH)
|
||
} catch (e) {
|
||
navBarHeight.value = statusBarH + 44
|
||
}
|
||
}
|
||
|
||
async function fetchPosterData() {
|
||
try {
|
||
const achievementRes = await getAchievement()
|
||
achievementData.value = achievementRes.data?.achievement || null
|
||
} catch (e) {
|
||
console.error('fetchPosterData error:', e)
|
||
}
|
||
}
|
||
|
||
async function onChooseAvatar(e) {
|
||
const avatarUrl = e.detail.avatarUrl
|
||
if (!avatarUrl) return
|
||
try {
|
||
uni.showLoading({ title: '上传头像...' })
|
||
const uploadRes = await uploadFile(avatarUrl)
|
||
await doUpdateProfile({ avatar_url: uploadRes.url })
|
||
} catch (err) {
|
||
console.error('头像上传失败:', err)
|
||
uni.showToast({ title: '头像更新失败', icon: 'none' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
|
||
function onChooseAvatarH5() {
|
||
uni.chooseImage({
|
||
count: 1,
|
||
sizeType: ['compressed'],
|
||
sourceType: ['album'],
|
||
success: async (res) => {
|
||
const tempPath = res.tempFilePaths[0]
|
||
try {
|
||
uni.showLoading({ title: '上传头像...' })
|
||
const uploadRes = await uploadFile(tempPath)
|
||
await doUpdateProfile({ avatar_url: uploadRes.url })
|
||
} catch (err) {
|
||
console.error('头像上传失败:', err)
|
||
uni.showToast({ title: '头像更新失败', icon: 'none' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
async function onNicknameChange(e) {
|
||
const nickname = (e.detail.value || '').trim()
|
||
if (!nickname || nickname === userStore.user?.nickname) return
|
||
try {
|
||
await doUpdateProfile({ nickname })
|
||
} catch (err) {
|
||
uni.showToast({ title: '昵称更新失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
async function doUpdateProfile(data) {
|
||
try {
|
||
const res = await updateUserProfile(data)
|
||
userStore.updateUser(res.data)
|
||
uni.showToast({ title: '修改成功', icon: 'success' })
|
||
} catch (e) {
|
||
console.error('doUpdateProfile error:', e)
|
||
uni.showToast({ title: '修改失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
function goOnboarding() {
|
||
uni.navigateTo({ url: '/pages/onboarding/index' })
|
||
}
|
||
|
||
function goNSTI() {
|
||
uni.navigateTo({ url: '/pages/nsti/index' })
|
||
}
|
||
|
||
function goSupervisor() {
|
||
uni.navigateTo({ url: '/pages/supervisor/index' })
|
||
}
|
||
|
||
function goAchievementPoster() {
|
||
uni.navigateTo({ url: '/pages/share/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' })
|
||
}
|
||
})
|
||
}
|
||
|
||
onShareAppMessage(() => {
|
||
return {
|
||
title: `${userName.value}正在解锁「${achievementTitle.value}」`,
|
||
path: 'pages/index/index'
|
||
}
|
||
})
|
||
|
||
onMounted(() => {
|
||
setupNavBar()
|
||
})
|
||
|
||
onShow(async () => {
|
||
await waitForLogin()
|
||
await profileStore.fetchProfile()
|
||
await fetchPosterData()
|
||
latestNSTIResult.value = getLatestNSTIResult()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page {
|
||
min-height: 100vh;
|
||
position: relative;
|
||
background-color: #F5F7F6;
|
||
padding: 0 32rpx 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.page-bg {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 500rpx;
|
||
background: linear-gradient(180deg, #DDF3EB 0%, #F5F7F6 100%);
|
||
z-index: 0;
|
||
}
|
||
|
||
.nav-placeholder,
|
||
.section,
|
||
.version {
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.section {
|
||
margin-bottom: 36rpx;
|
||
}
|
||
|
||
.section-label {
|
||
display: block;
|
||
margin: 0 0 16rpx 16rpx;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #666666;
|
||
}
|
||
|
||
.card {
|
||
background: #FFFFFF;
|
||
border-radius: 32rpx;
|
||
padding: 32rpx;
|
||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.03);
|
||
}
|
||
|
||
.user-section {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 32rpx;
|
||
}
|
||
|
||
.avatar-btn {
|
||
padding: 0;
|
||
margin: 0;
|
||
background: transparent;
|
||
border: none;
|
||
line-height: 1;
|
||
width: 128rpx;
|
||
height: 128rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.avatar-btn::after {
|
||
border: none;
|
||
}
|
||
|
||
.avatar {
|
||
width: 128rpx;
|
||
height: 128rpx;
|
||
border-radius: 50%;
|
||
background-color: #F0F4F2;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.user-copy {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.nickname-input {
|
||
font-size: 38rpx;
|
||
font-weight: 700;
|
||
color: #1A1A1A;
|
||
background: transparent;
|
||
border: none;
|
||
padding: 0;
|
||
height: 56rpx;
|
||
line-height: 56rpx;
|
||
width: 100%;
|
||
}
|
||
|
||
.user-name {
|
||
font-size: 38rpx;
|
||
font-weight: 700;
|
||
color: #1A1A1A;
|
||
}
|
||
|
||
.user-desc {
|
||
display: block;
|
||
margin-top: 8rpx;
|
||
font-size: 26rpx;
|
||
color: #999999;
|
||
}
|
||
|
||
.user-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16rpx;
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
.user-pill {
|
||
padding: 8rpx 20rpx;
|
||
border-radius: 999rpx;
|
||
background: #E8F5F0;
|
||
font-size: 22rpx;
|
||
font-weight: 600;
|
||
color: #10B981;
|
||
}
|
||
|
||
.user-pill-muted {
|
||
background: #F5F5F5;
|
||
color: #999999;
|
||
}
|
||
|
||
.menu-list {
|
||
padding: 8rpx 32rpx;
|
||
}
|
||
|
||
.menu-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24rpx;
|
||
padding: 24rpx 0;
|
||
background: transparent;
|
||
}
|
||
|
||
.menu-divider {
|
||
height: 1px;
|
||
background: #F0F0F0;
|
||
margin: 0;
|
||
}
|
||
|
||
.menu-icon {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.menu-icon-accent {
|
||
background: #E8F5F0;
|
||
}
|
||
|
||
.menu-icon-muted {
|
||
background: #F5F5F5;
|
||
}
|
||
|
||
.menu-icon-nsti {
|
||
background: linear-gradient(135deg, #FFE0F1 0%, #E6FFDE 100%);
|
||
}
|
||
|
||
.menu-glyph {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.menu-icon-accent .menu-glyph {
|
||
color: #10B981;
|
||
}
|
||
|
||
.menu-icon-nsti .menu-glyph {
|
||
color: #C2187A;
|
||
}
|
||
|
||
.menu-icon-muted .menu-glyph {
|
||
color: #999999;
|
||
}
|
||
|
||
.menu-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6rpx;
|
||
}
|
||
|
||
.menu-label {
|
||
font-size: 30rpx;
|
||
color: #1A1A1A;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.menu-desc {
|
||
font-size: 24rpx;
|
||
line-height: 1.4;
|
||
color: #999999;
|
||
}
|
||
|
||
.share-btn {
|
||
margin: 0;
|
||
padding: 12rpx 32rpx;
|
||
line-height: 1.5;
|
||
font-size: 26rpx;
|
||
border: none;
|
||
border-radius: 999rpx;
|
||
color: #FFFFFF;
|
||
background: #10B981;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.share-btn[disabled] {
|
||
background: #A7F3D0;
|
||
color: #FFFFFF;
|
||
}
|
||
|
||
.share-btn::after {
|
||
border: none;
|
||
}
|
||
|
||
.menu-arrow {
|
||
font-size: 36rpx;
|
||
color: #CCCCCC;
|
||
}
|
||
|
||
.version {
|
||
display: block;
|
||
text-align: center;
|
||
font-size: 24rpx;
|
||
color: #B0B0B0;
|
||
margin-top: 40rpx;
|
||
}
|
||
|
||
.bottom-safe {
|
||
height: calc(40rpx + env(safe-area-inset-bottom));
|
||
}
|
||
</style>
|