312 lines
9.5 KiB
JavaScript
312 lines
9.5 KiB
JavaScript
/**
|
||
* 价值论坛主页面
|
||
* 类似小红书/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;
|