408 lines
8.8 KiB
Vue
408 lines
8.8 KiB
Vue
<template>
|
||
<view class="profile-page">
|
||
<view class="avatar-section">
|
||
<view class="avatar-container">
|
||
<image class="avatar" v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill"></image>
|
||
<image v-else class="avatar" src="@/static/head.png" mode="aspectFill"></image>
|
||
<!-- 覆盖在头像上的微信选择头像授权按钮,仅小程序生效 -->
|
||
<!-- #ifdef MP-WEIXIN -->
|
||
<button class="avatar-choose-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button>
|
||
<!-- #endif -->
|
||
</view>
|
||
<view class="avatar-tip">点击头像更换</view>
|
||
</view>
|
||
|
||
<view class="form-section">
|
||
<!-- 昵称编辑区域 -->
|
||
<view class="form-item nickname-item" :class="{ editing: isEditingNickname }">
|
||
<view class="label">昵称</view>
|
||
<view class="value" v-if="!isEditingNickname" @click="startEditNickname">
|
||
<text class="value-text">{{ userInfo.nickName || '未设置' }}</text>
|
||
<uv-icon name="edit-pen" size="16" color="#999999"></uv-icon>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 昵称编辑输入框(展开状态) -->
|
||
<view class="nickname-edit-area" v-if="isEditingNickname">
|
||
<input
|
||
class="nickname-input"
|
||
v-model="newNickname"
|
||
placeholder="请输入新昵称"
|
||
maxlength="20"
|
||
:focus="true"
|
||
/>
|
||
<view class="edit-buttons">
|
||
<button class="cancel-btn" @click="cancelEditNickname">取消</button>
|
||
<button class="save-btn" @click="saveNickname">保存</button>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="label">手机号</view>
|
||
<view class="value">
|
||
<text class="value-text">{{ userInfo.phone ? maskPhone(userInfo.phone) : '未绑定' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item" v-if="userInfo.balanceAmount !== undefined">
|
||
<view class="label">余额</view>
|
||
<view class="value">
|
||
<text class="value-text amount">¥{{ userInfo.balanceAmount || '0.00' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted } from 'vue';
|
||
import { getMyIndexInfo, uploadUserAvatar, updateUserInfo } from '../../config/api/user.js';
|
||
|
||
// 响应式状态
|
||
const userInfo = ref({
|
||
nickName: '',
|
||
phone: '',
|
||
avatar: '',
|
||
balanceAmount: '0.00'
|
||
});
|
||
|
||
const newNickname = ref('');
|
||
const isEditingNickname = ref(false);
|
||
|
||
// 页面加载时初始化
|
||
onMounted(() => {
|
||
loadUserInfo();
|
||
});
|
||
|
||
// 获取用户信息
|
||
const loadUserInfo = async () => {
|
||
try {
|
||
const res = await getMyIndexInfo();
|
||
console.log('User info response:', res);
|
||
|
||
if (res.code == 401 || res.code == 40101) {
|
||
redirectToLogin();
|
||
return;
|
||
} else if (res.code == 200) {
|
||
userInfo.value = {
|
||
nickName: res.data.nickname,
|
||
phone: res.data.phone,
|
||
avatar: res.data.iconUrl,
|
||
balanceAmount: res.data.balanceAmount || '0.00',
|
||
isAdmin: res.data.isAdmin
|
||
};
|
||
uni.setStorageSync('userInfo', userInfo.value);
|
||
}
|
||
} catch (error) {
|
||
console.error('获取用户信息失败:', error);
|
||
uni.showToast({
|
||
title: '获取用户信息失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
};
|
||
|
||
// 跳转登录
|
||
const redirectToLogin = () => {
|
||
try {
|
||
const pages = getCurrentPages();
|
||
const current = pages && pages.length ? pages[pages.length - 1] : null;
|
||
const route = current && current.route ? ('/' + current.route) : '/pages/index/index';
|
||
const query = current && current.options ? Object.keys(current.options).map(k =>
|
||
`${k}=${encodeURIComponent(current.options[k])}`).join('&') : '';
|
||
const redirect = encodeURIComponent(query ? `${route}?${query}` : route);
|
||
uni.reLaunch({
|
||
url: `/pages/login/index?redirect=${redirect}`
|
||
});
|
||
} catch (e) {
|
||
uni.reLaunch({
|
||
url: '/pages/login/index'
|
||
});
|
||
}
|
||
};
|
||
|
||
// 小程序原生选择头像回调
|
||
const onChooseAvatar = async (e) => {
|
||
try {
|
||
const token = uni.getStorageSync('token');
|
||
if (!token) {
|
||
redirectToLogin();
|
||
return;
|
||
}
|
||
const avatarLocalPath = e?.detail?.avatarUrl;
|
||
if (!avatarLocalPath) {
|
||
uni.showToast({
|
||
title: '未选择头像',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
uni.showLoading({
|
||
title: '上传中...',
|
||
mask: true
|
||
});
|
||
const uploadRes = await uploadUserAvatar(avatarLocalPath);
|
||
const serverAvatar = uploadRes?.data?.url || uploadRes?.url || uploadRes?.data || '';
|
||
if (serverAvatar) {
|
||
userInfo.value = {
|
||
...userInfo.value,
|
||
avatar: serverAvatar
|
||
};
|
||
uni.setStorageSync('userInfo', userInfo.value);
|
||
}
|
||
uni.showToast({
|
||
title: '头像更新成功',
|
||
icon: 'success'
|
||
});
|
||
await loadUserInfo();
|
||
} catch (err) {
|
||
console.error('选择/上传头像失败:', err);
|
||
uni.showToast({
|
||
title: '头像更新失败',
|
||
icon: 'none'
|
||
});
|
||
} finally {
|
||
uni.hideLoading();
|
||
}
|
||
};
|
||
|
||
// 开始编辑昵称
|
||
const startEditNickname = () => {
|
||
newNickname.value = userInfo.value.nickName || '';
|
||
isEditingNickname.value = true;
|
||
};
|
||
|
||
// 取消编辑昵称
|
||
const cancelEditNickname = () => {
|
||
isEditingNickname.value = false;
|
||
newNickname.value = '';
|
||
};
|
||
|
||
// 保存昵称
|
||
const saveNickname = async () => {
|
||
if (!newNickname.value || !newNickname.value.trim()) {
|
||
uni.showToast({
|
||
title: '昵称不能为空',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
try {
|
||
uni.showLoading({
|
||
title: '保存中...',
|
||
mask: true
|
||
});
|
||
|
||
// 先获取最新的用户信息,确保数据是最新的
|
||
const latestUserInfo = await getMyIndexInfo();
|
||
|
||
if (latestUserInfo.code !== 200) {
|
||
throw new Error('获取用户信息失败');
|
||
}
|
||
|
||
// 使用最新的服务器数据,只修改昵称字段
|
||
const updateData = {
|
||
nickname: newNickname.value.trim(),
|
||
phone: latestUserInfo.data.phone,
|
||
iconUrl: latestUserInfo.data.iconUrl,
|
||
// 保留其他可能的字段
|
||
...latestUserInfo.data
|
||
};
|
||
|
||
// 确保昵称使用新值
|
||
updateData.nickname = newNickname.value.trim();
|
||
|
||
// 调用后端接口更新用户信息
|
||
const res = await updateUserInfo(updateData);
|
||
|
||
if (res.code === 200) {
|
||
// 更新成功后重新获取用户信息,确保数据同步
|
||
await loadUserInfo();
|
||
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '昵称修改成功',
|
||
icon: 'success'
|
||
});
|
||
isEditingNickname.value = false;
|
||
} else {
|
||
throw new Error(res.message || '修改失败');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('修改昵称失败:', error);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: error.message || '修改失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
};
|
||
|
||
// 手机号掩码函数
|
||
function maskPhone(phone) {
|
||
if (!phone) return '';
|
||
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.profile-page {
|
||
min-height: 100vh;
|
||
background-color: #f5f5f5;
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
}
|
||
|
||
.avatar-section {
|
||
background: linear-gradient(180deg, #D1FFE1 0%, #ffffff 100%);
|
||
padding: 60rpx 0 40rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.avatar-container {
|
||
position: relative;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.avatar {
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
border-radius: 80rpx;
|
||
background-color: #f0f0f0;
|
||
display: block;
|
||
}
|
||
|
||
/* 仅小程序端存在,此按钮覆盖在头像上捕获点击以触发选择头像 */
|
||
/* #ifdef MP-WEIXIN */
|
||
.avatar-choose-btn {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
border: none;
|
||
background: transparent;
|
||
padding: 0;
|
||
margin: 0;
|
||
opacity: 0;
|
||
border-radius: 80rpx;
|
||
}
|
||
/* #endif */
|
||
|
||
.avatar-tip {
|
||
font-size: 24rpx;
|
||
color: #999999;
|
||
}
|
||
|
||
.form-section {
|
||
margin: 20rpx 30rpx;
|
||
background-color: #ffffff;
|
||
border-radius: 20rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.form-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 32rpx 30rpx;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.form-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.form-item.nickname-item.editing {
|
||
border-bottom: none;
|
||
padding-bottom: 20rpx;
|
||
}
|
||
|
||
.label {
|
||
font-size: 30rpx;
|
||
color: #333333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.value {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.value-text {
|
||
font-size: 28rpx;
|
||
color: #666666;
|
||
margin-right: 10rpx;
|
||
}
|
||
|
||
.value-text.amount {
|
||
color: #e2231a;
|
||
font-weight: 600;
|
||
font-size: 32rpx;
|
||
}
|
||
|
||
/* 昵称编辑区域样式 */
|
||
.nickname-edit-area {
|
||
padding: 0 30rpx 30rpx 30rpx;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
animation: slideDown 0.3s ease;
|
||
}
|
||
|
||
@keyframes slideDown {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-20rpx);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.nickname-input {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
border: 1rpx solid #e0e0e0;
|
||
border-radius: 10rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 28rpx;
|
||
color: #333333;
|
||
box-sizing: border-box;
|
||
margin-bottom: 20rpx;
|
||
background-color: #fafafa;
|
||
}
|
||
|
||
.edit-buttons {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.cancel-btn,
|
||
.save-btn {
|
||
padding: 0 40rpx;
|
||
height: 64rpx;
|
||
line-height: 64rpx;
|
||
text-align: center;
|
||
border-radius: 32rpx;
|
||
font-size: 28rpx;
|
||
border: none;
|
||
min-width: 120rpx;
|
||
}
|
||
|
||
.cancel-btn {
|
||
background-color: #f5f5f5;
|
||
color: #666666;
|
||
}
|
||
|
||
.save-btn {
|
||
background: linear-gradient(135deg, #42d392 0%, #28c76f 100%);
|
||
color: #ffffff;
|
||
}
|
||
</style>
|
||
|