170 lines
5.1 KiB
TypeScript
170 lines
5.1 KiB
TypeScript
/**
|
|
* 帖子卡片组件 - HeroUI 深色风格
|
|
*/
|
|
import React from 'react';
|
|
import {
|
|
Box,
|
|
Flex,
|
|
Text,
|
|
Avatar,
|
|
HStack,
|
|
Tag,
|
|
Icon,
|
|
} from '@chakra-ui/react';
|
|
import { MessageCircle, Eye, ThumbsUp, Pin, Lock } from 'lucide-react';
|
|
import { formatDistanceToNow } from 'date-fns';
|
|
import { zhCN } from 'date-fns/locale';
|
|
import { motion } from 'framer-motion';
|
|
|
|
import { ForumPost } from '../../../types';
|
|
|
|
interface PostCardProps {
|
|
post: ForumPost;
|
|
onClick: () => void;
|
|
}
|
|
|
|
const PostCard: React.FC<PostCardProps> = ({ post, onClick }) => {
|
|
// 格式化时间 - 将 UTC 时间转换为北京时间
|
|
const formatTime = (dateStr: string) => {
|
|
const date = new Date(dateStr);
|
|
// 后端存储的是 UTC 时间,需要加 8 小时转换为北京时间
|
|
const beijingDate = new Date(date.getTime() + 8 * 60 * 60 * 1000);
|
|
return formatDistanceToNow(beijingDate, {
|
|
addSuffix: true,
|
|
locale: zhCN,
|
|
});
|
|
};
|
|
|
|
return (
|
|
<motion.div whileHover={{ scale: 1.005 }} whileTap={{ scale: 0.995 }}>
|
|
<Box
|
|
bg="rgba(255, 255, 255, 0.03)"
|
|
border="1px solid"
|
|
borderColor="rgba(255, 255, 255, 0.08)"
|
|
borderRadius="xl"
|
|
p={4}
|
|
mb={3}
|
|
cursor="pointer"
|
|
transition="all 0.2s"
|
|
_hover={{
|
|
bg: 'rgba(255, 255, 255, 0.06)',
|
|
borderColor: 'rgba(139, 92, 246, 0.3)',
|
|
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.3)',
|
|
}}
|
|
onClick={onClick}
|
|
>
|
|
<Flex>
|
|
{/* 作者头像 */}
|
|
<Avatar
|
|
size="md"
|
|
name={post.authorName}
|
|
src={post.authorAvatar}
|
|
mr={3}
|
|
bg="linear-gradient(135deg, rgba(139, 92, 246, 0.6), rgba(59, 130, 246, 0.6))"
|
|
/>
|
|
|
|
{/* 帖子内容 */}
|
|
<Box flex={1}>
|
|
{/* 标题和标记 */}
|
|
<HStack spacing={2} mb={1}>
|
|
{post.isPinned && (
|
|
<Icon as={Pin} color="purple.400" boxSize={4} />
|
|
)}
|
|
{post.isLocked && (
|
|
<Icon as={Lock} color="orange.400" boxSize={4} />
|
|
)}
|
|
<Text
|
|
fontWeight="bold"
|
|
fontSize="md"
|
|
color="white"
|
|
noOfLines={1}
|
|
>
|
|
{post.title}
|
|
</Text>
|
|
</HStack>
|
|
|
|
{/* 内容预览 */}
|
|
<Text
|
|
color="gray.400"
|
|
fontSize="sm"
|
|
noOfLines={2}
|
|
mb={2}
|
|
>
|
|
{post.content
|
|
.replace(/<[^>]*>/g, '') // 移除 HTML 标签
|
|
.replace(/!\[[^\]]*\]\([^)]+\)/g, '[图片]') // 将 Markdown 图片替换为 [图片]
|
|
.slice(0, 150)}
|
|
</Text>
|
|
|
|
{/* 标签 */}
|
|
{post.tags && post.tags.length > 0 && (
|
|
<HStack spacing={1} mb={2} flexWrap="wrap">
|
|
{post.tags.slice(0, 3).map(tag => (
|
|
<Tag
|
|
key={tag}
|
|
size="sm"
|
|
bg="rgba(59, 130, 246, 0.15)"
|
|
color="blue.300"
|
|
border="1px solid"
|
|
borderColor="rgba(59, 130, 246, 0.3)"
|
|
>
|
|
{tag}
|
|
</Tag>
|
|
))}
|
|
{post.tags.length > 3 && (
|
|
<Text fontSize="xs" color="gray.500">
|
|
+{post.tags.length - 3}
|
|
</Text>
|
|
)}
|
|
</HStack>
|
|
)}
|
|
|
|
{/* 底部信息 */}
|
|
<Flex align="center" justify="space-between">
|
|
{/* 作者和时间 */}
|
|
<HStack spacing={2} fontSize="sm" color="gray.500">
|
|
<Text fontWeight="medium" color="purple.300">{post.authorName}</Text>
|
|
<Text>·</Text>
|
|
<Text>{formatTime(post.createdAt)}</Text>
|
|
{post.lastReplyAt && post.lastReplyAt !== post.createdAt && (
|
|
<>
|
|
<Text>·</Text>
|
|
<Text>最后回复 {formatTime(post.lastReplyAt)}</Text>
|
|
</>
|
|
)}
|
|
</HStack>
|
|
|
|
{/* 统计数据 */}
|
|
<HStack spacing={4} fontSize="sm" color="gray.500">
|
|
<HStack spacing={1}>
|
|
<Icon as={MessageCircle} boxSize={4} />
|
|
<Text
|
|
color="purple.300"
|
|
bg="rgba(139, 92, 246, 0.15)"
|
|
px={1.5}
|
|
borderRadius="sm"
|
|
fontSize="xs"
|
|
fontWeight="bold"
|
|
>
|
|
{post.replyCount}
|
|
</Text>
|
|
</HStack>
|
|
<HStack spacing={1}>
|
|
<Icon as={Eye} boxSize={4} />
|
|
<Text>{post.viewCount}</Text>
|
|
</HStack>
|
|
<HStack spacing={1}>
|
|
<Icon as={ThumbsUp} boxSize={4} />
|
|
<Text>{post.likeCount}</Text>
|
|
</HStack>
|
|
</HStack>
|
|
</Flex>
|
|
</Box>
|
|
</Flex>
|
|
</Box>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
export default PostCard;
|