评论界面与后端连接完成

Change-Id: Id7d3f293b964c82533095f020d9720a43422ea68
diff --git a/react-ui/src/pages/Torrent/Comments/data.d.ts b/react-ui/src/pages/Torrent/Comments/data.d.ts
index 922cb29..1f3347b 100644
--- a/react-ui/src/pages/Torrent/Comments/data.d.ts
+++ b/react-ui/src/pages/Torrent/Comments/data.d.ts
@@ -13,3 +13,8 @@
     /** 父评论ID,用于回复 */
     parentId: number;
 }
+
+export interface responseSysTorrentComment {
+    /** 评论ID */
+    data: SysTorrentComment[];
+}
\ No newline at end of file
diff --git a/react-ui/src/pages/Torrent/Comments/index.tsx b/react-ui/src/pages/Torrent/Comments/index.tsx
index 9a9f25a..58167f7 100644
--- a/react-ui/src/pages/Torrent/Comments/index.tsx
+++ b/react-ui/src/pages/Torrent/Comments/index.tsx
@@ -1,8 +1,10 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
 import { useParams, useNavigate } from 'react-router-dom';
-import { Button, Card, Form, Input, List, message, Avatar } from 'antd';
-import { ArrowLeftOutlined } from '@ant-design/icons';
+import { Button, Card, Form, Input, List, message, Avatar, Spin, Empty } from 'antd';
+import { ArrowLeftOutlined, UserOutlined } from '@ant-design/icons';
 import { Layout } from 'antd';
+import { listComments, addComment } from './service';
+import { responseSysTorrentComment, SysTorrentComment } from './data';
 
 const { Content } = Layout;
 const { TextArea } = Input;
@@ -21,66 +23,108 @@
     const [form] = Form.useForm();
     const [comments, setComments] = useState<CommentItem[]>([]);
     const [submitting, setSubmitting] = useState(false);
+    const [loading, setLoading] = useState(false);
 
