Files
Driver-program/src/pages/profile/index.vue
2026-02-07 16:11:40 +08:00

794 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="profile-page">
<!-- 用户信息区域 -->
<view class="user-info">
<view class="avatar-section">
<view class="avatar-container" @click="previewAvatar">
<image v-if="userInfo.driverInfo?.data?.driverHead"
:src="getImageUrl(userInfo.driverInfo.data.driverHead)" :key="avatarKey"
class="avatar-image" mode="aspectFill" />
<view v-else class="avatar-placeholder">
<My size="60" color="#666" />
</view>
</view>
<view class="user-details">
<text class="username">{{ userInfo.driverInfo?.data?.driverName || userInfo.username || '点击登录' }}</text>
<text class="phone-number">{{ userInfo.driverInfo?.data?.driverTel || '未设置' }}</text>
</view>
</view>
</view>
<!-- 功能菜单区域 -->
<view class="menu-section">
<!-- 我的车辆 -->
<!-- <view class="menu-item" @click="goToMyVehicles">
<view class="menu-icon">
<Cart size="40" color="#666" />
</view>
<text class="menu-text">我的车辆</text>
<view class="menu-arrow">
<Right size="32" color="#999" />
</view>
</view> -->
<!-- 历史运送车次 -->
<view class="menu-item" @click="goToTrips">
<view class="menu-icon">
<Order size="40" color="#666" />
</view>
<view class="menu-content">
<text class="menu-text">历史运送车次</text>
</view>
<view class="menu-arrow">
<Right size="32" color="#999" />
</view>
</view>
<!-- 修改头像 -->
<view class="menu-item" @click="changeAvatar" :class="{ 'disabled': isUploadingAvatar }">
<view class="menu-icon">
<My size="40" color="#666" />
</view>
<view class="menu-content">
<text class="menu-text">{{ isUploadingAvatar ? '头像上传中...' : '修改头像' }}</text>
</view>
<view class="menu-arrow" v-if="!isUploadingAvatar">
<Right size="32" color="#999" />
</view>
<view class="menu-loading" v-else>
<text class="loading-text">...</text>
</view>
</view>
<!-- 实名认证 -->
<view class="menu-item" @click="goToIdentity">
<view class="menu-icon">
<My size="40" color="#666" />
</view>
<view class="menu-content">
<text class="menu-text">实名认证</text>
<view v-if="driverStatus" class="status-badge" :class="statusClass">
<text class="status-text">{{ statusText }}</text>
</view>
</view>
<view class="menu-arrow">
<Right size="32" color="#999" />
</view>
</view>
<!-- 修改密码 -->
<view class="menu-item" @click="changePassword">
<view class="menu-icon">
<Setting size="40" color="#666" />
</view>
<view class="menu-content">
<text class="menu-text">修改密码</text>
</view>
<view class="menu-arrow">
<Right size="32" color="#999" />
</view>
</view>
</view>
<!-- 退出登录按钮 -->
<view class="logout-section">
<nut-button type="danger" size="large" @click="handleLogout" v-if="userInfo.isLogin">
退出登录
</nut-button>
<nut-button type="primary" size="large" @click="login" v-else>
立即登录
</nut-button>
</view>
<!-- Toast 提示 -->
<nut-toast v-model:visible="showToast" :msg="toastMsg" />
<!-- 头像预览弹窗 -->
<nut-popup v-model:visible="showAvatarPreview" position="center">
<view class="avatar-preview-dialog">
<view class="preview-title">头像预览</view>
<view class="avatar-preview">
<image v-if="userInfo.driverInfo?.data?.driverHead || userInfo.avatar"
:src="userInfo.driverInfo?.data?.driverHead ? getImageUrl(userInfo.driverInfo.data.driverHead) : userInfo.avatar"
class="preview-avatar-image" mode="aspectFill" />
<view v-else class="preview-avatar-placeholder">
<My size="100" color="#666" />
</view>
</view>
<view class="preview-buttons">
<nut-button @click="showAvatarPreview = false">关闭</nut-button>
<nut-button type="primary" @click="selectNewAvatar" :disabled="isUploadingAvatar">
{{ isUploadingAvatar ? '上传中...' : '选择新头像' }}
</nut-button>
</view>
</view>
</nut-popup>
<!-- 修改密码弹窗 -->
<nut-popup v-model:visible="showPasswordDialog" position="center">
<view class="password-dialog">
<view class="dialog-title">修改密码</view>
<nut-form>
<nut-form-item label="原密码">
<nut-input v-model="passwordForm.oldPassword" type="password" placeholder="请输入原密码" />
</nut-form-item>
<nut-form-item label="新密码">
<nut-input v-model="passwordForm.newPassword" type="password" placeholder="请输入新密码" />
</nut-form-item>
<nut-form-item label="确认密码">
<nut-input v-model="passwordForm.confirmPassword" type="password" placeholder="请再次输入新密码" />
</nut-form-item>
</nut-form>
<view class="dialog-buttons">
<nut-button @click="showPasswordDialog = false">取消</nut-button>
<nut-button type="primary" @click="submitPassword">确认</nut-button>
</view>
</view>
</nut-popup>
</view>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick, computed } from 'vue'
import { Button, Toast, Avatar, Popup, Form, FormItem, Input } from '@nutui/nutui-taro'
import { Cart, Order, Setting, Right, My } from '@nutui/icons-vue'
import Taro, { useDidShow } from '@tarojs/taro'
import { getUserInfo, logout, isLoggedIn, saveUserInfo, checkLoginAndRedirect } from '../../utils/auth.js'
import { driverAPI } from '../../api/index'
import { API_CONFIG } from '../../utils/request.js'
// 用户信息
const userInfo = reactive({
isLogin: false,
username: '',
userId: '',
phone: '',
avatar: ''
})
// 页面加载时检查登录状态
onMounted(() => {
// 检查登录状态,未登录则跳转到登录页
if (!checkLoginAndRedirect()) {
return
}
// 已登录,获取用户信息
const storedUserInfo = getUserInfo()
if (storedUserInfo) {
Object.assign(userInfo, storedUserInfo)
}
})
// 页面显示时重新获取用户信息
useDidShow(() => {
// 检查登录状态,未登录则跳转到登录页
if (!checkLoginAndRedirect()) {
return
}
// 已登录,重新获取用户信息
const storedUserInfo = getUserInfo()
if (storedUserInfo) {
Object.assign(userInfo, storedUserInfo)
// 如果头像有变化,强制重新渲染
if (storedUserInfo.driverInfo?.data?.driverHead || storedUserInfo.avatar) {
avatarKey.value++
}
} else {
Object.assign(userInfo, {
isLogin: false,
username: '',
userId: '',
phone: '',
avatar: ''
})
}
// 刷新司机数据以获取最新的认证状态
refreshDriverData()
})
// Toast 相关
const showToast = ref(false)
const toastMsg = ref('')
// 头像预览弹窗
const showAvatarPreview = ref(false)
// 修改密码弹窗
const showPasswordDialog = ref(false)
const passwordForm = reactive({
oldPassword: '',
newPassword: '',
confirmPassword: ''
})
// 头像上传状态
const isUploadingAvatar = ref(false)
// 头像key用于强制重新渲染
const avatarKey = ref(0)
// 司机状态相关
const driverStatus = computed(() => {
return userInfo.driverInfo?.data?.driverStatus
})
const statusText = computed(() => {
const status = driverStatus.value
if (status === '0') return '待认证'
if (status === '1') return '正常'
if (status === '2') return '冻结'
return '未知状态'
})
// 获取图片完整URL
const getImageUrl = (path) => {
if (!path) return ''
// 如果已经是完整URL直接返回
if (path.startsWith('http')) {
return path
}
// 拼接API地址
return `${API_CONFIG.BASE_URL}api${path.startsWith('/') ? path : '/' + path}`
}
const statusClass = computed(() => {
const status = driverStatus.value
if (status === '0') return 'status-pending'
if (status === '1') return 'status-normal'
if (status === '2') return 'status-frozen'
return 'status-unknown'
})
// 显示提示
const showMessage = (msg) => {
toastMsg.value = msg
showToast.value = true
}
// 刷新司机数据
const refreshDriverData = async () => {
try {
const storedUserInfo = getUserInfo()
if (!storedUserInfo) {
console.error('无法获取用户信息')
return
}
// 获取用户ID
const userId = storedUserInfo.userId || storedUserInfo.id || storedUserInfo.user?.id
if (!userId) {
console.error('无法获取用户ID')
return
}
// 调用API获取最新的司机信息
const response = await driverAPI.getDriverInfo(userId)
if (response.statusCode === 200 && response.data) {
if (response.data.code === 0 && response.data.data) {
// 更新本地用户信息中的司机数据
const updatedUserInfo = {
...storedUserInfo,
driverInfo: {
...storedUserInfo.driverInfo,
data: response.data.data
}
}
// 保存更新后的用户信息
saveUserInfo(updatedUserInfo)
// 更新当前页面的用户信息
Object.assign(userInfo, updatedUserInfo)
} else {
console.error('获取司机信息失败:', response.data)
}
} else {
console.error('获取司机信息API调用失败:', response)
}
} catch (error) {
console.error('刷新司机数据失败:', error)
}
}
// 预览头像
const previewAvatar = () => {
if (!userInfo.isLogin) {
showMessage('请先登录')
return
}
showAvatarPreview.value = true
}
// 更换头像
const changeAvatar = () => {
if (!userInfo.isLogin) {
showMessage('请先登录')
return
}
selectNewAvatar()
}
// 选择新头像
const selectNewAvatar = () => {
showAvatarPreview.value = false
if (isUploadingAvatar.value) {
showMessage('头像上传中,请稍候...')
return
}
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFilePaths[0]
// 将图片转换为base64
Taro.getFileSystemManager().readFile({
filePath: tempFilePath,
encoding: 'base64',
success: (fileRes) => {
const base64Data = `data:image/jpeg;base64,${fileRes.data}`
// 上传头像
uploadAvatar(base64Data)
},
fail: (error) => {
console.error('读取图片文件失败:', error)
showMessage('头像处理失败,请重试')
}
})
},
fail: (error) => {
console.error('选择图片失败:', error)
showMessage('选择头像失败,请重试')
}
})
}
// 上传头像
const uploadAvatar = async (base64Data) => {
if (isUploadingAvatar.value) return
isUploadingAvatar.value = true
showMessage('正在上传头像...')
try {
// 获取当前用户的司机ID
const storedUserInfo = getUserInfo()
const driverId = storedUserInfo?.driverId || storedUserInfo?.id || userInfo.userId
if (!driverId) {
console.error('未找到司机ID无法上传头像')
showMessage('用户信息不完整,请重新登录')
return
}
const updateData = {
id: driverId,
driverHead: base64Data
}
const updateResponse = await driverAPI.updateDriverHead(updateData)
if (updateResponse.statusCode === 200 && updateResponse.data) {
if (updateResponse.data.code === 0) {
// 获取服务器返回的图片路径
const imagePath = updateResponse.data.data || updateResponse.data.path || updateResponse.data.url
if (imagePath) {
// 更新driverInfo.data.driverHead字段为服务器返回的路径
if (userInfo.driverInfo && userInfo.driverInfo.data) {
userInfo.driverInfo.data.driverHead = imagePath
}
// 强制触发响应式更新
await nextTick()
// 强制重新渲染头像
avatarKey.value++
// 更新本地存储的用户信息
if (storedUserInfo) {
// 同时更新driverInfo中的头像字段
if (storedUserInfo.driverInfo && storedUserInfo.driverInfo.data) {
storedUserInfo.driverInfo.data.driverHead = imagePath
}
Taro.setStorageSync('userInfo', storedUserInfo)
}
showMessage('头像更新成功')
} else {
console.error('服务器未返回图片路径')
showMessage('头像上传失败:未获取到图片路径')
}
} else {
const errorMsg = updateResponse.data?.msg || updateResponse.data?.message || '头像上传失败'
console.error('头像上传业务失败:', updateResponse.data)
showMessage(errorMsg)
}
} else {
const errorMsg = updateResponse.data?.msg || updateResponse.data?.message || '头像上传失败'
showMessage(errorMsg)
}
} catch (error) {
console.error('头像上传失败:', error)
showMessage('头像上传失败,请重试')
} finally {
isUploadingAvatar.value = false
}
}
// 我的车辆
const goToMyVehicles = () => {
showMessage('跳转到我的车辆页面')
// 这里可以跳转到车辆页面
// Taro.navigateTo({ url: '/pages/vehicle/index' })
}
// 历史运送车次
const goToTrips = () => {
Taro.navigateTo({ url: '/pages/history/index' })
}
// 实名认证
const goToIdentity = () => {
Taro.navigateTo({ url: '/pages/realname/index' })
}
// 修改密码
const changePassword = () => {
Taro.navigateTo({ url: '/pages/password/index' })
}
// 提交密码修改
const submitPassword = () => {
if (!passwordForm.oldPassword || !passwordForm.newPassword || !passwordForm.confirmPassword) {
showMessage('请填写完整信息')
return
}
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
showMessage('两次输入的密码不一致')
return
}
if (passwordForm.newPassword.length < 6) {
showMessage('密码长度不能少于6位')
return
}
// 这里调用修改密码的API
showMessage('密码修改成功')
showPasswordDialog.value = false
// 清空表单
passwordForm.oldPassword = ''
passwordForm.newPassword = ''
passwordForm.confirmPassword = ''
}
// 登录
const login = () => {
Taro.navigateTo({ url: '/pages/login/index' })
}
// 退出登录
const handleLogout = () => {
logout() // 使用认证工具中的退出登录函数
showMessage('已退出登录')
}
</script>
<style>
.profile-page {
min-height: 100vh;
background-color: #f5f5f5;
}
/* 用户信息区域 */
.user-info {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 60rpx 40rpx;
color: white;
}
.avatar-section {
display: flex;
align-items: center;
}
.avatar-container {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
overflow: hidden;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.avatar-image {
width: 100%;
height: 100%;
border-radius: 50%;
}
.avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.user-details {
margin-left: 40rpx;
flex: 1;
}
.username {
display: block;
font-size: 48rpx;
font-weight: bold;
margin-bottom: 16rpx;
}
.user-id {
display: block;
font-size: 28rpx;
opacity: 0.8;
margin-bottom: 12rpx;
}
.phone-number {
display: block;
font-size: 28rpx;
opacity: 0.8;
}
/* 状态徽章 */
.status-badge {
display: inline-block;
padding: 8rpx 16rpx;
border-radius: 20rpx;
margin-top: 10rpx;
font-size: 24rpx;
font-weight: bold;
width: fit-content;
max-width: 200rpx;
}
.status-pending {
background: rgba(255, 193, 7, 0.2);
color: #ffc107;
border: 1rpx solid rgba(255, 193, 7, 0.3);
}
.status-normal {
background: rgba(40, 167, 69, 0.2);
color: #28a745;
border: 1rpx solid rgba(40, 167, 69, 0.3);
}
.status-frozen {
background: rgba(220, 53, 69, 0.2);
color: #dc3545;
border: 1rpx solid rgba(220, 53, 69, 0.3);
}
.status-unknown {
background: rgba(108, 117, 125, 0.2);
color: #6c757d;
border: 1rpx solid rgba(108, 117, 125, 0.3);
}
.status-text {
font-size: 24rpx;
font-weight: bold;
}
/* 功能菜单区域 */
.menu-section {
background: white;
margin: 40rpx;
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.1);
}
.menu-item {
display: flex;
align-items: center;
padding: 40rpx;
border-bottom: 2rpx solid #f0f0f0;
transition: background-color 0.3s;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-item:active {
background-color: #f8f8f8;
}
.menu-item.disabled {
opacity: 0.6;
pointer-events: none;
}
.menu-icon {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f0f0f0;
border-radius: 16rpx;
margin-right: 30rpx;
}
.menu-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.menu-text {
font-size: 32rpx;
color: #333;
}
.menu-arrow {
color: #ccc;
}
.menu-loading {
display: flex;
align-items: center;
justify-content: center;
width: 60rpx;
height: 60rpx;
}
.loading-text {
font-size: 32rpx;
color: #667eea;
animation: loading 1s infinite;
}
@keyframes loading {
0%,
20% {
opacity: 0;
}
50% {
opacity: 1;
}
80%,
100% {
opacity: 0;
}
}
/* 退出登录区域 */
.logout-section {
padding: 40rpx;
margin-top: 40rpx;
}
/* 头像预览弹窗 */
.avatar-preview-dialog {
background: white;
border-radius: 24rpx;
padding: 40rpx;
width: 500rpx;
max-width: 90vw;
text-align: center;
}
.preview-title {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 30rpx;
color: #333;
}
.avatar-preview {
margin-bottom: 40rpx;
display: flex;
justify-content: center;
align-items: center;
}
.preview-avatar-image {
width: 200rpx;
height: 200rpx;
border-radius: 50%;
}
.preview-avatar-placeholder {
width: 200rpx;
height: 200rpx;
border-radius: 50%;
background: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
}
.preview-buttons {
display: flex;
justify-content: space-between;
gap: 20rpx;
}
.preview-buttons .nut-button {
flex: 1;
}
/* 修改密码弹窗 */
.password-dialog {
background: white;
border-radius: 24rpx;
padding: 40rpx;
width: 600rpx;
max-width: 90vw;
}
.dialog-title {
font-size: 36rpx;
font-weight: bold;
text-align: center;
margin-bottom: 40rpx;
color: #333;
}
.dialog-buttons {
display: flex;
justify-content: space-between;
margin-top: 40rpx;
gap: 20rpx;
}
.dialog-buttons .nut-button {
flex: 1;
}
</style>