修改个人信息界面和用户信息界面

newzwz
335942189@qq.com 3 days ago
parent 1ae1607686
commit 6bafbbeba6

@ -8,11 +8,9 @@
"name": "vue-frontend", "name": "vue-frontend",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@vue/devtools-api": "^7.7.6",
"axios": "^1.8.4", "axios": "^1.8.4",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"element-plus": "^2.9.8", "element-plus": "^2.9.8",
"perfect-debounce": "^1.0.0",
"pinia": "^3.0.2", "pinia": "^3.0.2",
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-router": "^4.5.0" "vue-router": "^4.5.0"

@ -47,6 +47,7 @@
<p class="user-name">{{ userInfo.userName }}</p> <p class="user-name">{{ userInfo.userName }}</p>
<div class="button-container"> <div class="button-container">
<button @click="goToProfile" class="dropdown-btn">个人中心</button> <button @click="goToProfile" class="dropdown-btn">个人中心</button>
<router-link to="/ChangeInformation" class="nav-btn">修改个人信息</router-link>
<button @click="logout" class="dropdown-btn">退出登录</button> <button @click="logout" class="dropdown-btn">退出登录</button>
</div> </div>
</div> </div>

@ -5,7 +5,7 @@ import PostDetail from '@/views/PostDetail.vue';
import MainPage from '@/views/MainPage.vue'; import MainPage from '@/views/MainPage.vue';
import UserPage from '@/views/UserPage.vue'; import UserPage from '@/views/UserPage.vue';
import NotificationList from '@/views/NotificationList.vue'; import NotificationList from '@/views/NotificationList.vue';
import FeedBack from '@/views/FeedBack.vue'; import ChangeInformation from '@/views/ChangeInformation.vue';
const routes = [ const routes = [
{ {
@ -29,7 +29,7 @@ const routes = [
component: PostDetail component: PostDetail
}, },
{ {
path: '/user/:userId', path: '/user',
name: 'UserPage', name: 'UserPage',
component: UserPage component: UserPage
}, },
@ -37,17 +37,18 @@ const routes = [
path: '/notificationlist', path: '/notificationlist',
name: 'NotificationList', name: 'NotificationList',
component: NotificationList component: NotificationList
},
{//反馈站页面
path: '/feedback',
name: 'FeedBack',
component: FeedBack
}, },
{//详细通知页面 {//详细通知页面
path: '/notification/:id', path: '/notification/:id',
name: 'NotificationDetail', name: 'NotificationDetail',
component: () => import('@/views/NotificationDetail.vue'), component: () => import('@/views/NotificationDetail.vue'),
props: true props: true
},
{
//修改个人信息界面
path:'/changeinformation',
name:'ChangeInformation',
component:ChangeInformation
} }
]; ];

