fix-img

Change-Id: Ida77fc6aed06b28e41e2abcb6ae09d5f63d016f2
diff --git a/src/pages/Forum/posts-create/CreatePost.jsx b/src/pages/Forum/posts-create/CreatePost.jsx
index 07f3a0c..b8add81 100644
--- a/src/pages/Forum/posts-create/CreatePost.jsx
+++ b/src/pages/Forum/posts-create/CreatePost.jsx
@@ -1,81 +1,4 @@
-// import React, { useState } from 'react';
-// import axios from 'axios';
-// import './CreatePost.css'; // 如果你打算加样式
 
-// 
-
-// const CreatePost = ({ user_id }) => {
-//   const [title, setTitle] = useState('');
-//   const [content, setContent] = useState('');
-//   const [imageUrl, setImageUrl] = useState('');
-//   const [message, setMessage] = useState('');
-//   const [error, setError] = useState('');
-
-//   const handleSubmit = async (e) => {
-//     e.preventDefault();
-//     setMessage('');
-//     setError('');
-
-//     if (!title.trim() || !content.trim()) {
-//       setError('标题和内容不能为空');
-//       return;
-//     }
-
-//     try {
-//       const res = await axios.post(`/echo/forum/posts/${user_id}/createPost`, {
-//         title,
-//         post_content: content,
-//         image_url: imageUrl
-//       });
-
-//       setMessage(`发帖成功,帖子ID:${res.data.post_id}`);
-//       setTitle('');
-//       setContent('');
-//       setImageUrl('');
-//     } catch (err) {
-//       console.error(err);
-//       setError(err.response?.data?.error || '发帖失败,请稍后重试');
-//     }
-//   };
-
-//   return (
-//     <div className="create-post-container">
-//       <h2>发表新帖子</h2>
-//       <form onSubmit={handleSubmit} className="create-post-form">
-//         <div className="form-group">
-//           <label>标题:</label>
-//           <input
-//             type="text"
-//             value={title}
-//             onChange={(e) => setTitle(e.target.value)}
-//             placeholder="输入帖子标题"
-//           />
-//         </div>
-//         <div className="form-group">
-//           <label>内容:</label>
-//           <textarea
-//             value={content}
-//             onChange={(e) => setContent(e.target.value)}
-//             placeholder="输入帖子内容"
-//           />
-//         </div>
-//         <div className="form-group">
-//           <label>图片链接(可选):</label>
-//           <input
-//             type="text"
-//             value={imageUrl}
-//             onChange={(e) => setImageUrl(e.target.value)}
-//             placeholder="例如:https://example.com/img.jpg"
-//           />
-//         </div>
-//         <button type="submit">发布</button>
-//       </form>
-
-//       {message && <p className="success-text">{message}</p>}
-//       {error && <p className="error-text">{error}</p>}
-//     </div>
-//   );
-// };
 
 // export default CreatePost;
 
diff --git a/src/pages/Forum/posts-detail/PostDetailPage.jsx b/src/pages/Forum/posts-detail/PostDetailPage.jsx
index 159bdfa..f703fd6 100644
--- a/src/pages/Forum/posts-detail/PostDetailPage.jsx
+++ b/src/pages/Forum/posts-detail/PostDetailPage.jsx
@@ -1,200 +1,171 @@
-import React, { useContext, useEffect, useState } from 'react';
+import React, { useContext, useEffect, useState } from 'react'; 
 import { useParams } from 'wouter';
 import { GoodTwo, Star } from '@icon-park/react';
-import { getPostDetail, getPostComments, likePost, unlikePost, addCommentToPost, collectPost, uncollectPost } from './api'; // 引入你的 API 函数
+import { getPostDetail, getPostComments, likePost, unlikePost, addCommentToPost, collectPost } from './api'; // 你的 API 函数
 import './PostDetailPage.css';
-import { UserContext, useUser } from '../../../context/UserContext'; // 注意路径
+import { UserContext } from '../../../context/UserContext'; // 用户上下文
 import Header from '../../../components/Header';
 
