1180 lines
30 KiB
Vue
1180 lines
30 KiB
Vue
<template>
|
|
<div class="profile-page">
|
|
<!-- 导航栏 -->
|
|
<nav class="navbar">
|
|
<div class="nav-center">
|
|
<h1 class="nav-title">个人资料</h1>
|
|
</div>
|
|
<div class="nav-right">
|
|
<el-dropdown @command="handleCommand">
|
|
<span class="el-dropdown-link">
|
|
<el-icon><More /></el-icon>
|
|
</span>
|
|
<template #dropdown>
|
|
<el-dropdown-menu>
|
|
<el-dropdown-item command="orders">我的订单</el-dropdown-item>
|
|
<el-dropdown-item command="points">积分记录</el-dropdown-item>
|
|
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- 个人信息 -->
|
|
<div class="profile-content">
|
|
<div class="profile-header">
|
|
<div class="avatar-section">
|
|
<el-avatar
|
|
:size="80"
|
|
:src="userInfo?.avatar"
|
|
class="user-avatar"
|
|
>
|
|
<el-icon><User /></el-icon>
|
|
</el-avatar>
|
|
<el-button
|
|
type="primary"
|
|
size="small"
|
|
@click="showAvatarUpload = true"
|
|
class="upload-btn"
|
|
>
|
|
更换头像
|
|
</el-button>
|
|
</div>
|
|
<div class="user-info">
|
|
<h2 class="username">{{ userInfo?.username }}</h2>
|
|
<p class="user-email">{{ userInfo?.email }}</p>
|
|
|
|
<!-- 审核状态显示 -->
|
|
<div class="audit-status">
|
|
<el-tag
|
|
:type="getAuditStatusType(userInfo?.auditStatus)"
|
|
size="small"
|
|
class="audit-tag"
|
|
>
|
|
{{ getAuditStatusText(userInfo?.auditStatus) }}
|
|
</el-tag>
|
|
<span v-if="userInfo?.auditStatus === 'pending'" class="audit-tip">
|
|
审核通过后可参与匹配
|
|
</span>
|
|
</div>
|
|
|
|
<div class="user-stats">
|
|
<div class="stat-item">
|
|
<span class="stat-number">{{ formatNumber(userStats.currentPoints) }}</span>
|
|
<span class="stat-label">积分</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-number">{{ formatNumber(userStats.orderCount) }}</span>
|
|
<span class="stat-label">订单</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-number">{{ formatNumber(userStats.pointsSpent) }}</span>
|
|
<span class="stat-label">兑换</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 个人资料表单 -->
|
|
<div class="profile-form">
|
|
<el-form
|
|
ref="profileFormRef"
|
|
:model="form"
|
|
:rules="rules"
|
|
label-width="80px"
|
|
>
|
|
<el-form-item label="用户名" prop="username">
|
|
<el-input v-model="form.username" placeholder="请输入用户名" />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="昵称" prop="nickname">
|
|
<el-input v-model="form.nickname" placeholder="请输入昵称" />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="邮箱" prop="email">
|
|
<el-input v-model="form.email" placeholder="请输入邮箱" />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="真实姓名" prop="realName">
|
|
<el-input v-model="form.realName" placeholder="请输入真实姓名" />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="身份证号" prop="idCard">
|
|
<el-input v-model="form.idCard" placeholder="请输入身份证号" maxlength="18" />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="手机号" prop="phone">
|
|
<el-input v-model="form.phone" placeholder="请输入手机号" maxlength="11" />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="微信收款码" prop="wechatQr">
|
|
<div class="qr-upload-container">
|
|
<el-upload
|
|
class="qr-uploader"
|
|
:action="uploadUrl"
|
|
:headers="uploadHeaders"
|
|
:before-upload="beforeQrUpload"
|
|
:on-success="(response) => handleQrUploadSuccess(response, 'wechat')"
|
|
:on-error="handleQrUploadError"
|
|
:show-file-list="false"
|
|
accept="image/*"
|
|
>
|
|
<img v-if="form.wechatQr" :src="formatImageUrl(form.wechatQr)" class="qr-preview" />
|
|
<div v-else class="qr-upload-placeholder">
|
|
<el-icon class="qr-upload-icon"><Plus /></el-icon>
|
|
<div class="qr-upload-text">上传微信收款码</div>
|
|
</div>
|
|
</el-upload>
|
|
<el-button v-if="form.wechatQr" type="danger" size="small" @click="removeQrCode('wechat')" class="remove-btn">
|
|
删除
|
|
</el-button>
|
|
</div>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="支付宝收款码" prop="alipayQr">
|
|
<div class="qr-upload-container">
|
|
<el-upload
|
|
class="qr-uploader"
|
|
:action="uploadUrl"
|
|
:headers="uploadHeaders"
|
|
:before-upload="beforeQrUpload"
|
|
:on-success="(response) => handleQrUploadSuccess(response, 'alipay')"
|
|
:on-error="handleQrUploadError"
|
|
:show-file-list="false"
|
|
accept="image/*"
|
|
>
|
|
<img v-if="form.alipayQr" :src="formatImageUrl(form.alipayQr)" class="qr-preview" />
|
|
<div v-else class="qr-upload-placeholder">
|
|
<el-icon class="qr-upload-icon"><Plus /></el-icon>
|
|
<div class="qr-upload-text">上传支付宝收款码</div>
|
|
</div>
|
|
</el-upload>
|
|
<el-button v-if="form.alipayQr" type="danger" size="small" @click="removeQrCode('alipay')" class="remove-btn">
|
|
删除
|
|
</el-button>
|
|
</div>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="银行卡号" prop="bankCard">
|
|
<el-input v-model="form.bankCard" placeholder="请输入银行卡号" />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="云闪付收款码" prop="unionpayQr">
|
|
<div class="qr-upload-container">
|
|
<el-upload
|
|
class="qr-uploader"
|
|
:action="uploadUrl"
|
|
:headers="uploadHeaders"
|
|
:before-upload="beforeQrUpload"
|
|
:on-success="(response) => handleQrUploadSuccess(response, 'unionpay')"
|
|
:on-error="handleQrUploadError"
|
|
:show-file-list="false"
|
|
accept="image/*"
|
|
>
|
|
<img v-if="form.unionpayQr" :src="formatImageUrl(form.unionpayQr)" class="qr-preview" />
|
|
<div v-else class="qr-upload-placeholder">
|
|
<el-icon class="qr-upload-icon"><Plus /></el-icon>
|
|
<div class="qr-upload-text">上传云闪付收款码</div>
|
|
</div>
|
|
</el-upload>
|
|
<el-button v-if="form.unionpayQr" type="danger" size="small" @click="removeQrCode('unionpay')" class="remove-btn">
|
|
删除
|
|
</el-button>
|
|
</div>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="营业执照" prop="businessLicense">
|
|
<div class="qr-upload-container">
|
|
<el-upload
|
|
class="qr-uploader"
|
|
:action="uploadUrl"
|
|
:headers="uploadHeaders"
|
|
:before-upload="beforeDocumentUpload"
|
|
:on-success="(response) => handleDocumentUploadSuccess(response, 'businessLicense')"
|
|
:on-error="handleQrUploadError"
|
|
:show-file-list="false"
|
|
accept="image/*"
|
|
>
|
|
<img v-if="form.businessLicense" :src="formatImageUrl(form.businessLicense)" class="qr-preview" />
|
|
<div v-else class="qr-upload-placeholder">
|
|
<el-icon class="qr-upload-icon"><Plus /></el-icon>
|
|
<div class="qr-upload-text">上传营业执照</div>
|
|
</div>
|
|
</el-upload>
|
|
<el-button v-if="form.businessLicense" type="danger" size="small" @click="removeDocument('businessLicense')" class="remove-btn">
|
|
删除
|
|
</el-button>
|
|
</div>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="身份证正面" prop="idCardFront">
|
|
<div class="qr-upload-container">
|
|
<el-upload
|
|
class="qr-uploader"
|
|
:action="uploadUrl"
|
|
:headers="uploadHeaders"
|
|
:before-upload="beforeDocumentUpload"
|
|
:on-success="(response) => handleDocumentUploadSuccess(response, 'idCardFront')"
|
|
:on-error="handleQrUploadError"
|
|
:show-file-list="false"
|
|
accept="image/*"
|
|
>
|
|
<img v-if="form.idCardFront" :src="formatImageUrl(form.idCardFront)" class="qr-preview" />
|
|
<div v-else class="qr-upload-placeholder">
|
|
<el-icon class="qr-upload-icon"><Plus /></el-icon>
|
|
<div class="qr-upload-text">上传身份证正面</div>
|
|
</div>
|
|
</el-upload>
|
|
<el-button v-if="form.idCardFront" type="danger" size="small" @click="removeDocument('idCardFront')" class="remove-btn">
|
|
删除
|
|
</el-button>
|
|
</div>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="身份证反面" prop="idCardBack">
|
|
<div class="qr-upload-container">
|
|
<el-upload
|
|
class="qr-uploader"
|
|
:action="uploadUrl"
|
|
:headers="uploadHeaders"
|
|
:before-upload="beforeDocumentUpload"
|
|
:on-success="(response) => handleDocumentUploadSuccess(response, 'idCardBack')"
|
|
:on-error="handleQrUploadError"
|
|
:show-file-list="false"
|
|
accept="image/*"
|
|
>
|
|
<img v-if="form.idCardBack" :src="formatImageUrl(form.idCardBack)" class="qr-preview" />
|
|
<div v-else class="qr-upload-placeholder">
|
|
<el-icon class="qr-upload-icon"><Plus /></el-icon>
|
|
<div class="qr-upload-text">上传身份证反面</div>
|
|
</div>
|
|
</el-upload>
|
|
<el-button v-if="form.idCardBack" type="danger" size="small" @click="removeDocument('idCardBack')" class="remove-btn">
|
|
删除
|
|
</el-button>
|
|
</div>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<div class="form-actions">
|
|
<el-button @click="resetForm">重置</el-button>
|
|
<el-button type="primary" @click="saveProfile" :loading="saving">
|
|
保存资料
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 修改密码 -->
|
|
<div class="password-section">
|
|
<h3>修改密码</h3>
|
|
<el-form
|
|
ref="passwordFormRef"
|
|
:model="passwordForm"
|
|
:rules="passwordRules"
|
|
label-width="100px"
|
|
>
|
|
<el-form-item label="当前密码" prop="currentPassword">
|
|
<el-input
|
|
v-model="passwordForm.currentPassword"
|
|
type="password"
|
|
placeholder="请输入当前密码"
|
|
show-password
|
|
/>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="新密码" prop="newPassword">
|
|
<el-input
|
|
v-model="passwordForm.newPassword"
|
|
type="password"
|
|
placeholder="请输入新密码"
|
|
show-password
|
|
/>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="确认密码" prop="confirmPassword">
|
|
<el-input
|
|
v-model="passwordForm.confirmPassword"
|
|
type="password"
|
|
placeholder="请确认新密码"
|
|
show-password
|
|
/>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<div class="form-actions">
|
|
<el-button @click="resetPasswordForm">重置</el-button>
|
|
<el-button type="primary" @click="changePassword" :loading="changingPassword">
|
|
修改密码
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 头像上传对话框 -->
|
|
<el-dialog
|
|
v-model="showAvatarUpload"
|
|
title="更换头像"
|
|
width="400px"
|
|
>
|
|
<el-upload
|
|
class="avatar-uploader"
|
|
action="#"
|
|
:show-file-list="false"
|
|
:before-upload="beforeAvatarUpload"
|
|
:http-request="uploadAvatar"
|
|
>
|
|
<img v-if="newAvatar" :src="newAvatar" class="avatar-preview" />
|
|
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
|
|
</el-upload>
|
|
<template #footer>
|
|
<span class="dialog-footer">
|
|
<el-button @click="showAvatarUpload = false">取消</el-button>
|
|
<el-button type="primary" @click="confirmAvatarUpload" :disabled="!newAvatar">
|
|
确定
|
|
</el-button>
|
|
</span>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive, onMounted, computed } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useUserStore } from '@/stores/user'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import {
|
|
ArrowLeft,
|
|
More,
|
|
User,
|
|
Plus
|
|
} from '@element-plus/icons-vue'
|
|
import api from '@/utils/api'
|
|
import { uploadURL, getImageUrl, getUploadConfig } from '@/config'
|
|
|
|
const router = useRouter()
|
|
const userStore = useUserStore()
|
|
|
|
// 响应式数据
|
|
const userInfo = ref({
|
|
id: '',
|
|
username: '',
|
|
nickname: '',
|
|
email: '',
|
|
phone: '',
|
|
realName: '',
|
|
idCard: '',
|
|
wechatQr: '',
|
|
alipayQr: '',
|
|
bankCard: '',
|
|
unionpayQr: '',
|
|
avatar: ''
|
|
})
|
|
|
|
const userStats = ref({
|
|
points: 0,
|
|
orders: 0,
|
|
exchanges: 0
|
|
})
|
|
|
|
const form = reactive({
|
|
username: '',
|
|
nickname: '',
|
|
email: '',
|
|
phone: '',
|
|
realName: '',
|
|
idCard: '',
|
|
wechatQr: '',
|
|
alipayQr: '',
|
|
bankCard: '',
|
|
unionpayQr: '',
|
|
businessLicense: '',
|
|
idCardFront: '',
|
|
idCardBack: ''
|
|
})
|
|
|
|
const passwordForm = reactive({
|
|
currentPassword: '',
|
|
newPassword: '',
|
|
confirmPassword: ''
|
|
})
|
|
|
|
const saving = ref(false)
|
|
const changingPassword = ref(false)
|
|
const showAvatarUpload = ref(false)
|
|
const newAvatar = ref('')
|
|
const profileFormRef = ref()
|
|
const passwordFormRef = ref()
|
|
|
|
// 上传配置
|
|
const uploadUrl = ref(uploadURL)
|
|
const uploadHeaders = computed(() => getUploadConfig().headers)
|
|
|
|
// 表单验证规则
|
|
const rules = {
|
|
username: [
|
|
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
|
{ min: 3, max: 20, message: '用户名长度在 3 到 20 个字符', trigger: 'blur' }
|
|
],
|
|
email: [
|
|
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
|
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
|
|
],
|
|
realName: [
|
|
{ required: true, message: '请输入真实姓名', trigger: 'blur' },
|
|
{ min: 2, max: 10, message: '真实姓名长度在 2 到 10 个字符', trigger: 'blur' }
|
|
],
|
|
idCard: [
|
|
{ required: true, message: '请输入身份证号', trigger: 'blur' },
|
|
{ pattern: /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/, message: '请输入正确的身份证号', trigger: 'blur' }
|
|
],
|
|
phone: [
|
|
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
|
]
|
|
}
|
|
|
|
const passwordRules = {
|
|
currentPassword: [
|
|
{ required: true, message: '请输入当前密码', trigger: 'blur' }
|
|
],
|
|
newPassword: [
|
|
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
|
{ min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }
|
|
],
|
|
confirmPassword: [
|
|
{ required: true, message: '请确认新密码', trigger: 'blur' },
|
|
{
|
|
validator: (rule, value, callback) => {
|
|
if (value !== passwordForm.newPassword) {
|
|
callback(new Error('两次输入密码不一致'))
|
|
} else {
|
|
callback()
|
|
}
|
|
},
|
|
trigger: 'blur'
|
|
}
|
|
]
|
|
}
|
|
|
|
// 方法
|
|
const handleCommand = (command) => {
|
|
switch (command) {
|
|
case 'orders':
|
|
router.push('/orders')
|
|
break
|
|
case 'points':
|
|
router.push('/points-history')
|
|
break
|
|
case 'logout':
|
|
handleLogout()
|
|
break
|
|
}
|
|
}
|
|
|
|
const handleLogout = async () => {
|
|
try {
|
|
await ElMessageBox.confirm('确定要退出登录吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
})
|
|
|
|
userStore.logout()
|
|
router.push('/login')
|
|
ElMessage.success('已退出登录')
|
|
} catch {
|
|
// 用户取消
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取用户信息
|
|
*/
|
|
const getUserInfo = async () => {
|
|
try {
|
|
const response = await api.get('/users/profile')
|
|
console.log(response.data,'response.data.data');
|
|
|
|
userInfo.value = response.data.user
|
|
|
|
// 同步到表单
|
|
Object.keys(form).forEach(key => {
|
|
form[key] = userInfo.value[key] || ''
|
|
})
|
|
} catch (error) {
|
|
console.error('获取用户信息失败:', error)
|
|
ElMessage.error('获取用户信息失败')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取用户统计信息
|
|
*/
|
|
const getUserStats = async () => {
|
|
try {
|
|
const response = await api.get('/users/stats')
|
|
userStats.value = response.data.stats
|
|
console.log(response.data.stats,'response.data');
|
|
|
|
} catch (error) {
|
|
console.error('获取用户统计失败:', error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 保存个人资料
|
|
*/
|
|
const saveProfile = async () => {
|
|
try {
|
|
await profileFormRef.value.validate()
|
|
|
|
// 记录更新前的审核状态
|
|
const previousAuditStatus = userInfo.value.auditStatus
|
|
|
|
saving.value = true
|
|
const response = await api.put('/users/profile', form)
|
|
|
|
// 更新本地数据
|
|
if (response.data.success) {
|
|
Object.assign(userInfo.value, response.data.data)
|
|
// 同步更新表单数据
|
|
Object.keys(form).forEach(key => {
|
|
form[key] = userInfo.value[key] || ''
|
|
})
|
|
|
|
// 检查是否需要重新审核
|
|
if (previousAuditStatus === 'approved' && userInfo.value.auditStatus === 'pending') {
|
|
ElMessage({
|
|
message: '您更新了关键信息,需要重新审核后才能参与匹配。审核通过前您可以正常使用其他功能。',
|
|
type: 'warning',
|
|
duration: 5000
|
|
})
|
|
} else {
|
|
ElMessage.success(response.data.message || '保存成功')
|
|
}
|
|
} else {
|
|
ElMessage.error(response.data.message || '保存失败')
|
|
}
|
|
} catch (error) {
|
|
console.error('保存个人资料失败:', error)
|
|
if (error.response) {
|
|
ElMessage.error(error.response.data.message || '保存失败')
|
|
} else {
|
|
ElMessage.error('保存失败')
|
|
}
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
|
|
const resetForm = () => {
|
|
Object.keys(form).forEach(key => {
|
|
form[key] = userInfo.value[key] || ''
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 修改密码
|
|
*/
|
|
const changePassword = async () => {
|
|
try {
|
|
await passwordFormRef.value.validate()
|
|
|
|
changingPassword.value = true
|
|
const response = await api.put('/auth/change-password', {
|
|
currentPassword: passwordForm.currentPassword,
|
|
newPassword: passwordForm.newPassword
|
|
})
|
|
|
|
if (response.data.success) {
|
|
ElMessage.success(response.data.message || '密码修改成功')
|
|
resetPasswordForm()
|
|
} else {
|
|
ElMessage.error(response.data.message || '密码修改失败')
|
|
}
|
|
} catch (error) {
|
|
console.error('密码修改失败:', error)
|
|
if (error.response) {
|
|
ElMessage.error(error.response.data.message || '密码修改失败')
|
|
} else {
|
|
ElMessage.error('密码修改失败')
|
|
}
|
|
} finally {
|
|
changingPassword.value = false
|
|
}
|
|
}
|
|
|
|
const resetPasswordForm = () => {
|
|
passwordForm.currentPassword = ''
|
|
passwordForm.newPassword = ''
|
|
passwordForm.confirmPassword = ''
|
|
passwordFormRef.value?.clearValidate()
|
|
}
|
|
|
|
const beforeAvatarUpload = (file) => {
|
|
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
|
|
const isLt2M = file.size / 1024 / 1024 < 2
|
|
|
|
if (!isJPG) {
|
|
ElMessage.error('头像只能是 JPG/PNG 格式!')
|
|
}
|
|
if (!isLt2M) {
|
|
ElMessage.error('头像大小不能超过 2MB!')
|
|
}
|
|
return isJPG && isLt2M
|
|
}
|
|
|
|
const uploadAvatar = async (options) => {
|
|
try {
|
|
const formData = new FormData()
|
|
formData.append('file', options.file)
|
|
|
|
const response = await api.post('/upload', formData, {
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data',
|
|
'Authorization': `Bearer ${userStore.token}`
|
|
}
|
|
})
|
|
|
|
if (response.data.success) {
|
|
newAvatar.value = response.data.url
|
|
ElMessage.success('头像上传成功')
|
|
} else {
|
|
ElMessage.error(response.data.message || '头像上传失败')
|
|
}
|
|
} catch (error) {
|
|
console.error('头像上传失败:', error)
|
|
ElMessage.error('头像上传失败')
|
|
}
|
|
}
|
|
|
|
const confirmAvatarUpload = async () => {
|
|
try {
|
|
if (!newAvatar.value) {
|
|
ElMessage.error('请先选择头像')
|
|
return
|
|
}
|
|
|
|
const response = await api.put('/users/profile', {
|
|
avatar: newAvatar.value
|
|
})
|
|
|
|
if (response.data.success) {
|
|
userInfo.value.avatar = newAvatar.value
|
|
form.avatar = newAvatar.value
|
|
showAvatarUpload.value = false
|
|
newAvatar.value = ''
|
|
ElMessage.success('头像更新成功')
|
|
} else {
|
|
ElMessage.error(response.data.message || '头像更新失败')
|
|
}
|
|
} catch (error) {
|
|
console.error('头像更新失败:', error)
|
|
if (error.response) {
|
|
ElMessage.error(error.response.data.message || '头像更新失败')
|
|
} else {
|
|
ElMessage.error('头像更新失败')
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 二维码上传前的验证
|
|
* @param {File} file - 上传的文件
|
|
* @returns {boolean} 是否通过验证
|
|
*/
|
|
const beforeQrUpload = (file) => {
|
|
const isImage = file.type.startsWith('image/')
|
|
const isLt5M = file.size / 1024 / 1024 < 5
|
|
|
|
if (!isImage) {
|
|
ElMessage.error('只能上传图片文件!')
|
|
return false
|
|
}
|
|
if (!isLt5M) {
|
|
ElMessage.error('图片大小不能超过 5MB!')
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* 处理二维码上传成功回调
|
|
* @param {Object} response - 上传接口返回的响应数据
|
|
* @param {string} type - 二维码类型 (wechat/alipay/unionpay)
|
|
*/
|
|
const handleQrUploadSuccess = (response, type) => {
|
|
if (response.success) {
|
|
// 更新对应的二维码字段
|
|
const fieldMap = {
|
|
wechat: 'wechatQr',
|
|
alipay: 'alipayQr',
|
|
unionpay: 'unionpayQr'
|
|
}
|
|
|
|
form[fieldMap[type]] = response.url
|
|
ElMessage.success('收款码上传成功')
|
|
} else {
|
|
ElMessage.error(response.message || '上传失败')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 处理二维码上传错误
|
|
* @param {Object} error - 错误信息
|
|
*/
|
|
const handleQrUploadError = (error) => {
|
|
console.error('上传错误:', error)
|
|
ElMessage.error('上传失败,请重试')
|
|
}
|
|
|
|
/**
|
|
* 删除二维码
|
|
* @param {string} type - 二维码类型 (wechat/alipay/unionpay)
|
|
*/
|
|
const removeQrCode = (type) => {
|
|
const fieldMap = {
|
|
wechat: 'wechatQr',
|
|
alipay: 'alipayQr',
|
|
unionpay: 'unionpayQr'
|
|
}
|
|
|
|
form[fieldMap[type]] = ''
|
|
ElMessage.success('收款码已删除')
|
|
}
|
|
|
|
/**
|
|
* 证件上传前的验证
|
|
* @param {File} file - 上传的文件
|
|
* @returns {boolean} 是否通过验证
|
|
*/
|
|
const beforeDocumentUpload = (file) => {
|
|
const isImage = file.type.startsWith('image/')
|
|
const isLt10M = file.size / 1024 / 1024 < 10
|
|
|
|
if (!isImage) {
|
|
ElMessage.error('只能上传图片文件!')
|
|
return false
|
|
}
|
|
if (!isLt10M) {
|
|
ElMessage.error('图片大小不能超过 10MB!')
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* 处理证件上传成功回调
|
|
* @param {Object} response - 上传接口返回的响应数据
|
|
* @param {string} type - 证件类型 (businessLicense/idCardFront/idCardBack)
|
|
*/
|
|
const handleDocumentUploadSuccess = (response, type) => {
|
|
if (response.success) {
|
|
form[type] = response.url
|
|
const typeNames = {
|
|
businessLicense: '营业执照',
|
|
idCardFront: '身份证正面',
|
|
idCardBack: '身份证反面'
|
|
}
|
|
ElMessage.success(`${typeNames[type]}上传成功`)
|
|
} else {
|
|
ElMessage.error(response.message || '上传失败')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除证件
|
|
* @param {string} type - 证件类型 (businessLicense/idCardFront/idCardBack)
|
|
*/
|
|
const removeDocument = (type) => {
|
|
form[type] = ''
|
|
const typeNames = {
|
|
businessLicense: '营业执照',
|
|
idCardFront: '身份证正面',
|
|
idCardBack: '身份证反面'
|
|
}
|
|
ElMessage.success(`${typeNames[type]}已删除`)
|
|
}
|
|
|
|
/**
|
|
* 获取图片URL - 使用配置文件处理
|
|
* @param {string} url - 相对路径或完整URL
|
|
* @returns {string} 处理后的图片URL
|
|
*/
|
|
const formatImageUrl = (url) => {
|
|
return getImageUrl(url)
|
|
}
|
|
|
|
/**
|
|
* 格式化数字显示,确保数字安全
|
|
* @param {number|string|null|undefined} value - 数字值
|
|
* @returns {string|number} 格式化后的数字
|
|
*/
|
|
const formatNumber = (value) => {
|
|
const num = parseInt(value)
|
|
return isNaN(num) ? 0 : num
|
|
}
|
|
|
|
/**
|
|
* 获取审核状态对应的标签类型
|
|
* @param {string} status - 审核状态
|
|
* @returns {string} Element Plus 标签类型
|
|
*/
|
|
const getAuditStatusType = (status) => {
|
|
switch (status) {
|
|
case 'approved':
|
|
return 'success'
|
|
case 'pending':
|
|
return 'warning'
|
|
case 'rejected':
|
|
return 'danger'
|
|
default:
|
|
return 'info'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取审核状态对应的文本
|
|
* @param {string} status - 审核状态
|
|
* @returns {string} 状态文本
|
|
*/
|
|
const getAuditStatusText = (status) => {
|
|
switch (status) {
|
|
case 'approved':
|
|
return '审核通过'
|
|
case 'pending':
|
|
return '待审核'
|
|
case 'rejected':
|
|
return '审核拒绝'
|
|
default:
|
|
return '未知状态'
|
|
}
|
|
}
|
|
|
|
// 生命周期
|
|
onMounted(() => {
|
|
getUserInfo()
|
|
getUserStats()
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.profile-page {
|
|
min-height: 100vh;
|
|
background-color: #f5f5f5;
|
|
}
|
|
|
|
.navbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 16px;
|
|
height: 56px;
|
|
background: white;
|
|
border-bottom: 1px solid #eee;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
}
|
|
|
|
.nav-left,
|
|
.nav-right {
|
|
flex: 1;
|
|
}
|
|
|
|
.nav-right {
|
|
text-align: right;
|
|
}
|
|
|
|
.back-btn {
|
|
color: #409eff;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.nav-title {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
font-weight: 500;
|
|
color: #333;
|
|
}
|
|
|
|
.el-dropdown-link {
|
|
cursor: pointer;
|
|
color: #409eff;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.profile-content {
|
|
padding: 20px 16px;
|
|
}
|
|
|
|
.profile-header {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
border-radius: 12px;
|
|
padding: 25px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
color: white;
|
|
}
|
|
|
|
.avatar-section {
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.user-avatar {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.upload-btn {
|
|
font-size: 12px;
|
|
}
|
|
|
|
.user-info {
|
|
text-align: center;
|
|
}
|
|
|
|
.username {
|
|
margin: 0 0 5px 0;
|
|
font-size: 22px;
|
|
color: white;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.user-email {
|
|
margin: 0 0 10px 0;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.audit-status {
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.audit-tag {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.audit-tip {
|
|
font-size: 12px;
|
|
color: rgba(255, 255, 255, 0.7);
|
|
background: rgba(255, 255, 255, 0.1);
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.user-stats {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 30px;
|
|
}
|
|
|
|
.stat-item {
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-number {
|
|
display: block;
|
|
font-size: 20px;
|
|
font-weight: bold;
|
|
color: white;
|
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 12px;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
}
|
|
|
|
.profile-form,
|
|
.password-section {
|
|
background: white;
|
|
border-radius: 12px;
|
|
padding: 25px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.password-section h3 {
|
|
margin: 0 0 20px 0;
|
|
font-size: 16px;
|
|
color: #333;
|
|
}
|
|
|
|
.form-actions {
|
|
text-align: center;
|
|
margin-top: 30px;
|
|
display: flex;
|
|
gap: 15px;
|
|
justify-content: center;
|
|
}
|
|
|
|
.form-actions .el-button {
|
|
min-width: 100px;
|
|
border-radius: 8px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.avatar-uploader {
|
|
text-align: center;
|
|
}
|
|
|
|
.avatar-uploader :deep(.el-upload) {
|
|
border: 1px dashed #d9d9d9;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: 0.2s;
|
|
width: 178px;
|
|
height: 178px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.avatar-uploader :deep(.el-upload:hover) {
|
|
border-color: #409eff;
|
|
}
|
|
|
|
.avatar-uploader-icon {
|
|
font-size: 28px;
|
|
color: #8c939d;
|
|
}
|
|
|
|
.avatar-preview {
|
|
width: 178px;
|
|
height: 178px;
|
|
object-fit: cover;
|
|
}
|
|
|
|
/* 二维码上传样式 */
|
|
.qr-upload-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
}
|
|
|
|
.qr-uploader {
|
|
width: 120px;
|
|
}
|
|
|
|
.qr-uploader :deep(.el-upload) {
|
|
border: 2px dashed #d9d9d9;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: 0.3s;
|
|
width: 120px;
|
|
height: 120px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background-color: #fafafa;
|
|
}
|
|
|
|
.qr-uploader :deep(.el-upload:hover) {
|
|
border-color: #409eff;
|
|
background-color: #f0f9ff;
|
|
}
|
|
|
|
.qr-upload-placeholder {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
height: 100%;
|
|
text-align: center;
|
|
}
|
|
|
|
.qr-upload-icon {
|
|
font-size: 24px;
|
|
color: #8c939d;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.qr-upload-text {
|
|
font-size: 12px;
|
|
color: #8c939d;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.qr-preview {
|
|
width: 120px;
|
|
height: 120px;
|
|
object-fit: cover;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.remove-btn {
|
|
align-self: flex-start;
|
|
}
|
|
|
|
/* 响应式设计 */
|
|
@media (max-width: 768px) {
|
|
.profile-content {
|
|
padding: 10px;
|
|
}
|
|
|
|
.profile-header,
|
|
.profile-form,
|
|
.password-section {
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.user-stats {
|
|
gap: 15px;
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.username {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.qr-uploader {
|
|
width: 100px;
|
|
}
|
|
|
|
.qr-uploader :deep(.el-upload) {
|
|
width: 100px;
|
|
height: 100px;
|
|
}
|
|
|
|
.qr-preview {
|
|
width: 100px;
|
|
height: 100px;
|
|
}
|
|
|
|
.form-actions {
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.form-actions .el-button {
|
|
width: 100%;
|
|
max-width: 200px;
|
|
}
|
|
|
|
.el-form {
|
|
:deep(.el-form-item__label) {
|
|
width: 80px !important;
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
}
|
|
</style> |