创作中心模块包含首页展示、个人中心、帖子审核。
“首页展示”支持广告轮播展示、推广帖子优先展示、分页显示所有帖子、导航栏便捷标签筛选帖子、全局标题模糊搜索帖子、点击帖子“查看更多”进入帖子详情页。帖子详情页展示帖子封面图片、作者时间、详细内容(可以插入种子链接对种子进行介绍与推广)等基本信息、对帖子点赞收藏举报评论回复、查看相关推荐帖子。相关推荐会推荐当前帖子作者的其他帖子(最多推荐5篇),还会推荐具有相似标签的其他帖子,两者总共最多推荐9篇帖子。
“个人中心”包含“我的中心”和“我的收藏”。
“我的中心”中可以管理已经成功发布的帖子(编辑、删除帖子),还可以发布新帖子。发布新帖子时除了填写帖子基本信息以外,帖子标签支持下拉多项选择,用户还可以选择帖子推广项目并进行支付。设置了多种推广项目,包含广告轮播推广、帖子置顶展示、限时优先展示、分类页首条展示。系统后台执行自动定时任务,每小时对帖子的推广时效性进行检查,如超出推广时限,则取消帖子的推广显示特权。用户点击发布帖子后帖子处于待审核状态,需要管理员审核通过才能正常发布在首页展示页面。编辑帖子时用户可以追加帖子推广,但如果帖子处于推广状态,则禁止修改推广项目。
“我的收藏”中可以便捷查看所有已收藏的帖子。
“帖子审核”包含“帖子发布管理”和“帖子举报管理”。“帖子审核”板块具有权限管理,只有管理员界面能够进入。
“帖子发布管理”对所有待审核帖子进行处理,支持预览待审核帖子详细内容,批准通过和拒绝通过选项。
“帖子举报管理”对所有用户的举报请求进行人工审核,如果举报内容属实,则将帖子下架处理,如果举报内容不属实,驳回举报请求。所有举报请求的处理结果均留存显示,方便后续再次审查。
Change-Id: If822351183e9d55a5a56ff5cf1e13b313fdbe231
diff --git a/src/pages/PostCenter/index.tsx b/src/pages/PostCenter/index.tsx
new file mode 100644
index 0000000..b81efb0
--- /dev/null
+++ b/src/pages/PostCenter/index.tsx
@@ -0,0 +1,301 @@
+import React, { useEffect, useState } from 'react';
+import { Row, Col, Pagination, Input, Carousel, Menu, Card, message, Button } from 'antd';
+import { SearchOutlined, AuditOutlined } from '@ant-design/icons';
+import { getPostList, getPromotionPosts } from '@/services/post';
+import PostCard from '../PostCenter/PostCard';
+import styles from './index.module.css';
+import { Post } from '../PostCenter/types';
+import { useNavigate } from 'react-router-dom';
+import { useModel } from 'umi';
+
+const { Search } = Input;
+
+const PostCenter: React.FC = () => {
+ const [posts, setPosts] = useState<Post[]>([]);
+ const [promotionPosts, setPromotionPosts] = useState<Post[]>([]);
+ const [total, setTotal] = useState<number>(0);
+ const [page, setPage] = useState<number>(1);
+ const [loading, setLoading] = useState<boolean>(false);
+ const [selectedCategory, setSelectedCategory] = useState<string>('all');
+ const [searchKeyword, setSearchKeyword] = useState<string>('');
+ const pageSize = 12;
+ const navigate = useNavigate();
+ const { initialState } = useModel('@@initialState');
+
+ // 检查是否为管理员 - 用户名包含admin
+ const isAdmin = initialState?.currentUser?.userName?.toLowerCase().includes('admin') || false;
+
+ const fetchPosts = async (current: number = 1, category?: string, searchTitle?: string) => {
+ try {
+ setLoading(true);
+ const params: any = {
+ pageNum: current,
+ pageSize: pageSize,
+ status: '1', // 只查询正常状态的帖子
+ };
+
+ // 根据分类筛选
+ if (category && category !== 'all') {
+ params.tags = category;
+ }
+
+ // 搜索关键词
+ if (searchTitle) {
+ params.title = searchTitle;
+ }
+
+ const response = await getPostList(params);
+
+ if (response.code === 200) {
+ // 确保返回的数据符合 Post 类型
+ const formattedPosts = (response.rows || []).map((post: API.Post.PostInfo) => ({
+ ...post,
+ id: post.postId, // 确保id字段映射正确
+ tags: post.tags ? post.tags.split(',') : [],
+ views: post.views || 0,
+ comments: post.comments || 0,
+ favorites: post.favorites || 0,
+ likes: post.likes || 0,
+ coverImage: post.coverImage || '/images/404.png', // 使用本地默认图片
+ isPromoted: post.promotionPlanId != null && post.promotionPlanId > 0, // 添加推广标识
+ }));
+ setPosts(formattedPosts);
+ setTotal(response.total || 0);
+ } else {
+ message.error(response.msg || '获取帖子列表失败');
+ setPosts([]);
+ setTotal(0);
+ }
+ } catch (error) {
+ console.error('获取帖子失败:', error);
+ message.error('获取帖子列表失败,请稍后重试');
+ setPosts([]);
+ setTotal(0);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const fetchPromotionPosts = async () => {
+ try {
+ const response = await getPromotionPosts();
+ if (response.code === 200) {
+ const formattedPosts = (response.data || []).map((post: API.Post.PostInfo) => ({
+ ...post,
+ id: post.postId,
+ tags: post.tags ? post.tags.split(',') : [],
+ views: post.views || 0,
+ comments: post.comments || 0,
+ favorites: post.favorites || 0,
+ likes: post.likes || 0,
+ coverImage: post.coverImage || '/images/404.png',
+ isPromoted: post.promotionPlanId != null && post.promotionPlanId > 0, // 添加推广标识
+ }));
+ setPromotionPosts(formattedPosts);
+ }
+ } catch (error) {
+ console.error('获取推广帖子失败:', error);
+ }
+ };
+
+ useEffect(() => {
+ fetchPosts(page, selectedCategory, searchKeyword);
+ fetchPromotionPosts();
+ }, [page, selectedCategory]);
+
+ const handleSearch = (value: string) => {
+ console.log('搜索:', value);
+ setSearchKeyword(value);
+ setPage(1); // 重置页码
+ fetchPosts(1, selectedCategory, value);
+ };
+
+ const handlePageChange = (newPage: number) => {
+ setPage(newPage);
+ fetchPosts(newPage, selectedCategory, searchKeyword);
+ };
+
+ const handleCategoryChange = (category: string) => {
+ setSelectedCategory(category);
+ setPage(1); // 重置页码
+ setSearchKeyword(''); // 清空搜索
+ fetchPosts(1, category, '');
+ };
+
+ return (
+ <div className={styles.postCenterContainer}>
+ {/* 顶部导航 */}
+ <div className={styles.headerNav}>
+ <div className={styles.categoryMenu}>
+ <Button
+ type={selectedCategory === 'all' ? 'primary' : 'text'}
+ onClick={() => handleCategoryChange('all')}
+ className={styles.categoryButton}
+ >
+ 首页
+ </Button>
+ <Button
+ type={selectedCategory === '日剧' ? 'primary' : 'text'}
+ onClick={() => handleCategoryChange('日剧')}
+ className={styles.categoryButton}
+ >
+ 日剧
+ </Button>
+ <Button
+ type={selectedCategory === '电影' ? 'primary' : 'text'}
+ onClick={() => handleCategoryChange('电影')}
+ className={styles.categoryButton}
+ >
+ 电影
+ </Button>
+ <Button
+ type={selectedCategory === '音乐' ? 'primary' : 'text'}
+ onClick={() => handleCategoryChange('音乐')}
+ className={styles.categoryButton}
+ >
+ 音乐
+ </Button>
+ <Button
+ type={selectedCategory === '合集' ? 'primary' : 'text'}
+ onClick={() => handleCategoryChange('合集')}
+ className={styles.categoryButton}
+ >
+ 合集
+ </Button>
+ <Button
+ type={selectedCategory === '动漫' ? 'primary' : 'text'}
+ onClick={() => handleCategoryChange('动漫')}
+ className={styles.categoryButton}
+ >
+ 动漫
+ </Button>
+ <Button
+ type={selectedCategory === '游戏' ? 'primary' : 'text'}
+ onClick={() => handleCategoryChange('游戏')}
+ className={styles.categoryButton}
+ >
+ 游戏
+ </Button>
+ </div>
+
+ <div className={styles.searchContainer}>
+ <Search
+ placeholder="搜索帖子..."
+ onSearch={handleSearch}
+ style={{ width: 300 }}
+ enterButton={<SearchOutlined />}
+ value={searchKeyword}
+ onChange={(e) => setSearchKeyword(e.target.value)}
+ />
+ </div>
+
+ <div className={styles.userCenter}>
+ {isAdmin && (
+ <Button
+ icon={<AuditOutlined />}
+ onClick={() => navigate('/post-review')}
+ style={{ marginRight: 16 }}
+ >
+ 帖子审核
+ </Button>
+ )}
+ <Button
+ type="primary"
+ onClick={() => navigate('/user-center')}
+ >
+ 个人中心
+ </Button>
+ </div>
+ </div>
+
+ {/* 轮播推荐图 */}
+ <div className={styles.carouselContainer}>
+ <Carousel autoplay>
+ {promotionPosts.length > 0 ? (
+ promotionPosts.map((post) => (
+ <div key={post.id} onClick={() => navigate(`/post-detail/${post.id}`)}>
+ <div
+ className={styles.carouselSlide}
+ style={{
+ backgroundImage: `linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0.4)), url(${post.coverImage})`,
+ backgroundSize: 'cover',
+ backgroundPosition: 'center',
+ height: '300px',
+ position: 'relative',
+ cursor: 'pointer'
+ }}
+ >
+ <div className={styles.carouselOverlay}>
+ <h2 className={styles.carouselTitle}>{post.title}</h2>
+ <p className={styles.carouselSummary}>{post.summary}</p>
+ <div className={styles.carouselMeta}>
+ <span>作者: {post.author}</span>
+ <span>浏览: {post.views}</span>
+ <span>点赞: {post.likes}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ ))
+ ) : (
+ // 默认轮播图
+ <>
+ <div>
+ <div
+ className={styles.carouselSlide}
+ style={{
+ backgroundImage: `url(/images/flower.jpg)`,
+ backgroundSize: 'cover',
+ backgroundPosition: 'center',
+ height: '300px',
+ }}
+ >
+ <div className={styles.carouselOverlay}>
+ <h2 className={styles.carouselTitle}>欢迎来到ThunderHub</h2>
+ <p className={styles.carouselSummary}>发现精彩内容,分享美好时光</p>
+ </div>
+ </div>
+ </div>
+ </>
+ )}
+ </Carousel>
+ </div>
+
+ {/* 卡片帖子区 */}
+ <div className={styles.postsSection}>
+ {selectedCategory !== 'all' && (
+ <div className={styles.categoryTitle}>
+ <h2>{selectedCategory} 分类</h2>
+ <p>共找到 {total} 篇相关帖子</p>
+ </div>
+ )}
+
+ <Row gutter={[24, 24]} className={styles.postsRow}>
+ {posts.map((post) => (
+ <Col xs={24} sm={12} lg={8} xl={6} key={post.id} className={styles.postCol}>
+ <PostCard post={post} />
+ </Col>
+ ))}
+ </Row>
+
+ {posts.length === 0 && !loading && (
+ <div className={styles.emptyState}>
+ <p>暂无相关帖子</p>
+ </div>
+ )}
+
+ <div className={styles.paginationContainer}>
+ <Pagination
+ current={page}
+ pageSize={pageSize}
+ total={total}
+ onChange={handlePageChange}
+ showTotal={(total) => `共 ${total} 条帖子`}
+ />
+ </div>
+ </div>
+ </div>
+ );
+};
+
+export default PostCenter;
\ No newline at end of file