Files
uni-fans-score/pages/my/index.vue
T
2025-11-12 11:25:25 +08:00

775 lines
19 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="my-page">
<view class="user-card" @click="navigateTo('/pages/userProfile/index')">
<view class="avatar-box">
<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>
</view>
<view class="user-text">
<view class="nickname">{{ userInfo.nickName || $t('user.clickToLogin') }}</view>
<view class="subtext">{{ userInfo.phone ? maskPhone(userInfo.phone) : $t('user.loginPrompt') }}</view>
</view>
<uv-icon type="right" size="16" color="#999"></uv-icon>
</view>
<!-- <view class="assets-card">
<view class="assets-left">
<view class="label">押金余额</view>
<view class="amount">¥{{ deposit }}</view>
</view>
<view class="assets-right" @click="handleWithdraw">
<text class="withdraw-btn">提现</text>
</view>
</view> -->
<view class="section">
<view class="banner-card" @click="navigateTo('/pages/join/index')">
<image class="banner-image" src="/static/userCenter_swiper.png" mode="aspectFill"></image>
</view>
<!-- <view class="section-title">常用服务</view> -->
<view class="list">
<view class="list-item" @click="handleQuickReturn">
<view class="left">
<image class="icon" src="/static/express_return.png" mode="aspectFit"></image>
<text class="title">{{ $t('user.quickReturn') }}<text style="font-size: 18rpx;">{{ $t('user.quickReturnDesc') }}</text></text>
</view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view>
<view class="list-item" @click="navigateTo('/pages/expressReturn/index')" v-if="showMenuItem">
<view class="left">
<image class="icon" src="/static/express.png" mode="aspectFit"></image>
<text class="title">{{ $t('user.expressReturn') }}</text>
</view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view>
<view class="list-item" @click="navigateTo('/pages/order/index')">
<view class="left">
<image class="icon" src="/static/orderList.png" mode="aspectFit"></image>
<text class="title">{{ $t('user.myOrders') }}</text>
</view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view>
<view class="list-item" @click="navigateTo('/pages/help/index')">
<view class="left">
<image class="icon" src="/static/customer-service.png" mode="aspectFit"></image>
<text class="title">{{ $t('user.customerService') }}</text>
</view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view>
<view class="list-item" @click="navigateTo('/pages/feedback/index')">
<view class="left">
<image class="icon" src="/static/suggess.png" mode="aspectFit"></image>
<text class="title">{{ $t('user.feedback') }}</text>
</view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view>
<!-- <view class="list-item" @click="navigateTo('/pages/legal/agreement')">
<view class="left">
<image class="icon" src="/static/business-licence.png" mode="aspectFit"></image>
<text class="title">{{ $t('user.businessLicense') }}</text>
</view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> -->
<view class="list-item" @click="navigateTo('/pages/join/index')">
<view class="left">
<image class="icon" src="/static/peopleInWork.png" mode="aspectFit"></image>
<text class="title">{{ $t('user.cooperation') }}</text>
</view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view>
<view class="list-item" @click="navigateTo('/pages/setting/index')">
<view class="left">
<image class="icon" src="/static/setting.png" mode="aspectFit"></image>
<text class="title">{{ $t('user.settings') }}</text>
</view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view>
</view>
</view>
<view class="footer-agreements">
<view class="link-box">
<text class="link" @click="navigateTo('/pages/legal/agreement')">{{ $t('user.userAgreement') }}</text>
<text class="sep"></text>
<text class="link" @click="navigateTo('/pages/legal/privacy')">{{ $t('user.privacyPolicy') }}</text>
</view>
<view class="version">{{ $t('user.version') }}{{ appVersion }}</view>
</view>
<!-- 保留授权弹窗暂不启用 -->
<!--
<u-popup ref="authPopup" mode="center" border-radius="15" width="600rpx" @open="onPopupOpen" @close="onPopupClose">
<view class="auth-popup">
<view class="auth-title">授权登录</view>
<view class="auth-desc">获取您的微信头像昵称等公开信息</view>
<view class="auth-buttons">
<button class="cancel-btn" @click="closeAuthPopup">取消</button>
<button class="confirm-btn" @click="getUserProfile">确定</button>
</view>
</view>
</u-popup>
-->
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted
} from 'vue';
import {
onShow
} from '@dcloudio/uni-app';
import {
wxLogin,
getUserInfo
} from '../../util/index.js';
import {
uploadUserAvatar
} from '../../config/api/user.js'
import {
URL
} from '../../config/url.js'
import { useI18n } from '@/utils/i18n.js'
// 设置页执行退出登录,此页不再直接调用
const { t: $t } = useI18n()
// 响应式状态
const userInfo = ref({});
const deposit = ref('0.00');
const openId = ref('');
const authPopup = ref(null); // u-popup 的引用
const isPopupVisible = ref(false);
const appVersion = ref('1.0.0');
const showMenuItem = ref(false)
// 页面加载时初始化
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('user.personalCenter')
})
getInfo();
initVersion();
});
// 页面显示时刷新用户信息
onShow(() => {
getInfo();
});
// 获取用户信息
const getInfo = async () => {
try {
const res = await getUserInfo();
console.log('User info response:', res);
if (res.code == 401 || res.code == 40101) {
redirectToLogin()
return
} else if (res.code == 200) {
// 保存openId
if (res.data.openId) {
openId.value = res.data.openId;
uni.setStorageSync('openId', res.data.openId);
}
// 更新用户信息
userInfo.value = {
nickName: res.data.nickname,
phone: res.data.phone,
avatar: res.data.iconUrl,
isAdmin: res.data.isAdmin
};
uni.setStorageSync('userInfo', userInfo.value);
deposit.value = res.data.balanceAmount || '0.00';
}
} catch (error) {
console.error('获取用户信息失败:', error);
uni.showToast({
title: $t('user.getUserInfoFailed'),
icon: 'none'
});
}
};
// 初始化应用版本号(多端兼容,取可用信息)
const initVersion = () => {
// #ifdef MP-WEIXIN
try {
const info = wx.getAccountInfoSync && wx.getAccountInfoSync();
if (info && info.miniProgram && info.miniProgram.version) {
appVersion.value = info.miniProgram.version;
}
} catch (e) {}
// #endif
// #ifdef APP-PLUS
try {
if (typeof plus !== 'undefined' && plus.runtime && plus.runtime.version) {
appVersion.value = plus.runtime.version;
}
} catch (e) {}
// #endif
};
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 navigateTo = (url) => {
uni.navigateTo({
url
});
};
// 处理快速归还
const handleQuickReturn = async () => {
try {
uni.showLoading({
title: $t('common.loading')
});
// 获取使用中的订单
const res = await uni.request({
url: `${URL}/app/order/inUse`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
});
uni.hideLoading();
if (res.statusCode === 401 || res.data?.code === 401 || res.data?.code === 40101) {
redirectToLogin();
return;
}
if (res.statusCode === 200 && res.data.code === 200 && res.data.data) {
const inUseOrder = res.data.data;
// 跳转到统一订单详情页面
uni.navigateTo({
url: `/pages/order/detail?orderId=${inUseOrder.orderId}&deviceId=${inUseOrder.deviceNo}`
});
} else {
uni.showToast({
title: $t('order.noOrder'),
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('获取使用中订单失败:', error);
uni.showToast({
title: $t('order.getOrderFailed'),
icon: 'none'
});
}
};
// 处理提现按钮点击
const handleWithdraw = () => {
navigateTo('/pages/deposit/index');
};
// 处理用户资料点击
const handleUserProfileClick = () => {
const token = uni.getStorageSync('token')
if (!token) {
redirectToLogin()
return
}
// #ifdef MP-WEIXIN
getUserProfile()
// #endif
// #ifndef MP-WEIXIN
uni.showToast({
title: $t('auth.pleaseUseInWechat'),
icon: 'none'
})
// #endif
};
// 小程序原生选择头像回调(需基础库>=2.21.2
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: $t('common.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: '头像已更新',
icon: 'success'
})
await getInfo()
} catch (err) {
console.error('选择/上传头像失败:', err)
uni.showToast({
title: '头像更新失败',
icon: 'none'
})
} finally {
uni.hideLoading()
}
}
// 打开授权弹窗
const openAuthPopup = () => {
if (authPopup.value) {
authPopup.value.open();
isPopupVisible.value = true;
}
};
// 弹窗打开事件处理
const onPopupOpen = () => {
console.log('授权弹窗已打开');
isPopupVisible.value = true;
// 这里可以添加弹窗打开后的逻辑
};
// 弹窗关闭事件处理
const onPopupClose = () => {
console.log('授权弹窗已关闭');
isPopupVisible.value = false;
// 这里可以添加弹窗关闭后的逻辑
};
// 获取微信用户个人信息
const getUserProfile = () => {
// #ifdef MP-WEIXIN
uni.showLoading({
title: $t('common.getting'),
mask: true
});
wx.getUserProfile({
desc: '用于完善会员资料',
success: (res) => {
console.log('获取用户信息成功:', res);
updateUserInfo(res.userInfo);
uploadAvatarAndRefresh(res.userInfo);
},
fail: (err) => {
console.error('获取用户信息失败:', err);
uni.showToast({
title: '获取用户信息失败',
icon: 'none'
});
},
complete: () => {
uni.hideLoading();
closeAuthPopup();
}
});
// #endif
// #ifndef MP-WEIXIN
uni.showToast({
title: $t('auth.pleaseUseInWechat'),
icon: 'none'
});
closeAuthPopup();
// #endif
};
// 更新用户信息
const updateUserInfo = async (wxUserInfo) => {
try {
// 更新本地用户信息
const updatedInfo = {
...userInfo.value,
nickName: wxUserInfo.nickName,
avatar: wxUserInfo.avatarUrl
};
userInfo.value = updatedInfo;
uni.setStorageSync('userInfo', updatedInfo);
// 这里可以添加调用后端API更新用户信息的代码
// const updateRes = await updateUserInfoApi({
// openId: openId.value,
// nickName: wxUserInfo.nickName,
// avatarUrl: wxUserInfo.avatarUrl,
// gender: wxUserInfo.gender
// });
uni.showToast({
title: $t('user.updateSuccess'),
icon: 'success'
});
// 更新完成后重新获取用户信息
getInfo();
} catch (error) {
console.error('更新用户信息失败:', error);
uni.showToast({
title: $t('user.updateFailed'),
icon: 'none'
});
}
};
// 下载并上传头像,更新用户信息
const uploadAvatarAndRefresh = async (wxUserInfo) => {
try {
const avatarUrl = wxUserInfo?.avatarUrl
if (!avatarUrl) {
uni.showToast({
title: '未获取到头像地址',
icon: 'none'
})
return
}
// 下载微信头像为本地临时文件
const tempFilePath = await new Promise((resolve, reject) => {
uni.downloadFile({
url: avatarUrl,
success: (res) => {
if (res.statusCode === 200 && res.tempFilePath) {
resolve(res.tempFilePath)
return
}
reject(new Error('头像下载失败'))
},
fail: reject
})
})
// 上传到后端
const uploadRes = await uploadUserAvatar(tempFilePath)
// 直接使用返回的头像地址(如果有),并刷新用户信息
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 getInfo()
} catch (error) {
console.error('头像上传失败:', error)
uni.showToast({
title: '头像上传失败',
icon: 'none'
})
} finally {
uni.hideLoading()
}
}
// 关闭授权弹窗
const closeAuthPopup = () => {
if (authPopup.value) {
authPopup.value.close();
isPopupVisible.value = false;
}
};
// 关于我们
const handleAboutUs = () => {
uni.showToast({
title: $t('help.functionDeveloping'),
icon: 'none'
});
};
// 隐私政策
const handlePrivacyPolicy = () => {
uni.showToast({
title: $t('help.functionDeveloping'),
icon: 'none'
});
};
// 手机号掩码函数
function maskPhone(phone) {
if (!phone) return '';
// 只处理11位手机号
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}
// 退出登录移动至设置页
</script>
<style lang="scss" scoped>
.my-page {
min-height: 100vh;
background-color: #ffffff;
position: -webkit-sticky;
position: sticky;
top: 0;
padding-bottom: env(safe-area-inset-bottom);
display: flex;
flex-direction: column;
}
.user-card {
display: flex;
align-items: center;
padding: 24rpx 30rpx;
// background-color: #D1FFE1;
background: linear-gradient(180deg, #D1FFE1 0%, #ffffff 100%);
// border-bottom: 1rpx solid #f0f0f0;
// margin: 0 20rpx;
}
.banner-card {
margin: 20rpx 30rpx 0 30rpx;
background-color: #ffffff;
border-radius: 50rpx;
overflow: hidden;
border: 1rpx solid #f0f0f0;
}
.banner-image {
width: 100%;
height: 260rpx;
border-radius: 50rpx;
display: block;
}
.avatar-box {
margin-right: 20rpx;
position: relative;
}
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50rpx;
background-color: #f0f0f0;
}
/* 仅小程序端存在,此按钮覆盖在头像上捕获点击以触发选择头像 */
/* #ifdef MP-WEIXIN */
.avatar-choose-btn {
position: absolute;
left: 0;
top: 0;
width: 100rpx;
height: 100rpx;
border: none;
background: transparent;
padding: 0;
margin: 0;
opacity: 0;
/* 保持可点击但不可见 */
}
/* #endif */
.user-text {
flex: 1;
}
.nickname {
font-size: 32rpx;
color: #222222;
font-weight: 600;
}
.subtext {
margin-top: 6rpx;
font-size: 24rpx;
color: #999999;
}
.assets-card {
margin: 20rpx 0;
padding: 24rpx 30rpx;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1rpx solid #f0f0f0;
border-bottom: 1rpx solid #f0f0f0;
}
.assets-left .label {
font-size: 26rpx;
color: #666666;
}
.assets-left .amount {
margin-top: 8rpx;
font-size: 40rpx;
color: #e2231a;
font-weight: 600;
}
.withdraw-btn {
display: inline-block;
padding: 14rpx 28rpx;
color: #e2231a;
border: 1rpx solid #e2231a;
border-radius: 6rpx;
font-size: 26rpx;
}
.section {
margin-top: 20rpx;
margin: 0 20rpx 20rpx 20rpx;
background-color: #ffffff;
flex: 1;
// border-top: 1rpx solid #f0f0f0;
// border-bottom: 1rpx solid #f0f0f0;
border-radius: 20rpx;
}
.section-title {
padding: 20rpx 30rpx;
font-size: 26rpx;
color: #999999;
}
.list-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 30rpx;
border-top: 1rpx solid #f5f5f5;
}
.list-item:first-child {
border-top: none;
}
.left {
display: flex;
align-items: center;
}
.icon {
width: 40rpx;
height: 40rpx;
margin-right: 16rpx;
}
.title {
font-size: 30rpx;
color: #333333;
}
.footer-agreements {
margin: 40rpx 0 20rpx 0;
position: absolute;
bottom: 10rpx;
left: 0;
right: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #999999;
font-size: 22rpx;
.link-box {
display: flex;
align-items: center;
justify-content: center;
}
}
.footer-agreements .link {
color: #999999;
}
.footer-agreements .sep {
margin: 0 10rpx;
color: #c0c0c0;
}
.footer-agreements .version {
margin-top: 10rpx;
color: #c0c0c0;
font-size: 20rpx;
}
/* 保留弹窗样式(未启用) */
.auth-popup {
background-color: #ffffff;
width: 100%;
padding: 40rpx;
border-radius: 12rpx;
}
.auth-title {
font-size: 34rpx;
font-weight: 600;
color: #333333;
text-align: center;
margin-bottom: 20rpx;
}
.auth-desc {
font-size: 28rpx;
color: #666666;
text-align: center;
margin-bottom: 40rpx;
}
.auth-buttons {
display: flex;
justify-content: space-between;
}
.cancel-btn,
.confirm-btn {
width: 240rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 40rpx;
font-size: 28rpx;
}
</style>