391 lines
12 KiB
JavaScript
391 lines
12 KiB
JavaScript
/**
|
|
* 帖子详情页
|
|
* 展示帖子完整内容、事件时间轴、评论区
|
|
*/
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Box,
|
|
Container,
|
|
Heading,
|
|
Text,
|
|
HStack,
|
|
VStack,
|
|
Avatar,
|
|
Badge,
|
|
Button,
|
|
Image,
|
|
SimpleGrid,
|
|
Spinner,
|
|
Center,
|
|
Flex,
|
|
IconButton,
|
|
Divider,
|
|
} from '@chakra-ui/react';
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
import { motion } from 'framer-motion';
|
|
import {
|
|
ArrowLeft,
|
|
Heart,
|
|
MessageCircle,
|
|
Eye,
|
|
Share2,
|
|
Bookmark,
|
|
} from 'lucide-react';
|
|
import { forumColors } from '@theme/forumTheme';
|
|
import {
|
|
getPostById,
|
|
likePost,
|
|
getEventsByPostId,
|
|
} from '@services/elasticsearchService';
|
|
import EventTimeline from './components/EventTimeline';
|
|
import CommentSection from './components/CommentSection';
|
|
import ImagePreviewModal from '@components/ImagePreviewModal';
|
|
|
|
const MotionBox = motion(Box);
|
|
|
|
const PostDetail = () => {
|
|
const { postId } = useParams();
|
|
const navigate = useNavigate();
|
|
|
|
const [post, setPost] = useState(null);
|
|
const [events, setEvents] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [isLiked, setIsLiked] = useState(false);
|
|
const [likes, setLikes] = useState(0);
|
|
|
|
// 图片预览相关状态
|
|
const [isImagePreviewOpen, setIsImagePreviewOpen] = useState(false);
|
|
const [previewImageIndex, setPreviewImageIndex] = useState(0);
|
|
|
|
// 加载帖子数据
|
|
useEffect(() => {
|
|
const loadPostData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
// 并行加载帖子和事件
|
|
const [postData, eventsData] = await Promise.all([
|
|
getPostById(postId),
|
|
getEventsByPostId(postId),
|
|
]);
|
|
|
|
setPost(postData);
|
|
setLikes(postData.likes_count || 0);
|
|
setEvents(eventsData);
|
|
} catch (error) {
|
|
console.error('加载帖子失败:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
loadPostData();
|
|
}, [postId]);
|
|
|
|
// 处理点赞
|
|
const handleLike = async () => {
|
|
try {
|
|
if (!isLiked) {
|
|
await likePost(postId);
|
|
setLikes((prev) => prev + 1);
|
|
setIsLiked(true);
|
|
}
|
|
} catch (error) {
|
|
console.error('点赞失败:', error);
|
|
}
|
|
};
|
|
|
|
// 打开图片预览
|
|
const handleImageClick = (index) => {
|
|
setPreviewImageIndex(index);
|
|
setIsImagePreviewOpen(true);
|
|
};
|
|
|
|
// 格式化时间
|
|
const formatTime = (dateString) => {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<Box minH="100vh" bg={forumColors.background.main} pt="80px">
|
|
<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>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (!post) {
|
|
return (
|
|
<Box minH="100vh" bg={forumColors.background.main} pt="80px">
|
|
<Center py="20">
|
|
<VStack spacing="4">
|
|
<Text color={forumColors.text.secondary} fontSize="lg">
|
|
帖子不存在或已被删除
|
|
</Text>
|
|
<Button
|
|
leftIcon={<ArrowLeft size={18} />}
|
|
onClick={() => navigate('/value-forum')}
|
|
bg={forumColors.gradients.goldPrimary}
|
|
color={forumColors.background.main}
|
|
_hover={{ opacity: 0.9 }}
|
|
>
|
|
返回论坛
|
|
</Button>
|
|
</VStack>
|
|
</Center>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box minH="100vh" bg={forumColors.background.main} pt="80px" pb="20">
|
|
<Container maxW="container.xl">
|
|
{/* 返回按钮 */}
|
|
<Button
|
|
leftIcon={<ArrowLeft size={18} />}
|
|
onClick={() => navigate('/value-forum')}
|
|
variant="ghost"
|
|
color={forumColors.text.secondary}
|
|
_hover={{ color: forumColors.text.primary }}
|
|
mb="6"
|
|
>
|
|
返回论坛
|
|
</Button>
|
|
|
|
<SimpleGrid columns={{ base: 1, lg: 3 }} spacing="6">
|
|
{/* 左侧:帖子内容 + 评论 */}
|
|
<Box gridColumn={{ base: '1', lg: '1 / 3' }}>
|
|
<VStack spacing="6" align="stretch">
|
|
{/* 帖子主体 */}
|
|
<MotionBox
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
bg={forumColors.background.card}
|
|
borderRadius="xl"
|
|
border="1px solid"
|
|
borderColor={forumColors.border.default}
|
|
overflow="hidden"
|
|
>
|
|
{/* 作者信息 */}
|
|
<Box p="6" borderBottomWidth="1px" borderColor={forumColors.border.default}>
|
|
<Flex justify="space-between" align="start">
|
|
<HStack spacing="3">
|
|
<Avatar
|
|
size="md"
|
|
name={post.author_name}
|
|
src={post.author_avatar}
|
|
bg={forumColors.gradients.goldPrimary}
|
|
color={forumColors.background.main}
|
|
/>
|
|
<VStack align="start" spacing="1">
|
|
<Text fontSize="md" fontWeight="600" color={forumColors.text.primary}>
|
|
{post.author_name}
|
|
</Text>
|
|
<Text fontSize="xs" color={forumColors.text.muted}>
|
|
发布于 {formatTime(post.created_at)}
|
|
</Text>
|
|
</VStack>
|
|
</HStack>
|
|
|
|
{/* 操作按钮 */}
|
|
<HStack spacing="2">
|
|
<IconButton
|
|
icon={<Share2 size={18} />}
|
|
variant="ghost"
|
|
color={forumColors.text.tertiary}
|
|
_hover={{ color: forumColors.primary[500] }}
|
|
aria-label="分享"
|
|
/>
|
|
<IconButton
|
|
icon={<Bookmark size={18} />}
|
|
variant="ghost"
|
|
color={forumColors.text.tertiary}
|
|
_hover={{ color: forumColors.primary[500] }}
|
|
aria-label="收藏"
|
|
/>
|
|
</HStack>
|
|
</Flex>
|
|
</Box>
|
|
|
|
{/* 帖子内容 */}
|
|
<Box p="6">
|
|
{/* 标题 */}
|
|
<Heading
|
|
as="h1"
|
|
fontSize="2xl"
|
|
fontWeight="bold"
|
|
color={forumColors.text.primary}
|
|
mb="4"
|
|
>
|
|
{post.title}
|
|
</Heading>
|
|
|
|
{/* 标签 */}
|
|
{post.tags && post.tags.length > 0 && (
|
|
<HStack spacing="2" mb="6" flexWrap="wrap">
|
|
{post.tags.map((tag, index) => (
|
|
<Badge
|
|
key={index}
|
|
bg={forumColors.gradients.goldSubtle}
|
|
color={forumColors.primary[500]}
|
|
border="1px solid"
|
|
borderColor={forumColors.border.gold}
|
|
borderRadius="full"
|
|
px="3"
|
|
py="1"
|
|
fontSize="sm"
|
|
>
|
|
#{tag}
|
|
</Badge>
|
|
))}
|
|
</HStack>
|
|
)}
|
|
|
|
{/* 正文 */}
|
|
<Text
|
|
fontSize="md"
|
|
color={forumColors.text.secondary}
|
|
lineHeight="1.8"
|
|
whiteSpace="pre-wrap"
|
|
mb="6"
|
|
>
|
|
{post.content}
|
|
</Text>
|
|
|
|
{/* 图片 */}
|
|
{post.images && post.images.length > 0 && (
|
|
<SimpleGrid columns={{ base: 1, sm: 2, md: 3 }} spacing="4" mb="6">
|
|
{post.images.map((img, index) => (
|
|
<Image
|
|
key={index}
|
|
src={img}
|
|
alt={`图片 ${index + 1}`}
|
|
borderRadius="md"
|
|
border="1px solid"
|
|
borderColor={forumColors.border.default}
|
|
cursor="pointer"
|
|
onClick={() => handleImageClick(index)}
|
|
_hover={{
|
|
transform: 'scale(1.05)',
|
|
boxShadow: forumColors.shadows.gold,
|
|
}}
|
|
transition="all 0.3s"
|
|
/>
|
|
))}
|
|
</SimpleGrid>
|
|
)}
|
|
</Box>
|
|
|
|
{/* 互动栏 */}
|
|
<Box
|
|
p="4"
|
|
borderTopWidth="1px"
|
|
borderColor={forumColors.border.default}
|
|
bg={forumColors.background.secondary}
|
|
>
|
|
<Flex justify="space-between" align="center">
|
|
<HStack spacing="6">
|
|
<HStack
|
|
spacing="2"
|
|
cursor="pointer"
|
|
onClick={handleLike}
|
|
color={isLiked ? forumColors.primary[500] : forumColors.text.tertiary}
|
|
_hover={{ color: forumColors.primary[500] }}
|
|
>
|
|
<Heart size={20} fill={isLiked ? 'currentColor' : 'none'} />
|
|
<Text fontSize="sm" fontWeight="500">
|
|
{likes}
|
|
</Text>
|
|
</HStack>
|
|
|
|
<HStack spacing="2" color={forumColors.text.tertiary}>
|
|
<MessageCircle size={20} />
|
|
<Text fontSize="sm" fontWeight="500">
|
|
{post.comments_count || 0}
|
|
</Text>
|
|
</HStack>
|
|
|
|
<HStack spacing="2" color={forumColors.text.tertiary}>
|
|
<Eye size={20} />
|
|
<Text fontSize="sm" fontWeight="500">
|
|
{post.views_count || 0}
|
|
</Text>
|
|
</HStack>
|
|
</HStack>
|
|
|
|
<Button
|
|
leftIcon={<Heart size={18} />}
|
|
onClick={handleLike}
|
|
bg={isLiked ? forumColors.primary[500] : 'transparent'}
|
|
color={isLiked ? forumColors.background.main : forumColors.text.primary}
|
|
border="1px solid"
|
|
borderColor={forumColors.border.gold}
|
|
_hover={{
|
|
bg: forumColors.gradients.goldPrimary,
|
|
color: forumColors.background.main,
|
|
}}
|
|
>
|
|
{isLiked ? '已点赞' : '点赞'}
|
|
</Button>
|
|
</Flex>
|
|
</Box>
|
|
</MotionBox>
|
|
|
|
{/* 评论区 */}
|
|
<MotionBox
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.2 }}
|
|
>
|
|
<CommentSection postId={postId} />
|
|
</MotionBox>
|
|
</VStack>
|
|
</Box>
|
|
|
|
{/* 右侧:事件时间轴 */}
|
|
<Box gridColumn={{ base: '1', lg: '3' }}>
|
|
<MotionBox
|
|
initial={{ opacity: 0, x: 20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.1 }}
|
|
position="sticky"
|
|
top="100px"
|
|
>
|
|
<EventTimeline events={events} />
|
|
</MotionBox>
|
|
</Box>
|
|
</SimpleGrid>
|
|
</Container>
|
|
|
|
{/* 图片预览弹窗 */}
|
|
<ImagePreviewModal
|
|
isOpen={isImagePreviewOpen}
|
|
onClose={() => setIsImagePreviewOpen(false)}
|
|
images={post?.images || []}
|
|
initialIndex={previewImageIndex}
|
|
/>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default PostDetail;
|