创作中心模块包含首页展示、个人中心、帖子审核。

“首页展示”支持广告轮播展示、推广帖子优先展示、分页显示所有帖子、导航栏便捷标签筛选帖子、全局标题模糊搜索帖子、点击帖子“查看更多”进入帖子详情页。帖子详情页展示帖子封面图片、作者时间、详细内容(可以插入种子链接对种子进行介绍与推广)等基本信息、对帖子点赞收藏举报评论回复、查看相关推荐帖子。相关推荐会推荐当前帖子作者的其他帖子(最多推荐5篇),还会推荐具有相似标签的其他帖子,两者总共最多推荐9篇帖子。

“个人中心”包含“我的中心”和“我的收藏”。
“我的中心”中可以管理已经成功发布的帖子(编辑、删除帖子),还可以发布新帖子。发布新帖子时除了填写帖子基本信息以外,帖子标签支持下拉多项选择,用户还可以选择帖子推广项目并进行支付。设置了多种推广项目,包含广告轮播推广、帖子置顶展示、限时优先展示、分类页首条展示。系统后台执行自动定时任务,每小时对帖子的推广时效性进行检查,如超出推广时限,则取消帖子的推广显示特权。用户点击发布帖子后帖子处于待审核状态,需要管理员审核通过才能正常发布在首页展示页面。编辑帖子时用户可以追加帖子推广,但如果帖子处于推广状态,则禁止修改推广项目。
“我的收藏”中可以便捷查看所有已收藏的帖子。

“帖子审核”包含“帖子发布管理”和“帖子举报管理”。“帖子审核”板块具有权限管理,只有管理员界面能够进入。
“帖子发布管理”对所有待审核帖子进行处理,支持预览待审核帖子详细内容,批准通过和拒绝通过选项。
“帖子举报管理”对所有用户的举报请求进行人工审核,如果举报内容属实,则将帖子下架处理,如果举报内容不属实,驳回举报请求。所有举报请求的处理结果均留存显示,方便后续再次审查。

Change-Id: If822351183e9d55a5a56ff5cf1e13b313fdbe231
diff --git a/src/pages/PostReview/index.tsx b/src/pages/PostReview/index.tsx
new file mode 100644
index 0000000..d3d6036
--- /dev/null
+++ b/src/pages/PostReview/index.tsx
@@ -0,0 +1,394 @@
+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; 
\ No newline at end of file