评论界面与后端连接完成
Change-Id: Id7d3f293b964c82533095f020d9720a43422ea68
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>