@ -0,0 +1,326 @@
<template>
<div class="change-info-container">
<h2 class="title">修改个人信息</h2>
<!-- 头像区域 -->
<div class="avatar-section">
<p class="section-title">头像</p>
<div class="avatar-preview">
<!-- 当前头像从接口获取初始值 -->
<img
:src="currentAvatar"
alt="当前头像"
class="current-avatar"
>
<!-- 新头像预览选择文件后显示 -->
<img
v-if="previewAvatar"
:src="previewAvatar"
alt="新头像预览"
class="new-avatar"
>
</div>
<!-- 文件选择按钮 -->
<label class="upload-btn">
选择新头像
<input
type="file"
accept="image/*"
@change="handleAvatarChange"
class="file-input"
>
</label>
<!-- 头像错误提示 -->
<p v-if="errors.avatar" class="error-tip">{{ errors.avatar }}</p>
</div>
<!-- 表单 -->
<form @submit.prevent="handleSubmit" class="form">
<!-- 昵称 -->
<div class="form-item">
<label class="label">昵称</label>
<input
type="text"
v-model.trim="formData.nickname"
class="input"
:class="{ 'input-error': errors.nickname }"
>
<p v-if="errors.nickname" class="error-tip">{{ errors.nickname }}</p>
</div>
<!-- 个人简介 -->
<div class="form-item">
<label class="label">个人简介</label>
<textarea
v-model.trim="formData.bio"
rows="4"
class="textarea"
:class="{ 'input-error': errors.bio }"
></textarea>
<p v-if="errors.bio" class="error-tip">{{ errors.bio }}</p>
</div>
<!-- 提交按钮 -->
<button type="submit" class="submit-btn">保存修改</button>
</form>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user.js'; // Pinia
import request from '@/utils/request'; //
import { ElMessage } from 'element-plus'; // 使Element Plus
const router = useRouter();
const userStore = useUserStore();
// ID
const userId = ref(userStore.userInfo.userid);
const isLoggedIn = userStore.isLoggedIn;
//
const formData = ref({
nickname: '',
bio: ''
});
//
const avatarFile = ref(null); //
const previewAvatar = ref(''); // URL
const currentAvatar = ref(''); //
const errors = ref({}); //
//
onMounted(async () => {
if (!isLoggedIn) {
ElMessage.error('请先登录');
router.push({ name: 'Login' });
return;
}
try {
// 使request
const res = await request.get(`/user/info/${userId.value}`);
if (res.code === 200) {
formData.value.nickname = res.data.nickname;
formData.value.bio = res.data.bio;
currentAvatar.value = res.data.avatar;
} else {
throw new Error(res.msg || '接口返回异常');
}
} catch (error) {
ElMessage.error(`获取用户信息失败:${error.message}`);
}
});
//
const handleAvatarChange = (e) => {
const file = e.target.files[0];
if (!file) return;
//
if (!file.type.startsWith('image/')) {
errors.value.avatar = '请选择图片文件jpg/png等';
return;
}
if (file.size > 5 * 1024 * 1024) { // 5MB
errors.value.avatar = '图片大小不能超过5MB';
return;
}
//
errors.value.avatar = null;
// URL
const reader = new FileReader();
reader.onload = (e) => previewAvatar.value = e.target.result;
reader.readAsDataURL(file);
//
avatarFile.value = file;
};
//
const validateForm = () => {
const newErrors = {};
// 20
if (!formData.value.nickname) {
newErrors.nickname = '昵称不能为空';
} else if (formData.value.nickname.length > 20) {
newErrors.nickname = '昵称最多20字符';
}
// 100
if (formData.value.bio.length > 100) {
newErrors.bio = '简介最多100字符';
}
errors.value = newErrors;
return Object.keys(newErrors).length === 0;
};
// /user/info
const handleSubmit = async () => {
if (!validateForm()) return;
try {
// FormData
const formDataToSubmit = new FormData();
//
if (avatarFile.value) {
formDataToSubmit.append('avatar', avatarFile.value);
}
// UserUpdateDTO
formDataToSubmit.append('nickname', formData.value.nickname);
formDataToSubmit.append('bio', formData.value.bio);
// PUT /user/info 使request
const res = await request.put(`/user/info/${userId.value}`, formDataToSubmit, {
headers: { 'Content-Type': 'multipart/form-data' } //
});
// code=200
if (res.code === 200) {
// URL
if (avatarFile.value) {
currentAvatar.value = res.data.avatar; // URL
}
ElMessage.success('修改成功!');
} else {
throw new Error(res.msg || '接口返回异常');
}
} catch (error) {
ElMessage.error(`修改失败:${error.message}`);
}
};
</script>
<style scoped>
/* 样式与原代码基本一致,仅调整无关字段的布局 */
.change-info-container {
max-width: 600px;
margin: 2rem auto;
padding: 2rem;
background: #f8f9fa;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.title {
color: #2c3e50;
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 2rem;
}
.section-title {
color: #34495e;
font-size: 1.1rem;
font-weight: 500;
margin-bottom: 1rem;
}
.avatar-section {
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #e0e0e0;
}
.avatar-preview {
display: flex;
align-items: center;
gap: 1.5rem;
margin-bottom: 1rem;
}
.current-avatar, .new-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
border: 2px solid #bdc3c7;
}
.new-avatar {
border-color: #3498db; /* 新头像边框用蓝色区分 */
}
.upload-btn {
display: inline-block;
padding: 0.5rem 1rem;
background: #3498db;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
transition: background 0.3s ease;
}
.upload-btn:hover {
background: #2980b9;
}
.file-input {
display: none; /* 隐藏原生文件输入框 */
}
.form-item {
margin-bottom: 1.5rem;
}
.label {
display: block;
color: #34495e;
font-size: 0.9rem;
font-weight: 500;
margin-bottom: 0.5rem;
}
.input, .textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #bdc3c7;
border-radius: 4px;
font-size: 0.9rem;
transition: border-color 0.3s ease;
}
.input:focus, .textarea:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.1);
}
.input-error {
border-color: #e74c3c;
}
.error-tip {
color: #e74c3c;
font-size: 0.8rem;
margin-top: 0.3rem;
}
.textarea {
resize: vertical;
min-height: 80px;
}
.submit-btn {
width: 100%;
padding: 0.75rem;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
transition: background 0.3s ease;
}
.submit-btn:hover {
background: #2980b9;
}
</style>

