blob: d3d60364db316ec2b16225916dbcea1f5bfc97e4 [file] [log] [blame]
meisiyu1d4aade2025-06-02 20:10:36 +08001import React, { useState, useEffect } from 'react';
2import {
3 Card,
4 Table,
5 Button,
6 Modal,
7 Tag,
8 Space,
9 message,
10 Popconfirm,
11 Typography,
12 Image,
13 Row,
14 Col,
15 Input,
16 Tabs
17} from 'antd';
18import {
19 EyeOutlined,
20 CheckOutlined,
21 CloseOutlined,
22 DeleteOutlined
23} from '@ant-design/icons';
24import { getReviewPosts, reviewPost, takeDownPost } from '@/services/post';
25import ReportManagement from './ReportManagement';
26import styles from './index.module.css';
27
28const { Title, Paragraph } = Typography;
29const { TextArea } = Input;
30const { TabPane } = Tabs;
31
32interface PostReviewProps {}
33
34const PostReview: React.FC<PostReviewProps> = () => {
35 const [activeTab, setActiveTab] = useState('review');
36 const [posts, setPosts] = useState<API.Post.PostInfo[]>([]);
37 const [loading, setLoading] = useState(false);
38 const [detailModalVisible, setDetailModalVisible] = useState(false);
39 const [reasonModalVisible, setReasonModalVisible] = useState(false);
40 const [currentPost, setCurrentPost] = useState<API.Post.PostInfo | null>(null);
41 const [currentAction, setCurrentAction] = useState<'approve' | 'reject' | 'takedown' | null>(null);
42 const [reason, setReason] = useState('');
43
44 useEffect(() => {
45 if (activeTab === 'review') {
46 fetchPendingPosts();
47 }
48 }, [activeTab]);
49
50 const fetchPendingPosts = async () => {
51 setLoading(true);
52 try {
53 const response = await getReviewPosts({
54 pageNum: 1,
55 pageSize: 100,
56 status: '0' // 只查询待审核的帖子
57 });
58
59 if (response.code === 200) {
60 setPosts(response.rows || []);
61 } else {
62 message.error(response.msg || '获取待审核帖子失败');
63 }
64 } catch (error) {
65 message.error('获取待审核帖子失败');
66 } finally {
67 setLoading(false);
68 }
69 };
70
71 const handleAction = async (postId: number, action: 'approve' | 'reject', reason?: string) => {
72 try {
73 const response = await reviewPost(postId, action, reason);
74
75 if (response.code === 200) {
76 message.success(action === 'approve' ? '帖子审核通过' : '帖子已拒绝');
77 fetchPendingPosts();
78 } else {
79 message.error(response.msg || '操作失败');
80 }
81 } catch (error) {
82 message.error('操作失败');
83 }
84 };
85
86 const handleTakeDown = async (postId: number, reason?: string) => {
87 try {
88 const response = await takeDownPost(postId, reason);
89
90 if (response.code === 200) {
91 message.success('帖子已下架');
92 fetchPendingPosts();
93 } else {
94 message.error(response.msg || '操作失败');
95 }
96 } catch (error) {
97 message.error('操作失败');
98 }
99 };
100
101 const showReasonModal = (post: API.Post.PostInfo, action: 'approve' | 'reject' | 'takedown') => {
102 setCurrentPost(post);
103 setCurrentAction(action);
104 setReason('');
105 setReasonModalVisible(true);
106 };
107
108 const handleReasonSubmit = () => {
109 if (!currentPost || !currentAction) return;
110
111 if (currentAction === 'takedown') {
112 handleTakeDown(currentPost.postId || 0, reason);
113 } else {
114 handleAction(currentPost.postId || 0, currentAction, reason);
115 }
116
117 setReasonModalVisible(false);
118 setCurrentPost(null);
119 setCurrentAction(null);
120 setReason('');
121 };
122
123 const handleViewDetail = (post: API.Post.PostInfo) => {
124 setCurrentPost(post);
125 setDetailModalVisible(true);
126 };
127
128 const columns = [
129 {
130 title: '帖子标题',
131 dataIndex: 'title',
132 key: 'title',
133 width: 200,
134 render: (text: string, record: API.Post.PostInfo) => (
135 <a onClick={() => handleViewDetail(record)}>{text}</a>
136 ),
137 },
138 {
139 title: '作者',
140 dataIndex: 'author',
141 key: 'author',
142 width: 100,
143 },
144 {
145 title: '发布时间',
146 dataIndex: 'publishTime',
147 key: 'publishTime',
148 width: 150,
149 },
150 {
151 title: '状态',
152 dataIndex: 'status',
153 key: 'status',
154 width: 100,
155 render: (status: string) => {
156 const statusMap: Record<string, { color: string; text: string }> = {
157 '0': { color: 'orange', text: '待审核' },
158 '1': { color: 'green', text: '已发布' },
159 '2': { color: 'red', text: '已拒绝' },
160 '3': { color: 'gray', text: '已下架' }
161 };
162 const statusInfo = statusMap[status] || { color: 'gray', text: '未知' };
163 return <Tag color={statusInfo.color}>{statusInfo.text}</Tag>;
164 },
165 },
166 {
167 title: '标签',
168 dataIndex: 'tags',
169 key: 'tags',
170 width: 150,
171 render: (tags: string) => {
172 if (!tags) return '-';
173 return tags.split(',').map(tag => (
174 <Tag key={tag} color="blue">{tag}</Tag>
175 ));
176 },
177 },
178 {
179 title: '操作',
180 key: 'action',
181 width: 250,
182 render: (text: any, record: API.Post.PostInfo) => (
183 <Space size="small">
184 <Button
185 type="link"
186 icon={<EyeOutlined />}
187 onClick={() => handleViewDetail(record)}
188 >
189 查看
190 </Button>
191 {record.status === '0' && (
192 <>
193 <Button
194 type="link"
195 icon={<CheckOutlined />}
196 style={{ color: 'green' }}
197 onClick={() => showReasonModal(record, 'approve')}
198 >
199 通过
200 </Button>
201 <Button
202 type="link"
203 danger
204 icon={<CloseOutlined />}
205 onClick={() => showReasonModal(record, 'reject')}
206 >
207 拒绝
208 </Button>
209 </>
210 )}
211 {record.status === '1' && (
212 <Button
213 type="link"
214 danger
215 icon={<DeleteOutlined />}
216 onClick={() => showReasonModal(record, 'takedown')}
217 >
218 下架
219 </Button>
220 )}
221 </Space>
222 ),
223 },
224 ];
225
226 return (
227 <div className={styles.postReviewContainer}>
228 <Card title="帖子审核管理">
229 <Tabs activeKey={activeTab} onChange={setActiveTab}>
230 <TabPane tab="帖子发布管理" key="review">
231 <Table
232 columns={columns}
233 dataSource={posts}
234 loading={loading}
235 rowKey="postId"
236 pagination={{
237 pageSize: 10,
238 showTotal: (total) => `共 ${total} 条记录`,
239 }}
240 />
241 </TabPane>
242
243 <TabPane tab="帖子举报管理" key="report">
244 <ReportManagement />
245 </TabPane>
246 </Tabs>
247 </Card>
248
249 {/* 帖子详情弹窗 */}
250 <Modal
251 title="帖子详情"
252 open={detailModalVisible}
253 onCancel={() => {
254 setDetailModalVisible(false);
255 setCurrentPost(null);
256 }}
257 footer={null}
258 width={800}
259 >
260 {currentPost && (
261 <div className={styles.postDetail}>
262 <Title level={3}>{currentPost.title}</Title>
263
264 <div className={styles.postMeta}>
265 <Row gutter={16}>
266 <Col span={12}>
267 <p><strong>作者:</strong>{currentPost.author}</p>
268 <p><strong>发布时间:</strong>{currentPost.publishTime}</p>
269 </Col>
270 <Col span={12}>
271 <p><strong>浏览量:</strong>{currentPost.views || 0}</p>
272 <p><strong>点赞数:</strong>{currentPost.likes || 0}</p>
273 </Col>
274 </Row>
275 </div>
276
277 {currentPost.coverImage && (
278 <div className={styles.postCover}>
279 <Image
280 src={currentPost.coverImage}
281 alt="封面图片"
282 style={{ maxWidth: '100%', maxHeight: '200px' }}
283 />
284 </div>
285 )}
286
287 <div className={styles.postTags}>
288 <strong>标签:</strong>
289 {currentPost.tags ? (
290 currentPost.tags.split(',').map(tag => (
291 <Tag key={tag} color="blue">{tag}</Tag>
292 ))
293 ) : (
294 <span>无标签</span>
295 )}
296 </div>
297
298 <div className={styles.postSummary}>
299 <strong>摘要:</strong>
300 <Paragraph>{currentPost.summary}</Paragraph>
301 </div>
302
303 <div className={styles.postContent}>
304 <strong>内容:</strong>
305 <div dangerouslySetInnerHTML={{ __html: currentPost.content || '' }} />
306 </div>
307
308 <div className={styles.postActions}>
309 <Space>
310 {currentPost.status === '0' && (
311 <>
312 <Button
313 type="primary"
314 icon={<CheckOutlined />}
315 onClick={() => {
316 showReasonModal(currentPost, 'approve');
317 setDetailModalVisible(false);
318 }}
319 >
320 通过审核
321 </Button>
322 <Button
323 danger
324 icon={<CloseOutlined />}
325 onClick={() => {
326 showReasonModal(currentPost, 'reject');
327 setDetailModalVisible(false);
328 }}
329 >
330 拒绝审核
331 </Button>
332 </>
333 )}
334 {currentPost.status === '1' && (
335 <Button
336 danger
337 icon={<DeleteOutlined />}
338 onClick={() => {
339 showReasonModal(currentPost, 'takedown');
340 setDetailModalVisible(false);
341 }}
342 >
343 强制下架
344 </Button>
345 )}
346 </Space>
347 </div>
348 </div>
349 )}
350 </Modal>
351
352 {/* 审核理由弹窗 */}
353 <Modal
354 title={
355 currentAction === 'approve' ? '审核通过' :
356 currentAction === 'reject' ? '审核拒绝' : '强制下架'
357 }
358 open={reasonModalVisible}
359 onOk={handleReasonSubmit}
360 onCancel={() => {
361 setReasonModalVisible(false);
362 setCurrentPost(null);
363 setCurrentAction(null);
364 setReason('');
365 }}
366 okText="确定"
367 cancelText="取消"
368 >
369 <div style={{ marginBottom: 16 }}>
370 <strong>帖子:</strong>{currentPost?.title}
371 </div>
372 <div>
373 <strong>
374 {currentAction === 'approve' ? '通过理由' :
375 currentAction === 'reject' ? '拒绝理由' : '下架理由'}
376 (可选):
377 </strong>
378 <TextArea
379 value={reason}
380 onChange={(e) => setReason(e.target.value)}
381 placeholder={
382 currentAction === 'approve' ? '请输入审核通过的理由...' :
383 currentAction === 'reject' ? '请输入审核拒绝的理由...' : '请输入强制下架的理由...'
384 }
385 rows={4}
386 style={{ marginTop: 8 }}
387 />
388 </div>
389 </Modal>
390 </div>
391 );
392};
393
394export default PostReview;