-    // 获取评论列表
-    const fetchComments = async () => {
+    // 格式化时间
+    const formatTime = (time: string | Date | null | undefined): string => {
+        if (!time) return '未知时间';
+
         try {
-            // 模拟评论数据
-            const mockComments: CommentItem[] = [
-                {
-                    id: 1,
-                    content: '这个种子非常好,下载速度很快!',
-                    createTime: '2024-01-15 14:30:00',
-                    createBy: '张三',
-                    torrentId: Number(torrentId)
-                },
-                {
-                    id: 2,
-                    content: '资源质量很高,感谢分享。',
-                    createTime: '2024-01-15 15:20:00',
-                    createBy: '李四',
-                    torrentId: Number(torrentId)
-                },
-                {
-                    id: 3,
-                    content: '这个版本很完整,推荐下载!',
-                    createTime: '2024-01-15 16:45:00',
-                    createBy: '王五',
-                    torrentId: Number(torrentId)
-                },
-                {
-                    id: 1,
-                    content: '这个种子非常好,下载速度很快!',
-                    createTime: '2024-01-15 14:30:00',
-                    createBy: '张三',
-                    torrentId: Number(torrentId)
-                },
-                {
-                    id: 2,
-                    content: '资源质量很高,感谢分享。',
-                    createTime: '2024-01-15 15:20:00',
-                    createBy: '李四',
-                    torrentId: Number(torrentId)
-                },
-                {
-                    id: 3,
-                    content: '这个版本很完整,推荐下载!',
-                    createTime: '2024-01-15 16:45:00',
-                    createBy: '王五',
-                    torrentId: Number(torrentId)
-                }
-            ];
-            setComments(mockComments);
+            const date = new Date(time);
+            const now = new Date();
+            const diff = now.getTime() - date.getTime();
+
+            // 小于1分钟
+            if (diff < 60 * 1000) {
+                return '刚刚';
+            }
+            // 小于1小时
+            if (diff < 60 * 60 * 1000) {
+                return `${Math.floor(diff / (60 * 1000))}分钟前`;
+            }
+            // 小于24小时
+            if (diff < 24 * 60 * 60 * 1000) {
+                return `${Math.floor(diff / (60 * 60 * 1000))}小时前`;
+            }
+            // 小于7天
+            if (diff < 7 * 24 * 60 * 60 * 1000) {
+                return `${Math.floor(diff / (24 * 60 * 60 * 1000))}天前`;
+            }
+
+            // 否则显示具体日期
+            return date.toLocaleDateString('zh-CN', {
+                year: 'numeric',
+                month: '2-digit',
+                day: '2-digit',
+                hour: '2-digit',
+                minute: '2-digit'
+            });
         } catch (error) {
-            message.error('获取评论失败');
+            return '未知时间';
         }
     };
 
+    // 获取用户显示名称
+    const getUserDisplayName = (userId: number | null | undefined): string => {
+        if (!userId) return '匿名用户';
+        return `用户${userId}`;
+    };
+
+    // 获取用户头像字符
+    const getAvatarChar = (userName: string): string => {
+        if (!userName || userName === '匿名用户') return '?';
+        // 如果是"用户123"格式,取数字的最后一位
+        const match = userName.match(/\d+$/);
+        if (match) {
+            return match[0].slice(-1);
+        }
+        return userName[0].toUpperCase();
+    };
+
+    // 获取评论列表
+    const fetchComments = useCallback(async () => {
+        if (!torrentId) return;
+
+        setLoading(true);
+        try {
+
+            const res = await listComments(Number(torrentId));
+            console.log('获取评论列表:', res);
+            if (res) {
+                const formattedComments: SysTorrentComment[] = res.data.map((comment: SysTorrentComment) => ({
+                    id: comment.commentId ?? 0,
+                    content: comment.content ?? '无内容',
+                    createTime: comment.createTime || '',
+                    createBy: getUserDisplayName(comment.userId),
+                    torrentId: comment.torrentId ?? 0,
+                }));
+
+                // 按时间倒序排列,最新的在前面
+                formattedComments.sort((a, b) => {
+                    const timeA = a.createTime ? new Date(a.createTime).getTime() : 0;
+                    const timeB = b.createTime ? new Date(b.createTime).getTime() : 0;
+                    return timeB - timeA;
+                });
+
+                setComments(formattedComments);
+            } else {
+                message.error(res?.msg || '获取评论列表失败');
+                setComments([]);
+            }
+        } catch (error) {
+            console.error('获取评论失败:', error);
+            message.error('获取评论失败,请稍后重试');
+            setComments([]);
+        } finally {
+            setLoading(false);
+        }
+    }, [torrentId]);
+
     useEffect(() => {
         if (torrentId) {
             fetchComments();
         }
-    }, [torrentId]);
+    }, [torrentId, fetchComments]);
 
     // 提交评论
     const handleSubmit = async () => {
@@ -88,28 +132,42 @@
             const values = await form.validateFields();
             setSubmitting(true);
 
-            // TODO: 替换为实际的API调用
-            // await addComment({
-            //   torrentId: Number(torrentId),
-            //   content: values.content,
-            // });
+            // 调用添加评论的API
+            const response = await addComment({
+                torrentId: Number(torrentId),
+                content: values.content.trim(),
+            });
 
-            message.success('评论成功');
-            form.resetFields();
-            fetchComments(); // 刷新评论列表
+            if (response?.code === 200) {
+                message.success('评论成功');
+                form.resetFields();
+                // 刷新评论列表
+                await fetchComments();
+            } else {
+                message.error(response?.msg || '评论失败,请稍后重试');
+            }
         } catch (error) {
-            message.error('评论失败');
+            console.error('评论失败:', error);
+            message.error('评论失败,请稍后重试');
         } finally {
             setSubmitting(false);
         }
     };
 
