xiangyixiangqin/miniapp/pages/profile/personal.vue
2026-01-06 19:00:44 +08:00

433 lines
10 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="userInfo.avatar || defaultAvatar"
mode="aspectFill"
/>
<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 { updateAvatar, updateNickname } from '@/api/user.js'
import { getMyProfile } from '@/api/profile.js'
export default {
name: 'PersonalPage',
setup() {
const userStore = useUserStore()
const defaultAvatar = '/static/logo.png'
const statusBarHeight = ref(20)
const nickname = ref('')
const userInfo = computed(() => ({
avatar: userStore.avatar,
xiangQinNo: userStore.xiangQinNo,
nickname: userStore.nickname
}))
// 监听 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 {
// 上传图片使用AppApi地址
const uploadRes = await new Promise((resolve, reject) => {
uni.uploadFile({
url: `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/api'}/app/upload`,
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) {
userStore.updateUserInfo({ avatar: uploadRes.data.url })
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,
handleBack,
handleChangeAvatar,
handleCopyXiangQinNo,
handleSave,
initNickname
}
},
// 页面显示时重新初始化
onShow() {
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>