前段
Change-Id: I718d4d07ea03c6d2b6bcbd4d426c5d1af2201bf4
diff --git a/src/components/HelpDetail.jsx b/src/components/HelpDetail.jsx
new file mode 100644
index 0000000..da50850
--- /dev/null
+++ b/src/components/HelpDetail.jsx
@@ -0,0 +1,368 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { useParams, useNavigate, useLocation } from 'react-router-dom';
+import {
+ getPostDetail,
+ addPostComment,
+ likePost
+} from '../api/helpPost';
+import {
+ likePostComment,
+ getCommentReplies,
+ addCommentReply
+} from '../api/helpComment';
+import './HelpDetail.css';
+
+const HelpDetail = () => {
+ const { id } = useParams();
+ const navigate = useNavigate();
+ const location = useLocation();
+ const fileInputRef = useRef(null);
+ const [post, setPost] = useState(null);
+ const [comments, setComments] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [newComment, setNewComment] = useState('');
+ const [newReply, setNewReply] = useState({});
+ const [images, setImages] = 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({
+ visible: false,
+ replyingTo: null,
+ replyingToUsername: '',
+ isReply: false
+ });
+
+ // 确保openReplyModal接收username参数
+ const openReplyModal = (commentId, username) => {
+ setReplyModal({
+ visible: true,
+ replyingTo: commentId,
+ replyingToUsername: username, // 确保这里接收username
+ isReply: false
+ });
+ };
+
+ // 关闭回复弹窗
+ const closeReplyModal = () => {
+ setReplyModal({
+ visible: false,
+ replyingTo: null,
+ replyingToUsername: '',
+ isReply: false
+ });
+ setReplyContent('');
+ };
+
+ const Comment = ({ comment, onLike, onReply, isReply = false }) => {
+ return (
+ <div className={`comment-container ${isReply ? "is-reply" : ""}`}>
+ <div className="comment-item">
+ <div className="comment-avatar">
+ {(comment.authorId || "?").charAt(0)} {/* 修复点 */}
+ </div>
+ <div className="comment-content">
+ <div className="comment-header">
+ <span className="comment-user">{comment.authorId || "匿名用户"}</span>
+ {comment.replyTo && (
+ <span className="reply-to">回复 @{comment.replyTo}</span>
+ )}
+ <span className="comment-time">
+ {new Date(comment.createTime).toLocaleString()}
+ </span>
+ </div>
+ <p className="comment-text">{comment.content}</p>
+ <div className="comment-actions">
+ <button onClick={() => onLike(comment.id)}>
+ 👍 ({comment.likeCount || 0})
+ </button>
+ <button onClick={() => onReply(comment.id, comment.authorId)}>
+ 回复
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ };
+
+ // 递归渲染评论组件
+ const renderComment = (comment, depth = 0) => {
+ return (
+ <div key={comment.id} style={{ marginLeft: `${depth * 30}px` }}>
+ <Comment
+ comment={comment}
+ onLike={handleLikeComment}
+ onReply={openReplyModal}
+ isReply={depth > 0}
+ />
+
+ {/* 递归渲染所有回复 */}
+ {comment.replies && comment.replies.map(reply =>
+ renderComment(reply, depth + 1)
+ )}
+ </div>
+ );
+ };
+
+
+ const fetchPostDetail = async () => {
+ try {
+ setLoading(true);
+ const response = await getPostDetail(id);
+ console.log('API Response:', JSON.parse(JSON.stringify(response.data.data.comments))); // 深度拷贝避免Proxy影响
+ setPost(response.data.data.post);
+ setComments(response.data.data.comments);
+ } catch (err) {
+ setError(err.response?.data?.message || '获取帖子详情失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchPostDetail();
+ }, [id]);
+
+ // 点赞帖子
+ const handleLikePost = async () => {
+ try {
+ await likePost(id);
+ setPost(prev => ({
+ ...prev,
+ likeCount: prev.likeCount + 1
+ }));
+ } catch (err) {
+ setError('点赞失败: ' + (err.response?.data?.message || err.message));
+ }
+ };
+
+ const handleCommentSubmit = async (e) => {
+ e.preventDefault();
+ if (!newComment.trim()) return;
+
+ try {
+ const username = localStorage.getItem('username');
+ const response = await addPostComment(id, {
+ content: newComment,
+ authorId: username
+ });
+
+ // 修改这里的响应处理逻辑
+ if (response.data && response.data.code === 200) {
+ await fetchPostDetail();
+
+ setNewComment('');
+ } else {
+ setError(response.data.message || '评论失败');
+ }
+ } catch (err) {
+ setError('评论失败: ' + (err.response?.data?.message || err.message));
+ }
+ };
+
+
+ const handleLikeComment = async (commentId) => {
+ try {
+ await likePostComment(commentId);
+
+ // 递归更新评论点赞数
+ const updateComments = (comments) => {
+ return comments.map(comment => {
+ // 当前评论匹配
+ if (comment.id === commentId) {
+ return { ...comment, likeCount: comment.likeCount + 1 };
+ }
+
+ // 递归处理回复
+ if (comment.replies && comment.replies.length > 0) {
+ return {
+ ...comment,
+ replies: updateComments(comment.replies)
+ };
+ }
+
+ return comment;
+ });
+ };
+
+ setComments(prev => updateComments(prev));
+ } catch (err) {
+ setError('点赞失败: ' + (err.response?.data?.message || err.message));
+ }
+ };
+
+
+ // 修改startReply函数
+ const startReply = (commentId) => {
+ if (activeReplyId === commentId) {
+ // 如果点击的是已经激活的回复按钮,则关闭
+ setActiveReplyId(null);
+ setReplyingTo(null);
+ } else {
+ // 否则打开新的回复框
+ setActiveReplyId(commentId);
+ setReplyingTo(commentId);
+ }
+ };
+
+ const handleReplySubmit = async (e) => {
+ e.preventDefault();
+ if (!replyContent.trim()) return;
+
+ try {
+ const username = localStorage.getItem('username');
+ const response = await addCommentReply(replyModal.replyingTo, {
+ content: replyContent,
+ authorId: username
+ });
+
+ console.log('回复响应:', response.data); // 调试
+
+ if (response.data && response.data.code === 200) {
+ await fetchPostDetail();
+
+ closeReplyModal();
+ }
+ } catch (err) {
+ console.error('回复错误:', err);
+ setError('回复失败: ' + (err.response?.data?.message || err.message));
+ }
+ };
+
+
+ // 返回按钮
+ const handleBack = () => {
+ const fromTab = location.state?.fromTab || 'share';
+ navigate(`/dashboard/help`);
+ };
+
+
+
+ const handleMarkSolved = () => {
+ // TODO: 实现标记为已解决的功能
+ setPost(prev => ({
+ ...prev,
+ isSolved: !prev.isSolved
+ }));
+ };
+
+ 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));
+ };
+
+
+
+ if (loading) return <div className="loading">加载中...</div>;
+ if (error) return <div className="error">{error}</div>;
+ if (!post) return <div className="error">帖子不存在</div>;
+
+ return (
+ <div className="help-detail-container">
+ <button className="back-button" onClick={handleBack}>
+ ← 返回求助区
+ </button>
+
+ <div className={`help-post ${post.isSolved ? 'solved' : ''}`}>
+ <div className="post-header">
+ <img
+ src={post.authorAvatar || 'https://via.placeholder.com/40'}
+ alt={post.authorId}
+ className="post-avatar"
+ />
+ <div className="post-meta">
+ <div className="post-author">{post.authorId}</div>
+ <div className="post-date">
+ {new Date(post.createTime).toLocaleString()}
+ </div>
+ </div>
+ {post.isSolved && <span className="solved-badge">已解决</span>}
+ </div>
+
+ <h1 className="post-title">{post.title}</h1>
+
+ <div className="post-content">
+ {post.content.split('\n').map((para, i) => (
+ <p key={i}>{para}</p>
+ ))}
+ </div>
+
+ <div className="post-actions">
+ <button
+ className={`like-button ${post.isLiked ? 'liked' : ''}`}
+ onClick={handleLikePost}
+ >
+ 👍 点赞 ({post.likeCount})
+ </button>
+ <button
+ className={`solve-button ${post.isSolved ? 'solved' : ''}`}
+ onClick={handleMarkSolved}
+ >
+ {post.isSolved ? '✓ 已解决' : '标记为已解决'}
+ </button>
+ </div>
+ </div>
+
+ <div className="comments-section">
+ <h2>评论 ({post.replyCount})</h2>
+
+ <form onSubmit={handleCommentSubmit} className="comment-form">
+ <textarea
+ value={newComment}
+ onChange={(e) => setNewComment(e.target.value)}
+ placeholder="写下你的评论..."
+ rows="3"
+ required
+ />
+ <button type="submit">发表评论</button>
+ </form>
+
+ <div className="comment-list">
+ {comments.map(comment => renderComment(comment))}
+ </div>
+
+ {replyModal.visible && (
+ <div className="reply-modal-overlay">
+ <div className="reply-modal">
+ <div className="modal-header">
+ <h3>回复 @{replyModal.replyingToUsername}</h3>
+ <button onClick={closeReplyModal} className="close-modal">×</button>
+ </div>
+ <form onSubmit={handleReplySubmit}>
+ <textarea
+ value={replyContent}
+ onChange={(e) => setReplyContent(e.target.value)}
+ placeholder={`回复 @${replyModal.replyingToUsername}...`}
+ rows="5"
+ autoFocus
+ required
+ />
+ <div className="modal-actions">
+ <button type="button" onClick={closeReplyModal} className="cancel-btn">
+ 取消
+ </button>
+ <button type="submit" className="submit-btn">
+ 发送回复
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+ )}
+
+ </div>
+ </div>
+ );
+};
+
+export default HelpDetail;
\ No newline at end of file