+    // 处理按下 Ctrl+Enter 提交
+    const handleKeyDown = (e: React.KeyboardEvent) => {
+        if (e.ctrlKey && e.key === 'Enter' && !submitting) {
+            handleSubmit();
+        }
+    };
+
     return (
         <Content style={{
             height: '100vh',
             display: 'flex',
             flexDirection: 'column',
-            overflow: 'hidden' // 防止内容溢出
+            overflow: 'hidden',
+            backgroundColor: '#f5f5f5'
         }}>
             {/* 顶部标题栏 */}
             <div style={{
@@ -118,7 +176,8 @@
                 display: 'flex',
                 alignItems: 'center',
                 backgroundColor: '#fff',
-                zIndex: 10
+                zIndex: 10,
+                boxShadow: '0 2px 8px rgba(0, 0, 0, 0.06)'
             }}>
                 <Button
                     type="link"
@@ -126,61 +185,120 @@
                     onClick={() => navigate(-1)}
                     style={{ marginRight: '10px', padding: 0 }}
                 />
-                <span style={{ fontSize: '16px', fontWeight: 'bold' }}>种子评论</span>
+                <span style={{ fontSize: '16px', fontWeight: 'bold' }}>
+                    种子评论 {comments.length > 0 && `(${comments.length})`}
+                </span>
             </div>
 
             {/* 评论列表区域 - 可滚动 */}
             <div style={{
                 flex: 1,
                 overflowY: 'auto',
-                padding: '0 16px',
-                paddingBottom: '16px'
+                padding: '16px',
+                paddingBottom: '8px'
             }}>
-                <List
-                    className="comment-list"
-                    itemLayout="horizontal"
-                    dataSource={comments}
-                    renderItem={(item) => (
-                        <List.Item>
-                            <List.Item.Meta
-                                avatar={<Avatar>{item.createBy[0]}</Avatar>}
-                                title={item.createBy}
-                                description={
-                                    <div>
-                                        <div>{item.content}</div>
-                                        <div style={{ color: '#8c8c8c', fontSize: '12px', marginTop: '8px' }}>
-                                            {item.createTime}
-                                        </div>
-                                    </div>
-                                }
-                            />
-                        </List.Item>
+                <Spin spinning={loading}>
+                    {comments.length === 0 && !loading ? (
+                        <Empty
+                            description="暂无评论"
+                            style={{ marginTop: '60px' }}
+                        />
+                    ) : (
+                        <List
+                            className="comment-list"
+                            itemLayout="horizontal"
+                            dataSource={comments}
+                            renderItem={(item) => (
+                                <Card
+                                    style={{
+                                        marginBottom: '12px',
+                                        borderRadius: '8px',
+                                        boxShadow: '0 1px 2px rgba(0, 0, 0, 0.03)'
+                                    }}
+                                    bodyStyle={{ padding: '12px 16px' }}
+                                >
+                                    <List.Item style={{ border: 'none', padding: 0 }}>
+                                        <List.Item.Meta
+                                            avatar={
+                                                <Avatar
+                                                    style={{
+                                                        backgroundColor: '#1890ff',
+                                                        verticalAlign: 'middle'
+                                                    }}
+                                                    size="default"
+                                                    icon={<UserOutlined />}
+                                                >
+                                                    {getAvatarChar(item.createBy)}
+                                                </Avatar>
+                                            }
+                                            title={
+                                                <div style={{
+                                                    display: 'flex',
+                                                    justifyContent: 'space-between',
+                                                    alignItems: 'center'
+                                                }}>
+                                                    <span style={{ fontWeight: 500 }}>{item.createBy}</span>
+                                                    <span style={{
+                                                        color: '#8c8c8c',
+                                                        fontSize: '12px',
+                                                        fontWeight: 'normal'
+                                                    }}>
+                                                        {formatTime(item.createTime)}
+                                                    </span>
+                                                </div>
+                                            }
+                                            description={
+                                                <div style={{
+                                                    marginTop: '8px',
+                                                    color: '#262626',
+                                                    fontSize: '14px',
+                                                    lineHeight: '22px',
+                                                    wordBreak: 'break-word'
+                                                }}>
+                                                    {item.content}
+                                                </div>
+                                            }
+                                        />
+                                    </List.Item>
+                                </Card>
+                            )}
+                        />
                     )}
-                />
+                </Spin>
             </div>
 
-            {/* 评论输入框 - 固定在父组件底部 */}
+            {/* 评论输入框 - 固定在底部 */}
             <div style={{
-                position: 'relative',
                 padding: '16px',
                 backgroundColor: '#fff',
                 borderTop: '1px solid #f0f0f0',
                 boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.06)'
             }}>
-                <Form form={form}>
+                <Form form={form} onFinish={handleSubmit}>
                     <Form.Item
                         name="content"
-                        rules={[{ required: true, message: '请输入评论内容' }]}
+                        rules={[
+                            { required: true, message: '请输入评论内容' },
+                            { whitespace: true, message: '评论内容不能为空' },
+                            { max: 500, message: '评论内容不能超过500个字符' }
+                        ]}
                         style={{ marginBottom: '12px' }}
                     >
-                        <TextArea rows={3} placeholder="请输入您的评论" />
+                        <TextArea
+                            rows={3}
+                            placeholder="请输入您的评论(Ctrl+Enter 快速提交)"
+                            maxLength={500}
+                            showCount
+                            onKeyDown={handleKeyDown}
+                            disabled={submitting}
+                        />
                     </Form.Item>
                     <Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
                         <Button
                             htmlType="submit"
                             loading={submitting}
-                            onClick={handleSubmit}
                             type="primary"
+                            disabled={loading}
                         >
                             提交评论
                         </Button>
diff --git a/react-ui/src/pages/Torrent/Comments/service.ts b/react-ui/src/pages/Torrent/Comments/service.ts
index f4243b6..1ea221a 100644
--- a/react-ui/src/pages/Torrent/Comments/service.ts
+++ b/react-ui/src/pages/Torrent/Comments/service.ts
@@ -3,9 +3,8 @@
 
 /** 获取种子评论列表 */
 export async function listComments(torrentId: number) {
-    return request<SysTorrentComment[]>(`/api/system/torrent/comment/list`, {
+    return request<SysTorrentComment[]>(`/api/system/torrent/comment/${torrentId}`, {
         method: 'get',
-        params: { torrentId },
     });
 }