ym923 | ed3de70 | 2025-06-09 20:10:16 +0800 | [diff] [blame^] | 1 | import React, { useState, useEffect } from 'react'; |
| 2 | import { |
| 3 | getCommentsByPostId, |
| 4 | createComment, |
| 5 | deleteComment, |
| 6 | likeComment, |
| 7 | unlikeComment, |
| 8 | } from '../api/comment'; |
| 9 | |
| 10 | import { |
| 11 | Card, |
| 12 | Button, |
| 13 | Input, |
| 14 | List, |
| 15 | Avatar, |
| 16 | Popconfirm, |
| 17 | message, |
| 18 | Space, |
| 19 | Tooltip |
| 20 | } from 'antd'; |
| 21 | |
| 22 | import { |
| 23 | LikeOutlined, |
| 24 | LikeFilled, |
| 25 | DeleteOutlined |
| 26 | } from '@ant-design/icons'; |
| 27 | |
| 28 | import axios from 'axios'; |
| 29 | import './Comment.css'; |
| 30 | |
| 31 | const { TextArea } = Input; |
| 32 | |
| 33 | const Comment = ({ postId, currentUser }) => { |
| 34 | const [comments, setComments] = useState([]); |
| 35 | const [newContent, setNewContent] = useState(''); |
| 36 | const [loading, setLoading] = useState(false); |
| 37 | const [userInfoMap, setUserInfoMap] = useState({}); |
| 38 | const [isCommenting, setIsCommenting] = useState(false); |
| 39 | |
| 40 | useEffect(() => { |
| 41 | loadComments(); |
| 42 | }, [postId, currentUser]); |
| 43 | |
| 44 | const loadComments = async () => { |
| 45 | try { |
| 46 | if (!postId) return; |
| 47 | |
| 48 | const commentList = await getCommentsByPostId(postId); |
| 49 | setComments(commentList); |
| 50 | |
| 51 | // 修复1: 统一使用 userid 而不是 id |
| 52 | const userIds = [ |
| 53 | ...new Set(commentList.map(c => c.userid)) |
| 54 | ]; |
| 55 | |
| 56 | // 如果当前用户存在,添加其 userid |
| 57 | if (currentUser && currentUser.userid) { |
| 58 | userIds.push(currentUser.userid); |
| 59 | } |
| 60 | |
| 61 | // 批量获取用户信息 |
| 62 | const userInfoPromises = userIds.map(async (id) => { |
| 63 | try { |
| 64 | const res = await axios.get(`http://localhost:8080/user/getDecoration?userid=${id}`); |
| 65 | if (res.data?.success) { |
| 66 | return { id, data: res.data.data }; |
| 67 | } |
| 68 | return { id, data: { username: `用户${id}`, image: '' } }; |
| 69 | } catch (error) { |
| 70 | return { id, data: { username: `用户${id}`, image: '' } }; |
| 71 | } |
| 72 | }); |
| 73 | |
| 74 | const userInfoResults = await Promise.all(userInfoPromises); |
| 75 | |
| 76 | // 构建用户信息映射 |
| 77 | const newUserInfoMap = {}; |
| 78 | userInfoResults.forEach(({ id, data }) => { |
| 79 | newUserInfoMap[id] = data; |
| 80 | }); |
| 81 | |
| 82 | setUserInfoMap(newUserInfoMap); |
| 83 | |
| 84 | } catch (error) { |
| 85 | console.error('加载评论失败:', error); |
| 86 | message.error('加载评论失败'); |
| 87 | } |
| 88 | }; |
| 89 | |
| 90 | const handleCreate = async () => { |
| 91 | // 检查用户是否登录 |
| 92 | if (!currentUser) { |
| 93 | message.error('请先登录后再评论'); |
| 94 | return; |
| 95 | } |
| 96 | |
| 97 | // 修复2: 确保当前用户有 userid 属性 |
| 98 | if (!currentUser.userid) { |
| 99 | message.error('用户信息异常,请重新登录'); |
| 100 | return; |
| 101 | } |
| 102 | |
| 103 | if (!newContent.trim()) { |
| 104 | message.warning('评论内容不能为空'); |
| 105 | return; |
| 106 | } |
| 107 | |
| 108 | setIsCommenting(true); |
| 109 | setLoading(true); |
| 110 | |
| 111 | // 修复3: 使用 currentUser.userid 而非 currentUser.id |
| 112 | const commentData = { |
| 113 | postid: postId, |
| 114 | userid: Number(currentUser.userid), |
| 115 | postCommentcontent: newContent, |
| 116 | commenttime: new Date().toISOString() |
| 117 | }; |
| 118 | |
| 119 | try { |
| 120 | await createComment(commentData); |
| 121 | setNewContent(''); |
| 122 | await loadComments(); // 重新加载评论,确保新评论显示 |
| 123 | message.success('评论发布成功'); |
| 124 | } catch (error) { |
| 125 | console.error('发布评论失败:', error); |
| 126 | message.error('发布评论失败'); |
| 127 | } finally { |
| 128 | setLoading(false); |
| 129 | setIsCommenting(false); |
| 130 | } |
| 131 | }; |
| 132 | |
| 133 | const handleDelete = async (commentid) => { |
| 134 | try { |
| 135 | await deleteComment(commentid); |
| 136 | message.success('删除成功'); |
| 137 | loadComments(); |
| 138 | } catch (error) { |
| 139 | console.error('删除评论失败:', error); |
| 140 | message.error('删除失败'); |
| 141 | } |
| 142 | }; |
| 143 | |
| 144 | const handleLike = async (commentid) => { |
| 145 | try { |
| 146 | await likeComment(commentid); |
| 147 | loadComments(); |
| 148 | } catch (error) { |
| 149 | console.error('点赞失败:', error); |
| 150 | message.error('操作失败'); |
| 151 | } |
| 152 | }; |
| 153 | |
| 154 | const handleUnlike = async (commentid) => { |
| 155 | try { |
| 156 | await unlikeComment(commentid); |
| 157 | loadComments(); |
| 158 | } catch (error) { |
| 159 | console.error('取消点赞失败:', error); |
| 160 | message.error('操作失败'); |
| 161 | } |
| 162 | }; |
| 163 | |
| 164 | // 获取用户信息(包括当前登录用户) |
| 165 | const getUserInfo = (userId) => { |
| 166 | // 修复4: 使用 currentUser.userid 而非 currentUser.id |
| 167 | if (currentUser && currentUser.userid && userId === currentUser.userid) { |
| 168 | return { |
| 169 | username: currentUser.username || `用户${userId}`, |
| 170 | image: currentUser.image || '', |
| 171 | decoration: currentUser.decoration || '' |
| 172 | }; |
| 173 | } |
| 174 | |
| 175 | // 对于其他用户,从userInfoMap中获取 |
| 176 | return userInfoMap[userId] || { |
| 177 | username: `用户${userId}`, |
| 178 | image: '', |
| 179 | decoration: '' |
| 180 | }; |
| 181 | }; |
| 182 | |
| 183 | return ( |
| 184 | <Card title="评论区" bordered={false} className="comment-card"> |
| 185 | {/* 评论输入框 - 根据用户登录状态调整 */} |
| 186 | {currentUser ? ( |
| 187 | <> |
| 188 | <TextArea |
| 189 | rows={3} |
| 190 | placeholder={`${currentUser.username},留下你的评论...`} |
| 191 | value={newContent} |
| 192 | onChange={(e) => setNewContent(e.target.value)} |
| 193 | className="comment-textarea" |
| 194 | disabled={loading} |
| 195 | /> |
| 196 | <div className="text-right mt-2"> |
| 197 | <Button |
| 198 | type="primary" |
| 199 | onClick={handleCreate} |
| 200 | loading={loading || isCommenting} |
| 201 | disabled={isCommenting} |
| 202 | > |
| 203 | {isCommenting ? '发布中...' : '发布评论'} |
| 204 | </Button> |
| 205 | </div> |
| 206 | </> |
| 207 | ) : ( |
| 208 | <div className="login-prompt"> |
| 209 | 请<a href="/login" className="login-link">登录</a>后发表评论 |
| 210 | </div> |
| 211 | )} |
| 212 | |
| 213 | {/* 评论列表 */} |
| 214 | <List |
| 215 | itemLayout="vertical" |
| 216 | dataSource={comments} |
| 217 | locale={{ emptyText: '暂无评论' }} |
| 218 | className="mt-6" |
| 219 | renderItem={(item) => { |
| 220 | const user = getUserInfo(item.userid); |
| 221 | // 修复5: 使用 userid 而非 id 比较当前用户 |
| 222 | const isCurrentUser = currentUser && currentUser.userid === item.userid; |
| 223 | |
| 224 | return ( |
| 225 | <List.Item |
| 226 | key={item.commentid} |
| 227 | className={isCurrentUser ? "current-user-comment" : ""} |
| 228 | actions={[ |
| 229 | <Tooltip title="点赞" key="like"> |
| 230 | <Space> |
| 231 | <Button |
| 232 | icon={<LikeOutlined />} |
| 233 | size="small" |
| 234 | onClick={() => handleLike(item.commentid)} |
| 235 | /> |
| 236 | {item.likes} |
| 237 | </Space> |
| 238 | </Tooltip>, |
| 239 | <Tooltip title="取消点赞" key="unlike"> |
| 240 | <Button |
| 241 | icon={<LikeFilled style={{ color: '#fadb14' }} />} |
| 242 | size="small" |
| 243 | onClick={() => handleUnlike(item.commentid)} |
| 244 | /> |
| 245 | </Tooltip>, |
| 246 | isCurrentUser && ( |
| 247 | <Popconfirm |
| 248 | title="确定要删除这条评论吗?" |
| 249 | onConfirm={() => handleDelete(item.commentid)} |
| 250 | okText="删除" |
| 251 | cancelText="取消" |
| 252 | key="delete" |
| 253 | > |
| 254 | <Button icon={<DeleteOutlined />} size="small" danger /> |
| 255 | </Popconfirm> |
| 256 | ) |
| 257 | ]} |
| 258 | extra={ |
| 259 | <div className="comment-time"> |
| 260 | {new Date(item.commenttime).toLocaleString()} |
| 261 | </div> |
| 262 | } |
| 263 | > |
| 264 | <List.Item.Meta |
| 265 | avatar={ |
| 266 | <Avatar |
| 267 | src={user.image || undefined} |
| 268 | alt={user.username} |
| 269 | className="comment-avatar" |
| 270 | > |
| 271 | {!user.image && user.username ? user.username.charAt(0).toUpperCase() : ''} |
| 272 | </Avatar> |
| 273 | } |
| 274 | title={ |
| 275 | <span className="comment-username"> |
| 276 | {user.username || `用户${item.userid}`} |
| 277 | {isCurrentUser && <span className="current-user-tag">(我)</span>} |
| 278 | </span> |
| 279 | } |
| 280 | description={<div className="comment-content">{item.postCommentcontent}</div>} |
| 281 | /> |
| 282 | </List.Item> |
| 283 | ); |
| 284 | }} |
| 285 | /> |
| 286 | </Card> |
| 287 | ); |
| 288 | }; |
| 289 | |
| 290 | export default Comment; |