| import React, { useState, useEffect, useCallback } from 'react'; |
| import { useParams, useNavigate } from 'react-router-dom'; |
| 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; |
| |
| interface CommentItem { |
| id: number; |
| content: string; |
| createTime: string; |
| createBy: string; |
| torrentId: number; |
| } |
| |
| const TorrentComments: React.FC = () => { |
| const { torrentId } = useParams<{ torrentId: string }>(); |
| const navigate = useNavigate(); |
| const [form] = Form.useForm(); |
| const [comments, setComments] = useState<CommentItem[]>([]); |
| const [submitting, setSubmitting] = useState(false); |
| const [loading, setLoading] = useState(false); |
| |
| // 格式化时间 |
| const formatTime = (time: string | Date | null | undefined): string => { |
| if (!time) return '未知时间'; |
| |
| try { |
| 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) { |
| 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, fetchComments]); |
| |
| // 提交评论 |
| const handleSubmit = async () => { |
| try { |
| const values = await form.validateFields(); |
| setSubmitting(true); |
| |
| // 调用添加评论的API |
| const response = await addComment({ |
| torrentId: Number(torrentId), |
| content: values.content.trim(), |
| }); |
| |
| if (response?.code === 200) { |
| message.success('评论成功'); |
| form.resetFields(); |
| // 刷新评论列表 |
| await fetchComments(); |
| } else { |
| message.error(response?.msg || '评论失败,请稍后重试'); |
| } |
| } catch (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', |
| backgroundColor: '#f5f5f5' |
| }}> |
| {/* 顶部标题栏 */} |
| <div style={{ |
| padding: '16px', |
| borderBottom: '1px solid #f0f0f0', |
| display: 'flex', |
| alignItems: 'center', |
| backgroundColor: '#fff', |
| zIndex: 10, |
| boxShadow: '0 2px 8px rgba(0, 0, 0, 0.06)' |
| }}> |
| <Button |
| type="link" |
| icon={<ArrowLeftOutlined />} |
| onClick={() => navigate(-1)} |
| style={{ marginRight: '10px', padding: 0 }} |
| /> |
| <span style={{ fontSize: '16px', fontWeight: 'bold' }}> |
| 种子评论 {comments.length > 0 && `(${comments.length})`} |
| </span> |
| </div> |
| |
| {/* 评论列表区域 - 可滚动 */} |
| <div style={{ |
| flex: 1, |
| overflowY: 'auto', |
| padding: '16px', |
| paddingBottom: '8px' |
| }}> |
| <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={{ |
| padding: '16px', |
| backgroundColor: '#fff', |
| borderTop: '1px solid #f0f0f0', |
| boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.06)' |
| }}> |
| <Form form={form} onFinish={handleSubmit}> |
| <Form.Item |
| name="content" |
| rules={[ |
| { required: true, message: '请输入评论内容' }, |
| { whitespace: true, message: '评论内容不能为空' }, |
| { max: 500, message: '评论内容不能超过500个字符' } |
| ]} |
| style={{ marginBottom: '12px' }} |
| > |
| <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} |
| type="primary" |
| disabled={loading} |
| > |
| 提交评论 |
| </Button> |
| </Form.Item> |
| </Form> |
| </div> |
| </Content> |
| ); |
| }; |
| |
| export default TorrentComments; |