254 lines
5.7 KiB
Vue
254 lines
5.7 KiB
Vue
<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>
|