Files
jurong_circle_frontdesk/src/views/Profile.vue
2025-07-31 13:54:37 +08:00

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>