blob: 202907b2dc4bf26a39516d167aa7c6f2b1bcdbe5 [file] [log] [blame]
San3yuana2ee30b2025-06-05 21:20:17 +08001import React, { useEffect, useState } from 'react';
San3yuana2ee30b2025-06-05 21:20:17 +08002import styles from './PostDetail.module.css';
San3yuan8166d1b2025-06-05 23:15:53 +08003import { Card, List, Typography, Button, Input, Spin, Empty, Divider } from 'antd';
San3yuana2ee30b2025-06-05 21:20:17 +08004import { getPostDetail } from '@/api/post';
5import { getPostComments } from '@/api/comment';
6import { useSearchParams } from 'react-router-dom';
7import request from '@/utils/request';
8import { useApi } from '@/hooks/request';
9import Navbar from '@/components/navbar/navbar';
San3yuan8166d1b2025-06-05 23:15:53 +080010import { DownloadOutlined, LikeOutlined, LikeFilled } from '@ant-design/icons';
San3yuana2ee30b2025-06-05 21:20:17 +080011
12const { Title, Text, Paragraph } = Typography;
13const { TextArea } = Input;
14
15export interface PostResponse {
San3yuan8166d1b2025-06-05 23:15:53 +080016 postId?: number;
17 userId?: number;
18 postTitle?: string;
19 postContent?: string;
20 createdAt?: number;
21 postType?: string;
22 isLocked?: boolean;
23 lockedReason?: string;
24 lockedAt?: string;
25 lockedBy?: number;
26 viewCount?: number;
27 hotScore?: number;
28 lastCalculated?: number;
29 [property: string]: any;
San3yuana2ee30b2025-06-05 21:20:17 +080030}
31
32export interface CommentResponse {
San3yuan8166d1b2025-06-05 23:15:53 +080033 commentId?: number;
34 content?: string;
35 createdAt?: number;
36 parentCommentId?: number | null;
37 postId?: number;
38 replies?: CommentResponse[];
39 userId?: number;
40 [property: string]: any;
San3yuana2ee30b2025-06-05 21:20:17 +080041}
42
43const PostDetail: React.FC = () => {
San3yuan8166d1b2025-06-05 23:15:53 +080044 const [searchParams] = useSearchParams();
45 const postId = searchParams.get('postId');
46 const { refresh: getPostDetailRefresh } = useApi(() => request.get(getPostDetail + `/${postId}`), false);
47 const { refresh: getPostCommentsRefresh } = useApi(() => request.get(getPostComments + `/${postId}`), false);
48 const [post, setPost] = useState<PostResponse | null>(null);
49 const [comments, setComments] = useState<CommentResponse[]>([]);
50 const [newComment, setNewComment] = useState<string>('');
51 const [loading, setLoading] = useState<boolean>(true);
52 const [liked, setLiked] = useState(false);
San3yuana2ee30b2025-06-05 21:20:17 +080053
San3yuan8166d1b2025-06-05 23:15:53 +080054 useEffect(() => {
55 if (!postId) return;
56 const fetchData = async () => {
57 setLoading(true);
58 const postRes = await getPostDetailRefresh();
59 if (!postRes || (postRes as any).error) {
60 setLoading(false);
61 return;
62 }
63 setPost(postRes as PostResponse);
San3yuana2ee30b2025-06-05 21:20:17 +080064
San3yuan8166d1b2025-06-05 23:15:53 +080065 const commentsRes = await getPostCommentsRefresh();
66 setComments(commentsRes as CommentResponse[]);
67 setLoading(false);
阳菜,放晴!ce4a6412025-06-08 14:35:23 +080068 console.log("postRes:", postRes);
69 console.log("commentsRes:", commentsRes);
San3yuan8166d1b2025-06-05 23:15:53 +080070 };
71 fetchData();
72 }, [postId]);
San3yuana2ee30b2025-06-05 21:20:17 +080073
San3yuan8166d1b2025-06-05 23:15:53 +080074 if (loading) return <div className={styles.center}><Spin /></div>;
75 if (!post) return <div className={styles.center}><Empty description="未找到帖子" /></div>;
San3yuana2ee30b2025-06-05 21:20:17 +080076
San3yuan8166d1b2025-06-05 23:15:53 +080077 return (
78 <div className={styles.container}>
79 {/* 固定导航栏 */}
80 <div className={styles.nav}>
81 <Navbar current={post.postType} />
82 </div>
83 {/* 内容区域 */}
84 <div className={styles.contentArea}>
85 <Card
86 title={<Title level={3} style={{ margin: 0 }}>{post.postTitle || "帖子标题"}</Title>}
87 className={styles.card}
88 bordered={false}
89 style={{ marginBottom: 24, boxShadow: '0 4px 24px rgba(0,0,0,0.08)' }}
90 extra={
91 <div style={{ display: 'flex', gap: 16 }}>
92 <Button
93 type="primary"
94 icon={<DownloadOutlined />}
95 onClick={() => {
96 // 下载逻辑
97 window.open(`/api/download/post/${post.postId}`, '_blank');
98 }}
99 >
100 下载
101 </Button>
102 <Button
103 type="primary"
104 icon={liked ? <LikeFilled /> : <LikeOutlined />}
105 style={liked ? { background: '#ccc', borderColor: '#ccc', color: '#888', cursor: 'not-allowed' } : {}}
106 disabled={liked}
107 onClick={() => setLiked(true)}
108 >
109 {liked ? '已点赞' : '点赞'}
110 </Button>
111 </div>
112 }
113 >
114 <div className={styles.metaRow}>
115 <Text type="secondary">作者ID: {post.userId}</Text>
116 <Text type="secondary">发布时间: {post.createdAt ? new Date(post.createdAt).toLocaleString() : "未知"}</Text>
117 <Text type="secondary">浏览量: {post.viewCount}</Text>
118 <Text type="secondary">类型: {post.postType}</Text>
119 <Text type="secondary">热度: {post.hotScore}</Text>
120 <Text type="secondary">最后计算: {post.lastCalculated ? new Date(post.lastCalculated).toLocaleString() : "无"}</Text>
121 </div>
122 {post.isLocked && (
123 <div className={styles.locked}>
124 <Text type="danger">本帖已锁定</Text>
125 {post.lockedReason && <Text type="secondary">(原因:{post.lockedReason})</Text>}
126 {post.lockedAt && <Text style={{ marginLeft: 8 }}>锁定时间: {post.lockedAt}</Text>}
127 {post.lockedBy !== 0 && <Text style={{ marginLeft: 8 }}>锁定人ID: {post.lockedBy}</Text>}
128 </div>
129 )}
130 <Divider style={{ margin: '16px 0' }} />
131 <Paragraph className={styles.contentText}>{post.postContent || "暂无内容"}</Paragraph>
132 </Card>
San3yuana2ee30b2025-06-05 21:20:17 +0800133
San3yuan8166d1b2025-06-05 23:15:53 +0800134 {/* 发布评论区域 */}
135 <Card className={styles.addCommentCard} style={{ marginBottom: 32, boxShadow: '0 2px 12px rgba(0,0,0,0.06)' }}>
136 <Title level={5} style={{ marginBottom: 12 }}>发布评论</Title>
137 <TextArea
138 rows={4}
139 value={newComment}
140 onChange={(e) => setNewComment(e.target.value)}
141 placeholder="写下你的评论..."
142 className={styles.textarea}
143 />
144 <Button
145 type="primary"
146 style={{ marginTop: 12, float: 'right' }}
147 onClick={() => setNewComment('')}
148 disabled={!newComment.trim()}
149 >
150 评论
151 </Button>
152 <div style={{ clear: 'both' }} />
153 </Card>
154
155 <Card
156 className={styles.commentListCard}
157 title={<Title level={4} style={{ margin: 0 }}>评论区</Title>}
158 bodyStyle={{ padding: 0 }}
159 >
160 <List
161 className={styles.commentList}
162 dataSource={comments}
163 locale={{ emptyText: <Empty description="暂无评论" /> }}
164 renderItem={(item) => (
165 <List.Item className={styles.commentItem} key={item.commentId}>
166 <List.Item.Meta
167 title={<Text strong>用户ID: {item.userId}</Text>}
168 description={
169 <>
170 <Text>{item.content}</Text>
171 <div style={{ fontSize: 12, color: '#888', marginTop: 8 }}>
172 {item.createdAt && new Date(item.createdAt).toLocaleString()}
173 </div>
174 </>
175 }
176 />
177 {/* 可递归渲染子评论 */}
178 {item.replies && item.replies.length > 0 && (
179 <List
180 className={styles.replyList}
181 dataSource={item.replies}
182 renderItem={reply => (
183 <List.Item className={styles.replyItem} key={reply.commentId}>
184 <List.Item.Meta
185 title={<Text strong>用户ID: {reply.userId}</Text>}
186 description={
187 <>
188 <Text>{reply.content}</Text>
189 <div style={{ fontSize: 12, color: '#888', marginTop: 8 }}>
190 {reply.createdAt && new Date(reply.createdAt).toLocaleString()}
191 </div>
192 </>
193 }
194 />
195 </List.Item>
196 )}
197 />
198 )}
199 </List.Item>
200 )}
201 />
202 </Card>
203 </div>
204 </div>
205 );
San3yuana2ee30b2025-06-05 21:20:17 +0800206};
207
208export default PostDetail;