新增三码适配

This commit is contained in:
2026-04-01 14:12:14 +08:00
parent 337a92d8c1
commit 9ca377907b
12 changed files with 741 additions and 676 deletions
+3 -1
View File
@@ -182,7 +182,9 @@
if (opts && opts.redirect) {
try {
redirect.value = decodeURIComponent(opts.redirect)
} catch (_) {}
} catch (err) {
}
}
// #ifdef H5
isHTML5.value = true
+16 -10
View File
@@ -2,7 +2,8 @@
<view class="my-page">
<view class="user-card" @click="navigateTo('/subPackages/user/userProfile/index')">
<view class="avatar-box">
<image class="avatar" v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill" lazy-load="true"></image>
<image class="avatar" v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill" lazy-load="true">
</image>
<image v-else class="avatar" src="@/static/head.png" mode="aspectFill" lazy-load="true"></image>
</view>
<view class="user-text">
@@ -29,7 +30,8 @@
<swiper class="banner-swiper" :indicator-dots="bannerImages.length > 1"
:autoplay="bannerImages.length > 1" :circular="true" :interval="3000">
<swiper-item v-for="(image, index) in bannerImages" :key="index">
<image class="banner-image" :src="image" mode="aspectFill" @click="handleBannerClick(index)" lazy-load="true">
<image class="banner-image" :src="image" mode="aspectFill" @click="handleBannerClick(index)"
lazy-load="true">
</image>
</swiper-item>
</swiper>
@@ -78,7 +80,8 @@
</view>
<view class="list-item" @click="navigateTo('/subPackages/service/help/index')">
<view class="left">
<image class="icon" src="/static/customer-service.png" mode="aspectFit" lazy-load="true"></image>
<image class="icon" src="/static/customer-service.png" mode="aspectFit" lazy-load="true">
</image>
<text class="title">{{ $t('user.customerService') }}</text>
</view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
@@ -118,9 +121,11 @@
<view class="footer-agreements">
<view class="link-box">
<text class="link" @click="navigateTo('/subPackages/other/legal/agreement')">{{ $t('user.userAgreement') }}</text>
<text class="link"
@click="navigateTo('/subPackages/other/legal/agreement')">{{ $t('user.userAgreement') }}</text>
<text class="sep"></text>
<text class="link" @click="navigateTo('/subPackages/other/legal/privacy')">{{ $t('user.privacyPolicy') }}</text>
<text class="link"
@click="navigateTo('/subPackages/other/legal/privacy')">{{ $t('user.privacyPolicy') }}</text>
</view>
<view class="version">{{ $t('user.version') }}{{ appVersion }}</view>
</view>
@@ -199,7 +204,7 @@
const res = await getCurrentAdvertisement({
appPlatform: appPlatform, // 微信平台
appType: 'user', // 用户端
pictureLocation:'userProfile_banner'
pictureLocation: 'userProfile_banner'
})
if (res && res.code === 200 && res.data) {
@@ -267,7 +272,7 @@
uni.setNavigationBarTitle({
title: t('user.personalCenter')
})
getInfo();
// getInfo();
initVersion();
getBannerImages(); // 加载广告
});
@@ -340,11 +345,12 @@
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: `/subPackages/user/login/index?redirect=${redirect}`
console.log(redirect);
uni.redirectTo({
url: redirect
})
} catch (e) {
uni.reLaunch({
uni.redirectTo({
url: '/subPackages/user/login/index'
})
}
+224 -212
View File
@@ -1,240 +1,252 @@
<template>
<view class="container">
<!-- 用户信息卡片 -->
<view class="user-card">
<view class="avatar">
<image :src="userInfo.avatar || '/static/images/default-avatar.png'" mode="aspectFill"></image>
</view>
<view class="user-info">
<text class="nickname">{{ userInfo.nickName || $t('user.notLoggedIn') }}</text>
<text class="phone">{{ userInfo.phone || $t('user.phoneNotBound') }}</text>
</view>
</view>
<view class="container">
<!-- 用户信息卡片 -->
<view class="user-card">
<view class="avatar">
<image :src="userInfo.avatar || '/static/images/default-avatar.png'" mode="aspectFill"></image>
</view>
<view class="user-info">
<text class="nickname">{{ userInfo.nickName || $t('user.notLoggedIn') }}</text>
<text class="phone">{{ userInfo.phone || $t('user.phoneNotBound') }}</text>
</view>
</view>
<!-- 余额卡片 -->
<view class="balance-card">
<view class="balance-title">{{ $t('userProfile.balance') }}</view>
<view class="balance-amount">{{ userInfo.balanceAmount || '0.00' }}</view>
<view class="balance-desc">{{ $t('user.balanceDesc') }}</view>
</view>
<!-- 余额卡片 -->
<view class="balance-card">
<view class="balance-title">{{ $t('userProfile.balance') }}</view>
<view class="balance-amount">{{ userInfo.balanceAmount || '0.00' }}</view>
<view class="balance-desc">{{ $t('user.balanceDesc') }}</view>
</view>
<!-- 功能菜单 -->
<view class="menu-list">
<view class="menu-item" @click="navigateTo('/pages/order/index')">
<text class="menu-icon">📋</text>
<text class="menu-text">{{ $t('user.myOrders') }}</text>
<text class="menu-arrow">></text>
</view>
<view class="menu-item" @click="navigateTo('/pages/feedback/index')">
<text class="menu-icon">💬</text>
<text class="menu-text">{{ $t('user.feedback') }}</text>
<text class="menu-arrow">></text>
</view>
<view class="menu-item" @click="navigateTo('/pages/help/index')">
<text class="menu-icon"></text>
<text class="menu-text">{{ $t('help.title') }}</text>
<text class="menu-arrow">></text>
</view>
</view>
<!-- 功能菜单 -->
<view class="menu-list">
<view class="menu-item" @click="navigateTo('/pages/order/index')">
<text class="menu-icon">📋</text>
<text class="menu-text">{{ $t('user.myOrders') }}</text>
<text class="menu-arrow">></text>
</view>
<view class="menu-item" @click="navigateTo('/pages/feedback/index')">
<text class="menu-icon">💬</text>
<text class="menu-text">{{ $t('user.feedback') }}</text>
<text class="menu-arrow">></text>
</view>
<view class="menu-item" @click="navigateTo('/pages/help/index')">
<text class="menu-icon"></text>
<text class="menu-text">{{ $t('help.title') }}</text>
<text class="menu-arrow">></text>
</view>
</view>
<!-- 退出登录按钮 -->
<view class="logout-btn" @click="handleLogout" v-if="isLogin">
<text>{{ $t('user.logout') }}</text>
</view>
</view>
<!-- 退出登录按钮 -->
<view class="logout-btn" @click="handleLogout" v-if="isLogin">
<text>{{ $t('user.logout') }}</text>
</view>
</view>
</template>
<script>
import { getUserInfo } from '@/util/index.js'
import { URL } from '@/config/url'
import {
getUserInfo
} from '@/util/index.js'
import {
URL
} from '@/config/url'
export default {
data() {
return {
userInfo: {},
isLogin: false
}
},
onLoad() {
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('user.personalCenter')
})
},
onShow() {
this.loadUserInfo()
},
methods: {
async loadUserInfo() {
try {
const res = await getUserInfo()
if (res.code === 401 || res.code === 40101) {
// 无提示跳转至登录
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: `/subPackages/user/login/index?redirect=${redirect}` })
} catch (e) {
uni.reLaunch({ url: '/subPackages/user/login/index' })
}
} else if (res.code === 200) {
this.userInfo = res.data
this.isLogin = true
} else {
this.isLogin = false
}
} catch (error) {
console.error('加载用户信息失败:', error)
this.isLogin = false
}
},
navigateTo(url) {
uni.navigateTo({ url })
},
handleLogout() {
uni.showModal({
title: this.$t('common.tips'),
content: this.$t('user.confirmLogout'),
success: (res) => {
if (res.confirm) {
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
this.isLogin = false
uni.showToast({
title: this.$t('user.logoutSuccess'),
icon: 'success'
})
setTimeout(() => {
uni.redirectTo({
url: '/subPackages/user/login/index'
})
}, 500)
}
}
})
}
}
}
export default {
data() {
return {
userInfo: {},
isLogin: false
}
},
onLoad() {
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('user.personalCenter')
})
},
onShow() {
this.loadUserInfo()
},
methods: {
async loadUserInfo() {
try {
const res = await getUserInfo()
if (res.code === 401 || res.code === 40101) {
// 无提示跳转至登录
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)
console.log(redirect);
uni.reLaunch({
url: redirect
})
} catch (e) {
uni.reLaunch({
url: '/subPackages/user/login/index'
})
}
} else if (res.code === 200) {
this.userInfo = res.data
this.isLogin = true
} else {
this.isLogin = false
}
} catch (error) {
console.error('加载用户信息失败:', error)
this.isLogin = false
}
},
navigateTo(url) {
uni.navigateTo({
url
})
},
handleLogout() {
uni.showModal({
title: this.$t('common.tips'),
content: this.$t('user.confirmLogout'),
success: (res) => {
if (res.confirm) {
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
this.isLogin = false
uni.showToast({
title: this.$t('user.logoutSuccess'),
icon: 'success'
})
setTimeout(() => {
uni.redirectTo({
url: '/subPackages/user/login/index'
})
}, 500)
}
}
})
}
}
}
</script>
<style>
.container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.user-card {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
display: flex;
align-items: center;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.user-card {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
display: flex;
align-items: center;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
overflow: hidden;
margin-right: 30rpx;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
overflow: hidden;
margin-right: 30rpx;
}
.avatar image {
width: 100%;
height: 100%;
}
.avatar image {
width: 100%;
height: 100%;
}
.user-info {
flex: 1;
}
.user-info {
flex: 1;
}
.nickname {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
display: block;
}
.nickname {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
display: block;
}
.phone {
font-size: 28rpx;
color: #666;
}
.phone {
font-size: 28rpx;
color: #666;
}
.balance-card {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.balance-card {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.balance-title {
font-size: 28rpx;
color: #666;
margin-bottom: 20rpx;
}
.balance-title {
font-size: 28rpx;
color: #666;
margin-bottom: 20rpx;
}
.balance-amount {
font-size: 48rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.balance-amount {
font-size: 48rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.balance-desc {
font-size: 24rpx;
color: #999;
}
.balance-desc {
font-size: 24rpx;
color: #999;
}
.menu-list {
background-color: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.menu-list {
background-color: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.menu-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.menu-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-icon {
font-size: 36rpx;
margin-right: 20rpx;
}
.menu-icon {
font-size: 36rpx;
margin-right: 20rpx;
}
.menu-text {
flex: 1;
font-size: 28rpx;
color: #333;
}
.menu-text {
flex: 1;
font-size: 28rpx;
color: #333;
}
.menu-arrow {
font-size: 28rpx;
color: #999;
}
.menu-arrow {
font-size: 28rpx;
color: #999;
}
.logout-btn {
margin-top: 40rpx;
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
text-align: center;
color: #ff4d4f;
font-size: 28rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
</style>
.logout-btn {
margin-top: 40rpx;
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
text-align: center;
color: #ff4d4f;
font-size: 28rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
</style>
+363 -353
View File
@@ -2,7 +2,8 @@
<view class="profile-page">
<view class="avatar-section">
<view class="avatar-container">
<image class="avatar" v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill" lazy-load="true"></image>
<image class="avatar" v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill" lazy-load="true">
</image>
<image v-else class="avatar" src="@/static/head.png" mode="aspectFill" lazy-load="true"></image>
<!-- 覆盖在头像上的微信选择头像授权按钮仅小程序生效 -->
<!-- #ifdef MP-WEIXIN -->
@@ -12,407 +13,416 @@
<!-- <button class="avatar-choose-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button> -->
<!-- #endif -->
</view>
<view class="avatar-tip">{{ $t('userProfile.clickToChange') }}</view>
</view>
<view class="form-section">
<!-- 昵称编辑区域 -->
<view class="form-item nickname-item" :class="{ editing: isEditingNickname }">
<view class="label">{{ $t('userProfile.nickname') }}</view>
<view class="value" v-if="!isEditingNickname" @click="startEditNickname">
<text class="value-text">{{ userInfo.nickName || $t('userProfile.notSet') }}</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="$t('userProfile.enterNickname')"
maxlength="20"
:focus="true"
/>
<view class="edit-buttons">
<button class="cancel-btn" @click="cancelEditNickname">{{ $t('common.cancel') }}</button>
<button class="save-btn" @click="saveNickname">{{ $t('common.save') }}</button>
</view>
<view class="avatar-tip">{{ $t('userProfile.clickToChange') }}</view>
</view>
<view class="form-item">
<view class="label">{{ $t('userProfile.phone') }}</view>
<view class="value">
<text class="value-text">{{ userInfo.phone ? maskPhone(userInfo.phone) : $t('userProfile.notBound') }}</text>
<view class="form-section">
<!-- 昵称编辑区域 -->
<view class="form-item nickname-item" :class="{ editing: isEditingNickname }">
<view class="label">{{ $t('userProfile.nickname') }}</view>
<view class="value" v-if="!isEditingNickname" @click="startEditNickname">
<text class="value-text">{{ userInfo.nickName || $t('userProfile.notSet') }}</text>
<uv-icon name="edit-pen" size="16" color="#999999"></uv-icon>
</view>
</view>
</view>
<!-- <view class="form-item" v-if="userInfo.balanceAmount !== undefined">
<!-- 昵称编辑输入框展开状态 -->
<view class="nickname-edit-area" v-if="isEditingNickname">
<input class="nickname-input" v-model="newNickname" :placeholder="$t('userProfile.enterNickname')"
maxlength="20" :focus="true" />
<view class="edit-buttons">
<button class="cancel-btn" @click="cancelEditNickname">{{ $t('common.cancel') }}</button>
<button class="save-btn" @click="saveNickname">{{ $t('common.save') }}</button>
</view>
</view>
<view class="form-item">
<view class="label">{{ $t('userProfile.phone') }}</view>
<view class="value">
<text
class="value-text">{{ userInfo.phone ? maskPhone(userInfo.phone) : $t('userProfile.notBound') }}</text>
</view>
</view>
<!-- <view class="form-item" v-if="userInfo.balanceAmount !== undefined">
<view class="label">{{ $t('userProfile.balance') }}</view>
<view class="value">
<text class="value-text amount">¥{{ userInfo.balanceAmount || '0.00' }}</text>
</view>
</view> -->
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { getMyIndexInfo, uploadUserAvatar, updateUserInfo } from '@/config/api/user.js';
import { useI18n } from '@/utils/i18n.js'
import {
ref,
reactive,
onMounted
} from 'vue';
import {
getMyIndexInfo,
uploadUserAvatar,
updateUserInfo
} from '@/config/api/user.js';
import {
useI18n
} from '@/utils/i18n.js'
const { t } = useI18n()
const {
t
} = useI18n()
// 响应式状态
const userInfo = ref({
nickName: '',
phone: '',
avatar: '',
balanceAmount: '0.00'
});
// 响应式状态
const userInfo = ref({
nickName: '',
phone: '',
avatar: '',
balanceAmount: '0.00'
});
const newNickname = ref('');
const isEditingNickname = ref(false);
const newNickname = ref('');
const isEditingNickname = ref(false);
// 页面加载时初始化
onMounted(() => {
uni.setNavigationBarTitle({
title: t('userProfile.title')
})
loadUserInfo();
});
// 页面加载时初始化
onMounted(() => {
uni.setNavigationBarTitle({
title: t('userProfile.title')
})
loadUserInfo();
});
// 获取用户信息
const loadUserInfo = async () => {
try {
const res = await getMyIndexInfo();
console.log('User info response:', res);
// 获取用户信息
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: t('user.getUserInfoFailed'),
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: `/subPackages/user/login/index?redirect=${redirect}`
});
} catch (e) {
uni.reLaunch({
url: '/subPackages/user/login/index'
});
}
};
// 小程序原生选择头像回调
const onChooseAvatar = async (e) => {
console.log(e.detail.avatarUrl,'获取头像详情');
try {
const token = uni.getStorageSync('token');
if (!token) {
redirectToLogin();
return;
}
const avatarLocalPath = e?.detail?.avatarUrl;
if (!avatarLocalPath) {
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: t('user.noAvatar'),
title: t('user.getUserInfoFailed'),
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: redirect
});
} catch (e) {
uni.reLaunch({
url: '/subPackages/user/login/index'
});
}
};
// 小程序原生选择头像回调
const onChooseAvatar = async (e) => {
console.log(e.detail.avatarUrl, '获取头像详情');
try {
const token = uni.getStorageSync('token');
if (!token) {
redirectToLogin();
return;
}
const avatarLocalPath = e?.detail?.avatarUrl;
if (!avatarLocalPath) {
uni.showToast({
title: t('user.noAvatar'),
icon: 'none'
});
return;
}
uni.showLoading({
title: t('userProfile.uploading'),
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: t('user.avatarUpdated'),
icon: 'success'
});
await loadUserInfo();
} catch (err) {
console.error('选择/上传头像失败:', err);
uni.showToast({
title: t('user.avatarUploadFailed'),
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: t('userProfile.nicknameRequired'),
icon: 'none'
});
return;
}
uni.showLoading({
title: t('userProfile.uploading'),
mask: true
});
const uploadRes = await uploadUserAvatar(avatarLocalPath);
const serverAvatar = uploadRes?.data?.url || uploadRes?.url || uploadRes?.data || '';
if (serverAvatar) {
userInfo.value = {
...userInfo.value,
avatar: serverAvatar
try {
uni.showLoading({
title: t('userProfile.saving'),
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
};
uni.setStorageSync('userInfo', userInfo.value);
}
uni.showToast({
title: t('user.avatarUpdated'),
icon: 'success'
});
await loadUserInfo();
} catch (err) {
console.error('选择/上传头像失败:', err);
uni.showToast({
title: t('user.avatarUploadFailed'),
icon: 'none'
});
} finally {
uni.hideLoading();
}
};
// 开始编辑昵称
const startEditNickname = () => {
newNickname.value = userInfo.value.nickName || '';
isEditingNickname.value = true;
};
// 确保昵称使用新值
updateData.nickname = newNickname.value.trim();
// 取消编辑昵称
const cancelEditNickname = () => {
isEditingNickname.value = false;
newNickname.value = '';
};
// 调用后端接口更新用户信息
const res = await updateUserInfo(updateData);
// 保存昵称
const saveNickname = async () => {
if (!newNickname.value || !newNickname.value.trim()) {
uni.showToast({
title: t('userProfile.nicknameRequired'),
icon: 'none'
});
return;
}
if (res.code === 200) {
// 更新成功后重新获取用户信息,确保数据同步
await loadUserInfo();
try {
uni.showLoading({
title: t('userProfile.saving'),
mask: true
});
uni.hideLoading();
uni.showToast({
title: t('userProfile.nicknameUpdated'),
icon: 'success'
});
isEditingNickname.value = false;
} else {
throw new Error(res.message || t('userProfile.updateFailed'));
}
// 先获取最新的用户信息,确保数据是最新的
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();
} catch (error) {
console.error('修改昵称失败:', error);
uni.hideLoading();
uni.showToast({
title: t('userProfile.nicknameUpdated'),
icon: 'success'
title: error.message || t('userProfile.updateFailed'),
icon: 'none'
});
isEditingNickname.value = false;
} else {
throw new Error(res.message || t('userProfile.updateFailed'));
}
};
} catch (error) {
console.error('修改昵称失败:', error);
uni.hideLoading();
uni.showToast({
title: error.message || t('userProfile.updateFailed'),
icon: 'none'
});
// 手机号掩码函数
function maskPhone(phone) {
if (!phone) return '';
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}
};
// 手机号掩码函数
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);
}
.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-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-container {
position: relative;
margin-bottom: 20rpx;
}
.avatar {
width: 160rpx;
height: 160rpx;
border-radius: 80rpx;
background-color: #f0f0f0;
display: block;
}
.avatar {
width: 160rpx;
height: 160rpx;
border-radius: 80rpx;
background-color: #f0f0f0;
display: block;
}
/* 仅小程序端存在,此按钮覆盖在头像上捕获点击以触发选择头像 */
/* #ifdef MP-WEIXIN || MP-ALIPAY */
.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 {
/* 仅小程序端存在,此按钮覆盖在头像上捕获点击以触发选择头像 */
/* #ifdef MP-WEIXIN || MP-ALIPAY */
.avatar-choose-btn {
position: absolute;
left: 0;
top: 0;
width: 160rpx;
height: 160rpx;
border: none;
background: transparent;
padding: 0;
margin: 0;
opacity: 0;
transform: translateY(-20rpx);
border-radius: 80rpx;
}
to {
opacity: 1;
transform: translateY(0);
/* #endif */
.avatar-tip {
font-size: 24rpx;
color: #999999;
}
}
.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;
}
.form-section {
margin: 20rpx 30rpx;
background-color: #ffffff;
border-radius: 20rpx;
overflow: hidden;
}
.edit-buttons {
display: flex;
justify-content: flex-end;
gap: 20rpx;
}
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx 30rpx;
border-bottom: 1rpx solid #f5f5f5;
transition: all 0.3s ease;
}
.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;
}
.form-item:last-child {
border-bottom: none;
}
.cancel-btn {
background-color: #f5f5f5;
color: #666666;
}
.form-item.nickname-item.editing {
border-bottom: none;
padding-bottom: 20rpx;
}
.save-btn {
background: linear-gradient(135deg, #42d392 0%, #28c76f 100%);
color: #ffffff;
}
</style>
.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>