@ -1,23 +1,25 @@
<template> <template>
<div class="user-page-container"> <div class="user-page-container">
<!-- 左侧内容 -->
<div class="left-container"> <div class="left-container">
<!-- 用户信息卡片 -->
<div class="user-info-card"> <div class="user-info-card">
<div class="user-avatar"> <div class="user-avatar">
<img src="@/assets/default-avatar/boy_4.png" alt="用户头像" /> <img :src="userInfo.avatar" alt="用户头像" />
</div> </div>
<div class="user-details"> <div class="user-details">
<h2 class="username">cp3</h2> <h2 class="username">{{ userInfo.userName }}</h2>
<p class="user-status">ONLINE</p> <p class="user-status">ONLINE</p>
<p class="user-motto">不经巨大的困难不会有伟大的事业</p> <p class="user-motto">{{ userInfo.motto || '暂无个性签名' }}</p>
</div> </div>
</div> </div>
<!-- 帖子列表 -->
<div class="user-posts"> <div class="user-posts">
<h3>你的帖子</h3> <h3>你的帖子</h3>
<div class="post-item" v-for="(post, index) in posts" :key="index" @click="goToPostDetail(post.id)"> <div
class="post-item"
v-for="post in userPosts"
:key="post.id"
@click="goToPostDetail(post.id)"
>
<h4 class="post-title">{{ post.title }}</h4> <h4 class="post-title">{{ post.title }}</h4>
<p class="post-summary">{{ post.summary }}</p> <p class="post-summary">{{ post.summary }}</p>
<div class="post-stats"> <div class="post-stats">
@ -29,27 +31,25 @@
</div> </div>
</div> </div>
<!-- 右侧内容 -->
<div class="right-container"> <div class="right-container">
<!-- 个人统计信息卡片 -->
<div class="user-stats-card"> <div class="user-stats-card">
<h3>cp3 个人信息</h3> <h3>{{ userInfo.userName }} 个人信息</h3>
<div class="stats"> <div class="stats">
<div class="stat-item"> <div class="stat-item">
<span>发帖数</span> <span>发帖数</span>
<span>5</span> <span>{{ userStats.postCount }}</span>
</div> </div>
<div class="stat-item"> <div class="stat-item">
<span>粉丝</span> <span>粉丝</span>
<span>123</span> <span>{{ userStats.followers }}</span>
</div> </div>
<div class="stat-item"> <div class="stat-item">
<span>你关注的人数</span> <span>你关注的人数</span>
<span>456K</span> <span>{{ userStats.following }}</span>
</div> </div>
<div class="stat-item"> <div class="stat-item">
<span>获赞数</span> <span>获赞数</span>
<span>789</span> <span>{{ userStats.likes }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -57,49 +57,58 @@
</div> </div>
</template> </template>
<script setup lang="js" name="UserPage"> <script setup>
import { ref } from 'vue'; import { ref, computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user.js';
const posts = ref([ const router = useRouter();
const userStore = useUserStore();
const userInfo = computed(() => userStore.userInfo);
const userPosts = ref([
{ {
id: 1, id: 1,
title: '雷霆连续两场或20分钟逆转什么水平', title: "雷霆连续两场20分钟逆转什么水平",
summary: '今天保罗带领雷霆完成逆转', summary: "今天保罗带领雷霆完成逆转末节狂胜15分",
likes: 1200, likes: 1200,
comments: 10, comments: 56,
favorites: 100, favorites: 230
}, },
{ {
id: 2, id: 2,
title: '一个普通人需要多大努力才能考上985', title: "一个普通人需要多大努力才能考上985",
summary: 'mmmmmmmm', summary: "分享我的备考经历每天学习12小时坚持300天...",
likes: 2200, likes: 2200,
comments: 20, comments: 108,
favorites: 200, favorites: 450
}, }
]); ]);
const router = useRouter(); const userStats = ref({
postCount: userPosts.value.length,
followers: 0,
following: 0,
likes: 0
});
const goToPostDetail = (postId) => { const goToPostDetail = (postId) => {
router.push({ name: 'PostDetail', params: { id: postId } }); router.push({ name: 'PostDetail', params: { id: postId } });
}; };
</script> </script>
<style scoped> <style scoped>
/* 页面整体布局 */
.user-page-container { .user-page-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
gap: 20px; gap: 20px;
padding: 20px; padding: 20px;
background-color: #f5f5f5; background-color: #f5f5f5;
max-width: 1200px; /* 设置最大宽度 */ max-width: 1200px;
margin: 0 auto; /* 居中并在两边留白 */ margin: 0 auto;
box-sizing: border-box; box-sizing: border-box;
} }
/* 左侧内容 */
.left-container { .left-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -107,12 +116,10 @@ const goToPostDetail = (postId) => {
flex: 3; flex: 3;
} }
/* 右侧内容 */
.right-container { .right-container {
flex: 1; flex: 1;
} }
/* 用户信息卡片样式 */
.user-info-card { .user-info-card {
display: flex; display: flex;
align-items: center; align-items: center;
@ -140,8 +147,9 @@ const goToPostDetail = (postId) => {
} }
.user-status { .user-status {
color: green; color: #54ac52;
font-weight: bold; font-weight: bold;
margin: 8px 0;
} }
.user-motto { .user-motto {
@ -149,7 +157,6 @@ const goToPostDetail = (postId) => {
font-size: 14px; font-size: 14px;
} }
/* 个人统计信息卡片样式 */
.user-stats-card { .user-stats-card {
padding: 20px; padding: 20px;
background-color: white; background-color: white;
@ -168,9 +175,9 @@ const goToPostDetail = (postId) => {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
font-size: 14px; font-size: 14px;
color: #333;
} }
/* 帖子列表样式 */
.user-posts { .user-posts {
padding: 20px; padding: 20px;
background-color: white; background-color: white;
@ -182,34 +189,31 @@ const goToPostDetail = (postId) => {
padding: 10px 0; padding: 10px 0;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
cursor: pointer; cursor: pointer;
transition: box-shadow 0.3s; transition: transform 0.3s, box-shadow 0.3s;
} }
.post-item:hover { .post-item:hover {
transform: translateY(-2px); /* 轻微上移不超过间距的1/4 */ transform: translateY(-2px);
box-shadow: box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1);
0 2px 6px rgba(0, 0, 0, 0.05), /* 主阴影(更柔和) */
0 1px 2px rgba(0, 0, 0, 0.1); /* 内阴影增加层次感 */
border-color: #e0e0e0;
}
.post-item:last-child {
border-bottom: none;
} }
.post-title { .post-title {
font-size: 18px; font-size: 18px;
margin: 0; font-weight: bold;
margin: 0 0 8px 0;
color: #2c3e50;
} }
.post-summary { .post-summary {
color: #666;
font-size: 14px; font-size: 14px;
color: #666;
margin-bottom: 8px;
} }
.post-stats { .post-stats {
display: flex; display: flex;
gap: 10px; gap: 15px;
font-size: 12px; font-size: 12px;
color: #999; color: #999;
margin-left: 20px;
} }
</style> </style>
Loading…
Cancel
Save