合并

Change-Id: I19ca58c58a513cba20c162c9ed7cbab90e060bf6
diff --git a/src/api/helpComment.js b/src/api/helpComment.js
index e711587..c1d00c0 100644
--- a/src/api/helpComment.js
+++ b/src/api/helpComment.js
@@ -9,5 +9,20 @@
 };

 

 export const addCommentReply = (commentId, replyData) => {

-  return api.post(`/help/comments/${commentId}/replies`, replyData);

+  const formData = new FormData();

+  formData.append('authorId', replyData.authorId);

+  formData.append('content', replyData.content);

+

+  // 如果有图片,添加到formData

+  if (replyData.image) {

+    formData.append('image', replyData.image);

+  }

+

+  return api.post(`/help/comments/${commentId}/replies`, formData);

+};

+

+export const deleteComment = (commentId, authorId) => {

+  return api.delete(`/help/comments/${commentId}`, {

+    params: { authorId }

+  });

 };
\ No newline at end of file
diff --git a/src/api/helpComment.test.js b/src/api/helpComment.test.js
index 2b7be4c..a4a7d99 100644
--- a/src/api/helpComment.test.js
+++ b/src/api/helpComment.test.js
@@ -1,6 +1,6 @@
 import MockAdapter from 'axios-mock-adapter';

 import { api } from './auth'; // 添加api导入

-import { likePostComment, getCommentReplies, addCommentReply } from './helpComment';

