add forum

This commit is contained in:
2025-11-15 09:10:26 +08:00
parent 2753fbc37f
commit 05b497de29
13 changed files with 2693 additions and 11 deletions

View File

@@ -0,0 +1,311 @@
/**
* 价值论坛主页面
* 类似小红书/X的帖子广场
*/
import React, { useState, useEffect } from 'react';
import {
Box,
Container,
Heading,
Text,
Button,
HStack,
VStack,
SimpleGrid,
Input,
InputGroup,
InputLeftElement,
Select,
Spinner,
Center,
useDisclosure,
Flex,
Badge,
} from '@chakra-ui/react';
import { Search, PenSquare, TrendingUp, Clock, Heart } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { forumColors } from '@theme/forumTheme';
import { getPosts, searchPosts } from '@services/elasticsearchService';
import PostCard from './components/PostCard';
import CreatePostModal from './components/CreatePostModal';
const MotionBox = motion(Box);
const ValueForum = () => {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [searchKeyword, setSearchKeyword] = useState('');
const [sortBy, setSortBy] = useState('created_at');
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [hasMore, setHasMore] = useState(true);
const { isOpen, onOpen, onClose } = useDisclosure();
// 获取帖子列表
const fetchPosts = async (currentPage = 1, reset = false) => {
try {
setLoading(true);
let result;
if (searchKeyword.trim()) {
result = await searchPosts(searchKeyword, {
page: currentPage,
size: 20,
});
} else {
result = await getPosts({
page: currentPage,
size: 20,
sort: sortBy,
order: 'desc',
});
}
if (reset) {
setPosts(result.posts);
} else {
setPosts((prev) => [...prev, ...result.posts]);
}
setTotal(result.total);
setHasMore(result.posts.length === 20);
} catch (error) {
console.error('获取帖子列表失败:', error);
} finally {
setLoading(false);
}
};
// 初始化加载
useEffect(() => {
fetchPosts(1, true);
}, [sortBy]);
// 搜索处理
const handleSearch = () => {
setPage(1);
fetchPosts(1, true);
};
// 加载更多
const loadMore = () => {
const nextPage = page + 1;
setPage(nextPage);
fetchPosts(nextPage, false);
};
// 发帖成功回调
const handlePostCreated = () => {
setPage(1);
fetchPosts(1, true);
};
// 排序选项
const sortOptions = [
{ value: 'created_at', label: '最新发布', icon: Clock },
{ value: 'likes_count', label: '最多点赞', icon: Heart },
{ value: 'views_count', label: '最多浏览', icon: TrendingUp },
];
return (
<Box
minH="100vh"
bg={forumColors.background.main}
pt="80px"
pb="20"
>
<Container maxW="container.xl">
{/* 顶部横幅 */}
<MotionBox
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
mb="10"
>
<VStack spacing="4" align="stretch">
{/* 标题区域 */}
<Flex justify="space-between" align="center">
<VStack align="start" spacing="2">
<Heading
as="h1"
fontSize="4xl"
fontWeight="bold"
bgGradient={forumColors.text.goldGradient}
bgClip="text"
>
价值论坛
</Heading>
<Text color={forumColors.text.secondary} fontSize="md">
分享投资见解追踪市场热点共同发现价值
</Text>
</VStack>
{/* 发帖按钮 */}
<Button
leftIcon={<PenSquare size={18} />}
bg={forumColors.gradients.goldPrimary}
color={forumColors.background.main}
size="lg"
fontWeight="bold"
onClick={onOpen}
_hover={{
transform: 'translateY(-2px)',
boxShadow: forumColors.shadows.goldHover,
}}
_active={{ transform: 'translateY(0)' }}
>
发布帖子
</Button>
</Flex>
{/* 搜索和筛选栏 */}
<Flex gap="4" align="center" flexWrap="wrap">
{/* 搜索框 */}
<InputGroup maxW="400px" flex="1">
<InputLeftElement pointerEvents="none">
<Search size={18} color={forumColors.text.tertiary} />
</InputLeftElement>
<Input
placeholder="搜索帖子标题、内容、标签..."
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
bg={forumColors.background.card}
border="1px solid"
borderColor={forumColors.border.default}
color={forumColors.text.primary}
_placeholder={{ color: forumColors.text.tertiary }}
_hover={{ borderColor: forumColors.border.light }}
_focus={{
borderColor: forumColors.border.gold,
boxShadow: `0 0 0 1px ${forumColors.border.goldGlow}`,
}}
/>
</InputGroup>
{/* 排序选项 */}
<HStack spacing="2">
{sortOptions.map((option) => {
const Icon = option.icon;
const isActive = sortBy === option.value;
return (
<Button
key={option.value}
leftIcon={<Icon size={16} />}
size="md"
variant={isActive ? 'solid' : 'outline'}
bg={isActive ? forumColors.gradients.goldPrimary : 'transparent'}
color={isActive ? forumColors.background.main : forumColors.text.secondary}
borderColor={forumColors.border.default}
onClick={() => setSortBy(option.value)}
_hover={{
bg: isActive
? forumColors.gradients.goldPrimary
: forumColors.background.hover,
borderColor: forumColors.border.gold,
}}
>
{option.label}
</Button>
);
})}
</HStack>
</Flex>
{/* 统计信息 */}
<HStack spacing="6" color={forumColors.text.tertiary} fontSize="sm">
<Text>
<Text as="span" color={forumColors.primary[500]} fontWeight="bold">{total}</Text>
</Text>
</HStack>
</VStack>
</MotionBox>
{/* 帖子网格 */}
{loading && page === 1 ? (
<Center py="20">
<VStack spacing="4">
<Spinner
size="xl"
thickness="4px"
speed="0.8s"
color={forumColors.primary[500]}
/>
<Text color={forumColors.text.secondary}>加载中...</Text>
</VStack>
</Center>
) : posts.length === 0 ? (
<Center py="20">
<VStack spacing="4">
<Text color={forumColors.text.secondary} fontSize="lg">
{searchKeyword ? '未找到相关帖子' : '暂无帖子,快来发布第一篇吧!'}
</Text>
{!searchKeyword && (
<Button
leftIcon={<PenSquare size={18} />}
bg={forumColors.gradients.goldPrimary}
color={forumColors.background.main}
onClick={onOpen}
_hover={{ opacity: 0.9 }}
>
发布帖子
</Button>
)}
</VStack>
</Center>
) : (
<>
<SimpleGrid columns={{ base: 1, md: 2, lg: 3, xl: 4 }} spacing="6">
<AnimatePresence>
{posts.map((post, index) => (
<MotionBox
key={post.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, delay: index * 0.05 }}
>
<PostCard post={post} />
</MotionBox>
))}
</AnimatePresence>
</SimpleGrid>
{/* 加载更多按钮 */}
{hasMore && (
<Center mt="10">
<Button
onClick={loadMore}
isLoading={loading}
loadingText="加载中..."
bg={forumColors.background.card}
color={forumColors.text.primary}
border="1px solid"
borderColor={forumColors.border.default}
_hover={{
borderColor: forumColors.border.gold,
bg: forumColors.background.hover,
}}
>
加载更多
</Button>
</Center>
)}
</>
)}
</Container>
{/* 发帖模态框 */}
<CreatePostModal
isOpen={isOpen}
onClose={onClose}
onPostCreated={handlePostCreated}
/>
</Box>
);
};
export default ValueForum;