xiangyixiangqin/miniapp/pages/profile/personal.vue
2026-01-18 18:13:01 +08:00

457 lines
11 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.

<template>
<view class="personal-page">
<!-- 顶部背景图 -->
<view class="top-bg">
<image src="/static/title_bg.png" mode="aspectFill" class="bg-img" />
</view>
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="navbar-back" @click="handleBack">
<text class="back-icon"></text>
</view>
<text class="navbar-title">个人资料</text>
<view class="navbar-placeholder"></view>
</view>
</view>
<!-- 头像区域 -->
<view class="avatar-section" :style="{ marginTop: (statusBarHeight + 44) + 'px' }">
<view class="avatar-wrapper" @click="handleChangeAvatar">
<image
class="avatar-img"
:src="avatarLoadError ? defaultAvatar : (userInfo.avatar || defaultAvatar)"
mode="aspectFill"
@error="onAvatarError"
/>
<view class="avatar-edit-icon">
<text></text>
</view>
</view>
</view>
<!-- 表单区域 -->
<view class="form-section">
<view class="form-item">
<text class="form-label">用户名</text>
<view class="form-input">
<input
type="text"
:value="nickname"
@input="e => nickname = e.detail.value"
placeholder="请输入用户名"
maxlength="20"
class="input-field"
/>
</view>
</view>
<view class="form-item">
<text class="form-label">相亲编号</text>
<view class="form-input disabled" @click="handleCopyXiangQinNo">
<text>{{ userInfo.xiangQinNo || '未设置' }}</text>
<text class="copy-icon">复制</text>
</view>
</view>
</view>
<!-- 保存按钮 -->
<view class="btn-section">
<button class="btn-save" @click="handleSave">保存</button>
</view>
</view>
</template>
<script>
import { ref, computed, onMounted, watch } from 'vue'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import { updateAvatar, updateNickname } from '@/api/user.js'
import { getMyProfile } from '@/api/profile.js'
import { getFullImageUrl } from '@/utils/image.js'
import config from '@/config/index.js'
export default {
name: 'PersonalPage',
setup() {
const userStore = useUserStore()
const configStore = useConfigStore()
const statusBarHeight = ref(20)
const nickname = ref('')
// 从统一配置获取 API 地址
const API_BASE_URL = config.API_BASE_URL
// 从 configStore 获取默认头像
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
const userInfo = computed(() => ({
avatar: getFullImageUrl(userStore.avatar),
xiangQinNo: userStore.xiangQinNo,
nickname: userStore.nickname
}))
// 头像加载失败处理
const avatarLoadError = ref(false)
const onAvatarError = () => {
avatarLoadError.value = true
}
// 监听 store 中的 nickname 变化,同步到本地
watch(() => userStore.nickname, (newVal) => {
if (newVal && !nickname.value) {
nickname.value = newVal
}
}, { immediate: true })
// 获取系统信息
const getSystemInfo = () => {
uni.getSystemInfo({
success: (res) => {
statusBarHeight.value = res.statusBarHeight || 20
}
})
}
// 返回
const handleBack = () => {
uni.navigateBack()
}
// 更换头像
const handleChangeAvatar = () => {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: async (res) => {
const tempFilePath = res.tempFilePaths[0]
uni.showLoading({ title: '上传中...' })
try {
// 上传图片(使用统一配置的地址)
const uploadRes = await new Promise((resolve, reject) => {
uni.uploadFile({
url: `${API_BASE_URL}/api/app/upload/image`,
filePath: tempFilePath,
name: 'file',
header: {
Authorization: `Bearer ${userStore.token}`
},
success: (res) => {
if (res.statusCode === 200) {
resolve(JSON.parse(res.data))
} else {
reject(new Error('上传失败'))
}
},
fail: reject
})
})
if (uploadRes.code === 0 && uploadRes.data?.url) {
// 调用后端API保存头像到数据库
const saveRes = await updateAvatar(uploadRes.data.url)
if (saveRes && saveRes.code === 0) {
// 更新 store 并重置头像加载错误状态
userStore.updateUserInfo({ avatar: uploadRes.data.url })
avatarLoadError.value = false
uni.showToast({ title: '头像更新成功', icon: 'success' })
} else {
uni.showToast({ title: saveRes?.message || '保存失败', icon: 'none' })
}
} else {
uni.showToast({ title: uploadRes.message || '上传失败', icon: 'none' })
}
} catch (error) {
console.error('上传头像失败:', error)
uni.showToast({ title: '上传失败', icon: 'none' })
} finally {
uni.hideLoading()
}
}
})
}
// 复制相亲编号
const handleCopyXiangQinNo = () => {
const xiangQinNo = userStore.xiangQinNo
if (!xiangQinNo) {
uni.showToast({ title: '暂无编号', icon: 'none' })
return
}
uni.setClipboardData({
data: xiangQinNo,
success: () => {
uni.showToast({ title: '已复制', icon: 'success' })
}
})
}
// 保存
const handleSave = async () => {
// 检查昵称是否有变化
if (nickname.value && nickname.value !== userStore.nickname) {
uni.showLoading({ title: '保存中...' })
try {
const res = await updateNickname(nickname.value)
if (res && res.code === 0) {
userStore.updateUserInfo({ nickname: nickname.value })
uni.hideLoading()
uni.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1000)
} else {
uni.hideLoading()
uni.showToast({ title: res?.message || '保存失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('保存失败:', error)
uni.showToast({ title: '保存失败', icon: 'none' })
}
} else {
uni.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1000)
}
}
// 初始化昵称 - 从API获取最新数据
const initNickname = async () => {
userStore.restoreFromStorage()
// 从 API 获取用户资料
try {
const res = await getMyProfile()
if (res && res.code === 0 && res.data) {
// 优先使用 nickname其次使用 surname姓氏
const profileNickname = res.data.nickname || res.data.surname || ''
if (profileNickname) {
nickname.value = profileNickname
userStore.updateUserInfo({ nickname: profileNickname })
}
}
} catch (error) {
console.error('获取用户资料失败:', error)
// API 失败时尝试从 store 获取
if (userStore.nickname) {
nickname.value = userStore.nickname
}
}
}
onMounted(() => {
getSystemInfo()
initNickname()
})
return {
userInfo,
defaultAvatar,
statusBarHeight,
nickname,
avatarLoadError,
onAvatarError,
handleBack,
handleChangeAvatar,
handleCopyXiangQinNo,
handleSave,
initNickname
}
},
// 页面显示时重新初始化
onShow() {
// 重置头像加载错误状态
if (this.avatarLoadError !== undefined) {
this.avatarLoadError = false
}
if (this.initNickname) {
this.initNickname()
}
}
}
</script>
<style lang="scss" scoped>
.personal-page {
height: 100vh;
background-color: #f5f6fa;
overflow: hidden;
}
// 顶部背景图
.top-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 400rpx;
z-index: 0;
.bg-img {
width: 100%;
height: 100%;
}
}
// 自定义导航栏
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
.navbar-back {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
.back-icon {
font-size: 64rpx;
color: #333;
font-weight: 300;
}
}
.navbar-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.navbar-placeholder {
width: 60rpx;
}
}
}
// 头像区域
.avatar-section {
position: relative;
z-index: 1;
display: flex;
justify-content: center;
padding: 60rpx 0;
.avatar-wrapper {
position: relative;
width: 180rpx;
height: 180rpx;
.avatar-img {
width: 100%;
height: 100%;
border-radius: 50%;
background: linear-gradient(135deg, #87ceeb 0%, #5fb3d4 100%);
}
.avatar-edit-icon {
position: absolute;
right: 0;
bottom: 0;
width: 56rpx;
height: 56rpx;
background: linear-gradient(135deg, #ffb5b5 0%, #ff9a9a 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 4rpx solid #fff;
text {
font-size: 28rpx;
color: #fff;
}
}
}
}
// 表单区域
.form-section {
position: relative;
z-index: 1;
padding: 0 32rpx;
.form-item {
margin-bottom: 32rpx;
.form-label {
display: block;
font-size: 28rpx;
color: #666;
margin-bottom: 16rpx;
}
.form-input {
background: #f0f0f0;
border-radius: 16rpx;
padding: 28rpx 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
text {
font-size: 32rpx;
color: #333;
}
.input-field {
flex: 1;
font-size: 32rpx;
color: #333;
background: transparent;
}
.copy-icon {
font-size: 26rpx;
color: #ff9a9a;
}
&.disabled {
background: #f0f0f0;
text {
color: #333;
}
}
}
}
}
// 保存按钮
.btn-section {
position: relative;
z-index: 1;
padding: 60rpx 32rpx;
.btn-save {
width: 100%;
height: 96rpx;
line-height: 96rpx;
background: linear-gradient(135deg, #ffb5b5 0%, #ff9a9a 100%);
border-radius: 48rpx;
font-size: 34rpx;
color: #fff;
border: none;
&::after {
border: none;
}
}
}
</style>