帖子的评论的前端

Change-Id: Ida12e1874052498f22e40d0604e936f0d65d5fb6
diff --git a/src/api/__tests__/comment.test.js b/src/api/__tests__/comment.test.js
new file mode 100644
index 0000000..a55dba7
--- /dev/null
+++ b/src/api/__tests__/comment.test.js
@@ -0,0 +1,83 @@
+// src/api/__tests__/comment.test.js
+const axios = require('axios');
+jest.mock('axios');
+
+const {
+    createComment,
+    deleteComment,
+    updateComment,
+    getCommentsByPostId,
+    likeComment,
+    unlikeComment
+} = require('../comment');
+
+describe('Comment API Tests', () => {
+    beforeEach(() => {
+        jest.clearAllMocks();
+    });
+
+    test('createComment should send POST request', async () => {
+        const mockData = { userid: 1, postid: 2, postCommentcontent: 'Test comment' };
+        axios.post.mockResolvedValue({ data: mockData });
+
+        const response = await createComment(mockData);
+
+        expect(axios.post).toHaveBeenCalledWith('http://localhost:8080/comment/create', mockData);
+        expect(response.data).toEqual(mockData);
+    });
+
+    test('deleteComment should send DELETE request', async () => {
+        axios.delete.mockResolvedValue({ data: true });
+
+        const response = await deleteComment(123);
+
+        expect(axios.delete).toHaveBeenCalledWith('http://localhost:8080/comment/delete/123');
+        expect(response.data).toBe(true);
+    });
+
+    test('updateComment should send PUT request', async () => {
+        const updatedData = { commentid: 1, postCommentcontent: 'Updated comment' };
+        axios.put.mockResolvedValue({ data: true });
+
+        const response = await updateComment(updatedData);
+
+        expect(axios.put).toHaveBeenCalledWith('http://localhost:8080/comment/update', updatedData);
+        expect(response.data).toBe(true);
+    });
+
+    test('getCommentsByPostId should fetch comments by post ID', async () => {
+        const mockResponse = { data: [{ commentid: 1, postCommentcontent: 'Nice!' }] };
+        axios.get.mockResolvedValue(mockResponse);
+
+        const response = await getCommentsByPostId(5);
+
+        expect(axios.get).toHaveBeenCalledWith('http://localhost:8080/comment/post/5');
+        expect(response).toEqual(mockResponse.data);
+    });
+
+    test('getCommentsByPostId should return empty array on error', async () => {
+        axios.get.mockRejectedValue(new Error('Network Error'));
+
+        const response = await getCommentsByPostId(99);
+
+        expect(response).toEqual([]);
+    });
+
+    test('likeComment should send POST request', async () => {
+        axios.post.mockResolvedValue({ data: true });
+
+        const response = await likeComment(10);
+
+        expect(axios.post).toHaveBeenCalledWith('http://localhost:8080/comment/like/10');
+        expect(response.data).toBe(true);
+    });
+
+    test('unlikeComment should send POST request', async () => {
+        axios.post.mockResolvedValue({ data: true });
+
+        const response = await unlikeComment(10);
+
+        expect(axios.post).toHaveBeenCalledWith('http://localhost:8080/comment/unlike/10');
+        expect(response.data).toBe(true);
+    });
+});
diff --git a/src/api/comment.js b/src/api/comment.js
new file mode 100644
index 0000000..28b2b4e
--- /dev/null
+++ b/src/api/comment.js
@@ -0,0 +1,40 @@
+import axios from 'axios';
+
+const BASE_URL = 'http://localhost:8080/comment';
+
+// 创建评论
+export const createComment = (commentData) => {
+    return axios.post(`${BASE_URL}/create`, commentData);
+};
+
+// 删除评论
+export const deleteComment = (commentId) => {
+    return axios.delete(`${BASE_URL}/delete/${commentId}`);
+};
+
+// 更新评论
+export const updateComment = (commentData) => {
+    return axios.put(`${BASE_URL}/update`, commentData);
+};
+
+// 获取某个帖子的所有评论
+// comment.js
+export async function getCommentsByPostId(postid) {
+    try {
+        const response = await axios.get(`${BASE_URL}/post/${postid}`);
+        return Array.isArray(response.data) ? response.data : []; // 确保返回数据是数组
+    } catch (error) {
+        console.error('获取评论失败', error);
+        return [];
+    }
+}
+
+// 点赞评论
+export const likeComment = (commentId) => {
+    return axios.post(`${BASE_URL}/like/${commentId}`);
+};
+
+// 取消点赞评论
+export const unlikeComment = (commentId) => {
+    return axios.post(`${BASE_URL}/unlike/${commentId}`);
+};
diff --git a/src/components/Comment.css b/src/components/Comment.css
new file mode 100644
index 0000000..e5529d3
--- /dev/null
+++ b/src/components/Comment.css
@@ -0,0 +1,128 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
+
+.comment-card {
+    max-width: 700px;
+    margin: 20px auto;
+    border-radius: 8px;
+    box-shadow: 0 4px 12px rgb(0 0 0 / 0.1);
+    background: #fff;
+    padding: 16px;
+}
+
+.comment-textarea {
+    border-radius: 6px;
+    resize: none;
+    font-size: 16px;
+}
+
+.text-right {
+    text-align: right;
+}
+
+.mt-2 {
+    margin-top: 0.5rem;
+}
+
+.mt-6 {
+    margin-top: 1.5rem;
+}
+
+.comment-time {
+    font-size: 12px;
+    color: #888;
+    margin-left: 8px;
+    white-space: nowrap;
+}
+
+.comment-username {
+    font-weight: 600;
+    color: #1890ff;
+    user-select: none;
+}
+
+.comment-content {
+    font-size: 14px;
+    line-height: 1.5;
+    color: #333;
+    white-space: pre-wrap;
+}
+
+/* 头像带蓝色边框 */
+.avatar-with-border {
+    border: 2px solid #1890ff !important;
+    border-radius: 50% !important;
+}
+
+
+
+body {
+    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+    background-color: #fffaf5;
+}
+
+/* 全局动画效果 */
+.transition {
+    transition: all 0.3s ease;
+}
+
+/* 卡片阴影效果 */
+.shadow-lg {
+    box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
+}
+
+.shadow-md {
+    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+}
+
+/* 圆角 */
+.rounded-xl {
+    border-radius: 1rem;
+}
+
+.rounded-lg {
+    border-radius: 0.75rem;
+}
+
+/* 渐变背景 */
+.bg-gradient-to-r {
+    background-size: 200% auto;
+    transition: background-position 0.5s ease;
+}
+
+.bg-gradient-to-r:hover {
+    background-position: right center;
+}
+
+/* 表单输入框动画 */
+input:focus,
+textarea:focus {
+    transform: translateY(-1px);
+}
+
+/* 卡片悬停效果 */
+.bg-white:hover {
+    transform: translateY(-3px);
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+    .text-xl {
+        font-size: 1.25rem;
+    }
+
+    .text-2xl {
+        font-size: 1.5rem;
+    }
+
+    .p-6 {
+        padding: 1.25rem;
+    }
+
+    .p-12 {
+        padding: 2rem;
+    }
+
+    .grid-cols-2 {
+        grid-template-columns: 1fr;
+    }
+}
\ No newline at end of file
diff --git a/src/components/Comment.jsx b/src/components/Comment.jsx
new file mode 100644
index 0000000..704636e
--- /dev/null
+++ b/src/components/Comment.jsx
@@ -0,0 +1,290 @@
+import React, { useState, useEffect } from 'react';
+import {
+    getCommentsByPostId,
+    createComment,
+    deleteComment,
+    likeComment,
+    unlikeComment,
+} from '../api/comment';
+
+import {
+    Card,
+    Button,
+    Input,
+    List,
+    Avatar,
+    Popconfirm,
+    message,
+    Space,
+    Tooltip
+} from 'antd';
+
+import {
+    LikeOutlined,
+    LikeFilled,
+    DeleteOutlined
+} from '@ant-design/icons';
+
+import axios from 'axios';
+import './Comment.css';
+
+const { TextArea } = Input;
+
+const Comment = ({ postId, currentUser }) => {
+    const [comments, setComments] = useState([]);
+    const [newContent, setNewContent] = useState('');
+    const [loading, setLoading] = useState(false);
+    const [userInfoMap, setUserInfoMap] = useState({});
+    const [isCommenting, setIsCommenting] = useState(false);
+
+    useEffect(() => {
+        loadComments();
+    }, [postId, currentUser]);
+
+    const loadComments = async () => {
+        try {
+            if (!postId) return;
+
+            const commentList = await getCommentsByPostId(postId);
+            setComments(commentList);
+
+            // 修复1: 统一使用 userid 而不是 id
+            const userIds = [
+                ...new Set(commentList.map(c => c.userid))
+            ];
+
+            // 如果当前用户存在,添加其 userid
+            if (currentUser && currentUser.userid) {
+                userIds.push(currentUser.userid);
+            }
+
+            // 批量获取用户信息
+            const userInfoPromises = userIds.map(async (id) => {
+                try {
+                    const res = await axios.get(`http://localhost:8080/user/getDecoration?userid=${id}`);
+                    if (res.data?.success) {
+                        return { id, data: res.data.data };
+                    }
+                    return { id, data: { username: `用户${id}`, image: '' } };
+                } catch (error) {
+                    return { id, data: { username: `用户${id}`, image: '' } };
+                }
+            });
+
+            const userInfoResults = await Promise.all(userInfoPromises);
+
+            // 构建用户信息映射
+            const newUserInfoMap = {};
+            userInfoResults.forEach(({ id, data }) => {
+                newUserInfoMap[id] = data;
+            });
+
+            setUserInfoMap(newUserInfoMap);
+
+        } catch (error) {
+            console.error('加载评论失败:', error);
+            message.error('加载评论失败');
+        }
+    };
+
+    const handleCreate = async () => {
+        // 检查用户是否登录
+        if (!currentUser) {
+            message.error('请先登录后再评论');
+            return;
+        }
+
+        // 修复2: 确保当前用户有 userid 属性
+        if (!currentUser.userid) {
+            message.error('用户信息异常,请重新登录');
+            return;
+        }
+
+        if (!newContent.trim()) {
+            message.warning('评论内容不能为空');
+            return;
+        }
+
+        setIsCommenting(true);
+        setLoading(true);
+
+        // 修复3: 使用 currentUser.userid 而非 currentUser.id
+        const commentData = {
+            postid: postId,
+            userid: Number(currentUser.userid),
+            postCommentcontent: newContent,
+            commenttime: new Date().toISOString()
+        };
+
+        try {
+            await createComment(commentData);
+            setNewContent('');
+            await loadComments(); // 重新加载评论,确保新评论显示
+            message.success('评论发布成功');
+        } catch (error) {
+            console.error('发布评论失败:', error);
+            message.error('发布评论失败');
+        } finally {
+            setLoading(false);
+            setIsCommenting(false);
+        }
+    };
+
+    const handleDelete = async (commentid) => {
+        try {
+            await deleteComment(commentid);
+            message.success('删除成功');
+            loadComments();
+        } catch (error) {
+            console.error('删除评论失败:', error);
+            message.error('删除失败');
+        }
+    };
+
+    const handleLike = async (commentid) => {
+        try {
+            await likeComment(commentid);
+            loadComments();
+        } catch (error) {
+            console.error('点赞失败:', error);
+            message.error('操作失败');
+        }
+    };
+
+    const handleUnlike = async (commentid) => {
+        try {
+            await unlikeComment(commentid);
+            loadComments();
+        } catch (error) {
+            console.error('取消点赞失败:', error);
+            message.error('操作失败');
+        }
+    };
+
+    // 获取用户信息(包括当前登录用户)
+    const getUserInfo = (userId) => {
+        // 修复4: 使用 currentUser.userid 而非 currentUser.id
+        if (currentUser && currentUser.userid && userId === currentUser.userid) {
+            return {
+                username: currentUser.username || `用户${userId}`,
+                image: currentUser.image || '',
+                decoration: currentUser.decoration || ''
+            };
+        }
+
+        // 对于其他用户,从userInfoMap中获取
+        return userInfoMap[userId] || {
+            username: `用户${userId}`,
+            image: '',
+            decoration: ''
+        };
+    };
+
+    return (
+        <Card title="评论区" bordered={false} className="comment-card">
+            {/* 评论输入框 - 根据用户登录状态调整 */}
+            {currentUser ? (
+                <>
+                    <TextArea
+                        rows={3}
+                        placeholder={`${currentUser.username},留下你的评论...`}
+                        value={newContent}
+                        onChange={(e) => setNewContent(e.target.value)}
+                        className="comment-textarea"
+                        disabled={loading}
+                    />
+                    <div className="text-right mt-2">
+                        <Button
+                            type="primary"
+                            onClick={handleCreate}
+                            loading={loading || isCommenting}
+                            disabled={isCommenting}
+                        >
+                            {isCommenting ? '发布中...' : '发布评论'}
+                        </Button>
+                    </div>
+                </>
+            ) : (
+                <div className="login-prompt">
+                    请<a href="/login" className="login-link">登录</a>后发表评论
+                </div>
+            )}
+
+            {/* 评论列表 */}
+            <List
+                itemLayout="vertical"
+                dataSource={comments}
+                locale={{ emptyText: '暂无评论' }}
+                className="mt-6"
+                renderItem={(item) => {
+                    const user = getUserInfo(item.userid);
+                    // 修复5: 使用 userid 而非 id 比较当前用户
+                    const isCurrentUser = currentUser && currentUser.userid === item.userid;
+
+                    return (
+                        <List.Item
+                            key={item.commentid}
+                            className={isCurrentUser ? "current-user-comment" : ""}
+                            actions={[
+                                <Tooltip title="点赞" key="like">
+                                    <Space>
+                                        <Button
+                                            icon={<LikeOutlined />}
+                                            size="small"
+                                            onClick={() => handleLike(item.commentid)}
+                                        />
+                                        {item.likes}
+                                    </Space>
+                                </Tooltip>,
+                                <Tooltip title="取消点赞" key="unlike">
+                                    <Button
+                                        icon={<LikeFilled style={{ color: '#fadb14' }} />}
+                                        size="small"
+                                        onClick={() => handleUnlike(item.commentid)}
+                                    />
+                                </Tooltip>,
+                                isCurrentUser && (
+                                    <Popconfirm
+                                        title="确定要删除这条评论吗?"
+                                        onConfirm={() => handleDelete(item.commentid)}
+                                        okText="删除"
+                                        cancelText="取消"
+                                        key="delete"
+                                    >
+                                        <Button icon={<DeleteOutlined />} size="small" danger />
+                                    </Popconfirm>
+                                )
+                            ]}
+                            extra={
+                                <div className="comment-time">
+                                    {new Date(item.commenttime).toLocaleString()}
+                                </div>
+                            }
+                        >
+                            <List.Item.Meta
+                                avatar={
+                                    <Avatar
+                                        src={user.image || undefined}
+                                        alt={user.username}
+                                        className="comment-avatar"
+                                    >
+                                        {!user.image && user.username ? user.username.charAt(0).toUpperCase() : ''}
+                                    </Avatar>
+                                }
+                                title={
+                                    <span className="comment-username">
+                                        {user.username || `用户${item.userid}`}
+                                        {isCurrentUser && <span className="current-user-tag">(我)</span>}
+                                    </span>
+                                }
+                                description={<div className="comment-content">{item.postCommentcontent}</div>}
+                            />
+                        </List.Item>
+                    );
+                }}
+            />
+        </Card>
+    );
+};
+
+export default Comment;
\ No newline at end of file