blob: 58167f725dd6c5dfcb7243e268803efa3c45e8cf [file] [log] [blame]
BirdNETMed9f2f92025-05-26 20:12:30 +08001import React, { useState, useEffect, useCallback } from 'react';
BirdNETMb0f71532025-05-26 17:37:33 +08002import { useParams, useNavigate } from 'react-router-dom';
BirdNETMed9f2f92025-05-26 20:12:30 +08003import { Button, Card, Form, Input, List, message, Avatar, Spin, Empty } from 'antd';
4import { ArrowLeftOutlined, UserOutlined } from '@ant-design/icons';
BirdNETMb0f71532025-05-26 17:37:33 +08005import { Layout } from 'antd';
BirdNETMed9f2f92025-05-26 20:12:30 +08006import { listComments, addComment } from './service';
7import { responseSysTorrentComment, SysTorrentComment } from './data';
BirdNETMb0f71532025-05-26 17:37:33 +08008
9const { Content } = Layout;
10const { TextArea } = Input;
11
12interface CommentItem {
13 id: number;
14 content: string;
15 createTime: string;
16 createBy: string;
17 torrentId: number;
18}
19
20const TorrentComments: React.FC = () => {
21 const { torrentId } = useParams<{ torrentId: string }>();
22 const navigate = useNavigate();
23 const [form] = Form.useForm();
24 const [comments, setComments] = useState<CommentItem[]>([]);
25 const [submitting, setSubmitting] = useState(false);
BirdNETMed9f2f92025-05-26 20:12:30 +080026 const [loading, setLoading] = useState(false);
BirdNETMb0f71532025-05-26 17:37:33 +080027
BirdNETMed9f2f92025-05-26 20:12:30 +080028 // 格式化时间
29 const formatTime = (time: string | Date | null | undefined): string => {
30 if (!time) return '未知时间';
31
BirdNETMb0f71532025-05-26 17:37:33 +080032 try {
BirdNETMed9f2f92025-05-26 20:12:30 +080033 const date = new Date(time);
34 const now = new Date();
35 const diff = now.getTime() - date.getTime();
36
37 // 小于1分钟
38 if (diff < 60 * 1000) {
39 return '刚刚';
40 }
41 // 小于1小时
42 if (diff < 60 * 60 * 1000) {
43 return `${Math.floor(diff / (60 * 1000))}分钟前`;
44 }
45 // 小于24小时
46 if (diff < 24 * 60 * 60 * 1000) {
47 return `${Math.floor(diff / (60 * 60 * 1000))}小时前`;
48 }
49 // 小于7天
50 if (diff < 7 * 24 * 60 * 60 * 1000) {
51 return `${Math.floor(diff / (24 * 60 * 60 * 1000))}天前`;
52 }
53
54 // 否则显示具体日期
55 return date.toLocaleDateString('zh-CN', {
56 year: 'numeric',
57 month: '2-digit',
58 day: '2-digit',
59 hour: '2-digit',
60 minute: '2-digit'
61 });
BirdNETMb0f71532025-05-26 17:37:33 +080062 } catch (error) {
BirdNETMed9f2f92025-05-26 20:12:30 +080063 return '未知时间';
BirdNETMb0f71532025-05-26 17:37:33 +080064 }
65 };
66
BirdNETMed9f2f92025-05-26 20:12:30 +080067 // 获取用户显示名称
68 const getUserDisplayName = (userId: number | null | undefined): string => {
69 if (!userId) return '匿名用户';
70 return `用户${userId}`;
71 };
72
73 // 获取用户头像字符
74 const getAvatarChar = (userName: string): string => {
75 if (!userName || userName === '匿名用户') return '?';
76 // 如果是"用户123"格式,取数字的最后一位
77 const match = userName.match(/\d+$/);
78 if (match) {
79 return match[0].slice(-1);
80 }
81 return userName[0].toUpperCase();
82 };
83
84 // 获取评论列表
85 const fetchComments = useCallback(async () => {
86 if (!torrentId) return;
87
88 setLoading(true);
89 try {
90
91 const res = await listComments(Number(torrentId));
92 console.log('获取评论列表:', res);
93 if (res) {
94 const formattedComments: SysTorrentComment[] = res.data.map((comment: SysTorrentComment) => ({
95 id: comment.commentId ?? 0,
96 content: comment.content ?? '无内容',
97 createTime: comment.createTime || '',
98 createBy: getUserDisplayName(comment.userId),
99 torrentId: comment.torrentId ?? 0,
100 }));
101
102 // 按时间倒序排列,最新的在前面
103 formattedComments.sort((a, b) => {
104 const timeA = a.createTime ? new Date(a.createTime).getTime() : 0;
105 const timeB = b.createTime ? new Date(b.createTime).getTime() : 0;
106 return timeB - timeA;
107 });
108
109 setComments(formattedComments);
110 } else {
111 message.error(res?.msg || '获取评论列表失败');
112 setComments([]);
113 }
114 } catch (error) {
115 console.error('获取评论失败:', error);
116 message.error('获取评论失败,请稍后重试');
117 setComments([]);
118 } finally {
119 setLoading(false);
120 }
121 }, [torrentId]);
122
BirdNETMb0f71532025-05-26 17:37:33 +0800123 useEffect(() => {
124 if (torrentId) {
125 fetchComments();
126 }
BirdNETMed9f2f92025-05-26 20:12:30 +0800127 }, [torrentId, fetchComments]);
BirdNETMb0f71532025-05-26 17:37:33 +0800128
129 // 提交评论
130 const handleSubmit = async () => {
131 try {
132 const values = await form.validateFields();
133 setSubmitting(true);
134
BirdNETMed9f2f92025-05-26 20:12:30 +0800135 // 调用添加评论的API
136 const response = await addComment({
137 torrentId: Number(torrentId),
138 content: values.content.trim(),
139 });
BirdNETMb0f71532025-05-26 17:37:33 +0800140
BirdNETMed9f2f92025-05-26 20:12:30 +0800141 if (response?.code === 200) {
142 message.success('评论成功');
143 form.resetFields();
144 // 刷新评论列表
145 await fetchComments();
146 } else {
147 message.error(response?.msg || '评论失败,请稍后重试');
148 }
BirdNETMb0f71532025-05-26 17:37:33 +0800149 } catch (error) {
BirdNETMed9f2f92025-05-26 20:12:30 +0800150 console.error('评论失败:', error);
151 message.error('评论失败,请稍后重试');
BirdNETMb0f71532025-05-26 17:37:33 +0800152 } finally {
153 setSubmitting(false);
154 }
155 };
156
BirdNETMed9f2f92025-05-26 20:12:30 +0800157 // 处理按下 Ctrl+Enter 提交
158 const handleKeyDown = (e: React.KeyboardEvent) => {
159 if (e.ctrlKey && e.key === 'Enter' && !submitting) {
160 handleSubmit();
161 }
162 };
163
BirdNETMb0f71532025-05-26 17:37:33 +0800164 return (
165 <Content style={{
166 height: '100vh',
167 display: 'flex',
168 flexDirection: 'column',
BirdNETMed9f2f92025-05-26 20:12:30 +0800169 overflow: 'hidden',
170 backgroundColor: '#f5f5f5'
BirdNETMb0f71532025-05-26 17:37:33 +0800171 }}>
172 {/* 顶部标题栏 */}
173 <div style={{
174 padding: '16px',
175 borderBottom: '1px solid #f0f0f0',
176 display: 'flex',
177 alignItems: 'center',
178 backgroundColor: '#fff',
BirdNETMed9f2f92025-05-26 20:12:30 +0800179 zIndex: 10,
180 boxShadow: '0 2px 8px rgba(0, 0, 0, 0.06)'
BirdNETMb0f71532025-05-26 17:37:33 +0800181 }}>
182 <Button
183 type="link"
184 icon={<ArrowLeftOutlined />}
185 onClick={() => navigate(-1)}
186 style={{ marginRight: '10px', padding: 0 }}
187 />
BirdNETMed9f2f92025-05-26 20:12:30 +0800188 <span style={{ fontSize: '16px', fontWeight: 'bold' }}>
189 种子评论 {comments.length > 0 && `(${comments.length})`}
190 </span>
BirdNETMb0f71532025-05-26 17:37:33 +0800191 </div>
192
193 {/* 评论列表区域 - 可滚动 */}
194 <div style={{
195 flex: 1,
196 overflowY: 'auto',
BirdNETMed9f2f92025-05-26 20:12:30 +0800197 padding: '16px',
198 paddingBottom: '8px'
BirdNETMb0f71532025-05-26 17:37:33 +0800199 }}>
BirdNETMed9f2f92025-05-26 20:12:30 +0800200 <Spin spinning={loading}>
201 {comments.length === 0 && !loading ? (
202 <Empty
203 description="暂无评论"
204 style={{ marginTop: '60px' }}
205 />
206 ) : (
207 <List
208 className="comment-list"
209 itemLayout="horizontal"
210 dataSource={comments}
211 renderItem={(item) => (
212 <Card
213 style={{
214 marginBottom: '12px',
215 borderRadius: '8px',
216 boxShadow: '0 1px 2px rgba(0, 0, 0, 0.03)'
217 }}
218 bodyStyle={{ padding: '12px 16px' }}
219 >
220 <List.Item style={{ border: 'none', padding: 0 }}>
221 <List.Item.Meta
222 avatar={
223 <Avatar
224 style={{
225 backgroundColor: '#1890ff',
226 verticalAlign: 'middle'
227 }}
228 size="default"
229 icon={<UserOutlined />}
230 >
231 {getAvatarChar(item.createBy)}
232 </Avatar>
233 }
234 title={
235 <div style={{
236 display: 'flex',
237 justifyContent: 'space-between',
238 alignItems: 'center'
239 }}>
240 <span style={{ fontWeight: 500 }}>{item.createBy}</span>
241 <span style={{
242 color: '#8c8c8c',
243 fontSize: '12px',
244 fontWeight: 'normal'
245 }}>
246 {formatTime(item.createTime)}
247 </span>
248 </div>
249 }
250 description={
251 <div style={{
252 marginTop: '8px',
253 color: '#262626',
254 fontSize: '14px',
255 lineHeight: '22px',
256 wordBreak: 'break-word'
257 }}>
258 {item.content}
259 </div>
260 }
261 />
262 </List.Item>
263 </Card>
264 )}
265 />
BirdNETMb0f71532025-05-26 17:37:33 +0800266 )}
BirdNETMed9f2f92025-05-26 20:12:30 +0800267 </Spin>
BirdNETMb0f71532025-05-26 17:37:33 +0800268 </div>
269
BirdNETMed9f2f92025-05-26 20:12:30 +0800270 {/* 评论输入框 - 固定在底部 */}
BirdNETMb0f71532025-05-26 17:37:33 +0800271 <div style={{
BirdNETMb0f71532025-05-26 17:37:33 +0800272 padding: '16px',
273 backgroundColor: '#fff',
274 borderTop: '1px solid #f0f0f0',
275 boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.06)'
276 }}>
BirdNETMed9f2f92025-05-26 20:12:30 +0800277 <Form form={form} onFinish={handleSubmit}>
BirdNETMb0f71532025-05-26 17:37:33 +0800278 <Form.Item
279 name="content"
BirdNETMed9f2f92025-05-26 20:12:30 +0800280 rules={[
281 { required: true, message: '请输入评论内容' },
282 { whitespace: true, message: '评论内容不能为空' },
283 { max: 500, message: '评论内容不能超过500个字符' }
284 ]}
BirdNETMb0f71532025-05-26 17:37:33 +0800285 style={{ marginBottom: '12px' }}
286 >
BirdNETMed9f2f92025-05-26 20:12:30 +0800287 <TextArea
288 rows={3}
289 placeholder="请输入您的评论(Ctrl+Enter 快速提交)"
290 maxLength={500}
291 showCount
292 onKeyDown={handleKeyDown}
293 disabled={submitting}
294 />
BirdNETMb0f71532025-05-26 17:37:33 +0800295 </Form.Item>
296 <Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
297 <Button
298 htmlType="submit"
299 loading={submitting}
BirdNETMb0f71532025-05-26 17:37:33 +0800300 type="primary"
BirdNETMed9f2f92025-05-26 20:12:30 +0800301 disabled={loading}
BirdNETMb0f71532025-05-26 17:37:33 +0800302 >
303 提交评论
304 </Button>
305 </Form.Item>
306 </Form>
307 </div>
308 </Content>
309 );
310};
311
312export default TorrentComments;