blob: d3d60364db316ec2b16225916dbcea1f5bfc97e4 [file] [log] [blame]
import React, { useState, useEffect } from 'react';
import {
Card,
Table,
Button,
Modal,
Tag,
Space,
message,
Popconfirm,
Typography,
Image,
Row,
Col,
Input,
Tabs
} from 'antd';
import {
EyeOutlined,
CheckOutlined,
CloseOutlined,
DeleteOutlined
} from '@ant-design/icons';
import { getReviewPosts, reviewPost, takeDownPost } from '@/services/post';
import ReportManagement from './ReportManagement';
import styles from './index.module.css';
const { Title, Paragraph } = Typography;
const { TextArea } = Input;
const { TabPane } = Tabs;
interface PostReviewProps {}
const PostReview: React.FC<PostReviewProps> = () => {
const [activeTab, setActiveTab] = useState('review');
const [posts, setPosts] = useState<API.Post.PostInfo[]>([]);
const [loading, setLoading] = useState(false);
const [detailModalVisible, setDetailModalVisible] = useState(false);
const [reasonModalVisible, setReasonModalVisible] = useState(false);
const [currentPost, setCurrentPost] = useState<API.Post.PostInfo | null>(null);
const [currentAction, setCurrentAction] = useState<'approve' | 'reject' | 'takedown' | null>(null);
const [reason, setReason] = useState('');
useEffect(() => {
if (activeTab === 'review') {
fetchPendingPosts();
}
}, [activeTab]);
const fetchPendingPosts = async () => {
setLoading(true);
try {
const response = await getReviewPosts({
pageNum: 1,
pageSize: 100,
status: '0' // 只查询待审核的帖子
});
if (response.code === 200) {
setPosts(response.rows || []);
} else {
message.error(response.msg || '获取待审核帖子失败');
}
} catch (error) {
message.error('获取待审核帖子失败');
} finally {
setLoading(false);
}
};
const handleAction = async (postId: number, action: 'approve' | 'reject', reason?: string) => {
try {
const response = await reviewPost(postId, action, reason);
if (response.code === 200) {
message.success(action === 'approve' ? '帖子审核通过' : '帖子已拒绝');
fetchPendingPosts();
} else {
message.error(response.msg || '操作失败');
}
} catch (error) {
message.error('操作失败');
}
};
const handleTakeDown = async (postId: number, reason?: string) => {
try {
const response = await takeDownPost(postId, reason);
if (response.code === 200) {
message.success('帖子已下架');
fetchPendingPosts();
} else {
message.error(response.msg || '操作失败');
}
} catch (error) {
message.error('操作失败');
}
};
const showReasonModal = (post: API.Post.PostInfo, action: 'approve' | 'reject' | 'takedown') => {
setCurrentPost(post);
setCurrentAction(action);
setReason('');
setReasonModalVisible(true);
};
const handleReasonSubmit = () => {
if (!currentPost || !currentAction) return;
if (currentAction === 'takedown') {
handleTakeDown(currentPost.postId || 0, reason);
} else {
handleAction(currentPost.postId || 0, currentAction, reason);
}
setReasonModalVisible(false);
setCurrentPost(null);
setCurrentAction(null);
setReason('');
};
const handleViewDetail = (post: API.Post.PostInfo) => {
setCurrentPost(post);
setDetailModalVisible(true);
};
const columns = [
{
title: '帖子标题',
dataIndex: 'title',
key: 'title',
width: 200,
render: (text: string, record: API.Post.PostInfo) => (
<a onClick={() => handleViewDetail(record)}>{text}</a>
),
},
{
title: '作者',
dataIndex: 'author',
key: 'author',
width: 100,
},
{
title: '发布时间',
dataIndex: 'publishTime',
key: 'publishTime',
width: 150,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (status: string) => {
const statusMap: Record<string, { color: string; text: string }> = {
'0': { color: 'orange', text: '待审核' },
'1': { color: 'green', text: '已发布' },
'2': { color: 'red', text: '已拒绝' },
'3': { color: 'gray', text: '已下架' }
};
const statusInfo = statusMap[status] || { color: 'gray', text: '未知' };
return <Tag color={statusInfo.color}>{statusInfo.text}</Tag>;
},
},
{
title: '标签',
dataIndex: 'tags',
key: 'tags',
width: 150,
render: (tags: string) => {
if (!tags) return '-';
return tags.split(',').map(tag => (
<Tag key={tag} color="blue">{tag}</Tag>
));
},
},
{
title: '操作',
key: 'action',
width: 250,
render: (text: any, record: API.Post.PostInfo) => (
<Space size="small">
<Button
type="link"
icon={<EyeOutlined />}
onClick={() => handleViewDetail(record)}
>
查看
</Button>
{record.status === '0' && (
<>
<Button
type="link"
icon={<CheckOutlined />}
style={{ color: 'green' }}
onClick={() => showReasonModal(record, 'approve')}
>
通过
</Button>
<Button
type="link"
danger
icon={<CloseOutlined />}
onClick={() => showReasonModal(record, 'reject')}
>
拒绝
</Button>
</>
)}
{record.status === '1' && (
<Button
type="link"
danger
icon={<DeleteOutlined />}
onClick={() => showReasonModal(record, 'takedown')}
>
下架
</Button>
)}
</Space>
),
},
];
return (
<div className={styles.postReviewContainer}>
<Card title="帖子审核管理">
<Tabs activeKey={activeTab} onChange={setActiveTab}>
<TabPane tab="帖子发布管理" key="review">
<Table
columns={columns}
dataSource={posts}
loading={loading}
rowKey="postId"
pagination={{
pageSize: 10,
showTotal: (total) => `共 ${total} 条记录`,
}}
/>
</TabPane>
<TabPane tab="帖子举报管理" key="report">
<ReportManagement />
</TabPane>
</Tabs>
</Card>
{/* 帖子详情弹窗 */}
<Modal
title="帖子详情"
open={detailModalVisible}
onCancel={() => {
setDetailModalVisible(false);
setCurrentPost(null);
}}
footer={null}
width={800}
>
{currentPost && (
<div className={styles.postDetail}>
<Title level={3}>{currentPost.title}</Title>
<div className={styles.postMeta}>
<Row gutter={16}>
<Col span={12}>
<p><strong>作者:</strong>{currentPost.author}</p>
<p><strong>发布时间:</strong>{currentPost.publishTime}</p>
</Col>
<Col span={12}>
<p><strong>浏览量:</strong>{currentPost.views || 0}</p>
<p><strong>点赞数:</strong>{currentPost.likes || 0}</p>
</Col>
</Row>
</div>
{currentPost.coverImage && (
<div className={styles.postCover}>
<Image
src={currentPost.coverImage}
alt="封面图片"
style={{ maxWidth: '100%', maxHeight: '200px' }}
/>
</div>
)}
<div className={styles.postTags}>
<strong>标签:</strong>
{currentPost.tags ? (
currentPost.tags.split(',').map(tag => (
<Tag key={tag} color="blue">{tag}</Tag>
))
) : (
<span>无标签</span>
)}
</div>
<div className={styles.postSummary}>
<strong>摘要:</strong>
<Paragraph>{currentPost.summary}</Paragraph>
</div>
<div className={styles.postContent}>
<strong>内容:</strong>
<div dangerouslySetInnerHTML={{ __html: currentPost.content || '' }} />
</div>
<div className={styles.postActions}>
<Space>
{currentPost.status === '0' && (
<>
<Button
type="primary"
icon={<CheckOutlined />}
onClick={() => {
showReasonModal(currentPost, 'approve');
setDetailModalVisible(false);
}}
>
通过审核
</Button>
<Button
danger
icon={<CloseOutlined />}
onClick={() => {
showReasonModal(currentPost, 'reject');
setDetailModalVisible(false);
}}
>
拒绝审核
</Button>
</>
)}
{currentPost.status === '1' && (
<Button
danger
icon={<DeleteOutlined />}
onClick={() => {
showReasonModal(currentPost, 'takedown');
setDetailModalVisible(false);
}}
>
强制下架
</Button>
)}
</Space>
</div>
</div>
)}
</Modal>
{/* 审核理由弹窗 */}
<Modal
title={
currentAction === 'approve' ? '审核通过' :
currentAction === 'reject' ? '审核拒绝' : '强制下架'
}
open={reasonModalVisible}
onOk={handleReasonSubmit}
onCancel={() => {
setReasonModalVisible(false);
setCurrentPost(null);
setCurrentAction(null);
setReason('');
}}
okText="确定"
cancelText="取消"
>
<div style={{ marginBottom: 16 }}>
<strong>帖子:</strong>{currentPost?.title}
</div>
<div>
<strong>
{currentAction === 'approve' ? '通过理由' :
currentAction === 'reject' ? '拒绝理由' : '下架理由'}
(可选):
</strong>
<TextArea
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder={
currentAction === 'approve' ? '请输入审核通过的理由...' :
currentAction === 'reject' ? '请输入审核拒绝的理由...' : '请输入强制下架的理由...'
}
rows={4}
style={{ marginTop: 8 }}
/>
</div>
</Modal>
</div>
);
};
export default PostReview;