+const formatImageUrl = (url) => {
+  if (!url) return '';
+
+  if (url.startsWith('http')) return url;
+
+  // 如果是 /images/... ,替换成 /uploads/post/...
+  if (url.startsWith('/images/')) {
+    // 这里把 /images/ 替换成 /uploads/post/
+    return `http://localhost:8080/uploads/post/${url.slice('/images/'.length)}`;
+  }
+
+  // 其它情况默认直接拼接,不加斜杠
+  return `http://localhost:8080${url.startsWith('/') ? '' : '/'}${url}`;
+};
+
+
+// 头像地址格式化,处理 avatarUrl 字段
+export function formatAvatarUrlNoDefault(avatarUrl) {
+  if (!avatarUrl) return '';
+  if (avatarUrl.startsWith('http')) return avatarUrl;
+  return `http://localhost:8080${avatarUrl}`;
+}
+
 const PostDetailPage = () => {
-  const { postId } = useParams(); // 获取帖子ID
+  const { postId } = useParams();
+  const { user } = useContext(UserContext);
+
   const [postDetail, setPostDetail] = useState(null);
   const [comments, setComments] = useState([]);
   const [loading, setLoading] = useState(true);
   const [errorMsg, setErrorMsg] = useState('');
-  const [newComment, setNewComment] = useState(''); // 新评论内容
-  // const [isAnonymous, setIsAnonymous] = useState(false); // 是否匿名
-  const [isLiked, setIsLiked] = useState(false); // 是否已点赞
-  const [isCollected, setIsCollected] = useState(false); // 是否已收藏
-  const [replyToCommentId, setReplyToCommentId] = useState(null); // 回复的评论ID
+  const [newComment, setNewComment] = useState('');
+  const [isLiked, setIsLiked] = useState(false);
+  const [isCollected, setIsCollected] = useState(false);
+  const [replyToCommentId, setReplyToCommentId] = useState(null);
   const [replyToUsername, setReplyToUsername] = useState(null);
 
-  // 获取当前用户ID(假设从上下文中获取)
-  const { user } = useContext(UserContext);
-  // const { user } = useUser(); // 你需要从用户上下文获取用户 ID
-
   useEffect(() => {
-    const fetchPostDetail = async () => {
+    const fetchData = async () => {
       setLoading(true);
       setErrorMsg('');
       try {
-        // 获取帖子详情
         const postData = await getPostDetail(postId);
         setPostDetail(postData);
-
-        // 获取帖子评论
         const commentsData = await getPostComments(postId);
         setComments(commentsData);
 
-        // 设置是否已经点赞
-        if (postData.likedByUser) {
-          setIsLiked(true);
-        } else {
-          setIsLiked(false);
-        }
-
-        // 设置是否已经收藏
-        if (postData.collectedByUser) {
-          setIsCollected(true);
-        } else {
-          setIsCollected(false);
-        }
+        setIsLiked(!!postData.likedByUser);
+        setIsCollected(!!postData.collectedByUser);
       } catch (err) {
-        console.error('加载失败:', err);
+        console.error(err);
         setErrorMsg('加载失败,请稍后重试');
       } finally {
         setLoading(false);
       }
     };
-
-    fetchPostDetail();
+    fetchData();
   }, [postId]);
 
-  // 点赞功能
+  // 点赞
   const toggleLike = async () => {
-    if (!user) {
-      alert('请先登录');
-      return;
-    }
-
+    if (!user) return alert('请先登录');
     try {
       if (isLiked) {
-        // 取消点赞
         await unlikePost(postId, user.userId);
         setIsLiked(false);
-        setPostDetail((prev) => ({
+        setPostDetail(prev => ({
           ...prev,
           postLikeNum: prev.postLikeNum - 1,
         }));
       } else {
-        // 点赞
         await likePost(postId, user.userId);
         setIsLiked(true);
-        setPostDetail((prev) => ({
+        setPostDetail(prev => ({
           ...prev,
           postLikeNum: prev.postLikeNum + 1,
         }));
       }
-    } catch (err) {
-      console.error('点赞失败:', err);
+    } catch {
       alert('点赞失败,请稍后再试');
     }
   };
 
-// 收藏功能
-const toggleCollect = async () => {
-    if (!user) {
-        alert('请先登录');
-        return;
+  // 收藏
+  const toggleCollect = async () => {
+    if (!user) return alert('请先登录');
+    try {
+      if (isCollected) {
+        await collectPost(postId, user.userId, 'cancel');
+        setIsCollected(false);
+        setPostDetail(prev => ({
+          ...prev,
+          postCollectNum: prev.postCollectNum - 1,
+        }));
+      } else {
+        await collectPost(postId, user.userId, 'collect');
+        setIsCollected(true);
+        setPostDetail(prev => ({
+          ...prev,
+          postCollectNum: prev.postCollectNum + 1,
+        }));
+      }
+    } catch {
+      alert('收藏失败,请稍后再试');
     }
+  };
+
+  // 添加评论
+  const handleAddComment = async () => {
+    if (!user || !user.userId) return alert('请先登录后再评论');
+    if (!newComment.trim()) return alert('评论内容不能为空');
 
     try {
-        if (isCollected) {
-            // 取消收藏 - 使用原有的collectPost函数,传递action: "cancel"
-            await collectPost(postId, user.userId, "cancel");
-            setIsCollected(false);
-            setPostDetail((prev) => ({
-                ...prev,
-                postCollectNum: prev.postCollectNum - 1,
-            }));
-        } else {
-            // 收藏
-            await collectPost(postId, user.userId, "collect");
-            setIsCollected(true);
-            setPostDetail((prev) => ({
-                ...prev,
-                postCollectNum: prev.postCollectNum + 1,
-            }));
-        }
-    } catch (err) {
-        console.error('收藏操作失败:', err);
-        alert('收藏操作失败,请稍后再试');
+      const commentPayload = {
+        content: newComment,
+        userId: user.userId,
+        isAnonymous: false,
+        com_comment_id: replyToCommentId || null,
+      };
+      const commentData = await addCommentToPost(postId, commentPayload);
+
+      const newCommentItem = {
+        commentId: commentData?.commentId || Date.now(),
+        post_id: postId,
+        userId: user.userId,
+        username: user.username || '匿名',
+        content: newComment,
+        commentTime: new Date().toISOString(),
+        comCommentId: replyToCommentId,
+        userAvatar: user.avatar_url || '',
+      };
+
+      setComments(prev => [newCommentItem, ...prev]);
+      setNewComment('');
+      setReplyToCommentId(null);
+      setReplyToUsername(null);
+    } catch (error) {
+      alert(error.response?.data?.message || '评论失败,请稍后再试');
     }
-};
+  };
 
-// 添加评论
-const handleAddComment = async () => {
-  // 直接使用组件顶层获取的 user
-  if (!user || !user.userId) {
-    alert('请先登录后再评论');
-    return;
-  }
-
-  if (!newComment.trim()) {
-    alert('评论内容不能为空');
-    return;
-  }
-
-  try {
-    // 构建评论数据
-    const commentPayload = {
-      content: newComment,
-      userId: user.userId, // 使用已获取的用户ID
-      isAnonymous: false,
-      com_comment_id: replyToCommentId || null,
-    };
-
-    // 发送评论请求
-    const commentData = await addCommentToPost(postId, commentPayload);
-
-    // 更新评论列表
-    const newCommentItem = {
-      commentId: commentData?.commentId || Date.now(),
-      post_id: postId,
-      userId: user.userId,
-      content: newComment,
-      commentTime: new Date().toISOString(),
-      comCommentId: replyToCommentId,
-    };
-
-    setComments((prevComments) => [newCommentItem, ...prevComments]);
-
-    // 重置表单
-    setNewComment('');
-    setReplyToCommentId(null);
-
-    // alert('评论成功!');
-  } catch (error) {
-    console.error('评论失败:', error);
-    
-    const errorMessage = 
-      error.response?.data?.message || 
-      error.message || 
-      '评论失败,请稍后再试';
-      
-    alert(errorMessage);
-  }
-};
-
-
-
-  // 回复评论
+  // 回复按钮点击
   const handleReply = (commentId) => {
-  setReplyToCommentId(commentId);
-  const comment = comments.find(c => c.commentId === commentId);
-  if (comment) {
-    // 这里用用户名或者用户ID
-    setReplyToUsername(comment.username || comment.userId);
-  } else {
-    setReplyToUsername(null);
-  }
-};
+    setReplyToCommentId(commentId);
+    const comment = comments.find(c => c.commentId === commentId);
+    setReplyToUsername(comment?.username || comment?.userId || '未知用户');
+  };
 
-const findUsernameByCommentId = (id) => {
-  const comment = comments.find(c => c.commentId === id);
-  return comment ? (comment.username || comment.userId) : '未知用户';
-};
+  // 查找回复的用户名
+  const findUsernameByCommentId = (id) => {
+    const comment = comments.find(c => c.commentId === id);
+    return comment ? (comment.username || comment.userId || '未知用户') : '未知用户';
+  };
 
+  // 帖子图片处理,imgUrl 是单字符串,包装成数组
+  const getPostImages = () => {
+    if (!postDetail) return [];
+    if (postDetail.imgUrl) return [formatImageUrl(postDetail.imgUrl)];
+    return [];
+  };
 
   return (
     <div className="post-detail-page">
@@ -207,122 +178,116 @@
         <div className="post-detail">
           <h1>{postDetail.title}</h1>
           <div className="post-meta">
-            <span className="post-user">用户ID: {postDetail.user_id}</span>
+            <div className="post-user-info">
+              {postDetail.avatarUrl ? (
+                <img
+                  className="avatar"
+                  src={formatAvatarUrlNoDefault(postDetail.avatarUrl)}
+                  alt={postDetail.username || '用户头像'}
+                />
+              ) : null}
+              <span className="post-username">{postDetail.username || '匿名用户'}</span>
+            </div>
             <span className="post-time">
-            发布时间:{new Date(postDetail.postTime).toLocaleString()}
+              发布时间:{new Date(postDetail.postTime).toLocaleString()}
             </span>
           </div>
+
           <div className="post-content">
             <p>{postDetail.postContent}</p>
-            {Array.isArray(postDetail.imgUrl) ? (
-              <div className="post-images">
-                {postDetail.imgUrl.map((url, idx) => (
-                  <img key={idx} src={url} alt={`图片${idx}`} />
-                ))}
-              </div>
-            ) : (
-              postDetail.imgUrl && (
-                <img className="post-image" src={postDetail.imgUrl} alt="帖子图片" />
-              )
-            )}
-
+            <div className="post-images">
+              {getPostImages().map((url, idx) => (
+                <img key={idx} src={url} alt={`图片${idx + 1}`} />
+              ))}
+            </div>
           </div>
 
-          {/* 点赞和收藏 */}
           <div className="post-actions">
-            <button
-              className="icon-btn"
-              onClick={toggleLike} // 点赞操作
-            >
-              <GoodTwo
-                theme="outline"
-                size="20"
-                fill={isLiked ? '#f00' : '#ccc'} // 如果已点赞,显示红色
-              />
+            <button className="icon-btn" onClick={toggleLike} title="点赞">
+              <GoodTwo theme="outline" size="20" fill={isLiked ? '#f00' : '#ccc'} />
               <span>{postDetail.postLikeNum}</span>
             </button>
-            <button
-              className="icon-btn"
-              onClick={toggleCollect} // 收藏操作
-            >
-              <Star
-                theme="outline"
-                size="20"
-                fill={isCollected ? '#ffd700' : '#ccc'} // 如果已收藏,显示金色
-              />
+            <button className="icon-btn" onClick={toggleCollect} title="收藏">
+              <Star theme="outline" size="20" fill={isCollected ? '#ffd700' : '#ccc'} />
               <span>{postDetail.postCollectNum}</span>
             </button>
           </div>
-          
+
           <hr className="divider" />
-          {/* 评论部分 */}
+
           <h3>评论区</h3>
-          <div className="comments-section">      
-            {comments.length ? (
-              comments.map((comment) => (
-                <div key={comment.commentId} className="comment">
-                  <div className="comment-header">
-                    <span className="comment-user">用户 ID: {comment.userId}</span>
-                    <button className="reply-btn" onClick={() => handleReply(comment.commentId)}>回复</button>
-                  </div>
-
-                  <p className="comment-content">
-                    {comment.comCommentId ? (
-                      <>
-                        <span className="reply-to">回复 {findUsernameByCommentId(comment.comCommentId)}:</span>
-                        {comment.content}
-                      </>
-                    ) : (
-                      comment.content
-                    )}
-                  </p>
-
-                  <div className="comment-time">
-                    {new Date(comment.commentTime).toLocaleString()}
-                  </div>
-
-                  {/* 回复框 */}
-                  {replyToCommentId === comment.commentId && (
-                    <div className="reply-form">
-                      <div className="replying-to">
-                        回复 <strong>{replyToUsername}</strong>:
-                      </div>
-                      <textarea
-                        placeholder="输入你的回复..."
-                        value={newComment}
-                        onChange={(e) => setNewComment(e.target.value)}
+          <div className="comments-section">
+            {comments.length ? comments.map(comment => (
+              <div key={comment.commentId} className="comment">
+                <div className="comment-header">
+                  <div className="comment-user-info">
+                    {comment.userAvatar ? (
+                      <img
+                        className="avatar-small"
+                        src={formatAvatarUrlNoDefault(comment.userAvatar)}
+                        alt={comment.username || '用户头像'}
                       />
-                      <div className="comment-options">
-                        <button onClick={handleAddComment}>发布回复</button>
-                      </div>
-                    </div>
-                  )}
+                    ) : null}
+                    <span className="comment-username">{comment.username || '匿名用户'}</span>
+                  </div>
+                  <button className="reply-btn" onClick={() => handleReply(comment.commentId)}>回复</button>
                 </div>
-              ))
-            ) : (
-              <p>暂无评论</p>
-            )}
 
+                <p className="comment-content">
+                  {comment.comCommentId ? (
+                    <>
+                      <span className="reply-to">回复 {findUsernameByCommentId(comment.comCommentId)}:</span>
+                      {comment.content}
+                    </>
+                  ) : (
+                    comment.content
+                  )}
+                </p>
 
-            {/* 添加评论表单 */}
-            <div className="add-comment-form">
-              <textarea
-                placeholder="输入你的评论..."
-                value={newComment}
-                onChange={(e) => setNewComment(e.target.value)}
-              />
-              <div className="comment-options">
-                {/* <label>
-                  <input
-                    type="checkbox"
-                    checked={isAnonymous}
-                    onChange={() => setIsAnonymous(!isAnonymous)}
-                  />
-                  匿名评论
-                </label> */}
-                <button onClick={handleAddComment}>发布评论</button>
+                <div className="comment-time">
+                  {new Date(comment.commentTime).toLocaleString()}
+                </div>
+
+                {replyToCommentId === comment.commentId && (
+                  <div className="reply-form">
+                    <div className="replying-to">
+                      回复 <strong>{replyToUsername}</strong>:
+                    </div>
+                    <textarea
+                      placeholder="输入你的回复..."
+                      value={newComment}
+                      onChange={(e) => setNewComment(e.target.value)}
+                    />
+                    <div className="comment-options">
+                      <button onClick={handleAddComment}>发布回复</button>
+                      <button
+                        onClick={() => {
+                          setReplyToCommentId(null);
+                          setReplyToUsername(null);
+                          setNewComment('');
+                        }}
+                        style={{ marginLeft: '8px' }}
+                      >
+                        取消
+                      </button>
+                    </div>
+                  </div>
+                )}
               </div>
-            </div>
+            )) : <p>暂无评论</p>}
+
+            {!replyToCommentId && (
+              <div className="add-comment-form">
+                <textarea
+                  placeholder="输入你的评论..."
+                  value={newComment}
+                  onChange={(e) => setNewComment(e.target.value)}
+                />
+                <div className="comment-options">
+                  <button onClick={handleAddComment}>发布评论</button>
+                </div>
+              </div>
+            )}
           </div>
         </div>
       ) : (
@@ -332,4 +297,4 @@
   );
 };
 
-export default PostDetailPage;
\ No newline at end of file
+export default PostDetailPage;
diff --git a/src/pages/Forum/posts-main/components/CreatePostButton.jsx b/src/pages/Forum/posts-main/components/CreatePostButton.jsx
index 0f23e82..e324056 100644
--- a/src/pages/Forum/posts-main/components/CreatePostButton.jsx
+++ b/src/pages/Forum/posts-main/components/CreatePostButton.jsx
@@ -2,20 +2,18 @@
 import axios from 'axios';
 import { Edit } from '@icon-park/react';
 import './CreatePostButton.css';
-
-const user = JSON.parse(localStorage.getItem('user')); // user = { user_id: 123, ... }
-const userId = user?.user_id;
-
+import { useUser } from '../../../../context/UserContext';
 
 const CreatePostButton = () => {
-  const [showModal, setShowModal] = useState(false);
+  const { user } = useUser();
+  const userId = user?.userId;  // 这里改为 userId,跟 UserContext 统一
 
+  const [showModal, setShowModal] = useState(false);
   const [title, setTitle] = useState('');
   const [content, setContent] = useState('');
   const [previewUrls, setPreviewUrls] = useState([]);
   const [files, setFiles] = useState([]);
 
-  // 选择图片并预览
   const handleImageChange = (e) => {
     const selectedFiles = Array.from(e.target.files);
     if (!selectedFiles.length) return;
@@ -24,6 +22,10 @@
   };
 
   const handleSubmit = async () => {
+    if (!userId) {
+      alert('用户未登录或用户ID未获取到,无法发帖');
+      return;
+    }
     if (!title.trim() || !content.trim()) {
       alert('标题和内容均为必填项');
       return;
@@ -34,7 +36,7 @@
     formData.append('postContent', content.trim());
 
     files.forEach(file => {
-      formData.append('imageUrl', file); // 多文件使用同一个字段名
+      formData.append('imageUrl', file);
     });
 
     try {
@@ -46,7 +48,6 @@
         }
       );
 
-      // 清空表单
       setTitle('');
       setContent('');
       setFiles([]);
@@ -119,4 +120,3 @@
 };
 
 export default CreatePostButton;
-
diff --git a/src/pages/Forum/posts-main/components/PostList.jsx b/src/pages/Forum/posts-main/components/PostList.jsx
index 9e088ea..c1d9b7b 100644
--- a/src/pages/Forum/posts-main/components/PostList.jsx
+++ b/src/pages/Forum/posts-main/components/PostList.jsx
@@ -3,8 +3,16 @@
 import { Link } from 'wouter';
 import { GoodTwo, Star, Delete } from '@icon-park/react';
 import { likePost } from '../../posts-detail/api';
+import { formatAvatarUrl } from '../../../../components/utils/avatar';
 import './PostList.css';
 
+// 修改后的封面图 URL 拼接函数
+const formatImageUrl = (url) => {
+  if (!url) return '';
+  const filename = url.split('/').pop(); // 提取文件名部分
+  return `http://localhost:8080/uploads/post/${filename}`;
+};
+
 const PostList = ({ search }) => {
   const [posts, setPosts] = useState([]);
   const [page, setPage] = useState(1);
@@ -21,49 +29,26 @@
       setErrorMsg('');
       try {
         const res = await axios.get(`/echo/forum/posts/getAllPosts`, {
-          params: {
-            page,
-            pageSize: size,
-            sortBy: 'createdAt',
-            order: 'desc'
-          }
+          params: { page, pageSize: size, sortBy: 'createdAt', order: 'desc' }
         });
 
-        // 检查响应结构是否符合预期
-        if (!res.data || !Array.isArray(res.data.posts)) {
-          throw new Error('API返回格式不正确');
-        }
+        if (!res.data || !Array.isArray(res.data.posts)) throw new Error('API返回格式不正确');
 
-        const postsData = res.data.posts || [];
+        const postsData = res.data.posts;
 
-        const userIds = [...new Set(postsData.map(post => post.user_id))];
+        const filteredPosts = postsData.filter(post =>
+          post.title?.toLowerCase().includes(search.toLowerCase())
+        );
 
-        const profiles = await Promise.all(userIds.map(async id => {
-          try {
-            const r = await axios.get(`/echo/user/profile`, {
-              params: { user_id: id }
-            });
-            return { id, profile: r.data };
-          } catch (e) {
-            return { id, profile: { nickname: '未知用户', avatar_url: 'default-avatar.png' } };
-          }
+        const postsWithProfiles = filteredPosts.map(post => ({
+          ...post,
+          username: post.username || '未知用户',
+          avatarUrl: formatAvatarUrl(post.avatarUrl || ''),
+          liked: false,
+          collected: false,
+          commentCount: 0
         }));
 
-        const userMap = {};
-        profiles.forEach(({ id, profile }) => {
-          userMap[id] = profile;
-        });
-
-        const postsWithProfiles = postsData
-          .filter(post => post.title.toLowerCase().includes(search.toLowerCase()))
-          .map(post => ({
-            ...post,
-            userProfile: userMap[post.user_id] || { nickname: '未知用户', avatar_url: 'default-avatar.png' },
-            liked: false,
-            collected: false,
-            commentCount: 0
-          }));
-
         setPosts(postsWithProfiles);
         setTotal(res.data.total || 0);
       } catch (err) {
@@ -78,77 +63,65 @@
   }, [page, search]);
 
   const toggleLike = async (postNo, liked, userId) => {
-  try {
-    if (liked) {
-      // 修改为 POST 请求,并带上 user_id 参数
-      await axios.post(`/echo/forum/posts/${postNo}/unlike`, {
-        user_id: userId
-      });
-    } else {
-      await likePost(postNo, userId); // 你已有的点赞逻辑
+    try {
+      if (liked) {
+        await axios.post(`/echo/forum/posts/${postNo}/unlike`, { user_id: userId });
+      } else {
+        await likePost(postNo, userId);
+      }
+
+      setPosts(posts =>
+        posts.map(post =>
+          post.postNo === postNo
+            ? {
+                ...post,
+                liked: !liked,
+                likeCount: liked ? post.likeCount - 1 : post.likeCount + 1
+              }
+            : post
+        )
+      );
+    } catch (err) {
+      console.error('点赞失败:', err);
+      alert('点赞操作失败,请稍后重试');
     }
+  };
 
-    setPosts(posts =>
-      posts.map(post =>
-        post.postNo === postNo
-          ? { ...post, liked: !liked, likeCount: liked ? post.likeCount - 1 : post.likeCount + 1 }
-          : post
-      )
-    );
-  } catch (err) {
-    console.error('点赞失败:', err);
-    alert('点赞操作失败,请稍后重试');
-  }
-};
-
-
-  // 收藏帖子
   const toggleCollect = async (postNo, collected, userId) => {
-  try {
-    if (collected) {
-      // 取消收藏:DELETE 请求 + JSON 请求体
-      await axios.delete(`/echo/forum/posts/${postNo}/uncollect`, {
-        data: { user_id: userId } // 注意:DELETE 请求的请求体需放在 data 字段中
-      });
-    } else {
-      // 收藏:POST 请求 + JSON 请求体
-      await axios.post(`/echo/forum/posts/${postNo}/collect`, {
-        user_id: userId
-      });
+    try {
+      if (collected) {
+        await axios.delete(`/echo/forum/posts/${postNo}/uncollect`, {
+          data: { user_id: userId }
+        });
+      } else {
+        await axios.post(`/echo/forum/posts/${postNo}/collect`, {
+          user_id: userId
+        });
+      }
+
+      setPosts(posts =>
+        posts.map(post =>
+          post.postNo === postNo
+            ? {
+                ...post,
+                collected: !collected,
+                collectCount: collected ? post.collectCount - 1 : post.collectCount + 1
+              }
+            : post
+        )
+      );
+    } catch (err) {
+      console.error('收藏操作失败:', err);
+      alert('收藏操作失败,请稍后重试');
     }
+  };
 
-    setPosts(posts =>
-      posts.map(post =>
-        post.postNo === postNo
-          ? {
-              ...post,
-              collected: !collected,
-              collectCount: collected ? post.collectCount - 1 : post.collectCount + 1
-            }
-          : post
-      )
-    );
-  } catch (err) {
-    console.error('收藏操作失败:', err.response?.data || err.message);
-    alert('收藏操作失败,请稍后重试');
-  }
-};
-
-
-
-  // 删除帖子
   const handleDeletePost = async (postNo) => {
     if (window.confirm('确定要删除这篇帖子吗?')) {
       try {
         await axios.delete(`/echo/forum/posts/${postNo}/deletePost`);
-        
-        // 从列表中移除已删除的帖子
         setPosts(posts => posts.filter(post => post.postNo !== postNo));
-        
-        // 如果删除后当前页没有帖子了,尝试加载上一页
-        if (posts.length === 1 && page > 1) {
-          setPage(page - 1);
-        }
+        if (posts.length === 1 && page > 1) setPage(page - 1);
       } catch (err) {
         console.error('删除帖子失败:', err);
         alert('删除帖子失败,请稍后再试');
@@ -161,43 +134,52 @@
       {loading ? <p>加载中...</p> :
         errorMsg ? <p className="error-text">{errorMsg}</p> :
           posts.length === 0 ? <p>暂无帖子。</p> :
-            posts.map(post => (
-              <div
-                key={post.postNo}
-                className="post-card"
-                style={{ backgroundColor: '#e9ded2' }}
-              >
-                <div className="user-info">
-                  <img className="avatar" src={post.userProfile.avatar_url} alt="头像" />
-                  <span className="nickname" style={{ color: '#755e50' }}>{post.userProfile.nickname}</span>
-                </div>
-                {post.imgUrl && (
-                  <img className="cover-image" src={post.imgUrl} alt="封面" />
-                )}
-                <h3 style={{ color: '#000000' }}>{post.title}</h3>
-                <div className="post-meta">
-                  <span>发布时间:{new Date(post.createdAt).toLocaleString()}</span>
-                  <div className="post-actions">
-                    <button className="icon-btn" onClick={() => toggleLike(post.postNo, post.liked, post.user_id)}>
-                      <GoodTwo theme="outline" size="24" fill={post.liked ? '#f00' : '#fff'} />
-                      <span>{post.likeCount}</span>
-                    </button>
+            posts.map(post => {
+              const createdAtDate = new Date(post.createdAt);
+              const timeText = isNaN(createdAtDate.getTime()) ? '时间未设置' : createdAtDate.toLocaleString();
 
-                    <button className="icon-btn" onClick={() => toggleCollect(post.postNo, post.collected, post.user_id)}>
-                      <Star theme="outline" size="24" fill={post.collected ? '#ffd700' : '#fff'} />
-                      <span>{post.collectCount}</span>
-                    </button>
-                    
-                    <button className="icon-btn" onClick={() => handleDeletePost(post.postNo)}>
-                      <Delete theme="outline" size="24" fill="#333" />
-                    </button>
+              let coverImage = null;
+              if (post.imgUrl) {
+                const imgs = post.imgUrl.split(',').map(i => i.trim()).filter(Boolean);
+                coverImage = imgs.length > 0 ? formatImageUrl(imgs[0]) : null;
+              }
+
+              return (
+                <div key={post.postNo} className="post-card" style={{ backgroundColor: '#e9ded2' }}>
+                  <div className="user-info">
+                    <img
+                      className="avatar"
+                      src={post.avatarUrl}
+                      alt="头像"
+                    />
+                    <span className="nickname" style={{ color: '#755e50' }}>{post.username}</span>
+                  </div>
+
+                  {coverImage && <img className="cover-image" src={coverImage} alt="封面" />}
+
+                  <h3 style={{ color: '#000000' }}>{post.title || '无标题'}</h3>
+                  <div className="post-meta">
+                    <span>发布时间:{timeText}</span>
+                    <div className="post-actions">
+                      <button className="icon-btn" onClick={() => toggleLike(post.postNo, post.liked, post.user_id)}>
+                        <GoodTwo theme="outline" size="24" fill={post.liked ? '#f00' : '#fff'} />
+                        <span>{post.likeCount}</span>
+                      </button>
+                      <button className="icon-btn" onClick={() => toggleCollect(post.postNo, post.collected, post.user_id)}>
+                        <Star theme="outline" size="24" fill={post.collected ? '#ffd700' : '#fff'} />
+                        <span>{post.collectCount}</span>
+                      </button>
+                      <button className="icon-btn" onClick={() => handleDeletePost(post.postNo)}>
+                        <Delete theme="outline" size="24" fill="#333" />
+                      </button>
+                    </div>
+                  </div>
+                  <div className="detail-button-wrapper">
+                    <Link href={`/forum/post/${post.postNo}`} className="detail-button">查看详情</Link>
                   </div>
                 </div>
-                <div className="detail-button-wrapper">
-                  <Link href={`/forum/post/${post.postNo}`} className="detail-button">查看详情</Link>
-                </div>
-              </div>
-            ))
+              );
+            })
       }
 
       <div className="pagination">
@@ -209,4 +191,4 @@
   );
 };
 
-export default PostList;
\ No newline at end of file
+export default PostList;
diff --git a/src/pages/PublishSeed/PublishSeed.jsx b/src/pages/PublishSeed/PublishSeed.jsx
index e3be08c..f27ebc2 100644
--- a/src/pages/PublishSeed/PublishSeed.jsx
+++ b/src/pages/PublishSeed/PublishSeed.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useRef } from 'react';
 import axios from 'axios';
 import Header from '../../components/Header';
 import './PublishSeed.css';
@@ -9,23 +9,36 @@
   const [description, setDescription] = useState('');
   const [tags, setTags] = useState([]);
   const [category, setCategory] = useState('movie');
-  const [file, setFile] = useState(null);
   const [imageUrl, setImageUrl] = useState('');
   const [message, setMessage] = useState('');
   const [isLoading, setIsLoading] = useState(false);
+  const [fileName, setFileName] = useState('');
 
-  const { user } = useUser(); // 从上下文中获取当前登录用户信息
+  const fileInputRef = useRef(null); // ✅ 获取 input file 引用
+  const { user } = useUser();
 
   const handleTagsChange = (e) => {
     setTags(e.target.value.split(',').map(tag => tag.trim()));
   };
 
+  const handleFileButtonClick = () => {
+    fileInputRef.current?.click(); // 点击隐藏的 input
+  };
+
   const handleFileChange = (e) => {
-    setFile(e.target.files[0]);
+    const selectedFile = e.target.files[0];
+    if (selectedFile) {
+      setFileName(selectedFile.name); // 仅展示文件名
+    }
   };
 
   const handleSubmit = async (e) => {
     e.preventDefault();
+    console.log('[handleSubmit] 表单提交开始');
+
+    const currentFile = fileInputRef.current?.files[0]; // ✅ 获取文件
+    console.log('[handleSubmit] currentFile:', currentFile);
+
     setIsLoading(true);
     setMessage('');
 
@@ -35,20 +48,20 @@
       return;
     }
 
-    if (!file || !file.name.endsWith('.torrent')) {
+    if (!currentFile || !currentFile.name.toLowerCase().endsWith('.torrent')) {
       setMessage('请上传一个 .torrent 文件');
       setIsLoading(false);
       return;
     }
 
     const formData = new FormData();
-    formData.append('file', file); // 文件字段
+    formData.append('file', currentFile);
     formData.append('title', title);
     formData.append('description', description);
     formData.append('category', category);
     formData.append('imageUrl', imageUrl);
-    formData.append('tags', tags.join(',')); // 后端使用字符串或数组自行处理
-    formData.append('uploader', user.id); // 添加上传者 ID(必须字段)
+    formData.append('tags', tags.join(','));
+    formData.append('uploader', user.id);
 
     try {
       const response = await axios.post('/seeds/upload', formData, {
@@ -63,7 +76,7 @@
         setMessage(response.data.msg || '上传失败,请稍后再试');
       }
     } catch (error) {
-      console.error(error);
+      console.error('[handleSubmit] 上传失败:', error);
       setMessage('上传失败,发生了错误');
     } finally {
       setIsLoading(false);
@@ -121,16 +134,17 @@
 
           <div className="seed-file">
             <label>种子文件</label>
-            <label className="seed-file-label">
+            <div className="seed-file-label" onClick={handleFileButtonClick}>
               点击选择文件
-              <input
-                type="file"
-                accept=".torrent"
-                onChange={handleFileChange}
-                style={{ display: 'none' }}
-              />
-            </label>
-            {file && <div style={{ marginTop: '5px' }}>{file.name}</div>}
+            </div>
+            <input
+              type="file"
+              accept=".torrent"
+              ref={fileInputRef}
+              onChange={handleFileChange}
+              style={{ display: 'none' }}
+            />
+            {fileName && <div style={{ marginTop: '5px' }}>{fileName}</div>}
           </div>
 
           <div className="form-group">
diff --git a/src/pages/PublishSeed/SimpleUploader.jsx b/src/pages/PublishSeed/SimpleUploader.jsx
new file mode 100644
index 0000000..0f44b6a
--- /dev/null
+++ b/src/pages/PublishSeed/SimpleUploader.jsx
@@ -0,0 +1,45 @@
+import React, { useRef, useState } from 'react';
+import axios from 'axios';
+
+const SimpleUploader = () => {
+  const fileInputRef = useRef(null);
+  const [message, setMessage] = useState('');
+
+  const handleUpload = async () => {
+    const file = fileInputRef.current?.files[0];
+    console.log('[handleUpload] file:', file);
+
+    if (!file) {
+      setMessage('请先选择文件');
+      return;
+    }
+
+    const formData = new FormData();
+    formData.append('file', file);
+
+    try {
+      const response = await axios.post('/seeds/upload', formData, {
+        headers: { 'Content-Type': 'multipart/form-data' },
+      });
+
+      console.log('[handleUpload] response:', response);
+      setMessage(response.data?.msg || '上传成功');
+    } catch (err) {
+      console.error('[handleUpload] 上传失败:', err);
+      setMessage('上传失败');
+    }
+  };
+
+  return (
+    <div style={{ padding: '2rem' }}>
+      <h2>种子上传测试</h2>
+      <input type="file" accept=".torrent" ref={fileInputRef} />
+      <button onClick={handleUpload} style={{ marginLeft: '1rem' }}>
+        上传
+      </button>
+      <div style={{ marginTop: '1rem' }}>{message}</div>
+    </div>
+  );
+};
+
+export default SimpleUploader;
diff --git a/src/pages/UserCenter/UserProfile.css b/src/pages/UserCenter/UserProfile.css
index b63c408..d2f848b 100644
--- a/src/pages/UserCenter/UserProfile.css
+++ b/src/pages/UserCenter/UserProfile.css
@@ -55,4 +55,25 @@
   padding: 10% 20%;
   margin-left: 5%;
   margin-right: 5%;
-}  
\ No newline at end of file
+}  
+.avatar-wrapper {
+  position: relative;
+  display: inline-block;
+}
+
+.avatar-upload-label {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  background: #3498db;
+  color: white;
+  padding: 4px 8px;
+  font-size: 12px;
+  cursor: pointer;
+  border-radius: 4px;
+  opacity: 0.85;
+}
+
+.avatar-upload-label:hover {
+  opacity: 1;
+}
diff --git a/src/pages/UserCenter/UserProfile.jsx b/src/pages/UserCenter/UserProfile.jsx
index a10657c..fb472c8 100644
--- a/src/pages/UserCenter/UserProfile.jsx
+++ b/src/pages/UserCenter/UserProfile.jsx
@@ -3,17 +3,17 @@
 import './UserProfile.css';
 import UserNav from './UserNav';
 import Header from '../../components/Header';
-import { useUser } from '../../context/UserContext'; 
+import { useUser } from '../../context/UserContext';
+
+const DEFAULT_AVATAR_URL = `${process.env.PUBLIC_URL}/default-avatar.png`;
 
 const UserProfile = () => {
-  const { user, loading } = useUser(); // 从上下文拿用户和加载状态
+  const { user, loading } = useUser();
   const [userProfile, setUserProfile] = useState(null);
   const [error, setError] = useState(null);
 
   useEffect(() => {
-    if (loading) return; // 用户信息还没加载完,先不请求
-    console.log('用户:', user, '加载:', loading);
-
+    if (loading) return;
     if (!user || !user.userId) {
       setError('未登录或用户信息缺失');
       setUserProfile(null);
@@ -23,11 +23,7 @@
     const fetchUserProfile = async () => {
       try {
         setError(null);
-        const response = await axios.get(`/echo/user/${user.userId}/getProfile`);
-
-        console.log('响应数据:', response); // 调试用
-        const raw = response.data;
-        console.log('raw:', raw); // 调试用
+        const { data: raw } = await axios.get(`/echo/user/${user.userId}/getProfile`);
 
         if (!raw) {
           setError('用户数据为空');
@@ -36,7 +32,9 @@
         }
 
         const profile = {
-          avatar_url: raw.avatarUrl || 'https://example.com/default-avatar.jpg',
+          avatarUrl: raw.avatarUrl
+            ? `${process.env.REACT_APP_AVATAR_BASE_URL}${raw.avatarUrl}`
+            : DEFAULT_AVATAR_URL,
           nickname: raw.username || '未知用户',
           email: raw.email || '未填写',
           gender: raw.gender || '保密',
@@ -44,19 +42,15 @@
           interests: raw.hobbies ? raw.hobbies.split(',') : [],
           level: raw.level || '未知',
           experience: raw.experience ?? 0,
-          upload_amount: raw.uploadCount ?? 0,
-          download_amount: raw.downloadCount ?? 0,
-          share_rate: raw.shareRate ?? 0,
-          joined_date: raw.registrationTime,
+          uploadAmount: raw.uploadCount ?? 0,
+          downloadAmount: raw.downloadCount ?? 0,
+          shareRate: raw.shareRate ?? 0,
+          joinedDate: raw.registrationTime,
         };
 
         setUserProfile(profile);
       } catch (err) {
-        if (err.response?.status === 404) {
-          setError('用户不存在');
-        } else {
-          setError('请求失败,请稍后再试');
-        }
+        setError(err.response?.status === 404 ? '用户不存在' : '请求失败,请稍后再试');
         setUserProfile(null);
       }
     };
@@ -64,10 +58,54 @@
     fetchUserProfile();
   }, [user, loading]);
 
+  const handleAvatarUpload = async (e) => {
+    const file = e.target.files[0];
+    if (!file) return;
+
+    const formData = new FormData();
+    formData.append('file', file);
+
+    try {
+      const { data } = await axios.post(
+        `/echo/user/${user.userId}/uploadAvatar`,
+        formData,
+        { headers: { 'Content-Type': 'multipart/form-data' } }
+      );
+
+      if (data?.avatarUrl) {
+        setUserProfile((prev) => ({
+          ...prev,
+          avatarUrl: `${process.env.REACT_APP_AVATAR_BASE_URL}${data.avatarUrl}`,
+        }));
+        alert('头像上传成功');
+      } else {
+        alert('头像上传成功,但未返回新头像地址');
+      }
+    } catch (err) {
+      console.error('上传失败:', err);
+      alert('头像上传失败,请重试');
+    }
+  };
+
   if (loading) return <p>正在加载用户信息...</p>;
   if (error) return <p className="error">{error}</p>;
   if (!userProfile) return null;
 
+  const {
+    avatarUrl,
+    nickname,
+    email,
+    gender,
+    bio,
+    interests,
+    level,
+    experience,
+    uploadAmount,
+    downloadAmount,
+    shareRate,
+    joinedDate,
+  } = userProfile;
+
   return (
     <div className="user-profile-container">
       <Header />
@@ -78,25 +116,33 @@
         <div className="common-card">
           <div className="right-content">
             <div className="profile-header">
-              <img
-                src={userProfile.avatar_url}
-                alt={userProfile.nickname}
-                className="avatar"
-              />
-              <h1>{userProfile.nickname}</h1>
+              <div className="avatar-wrapper">
+                <img src={avatarUrl} alt={nickname} className="avatar" />
+                <label htmlFor="avatar-upload" className="avatar-upload-label">
+                  上传头像
+                </label>
+                <input
+                  type="file"
+                  id="avatar-upload"
+                  accept="image/*"
+                  style={{ display: 'none' }}
+                  onChange={handleAvatarUpload}
+                />
+              </div>
+              <h1>{nickname}</h1>
             </div>
 
             <div className="profile-details">
-              <p><strong>邮箱:</strong>{userProfile.email}</p>
-              <p><strong>性别:</strong>{userProfile.gender}</p>
-              <p><strong>个人简介:</strong>{userProfile.bio}</p>
-              <p><strong>兴趣:</strong>{userProfile.interests.length > 0 ? userProfile.interests.join(', ') : '无'}</p>
-              <p><strong>等级:</strong>{userProfile.level}</p>
-              <p><strong>经验:</strong>{userProfile.experience}</p>
-              <p><strong>上传量:</strong>{userProfile.upload_amount}</p>
-              <p><strong>下载量:</strong>{userProfile.download_amount}</p>
-              <p><strong>分享率:</strong>{(userProfile.share_rate * 100).toFixed(2)}%</p>
-              <p><strong>加入时间:</strong>{new Date(userProfile.joined_date).toLocaleDateString()}</p>
+              <p><strong>邮箱:</strong>{email}</p>
+              <p><strong>性别:</strong>{gender}</p>
+              <p><strong>个人简介:</strong>{bio}</p>
+              <p><strong>兴趣:</strong>{interests.length > 0 ? interests.join(', ') : '无'}</p>
+              <p><strong>等级:</strong>{level}</p>
+              <p><strong>经验:</strong>{experience}</p>
+              <p><strong>上传量:</strong>{uploadAmount}</p>
+              <p><strong>下载量:</strong>{downloadAmount}</p>
+              <p><strong>分享率:</strong>{(shareRate * 100).toFixed(2)}%</p>
+              <p><strong>加入时间:</strong>{new Date(joinedDate).toLocaleDateString()}</p>
             </div>
           </div>
         </div>