|
|
@ -4,7 +4,7 @@ |
|
|
<Menu |
|
|
<Menu |
|
|
@menu-click="handleSidebarClick" |
|
|
@menu-click="handleSidebarClick" |
|
|
/> |
|
|
/> |
|
|
|
|
|
|
|
|
<!-- 主内容区域 --> |
|
|
<!-- 主内容区域 --> |
|
|
<div class="main-content"> |
|
|
<div class="main-content"> |
|
|
<div class="profile-container"> |
|
|
<div class="profile-container"> |
|
|
@ -13,15 +13,15 @@ |
|
|
<h1 class="page-title">个人主页</h1> |
|
|
<h1 class="page-title">个人主页</h1> |
|
|
<p class="page-subtitle">管理您的个人信息和账户设置</p> |
|
|
<p class="page-subtitle">管理您的个人信息和账户设置</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<!-- 个人信息卡片 --> |
|
|
<!-- 个人信息卡片 --> |
|
|
<div class="profile-card"> |
|
|
<div class="profile-card"> |
|
|
<!-- 头像区域 --> |
|
|
<!-- 头像区域 --> |
|
|
<div class="avatar-section"> |
|
|
<div class="avatar-section"> |
|
|
<div class="avatar-container"> |
|
|
<div class="avatar-container"> |
|
|
<img |
|
|
<img |
|
|
:src="userProfile.avatar" |
|
|
:src="userProfile.avatar" |
|
|
alt="用户头像" |
|
|
alt="用户头像" |
|
|
class="avatar-image" |
|
|
class="avatar-image" |
|
|
@error="handleAvatarError" |
|
|
@error="handleAvatarError" |
|
|
> |
|
|
> |
|
|
@ -29,11 +29,11 @@ |
|
|
<span class="camera-icon">📷</span> |
|
|
<span class="camera-icon">📷</span> |
|
|
<span class="overlay-text">更换头像</span> |
|
|
<span class="overlay-text">更换头像</span> |
|
|
</div> |
|
|
</div> |
|
|
<input |
|
|
<input |
|
|
type="file" |
|
|
type="file" |
|
|
ref="fileInput" |
|
|
ref="fileInput" |
|
|
@change="handleAvatarChange" |
|
|
@change="handleAvatarChange" |
|
|
accept="image/*" |
|
|
accept="image/*" |
|
|
class="hidden-input" |
|
|
class="hidden-input" |
|
|
> |
|
|
> |
|
|
</div> |
|
|
</div> |
|
|
@ -44,40 +44,40 @@ |
|
|
|
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<!-- 用户信息表单 --> |
|
|
<!-- 用户信息表单 --> |
|
|
<div class="info-section"> |
|
|
<div class="info-section"> |
|
|
<h2 class="section-title">修改密码</h2> |
|
|
<h2 class="section-title">修改密码</h2> |
|
|
<form class="password-form" @submit.prevent="changePassword"> |
|
|
<form class="password-form" @submit.prevent="changePassword"> |
|
|
<div class="form-group"> |
|
|
<div class="form-group"> |
|
|
<label for="currentPassword" class="form-label">当前密码</label> |
|
|
<label for="currentPassword" class="form-label">当前密码</label> |
|
|
<input |
|
|
<input |
|
|
type="password" |
|
|
type="password" |
|
|
id="currentPassword" |
|
|
id="currentPassword" |
|
|
v-model="passwordForm.currentPassword" |
|
|
v-model="passwordForm.currentPassword" |
|
|
class="form-input" |
|
|
class="form-input" |
|
|
placeholder="请输入当前密码" |
|
|
placeholder="请输入当前密码" |
|
|
required |
|
|
required |
|
|
> |
|
|
> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="form-group"> |
|
|
<div class="form-group"> |
|
|
<label for="newPassword" class="form-label">新密码</label> |
|
|
<label for="newPassword" class="form-label">新密码</label> |
|
|
<input |
|
|
<input |
|
|
type="password" |
|
|
type="password" |
|
|
id="newPassword" |
|
|
id="newPassword" |
|
|
v-model="passwordForm.newPassword" |
|
|
v-model="passwordForm.newPassword" |
|
|
class="form-input" |
|
|
class="form-input" |
|
|
placeholder="请输入新密码" |
|
|
placeholder="请输入新密码" |
|
|
required |
|
|
required |
|
|
> |
|
|
> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="form-group"> |
|
|
<div class="form-group"> |
|
|
<label for="confirmPassword" class="form-label">确认新密码</label> |
|
|
<label for="confirmPassword" class="form-label">确认新密码</label> |
|
|
<input |
|
|
<input |
|
|
type="password" |
|
|
type="password" |
|
|
id="confirmPassword" |
|
|
id="confirmPassword" |
|
|
v-model="passwordForm.confirmPassword" |
|
|
v-model="passwordForm.confirmPassword" |
|
|
class="form-input" |
|
|
class="form-input" |
|
|
placeholder="请再次输入新密码" |
|
|
placeholder="请再次输入新密码" |
|
|
@ -85,7 +85,7 @@ |
|
|
> |
|
|
> |
|
|
<p v-if="passwordMismatch" class="error-text">两次输入的密码不一致</p> |
|
|
<p v-if="passwordMismatch" class="error-text">两次输入的密码不一致</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="form-actions"> |
|
|
<div class="form-actions"> |
|
|
<button type="submit" class="save-button" :disabled="passwordSaving || passwordMismatch"> |
|
|
<button type="submit" class="save-button" :disabled="passwordSaving || passwordMismatch"> |
|
|
<span v-if="!passwordSaving">修改密码</span> |
|
|
<span v-if="!passwordSaving">修改密码</span> |
|
|
@ -141,8 +141,8 @@ const errorMessage = ref(''); |
|
|
|
|
|
|
|
|
// 计算属性 |
|
|
// 计算属性 |
|
|
const passwordMismatch = computed(() => { |
|
|
const passwordMismatch = computed(() => { |
|
|
return passwordForm.value.newPassword && |
|
|
return passwordForm.value.newPassword && |
|
|
passwordForm.value.confirmPassword && |
|
|
passwordForm.value.confirmPassword && |
|
|
passwordForm.value.newPassword !== passwordForm.value.confirmPassword; |
|
|
passwordForm.value.newPassword !== passwordForm.value.confirmPassword; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
@ -168,45 +168,45 @@ const handleAvatarChange = async (event) => { |
|
|
errorMessage.value = '请选择图片文件'; |
|
|
errorMessage.value = '请选择图片文件'; |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 检查文件大小 (限制为2MB) |
|
|
// 检查文件大小 (限制为2MB) |
|
|
if (file.size > 2 * 1024 * 1024) { |
|
|
if (file.size > 2 * 1024 * 1024) { |
|
|
errorMessage.value = '图片大小不能超过2MB'; |
|
|
errorMessage.value = '图片大小不能超过2MB'; |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 开始上传 |
|
|
// 开始上传 |
|
|
avatarUploading.value = true; |
|
|
avatarUploading.value = true; |
|
|
avatarUploadSuccess.value = false; |
|
|
avatarUploadSuccess.value = false; |
|
|
errorMessage.value = ''; |
|
|
errorMessage.value = ''; |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// 从localStorage获取token |
|
|
// 从localStorage获取token |
|
|
const token = localStorage.getItem('token'); |
|
|
const token = localStorage.getItem('token'); |
|
|
|
|
|
|
|
|
if (!token) { |
|
|
if (!token) { |
|
|
errorMessage.value = '用户未登录,请先登录'; |
|
|
errorMessage.value = '用户未登录,请先登录'; |
|
|
avatarUploading.value = false; |
|
|
avatarUploading.value = false; |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 创建FormData对象 |
|
|
// 创建FormData对象 |
|
|
const formData = new FormData(); |
|
|
const formData = new FormData(); |
|
|
formData.append('avatar', file); |
|
|
formData.append('avatar', file); |
|
|
formData.append('token', token); |
|
|
formData.append('token', token); |
|
|
|
|
|
|
|
|
console.log('准备上传头像文件:', file.name); |
|
|
console.log('准备上传头像文件:', file.name); |
|
|
console.log('Token:', token); |
|
|
console.log('Token:', token); |
|
|
|
|
|
|
|
|
// 调用API上传头像 |
|
|
// 调用API上传头像 |
|
|
const response = await updateAvatar(formData,{ |
|
|
const response = await updateAvatar(formData,{ |
|
|
headers: { |
|
|
headers: { |
|
|
'Content-Type': 'multipart/form-data' |
|
|
'Content-Type': 'multipart/form-data' |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
console.log('收到响应:', response); |
|
|
console.log('收到响应:', response); |
|
|
|
|
|
|
|
|
if (response.success) { |
|
|
if (response.success) { |
|
|
// 使用返回的头像路径 |
|
|
// 使用返回的头像路径 |
|
|
// 确保头像URL是完整的路径 |
|
|
// 确保头像URL是完整的路径 |
|
|
@ -216,11 +216,11 @@ const handleAvatarChange = async (event) => { |
|
|
avatarPath = avatarPath; |
|
|
avatarPath = avatarPath; |
|
|
} |
|
|
} |
|
|
userProfile.value.avatar = avatarPath; |
|
|
userProfile.value.avatar = avatarPath; |
|
|
|
|
|
|
|
|
// 显示成功消息 |
|
|
// 显示成功消息 |
|
|
avatarUploadSuccess.value = true; |
|
|
avatarUploadSuccess.value = true; |
|
|
successMessage.value = '头像更新成功'; |
|
|
successMessage.value = '头像更新成功'; |
|
|
|
|
|
|
|
|
// 3秒后隐藏成功提示 |
|
|
// 3秒后隐藏成功提示 |
|
|
setTimeout(() => { |
|
|
setTimeout(() => { |
|
|
avatarUploadSuccess.value = false; |
|
|
avatarUploadSuccess.value = false; |
|
|
@ -236,7 +236,7 @@ const handleAvatarChange = async (event) => { |
|
|
stack: error.stack, |
|
|
stack: error.stack, |
|
|
name: error.name |
|
|
name: error.name |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// 提供更详细的错误信息 |
|
|
// 提供更详细的错误信息 |
|
|
if (error.response) { |
|
|
if (error.response) { |
|
|
// 服务器响应了错误状态码 |
|
|
// 服务器响应了错误状态码 |
|
|
@ -261,12 +261,12 @@ const updateProfile = () => { |
|
|
profileSaving.value = true; |
|
|
profileSaving.value = true; |
|
|
errorMessage.value = ''; |
|
|
errorMessage.value = ''; |
|
|
successMessage.value = ''; |
|
|
successMessage.value = ''; |
|
|
|
|
|
|
|
|
// 模拟API请求 |
|
|
// 模拟API请求 |
|
|
setTimeout(() => { |
|
|
setTimeout(() => { |
|
|
profileSaving.value = false; |
|
|
profileSaving.value = false; |
|
|
successMessage.value = '个人信息更新成功'; |
|
|
successMessage.value = '个人信息更新成功'; |
|
|
|
|
|
|
|
|
// 3秒后隐藏成功提示 |
|
|
// 3秒后隐藏成功提示 |
|
|
setTimeout(() => { |
|
|
setTimeout(() => { |
|
|
successMessage.value = ''; |
|
|
successMessage.value = ''; |
|
|
@ -279,38 +279,38 @@ const changePassword = async () => { |
|
|
if (passwordMismatch.value) { |
|
|
if (passwordMismatch.value) { |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
passwordSaving.value = true; |
|
|
passwordSaving.value = true; |
|
|
errorMessage.value = ''; |
|
|
errorMessage.value = ''; |
|
|
successMessage.value = ''; |
|
|
successMessage.value = ''; |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// 从localStorage获取token |
|
|
// 从localStorage获取token |
|
|
const token = localStorage.getItem('token'); |
|
|
const token = localStorage.getItem('token'); |
|
|
|
|
|
|
|
|
if (!token) { |
|
|
if (!token) { |
|
|
errorMessage.value = '用户未登录,请先登录'; |
|
|
errorMessage.value = '用户未登录,请先登录'; |
|
|
passwordSaving.value = false; |
|
|
passwordSaving.value = false; |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 调用API修改密码 |
|
|
// 调用API修改密码 |
|
|
const response = await updatePassword({ |
|
|
const response = await updatePassword({ |
|
|
token, |
|
|
token, |
|
|
currentPassword: passwordForm.value.currentPassword, |
|
|
currentPassword: passwordForm.value.currentPassword, |
|
|
newPassword: passwordForm.value.newPassword |
|
|
newPassword: passwordForm.value.newPassword |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
if (response.success) { |
|
|
if (response.success) { |
|
|
successMessage.value = '密码修改成功'; |
|
|
successMessage.value = '密码修改成功'; |
|
|
|
|
|
|
|
|
// 清空表单 |
|
|
// 清空表单 |
|
|
passwordForm.value = { |
|
|
passwordForm.value = { |
|
|
currentPassword: '', |
|
|
currentPassword: '', |
|
|
newPassword: '', |
|
|
newPassword: '', |
|
|
confirmPassword: '' |
|
|
confirmPassword: '' |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// 3秒后隐藏成功提示 |
|
|
// 3秒后隐藏成功提示 |
|
|
setTimeout(() => { |
|
|
setTimeout(() => { |
|
|
successMessage.value = ''; |
|
|
successMessage.value = ''; |
|
|
@ -331,15 +331,15 @@ onMounted(async () => { |
|
|
try { |
|
|
try { |
|
|
// 从localStorage获取token |
|
|
// 从localStorage获取token |
|
|
const token = localStorage.getItem('token'); |
|
|
const token = localStorage.getItem('token'); |
|
|
|
|
|
|
|
|
console.log('Profile组件挂载,获取到的token:', token); |
|
|
console.log('Profile组件挂载,获取到的token:', token); |
|
|
|
|
|
|
|
|
if (token) { |
|
|
if (token) { |
|
|
// 调用API获取用户信息 |
|
|
// 调用API获取用户信息 |
|
|
const response = await getUserProfile(token); |
|
|
const response = await getUserProfile(token); |
|
|
|
|
|
|
|
|
console.log('获取用户信息响应:', response); |
|
|
console.log('获取用户信息响应:', response); |
|
|
|
|
|
|
|
|
if (response.success) { |
|
|
if (response.success) { |
|
|
// 更新用户信息 |
|
|
// 更新用户信息 |
|
|
// 如果头像路径是相对路径,需要添加服务器前缀 |
|
|
// 如果头像路径是相对路径,需要添加服务器前缀 |
|
|
@ -347,9 +347,9 @@ onMounted(async () => { |
|
|
if (avatarUrl.startsWith('/resource/')) { |
|
|
if (avatarUrl.startsWith('/resource/')) { |
|
|
avatarUrl = avatarUrl; // 相对路径,直接使用 |
|
|
avatarUrl = avatarUrl; // 相对路径,直接使用 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
console.log('设置头像URL:', avatarUrl); |
|
|
console.log('设置头像URL:', avatarUrl); |
|
|
|
|
|
|
|
|
userProfile.value = { |
|
|
userProfile.value = { |
|
|
username: response.user.username, |
|
|
username: response.user.username, |
|
|
avatar: avatarUrl |
|
|
avatar: avatarUrl |
|
|
@ -357,7 +357,7 @@ onMounted(async () => { |
|
|
} else { |
|
|
} else { |
|
|
console.error('获取用户信息失败:', response.message); |
|
|
console.error('获取用户信息失败:', response.message); |
|
|
errorMessage.value = response.message || '获取用户信息失败'; |
|
|
errorMessage.value = response.message || '获取用户信息失败'; |
|
|
|
|
|
|
|
|
// 如果是token过期或无效,清除token并跳转到登录页 |
|
|
// 如果是token过期或无效,清除token并跳转到登录页 |
|
|
if (response.message && response.message.includes('登录')) { |
|
|
if (response.message && response.message.includes('登录')) { |
|
|
localStorage.removeItem('token'); |
|
|
localStorage.removeItem('token'); |
|
|
@ -376,40 +376,23 @@ onMounted(async () => { |
|
|
</script> |
|
|
</script> |
|
|
|
|
|
|
|
|
<style scoped> |
|
|
<style scoped> |
|
|
.app-container { |
|
|
|
|
|
|
|
|
.app-container{ |
|
|
display: flex; |
|
|
display: flex; |
|
|
height: 100vh; |
|
|
height: 100vh; |
|
|
overflow: hidden; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.main-content { |
|
|
.main-content { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
height: 100vh; |
|
|
|
|
|
background-color: #F5F8FF; |
|
|
flex: 1; |
|
|
flex: 1; |
|
|
|
|
|
overflow: auto;; |
|
|
padding: 1.5rem; |
|
|
padding: 1.5rem; |
|
|
overflow-y: auto; |
|
|
|
|
|
height: 100%; |
|
|
|
|
|
background-color: #f5f7fa; |
|
|
|
|
|
transition: margin-left 0.3s ease; |
|
|
transition: margin-left 0.3s ease; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/* 确保左侧导航栏完全固定 */ |
|
|
|
|
|
.app-container > :first-child { |
|
|
|
|
|
position: fixed; |
|
|
|
|
|
height: 100vh; |
|
|
|
|
|
z-index: 10; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* 为右侧内容添加左边距,避免被固定导航栏遮挡 */ |
|
|
|
|
|
.main-content { |
|
|
|
|
|
margin-left: 240px; /* 与Menu.vue中的sidebar-container宽度相同 */ |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* 当菜单折叠时调整内容区域 */ |
|
|
|
|
|
.app-container:has(.sidebar-container.collapsed) .main-content { |
|
|
|
|
|
margin-left: 60px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.profile-container { |
|
|
.profile-container { |
|
|
max-width: 700px; |
|
|
width: 97%; |
|
|
margin: 0 auto; |
|
|
margin: 0 auto; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -454,7 +437,6 @@ onMounted(async () => { |
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
|
|
padding: 1.5rem; |
|
|
padding: 1.5rem; |
|
|
margin-bottom: 1.5rem; |
|
|
margin-bottom: 1.5rem; |
|
|
max-width: 600px; |
|
|
|
|
|
margin-left: auto; |
|
|
margin-left: auto; |
|
|
margin-right: auto; |
|
|
margin-right: auto; |
|
|
} |
|
|
} |
|
|
|