Files
qitongxue-wx/src/pages/mine/profile.vue
2026-09-17 14:39:15 +08:00

254 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
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.
<script setup lang="ts">
import { onShow } from '@dcloudio/uni-app'
import { ref, watch } from 'vue'
import Sticker from '@/components/Sticker.vue'
import { useUserStore } from '@/stores/user'
import { AVATAR_PRESETS, presetOf } from '@/utils/avatar'
import { navPadTop } from '@/utils/safearea'
const userStore = useUserStore()
/** 导航栏顶部留白:MP 用真实状态栏高度 */
const navPad = navPadTop()
const nickname = ref('')
const avatarUrl = ref('')
const saving = ref(false)
function syncFromStore() {
const u = userStore.user
if (!u)
return
// 用户正在编辑时不覆盖输入
nickname.value = nickname.value || u.nickname || ''
avatarUrl.value = avatarUrl.value || u.avatar_url || AVATAR_PRESETS[0].key
}
onShow(syncFromStore)
// 登录态异步加载完成晚于 onShow 时兜底回填
watch(() => userStore.user, syncFromStore, { immediate: true })
function pickAvatar(key: string) {
avatarUrl.value = key
}
async function save() {
const name = nickname.value.trim()
if (!name) {
uni.showToast({ title: '昵称不能为空', icon: 'none' })
return
}
saving.value = true
try {
await userStore.updateProfile(name, avatarUrl.value)
uni.showToast({ title: '已保存', icon: 'success' })
setTimeout(() => uni.navigateBack(), 500)
}
finally {
saving.value = false
}
}
function goBack() {
uni.navigateBack()
}
</script>
<template>
<view class="page">
<view class="nav" :style="{ paddingTop: navPad }">
<view class="nav__back press" hover-class="press-active" @tap="goBack">
<wd-icon name="arrow-left" size="36rpx" color="#3d2a1e" />
</view>
<text class="nav__title">
个人信息
</text>
<view class="nav__slot" />
</view>
<!-- 头像预览 -->
<view class="preview">
<view class="preview__disc" :style="{ background: presetOf(avatarUrl)?.bg || '#FFE8DC' }">
<Sticker
v-if="presetOf(avatarUrl)"
:type="presetOf(avatarUrl)!.sticker"
:size="120"
/>
<image v-else-if="avatarUrl" :src="avatarUrl" class="preview__img" />
</view>
</view>
<!-- 头像选择 -->
<view class="card">
<text class="card__title">
选择头像
</text>
<view class="avatars">
<view
v-for="p in AVATAR_PRESETS"
:key="p.key"
class="avatar press"
:class="{ 'avatar--on': avatarUrl === p.key }"
:style="{ background: p.bg }"
hover-class="press-active"
@tap="pickAvatar(p.key)"
>
<Sticker :type="p.sticker" :size="64" />
<view v-if="avatarUrl === p.key" class="avatar__check">
<wd-icon name="check" size="22rpx" color="#ffffff" />
</view>
</view>
</view>
</view>
<!-- 昵称 -->
<view class="card">
<text class="card__title">
昵称
</text>
<input
v-model="nickname"
class="nickname"
placeholder="给自己起个名字"
placeholder-style="color:#C9AE9C"
:maxlength="32"
>
</view>
<!-- 底部悬浮保存 -->
<view class="save-bar">
<wd-button
type="primary"
block
:loading="saving"
custom-style="border-radius: 999rpx; height: 88rpx; font-size: 30rpx; box-shadow: 0 12rpx 28rpx rgba(251, 78, 20, 0.32);"
@click="save"
>
保存
</wd-button>
</view>
</view>
</template>
<style scoped>
.page {
min-height: 100vh;
padding: 0 32rpx calc(180rpx + env(safe-area-inset-bottom, 0px));
box-sizing: border-box;
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 0 20rpx;
}
.nav__back {
flex-shrink: 0;
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background: #ffffff;
border: 1rpx solid #ffe9dd;
box-shadow: 0 6rpx 16rpx rgba(107, 47, 15, 0.08);
display: flex;
align-items: center;
justify-content: center;
}
.nav__title {
font-size: 34rpx;
font-weight: 700;
color: #3d2a1e;
}
.nav__slot {
flex-shrink: 0;
width: 64rpx;
height: 64rpx;
}
.preview {
display: flex;
justify-content: center;
padding: 24rpx 0 36rpx;
}
.preview__disc {
width: 200rpx;
height: 200rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 6rpx solid #ffffff;
box-shadow: 0 16rpx 40rpx rgba(107, 47, 15, 0.12);
overflow: hidden;
}
.preview__img {
width: 100%;
height: 100%;
}
.card {
background: #ffffff;
border: 1rpx solid rgba(255, 195, 155, 0.5);
border-radius: 30rpx;
padding: 26rpx 28rpx;
margin-top: 24rpx;
box-shadow: 0 10rpx 26rpx rgba(107, 47, 15, 0.06);
}
.card__title {
display: block;
font-size: 26rpx;
font-weight: 700;
color: #3d2a1e;
margin-bottom: 20rpx;
}
.avatars {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 16rpx;
}
.avatar {
position: relative;
aspect-ratio: 1;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
border: 3rpx solid transparent;
}
.avatar--on {
border-color: #ff6b35;
box-shadow: 0 8rpx 18rpx rgba(251, 78, 20, 0.25);
}
.avatar__check {
position: absolute;
right: -6rpx;
top: -6rpx;
width: 36rpx;
height: 36rpx;
border-radius: 50%;
background: #ff6b35;
display: flex;
align-items: center;
justify-content: center;
border: 3rpx solid #ffffff;
}
.nickname {
width: 100%;
box-sizing: border-box;
height: 88rpx;
background: #fff9f2;
border: 2rpx solid #ffe3d3;
border-radius: 20rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #3d2a1e;
}
.save-bar {
position: fixed;
left: 32rpx;
right: 32rpx;
bottom: calc(20rpx + env(safe-area-inset-bottom, 0px));
z-index: 8;
}
</style>