+import { likePostComment, getCommentReplies, addCommentReply, deleteComment } from './helpComment';

 

 describe('求助帖评论API', () => {

   let mockAxios;

@@ -34,11 +34,53 @@
   });

 

   describe('addCommentReply - 添加评论回复', () => {

-    it('应该正确发送回复内容', async () => {

-      const testData = { content: '测试回复', author: 'user1' };

-      mockAxios.onPost('/help/comments/789/replies', testData).reply(200, { code: 200 });

+    it('应该正确发送回复内容(无图片)', async () => {

+      const commentId = '789';

+      const replyData = {

+        authorId: 'user1',

+        content: '测试回复'

+      };

+      

+      mockAxios.onPost(`/help/comments/${commentId}/replies`).reply(config => {

+        const data = config.data;

+        expect(data.get('authorId')).toBe(replyData.authorId);

+        expect(data.get('content')).toBe(replyData.content);

+        expect(data.has('image')).toBe(false);

+        return [200, { code: 200 }];

+      });

 

-      const response = await addCommentReply('789', testData);

+      const response = await addCommentReply(commentId, replyData);

+      expect(response.status).toBe(200);

+    });

+    it('应该正确处理带图片的回复', async () => {

+      const commentId = '789';

+      const replyData = {

+        authorId: 'user1',

+        content: '测试回复',

+        image: new File(['content'], 'reply.jpg')

+      };

+      

+      mockAxios.onPost(`/help/comments/${commentId}/replies`).reply(config => {

+        const data = config.data;

+        expect(data.get('image')).toBeInstanceOf(File);

+        return [200, { code: 200 }];

+      });

+

+      const response = await addCommentReply(commentId, replyData);

+      expect(response.status).toBe(200);

+    });

+  });

+  describe('deleteComment - 删除评论', () => {

+    it('应该正确发送删除请求', async () => {

+      const commentId = '101112';

+      const authorId = 'user1';

+      

+      mockAxios.onDelete(`/help/comments/${commentId}`).reply(config => {

+        expect(config.params).toEqual({ authorId });

+        return [200, { code: 200 }];

+      });

+

+      const response = await deleteComment(commentId, authorId);

       expect(response.status).toBe(200);

     });

   });

diff --git a/src/api/helpPost.js b/src/api/helpPost.js
index 426e92b..4813aa3 100644
--- a/src/api/helpPost.js
+++ b/src/api/helpPost.js
@@ -1,12 +1,19 @@
 // src/api/helpPost.js

 import { api } from './auth'; // 复用已有的axios实例

 

-export const createPost = (title, content, authorId) => {

-  return api.post('/help/posts', { 

-    title, 

-    content, 

-    authorId 

-  });

+export const createPost = (title, content, authorId, selectedImage) => {

+  // 创建 FormData 对象

+  const formData = new FormData();

+  formData.append('title', title);

+  formData.append('content', content);

+  formData.append('authorId', authorId);

+  

+  // 如果有图片,添加到 FormData

+  if (selectedImage) {

+    formData.append('image', selectedImage);

+  }

+

+  return api.post('/help/posts', formData);

 };

 

 export const getPosts = (page = 1, size = 5) => {

@@ -19,10 +26,28 @@
   return api.get(`/help/posts/${postId}`);

 };

 

-export const likePost = (postId) => {

-  return api.post(`/help/posts/${postId}/like`);

+export const likePost = (postId, data) => {

+  return api.post(`/help/posts/${postId}/like`, null, {

+    params: data

+  });

 };

 

 export const addPostComment = (postId, commentData) => {

-  return api.post(`/help/posts/${postId}/comments`, commentData);

+  // 创建FormData对象来处理文件上传

+  const formData = new FormData();

+  formData.append('authorId', commentData.authorId);

+  formData.append('content', commentData.content);

+  

+  // 如果有图片,添加到formData

+  if (commentData.commentImage) {

+    formData.append('image', commentData.commentImage);

+  }

+

+  return api.post(`/help/posts/${postId}/comments`, formData);

+};

+

+export const deletePost = (postId, authorId) => {

+  return api.delete(`/help/posts/${postId}`, {

+    params: { authorId }

+  });

 };
\ No newline at end of file
diff --git a/src/api/helpPost.test.js b/src/api/helpPost.test.js
index e163090..b445b4d 100644
--- a/src/api/helpPost.test.js
+++ b/src/api/helpPost.test.js
@@ -1,6 +1,6 @@
 import MockAdapter from 'axios-mock-adapter';

 import { api } from './auth'; // 添加api导入

-import { createPost, getPosts, getPostDetail, likePost, addPostComment } from './helpPost';

+import { createPost, getPosts, getPostDetail, likePost, addPostComment,deletePost } from './helpPost';

 

 describe('求助帖API', () => {

   let mockAxios;

@@ -14,18 +14,49 @@
   });

 

   describe('createPost - 创建求助帖', () => {

-    it('应该正确发送帖子数据', async () => {

+    it('应该正确发送无图片帖子数据', async () => {

       const postData = {

         title: '测试标题',

         content: '测试内容',

         authorId: 'user123'

       };

-      mockAxios.onPost('/help/posts', postData).reply(201, { code: 201 });

+      // 使用函数匹配器来验证FormData内容

+      mockAxios.onPost('/help/posts').reply(config => {

+        const data = config.data;

+        expect(data.get('title')).toBe(postData.title);

+        expect(data.get('content')).toBe(postData.content);

+        expect(data.get('authorId')).toBe(postData.authorId);

+        expect(data.has('image')).toBe(false);

+        return [201, { code: 201 }];

+      });

 

       const response = await createPost(postData.title, postData.content, postData.authorId);

       expect(response.status).toBe(201);

     });

   });

+  it('应该正确处理带图片的帖子', async () => {

+    const postData = {

+      title: '测试标题',

+      content: '测试内容',

+      authorId: 'user123',

+      selectedImage: new File(['content'], 'test.jpg')

+    };

+    

+    mockAxios.onPost('/help/posts').reply(config => {

+      const data = config.data;

+      expect(data.get('image')).toBeInstanceOf(File);

+      return [201, { code: 201 }];

+    });

+

+    const response = await createPost(

+      postData.title, 

+      postData.content, 

+      postData.authorId, 

+      postData.selectedImage

+    );

+    expect(response.status).toBe(201);

+  });

+

 

   describe('getPosts - 获取求助帖列表', () => {

     it('应该支持分页参数', async () => {

@@ -49,11 +80,53 @@
   });

 

   describe('addPostComment - 添加帖子评论', () => {

-    it('应该正确发送评论数据', async () => {

-      const comment = { content: '测试评论', author: 'user1' };

-      mockAxios.onPost('/help/posts/post456/comments', comment).reply(200, { code: 200 });

+    it('应该正确发送评论数据(无图片)', async () => {

+      const postId = 'post456';

+      const commentData = {

+        authorId: 'user1',

+        content: '测试评论'

+      };

       

-      const response = await addPostComment('post456', comment);

+      mockAxios.onPost(`/help/posts/${postId}/comments`).reply(config => {

+        const data = config.data;

+        expect(data.get('authorId')).toBe(commentData.authorId);

+        expect(data.get('content')).toBe(commentData.content);

+        expect(data.has('image')).toBe(false);

+        return [200, { code: 200 }];

+      });

+      

+      const response = await addPostComment('post456', commentData);

+      expect(response.status).toBe(200);

+    });

+    it('应该正确处理带图片的评论', async () => {

+      const postId = 'post456';

+      const commentData = {

+        authorId: 'user1',

+        content: '测试评论',

+        commentImage: new File(['content'], 'comment.jpg')

+      };

+      

+      mockAxios.onPost(`/help/posts/${postId}/comments`).reply(config => {

+        const data = config.data;

+        expect(data.get('image')).toBeInstanceOf(File);

+        return [200, { code: 200 }];

+      });

+

+      const response = await addPostComment(postId, commentData);

+      expect(response.status).toBe(200);

+    });

+  });

+  describe('deletePost - 删除帖子', () => {

+    it('应该正确发送删除请求', async () => {

+      const postId = 'post789';

+      const authorId = 'user1';

+      

+      mockAxios.onDelete(`/help/posts/${postId}`).reply(config => {

+        expect(config.params).toEqual({ authorId });

+        return [200, { code: 200 }];

+      });

+

+      const response = await deletePost(postId, authorId);

       expect(response.status).toBe(200);

     });

   });

diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx
index 97a01cc..836daa7 100644
--- a/src/components/Dashboard.jsx
+++ b/src/components/Dashboard.jsx
@@ -3,7 +3,7 @@
 // import { getUserInfo } from '../api/auth';  

 import {createTorrent, getTorrents} from '../api/torrent';

 import './Dashboard.css';

-import {createPost, getPosts} from '../api/helpPost';

+import {createPost, getPosts, getPostDetail} from '../api/helpPost';

 

 

 const Dashboard = ({onLogout}) => {

@@ -34,6 +34,8 @@
     const [helpError, setHelpError] = useState(null);

     const [currentPage, setCurrentPage] = useState(1);

     const [totalPages, setTotalPages] = useState(1);

+    const [likedPosts,setLikedPosts] = useState({});

+

 

     // 添加状态

     const [torrentPosts, setTorrentPosts] = useState([]);

@@ -179,28 +181,29 @@
     const handlePostSubmit = async (e) => {

         e.preventDefault();

         try {

-            const username = localStorage.getItem('username');

-            const response = await createPost(

-                postTitle,

-                postContent,

-                username

-            );

-

-            if (response.data.code === 200) {

-                // 刷新帖子列表

-                await fetchHelpPosts();

-                // 重置表单

-                setShowPostModal(false);

-                setPostTitle('');

-                setPostContent('');

-                setSelectedImage(null);

-            } else {

-                setHelpError(response.data.message || '发帖失败');

-            }

+          const username = localStorage.getItem('username');

+          const response = await createPost(

+            postTitle,

+            postContent,

+            username,

+            selectedImage

+          );

+          

+          if (response.data.code === 200) {

+            // 刷新帖子列表

+            await fetchHelpPosts(currentPage);

+            // 重置表单

+            setShowPostModal(false);

+            setPostTitle('');

+            setPostContent('');

+            setSelectedImage(null);

+          } else {

+            setHelpError(response.data.message || '发帖失败');

+          }

         } catch (err) {

-            setHelpError(err.message || '发帖失败');

+          setHelpError(err.message || '发帖失败');

         }

-    };

+      };

 

     // 获取Torrent帖子列表

     const fetchTorrentPosts = async (page = 1) => {

@@ -232,20 +235,40 @@
     const fetchHelpPosts = async (page = 1) => {

         setHelpLoading(true);

         try {

-            const response = await getPosts(page);

-            if (response.data.code === 200) {

-                setHelpPosts(response.data.data.records);

-                setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条

-                setCurrentPage(page);

-            } else {

-                setHelpError(response.data.message || '获取求助帖失败');

-            }

+          const response = await getPosts(page);

+          if (response.data.code === 200) {

+            const postsWithCounts = await Promise.all(

+              response.data.data.records.map(async (post) => {

+                try {

+                  const detailResponse = await getPostDetail(post.id);

+                  if (detailResponse.data.code === 200) {

+                    return {

+                      ...post,

+                      replyCount: detailResponse.data.data.post.replyCount || 0,

+                      isLiked: false // 根据需要添加其他字段

+                    };

+                  }

+                  return post; // 如果获取详情失败,返回原始帖子数据

+                } catch (err) {

+                  console.error(`获取帖子${post.id}详情失败:`, err);

+                  return post;

+                }

+              })

+            );

+            setHelpPosts(postsWithCounts);

+            setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条

+            setCurrentPage(page);

+          } else {

+            setHelpError(response.data.message || '获取求助帖失败');

+          }

         } catch (err) {

-            setHelpError(err.message || '获取求助帖失败');

+          setHelpError(err.message || '获取求助帖失败');

         } finally {

-            setHelpLoading(false);

+          setHelpLoading(false);

         }

-    };

+      };

+

+

     useEffect(() => {

         if (activeTab === 'help') {

             fetchHelpPosts(currentPage);

diff --git a/src/components/HelpDetail.jsx b/src/components/HelpDetail.jsx
index da50850..7e7b7c8 100644
--- a/src/components/HelpDetail.jsx
+++ b/src/components/HelpDetail.jsx
@@ -3,12 +3,14 @@
 import { 

   getPostDetail,

   addPostComment,

-  likePost

+  likePost,

+  deletePost

 } from '../api/helpPost';

 import {

   likePostComment,

   getCommentReplies,

-  addCommentReply

+  addCommentReply,

+  deleteComment

 } from '../api/helpComment';

 import './HelpDetail.css';

 

@@ -22,12 +24,13 @@
   const [loading, setLoading] = useState(true);

   const [error, setError] = useState(null);

   const [newComment, setNewComment] = useState('');

-  const [newReply, setNewReply] = useState({});

-  const [images, setImages] = useState([]);

+  const [replyContent, setReplyContent] = useState('');

+  const [replyImage, setReplyImage] = useState([]);

+  const [commentImage, setCommentImage] = useState([]);

   const [expandedReplies, setExpandedReplies] = useState({}); // 记录哪些评论的回复是展开的

   const [loadingReplies, setLoadingReplies] = useState({});

   const [setReplyingTo] = useState(null);

-  const [replyContent, setReplyContent] = useState('');

+  

 

   const [activeReplyId, setActiveReplyId] = useState(null);

     const [replyModal, setReplyModal] = useState({

@@ -58,7 +61,7 @@
       setReplyContent('');

     };

 

-    const Comment = ({ comment, onLike, onReply, isReply = false }) => {

+    const Comment = ({ comment, onLike, onReply, onDelete, isReply = false }) => {

       return (

         <div className={`comment-container ${isReply ? "is-reply" : ""}`}>

           <div className="comment-item">

@@ -76,6 +79,17 @@
                 </span>

               </div>

               <p className="comment-text">{comment.content}</p>

+              {/* 添加评论图片展示 */}

+              {comment.imageUrl && (

+                <div className="comment-image-container">

+                  <img 

+                    src={`http://localhost:8088${comment.imageUrl}`} 

+                    alt="评论图片" 

+                    className="comment-image"

+                    onClick={() => window.open(comment.imageUrl, '_blank')}

+                  />

+                </div>

+              )}

               <div className="comment-actions">

                 <button onClick={() => onLike(comment.id)}>

                   👍 ({comment.likeCount || 0})

@@ -83,6 +97,14 @@
                 <button onClick={() => onReply(comment.id, comment.authorId)}>

                   回复

                 </button>

+                {comment.authorId === localStorage.getItem('username') && (

+                  <button 

+                    className="delete-comment-btn"

+                    onClick={() => onDelete(comment.id)}

+                  >

+                    删除

+                  </button>

+                )}

               </div>

             </div>

           </div>

@@ -99,6 +121,7 @@
           onLike={handleLikeComment}

           onReply={openReplyModal}

           isReply={depth > 0}

+          onDelete={handleDeleteComment}

         />

         

         {/* 递归渲染所有回复 */}

@@ -140,6 +163,19 @@
         setError('点赞失败: ' + (err.response?.data?.message || err.message));

       }

     };

+

+     // 添加删除处理函数

+     const handleDeletePost = async (postId) => {

+      if (window.confirm('确定要删除这个帖子吗?所有评论也将被删除!')) {

+        try {

+          const username = localStorage.getItem('username');

+          await deletePost(postId, username);

+          navigate('/dashboard/help'); // 删除成功后返回求助区

+        } catch (err) {

+          setError('删除失败: ' + (err.response?.data?.message || err.message));

+        }

+      }

+    };

   

     const handleCommentSubmit = async (e) => {

       e.preventDefault();

@@ -196,6 +232,18 @@
       }

     };

 

+    const handleDeleteComment = async (commentId) => {

+      if (window.confirm('确定要删除这条评论吗?')) {

+        try {

+          const username = localStorage.getItem('username');

+          await deleteComment(commentId, username);

+          await fetchPostDetail(); // 刷新评论列表

+        } catch (err) {

+          setError('删除失败: ' + (err.response?.data?.message || err.message));

+        }

+      }

+    };

+

 

     // 修改startReply函数

     const startReply = (commentId) => {

@@ -217,15 +265,16 @@
       try {

         const username = localStorage.getItem('username');

         const response = await addCommentReply(replyModal.replyingTo, {

+          authorId: username,

           content: replyContent,

-          authorId: username

+          image: replyImage

         });

     

         console.log('回复响应:', response.data); // 调试

         

         if (response.data && response.data.code === 200) {

           await fetchPostDetail();

-    

+          setReplyContent('');

           closeReplyModal();

         }

       } catch (err) {

@@ -251,15 +300,15 @@
     }));

   };

 

-  const handleImageUpload = (e) => {

-    const files = Array.from(e.target.files);

-    const newImages = files.map(file => URL.createObjectURL(file));

-    setImages(prev => [...prev, ...newImages]);

-  };

+  // const handleImageUpload = (e) => {

+  //   const files = Array.from(e.target.files);

+  //   const newImages = files.map(file => URL.createObjectURL(file));

+  //   setImages(prev => [...prev, ...newImages]);

+  // };

 

-  const handleRemoveImage = (index) => {

-    setImages(prev => prev.filter((_, i) => i !== index));

-  };

+  // const handleRemoveImage = (index) => {

+  //   setImages(prev => prev.filter((_, i) => i !== index));

+  // };

 

   

 

@@ -284,9 +333,19 @@
             <div className="post-author">{post.authorId}</div>

             <div className="post-date">

               {new Date(post.createTime).toLocaleString()}

-            </div>

+              </div>

           </div>

-          {post.isSolved && <span className="solved-badge">已解决</span>}

+          {post.isSolved && <span ClassName="solved-badge">已解决</span>}

+          <div classname="delete-post">

+            {post.authorId === localStorage.getItem('username') && (

+              <button 

+                className="delete-button"

+                onClick={() => handleDeletePost(post.id)}

+              >

+                删除帖子

+              </button>

+            )}

+          </div>

         </div>

         

         <h1 className="post-title">{post.title}</h1>

@@ -295,6 +354,21 @@
           {post.content.split('\n').map((para, i) => (

             <p key={i}>{para}</p>

           ))}

+          {/* 添加帖子图片展示 */}

+          {post.imageUrl && (

+            <div className="post-image-container">

+              <img 

+                src={`http://localhost:8088${post.imageUrl}`} 

+                alt="帖子图片" 

+                className="post-image"

+                // onError={(e) => {

+                //   e.target.onerror = null; 

+                //   e.target.src = 'https://via.placeholder.com/400x300?text=图片加载失败';

+                //   console.error('图片加载失败:', post.imageUrl);

+                // }}

+              />

+            </div>

+          )}

         </div>

         

         <div className="post-actions">

@@ -325,8 +399,21 @@
             required

           />

           <button type="submit">发表评论</button>

+

+          {/* 图片上传部分 */}

+          <div className="form-group">

+            <div className="upload-image-btn">

+              <input 

+                type="file" 

+                accept="image/*" 

+                onChange={(e) => setCommentImage(e.target.files[0])} 

+                data-testid="comment-image-input"  

+              />

+            </div>

+          </div>

         </form>

         

+        

         <div className="comment-list">

           {comments.map(comment => renderComment(comment))}

         </div>

@@ -347,6 +434,18 @@
                   autoFocus

                   required

                 />

+

+                {/* 图片上传部分 */}

+                <div className="form-group">

+                  <div className="upload-image-btn">

+                    <input 

+                      type="file" 

+                      accept="image/*" 

+                      onChange={(e) => setReplyImage(e.target.files[0])} 

+                    />

+                  </div>

+                </div>

+

                 <div className="modal-actions">

                   <button type="button" onClick={closeReplyModal} className="cancel-btn">

                     取消