feat(nsti): add nicotine personality test flow (#36)
* fix: polish logs filter and stats money display * fix: align floating tabs on logs and stats * feat: enhance user profile and achievement features - Add functionality for users to update their profile picture and nickname - Implement achievement theme selection in onboarding - Update API integration for profile updates and achievement themes - Refine UI elements for better user interaction and experience * feat: 梦想清单页与戒烟相关 API - 梦想清单:系统导航栏、浮动添加、图标来自后台预设 - dream-presets API、pages.json 导航样式 Made-with: Cursor * feat(nsti): add nicotine personality test flow
This commit is contained in:
+138
-4
@@ -6,10 +6,28 @@
|
||||
<view class="section">
|
||||
<text class="section-label">当前账号</text>
|
||||
<view class="user-section card">
|
||||
<image class="avatar" :src="userAvatar" mode="aspectFill"></image>
|
||||
<!-- #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>
|
||||
<text class="user-desc">已连接戒烟记录与统计数据</text>
|
||||
<!-- #endif -->
|
||||
<text class="user-desc">点击头像或昵称可修改</text>
|
||||
<view class="user-meta">
|
||||
<text class="user-pill">{{ modeText }}</text>
|
||||
<text class="user-pill user-pill-muted">{{ shareToken ? '分享已启用' : '分享未生成' }}</text>
|
||||
@@ -41,6 +59,19 @@
|
||||
|
||||
<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>
|
||||
@@ -91,10 +122,12 @@
|
||||
<script setup>
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { onShareAppMessage, onShow } from '@dcloudio/uni-app'
|
||||
import * as api from '@/api'
|
||||
import { useProfileStore } from '@/stores/profile'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useLogin } from '@/hooks/useLogin'
|
||||
import { createShare } from '@/api/smoke'
|
||||
import { updateUserProfile, uploadFile } from '@/api/auth'
|
||||
import { getLatestNSTIResult } from '@/utils/nsti'
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
const userStore = useUserStore()
|
||||
@@ -104,6 +137,7 @@ const shareToken = ref('')
|
||||
const shareExpireAt = ref('')
|
||||
const shareLoading = ref(false)
|
||||
const navBarHeight = ref(0)
|
||||
const latestNSTIResult = 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')
|
||||
@@ -112,6 +146,9 @@ const modeText = computed(() => {
|
||||
if (userStore.mode === 'record') return '记录抽烟'
|
||||
return '未选择'
|
||||
})
|
||||
const nstiDesc = computed(() => latestNSTIResult.value
|
||||
? `你上次测出:${latestNSTIResult.value.name}`
|
||||
: '10 道题,测测你是哪一种抽象戒烟人格')
|
||||
|
||||
const shareDesc = computed(() => {
|
||||
if (!shareToken.value) {
|
||||
@@ -154,7 +191,7 @@ async function prepareShareToken(showToast = false) {
|
||||
if (shareLoading.value) return
|
||||
shareLoading.value = true
|
||||
try {
|
||||
const res = await api.createShare({ days: 7 })
|
||||
const res = await createShare({ days: 7 })
|
||||
shareToken.value = res.data?.share_token || ''
|
||||
shareExpireAt.value = res.data?.expire_at || ''
|
||||
if (showToast) {
|
||||
@@ -184,10 +221,71 @@ function previewSharePage() {
|
||||
})
|
||||
}
|
||||
|
||||
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 clearCache() {
|
||||
uni.showModal({
|
||||
title: '清除缓存',
|
||||
@@ -229,6 +327,7 @@ onShow(async () => {
|
||||
await waitForLogin()
|
||||
await profileStore.fetchProfile()
|
||||
await prepareShareToken(false)
|
||||
latestNSTIResult.value = getLatestNSTIResult()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -283,6 +382,21 @@ onShow(async () => {
|
||||
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;
|
||||
@@ -298,6 +412,18 @@ onShow(async () => {
|
||||
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;
|
||||
@@ -368,6 +494,10 @@ onShow(async () => {
|
||||
background: #F5F5F5;
|
||||
}
|
||||
|
||||
.menu-icon-nsti {
|
||||
background: linear-gradient(135deg, #FFE0F1 0%, #E6FFDE 100%);
|
||||
}
|
||||
|
||||
.menu-glyph {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
@@ -377,6 +507,10 @@ onShow(async () => {
|
||||
color: #10B981;
|
||||
}
|
||||
|
||||
.menu-icon-nsti .menu-glyph {
|
||||
color: #C2187A;
|
||||
}
|
||||
|
||||
.menu-icon-muted .menu-glyph {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user