个股论坛重做

This commit is contained in:
2026-01-06 15:29:10 +08:00
parent bea9b11184
commit 36316d4d7b
2 changed files with 38 additions and 13 deletions

View File

@@ -14,7 +14,6 @@ import {
Tag, Tag,
Divider, Divider,
Textarea, Textarea,
useColorModeValue,
Spinner, Spinner,
useToast, useToast,
} from '@chakra-ui/react'; } from '@chakra-ui/react';

View File

@@ -9,7 +9,6 @@ import {
Avatar, Avatar,
IconButton, IconButton,
HStack, HStack,
useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { MdThumbUp, MdReply, MdMoreVert } from 'react-icons/md'; import { MdThumbUp, MdReply, MdMoreVert } from 'react-icons/md';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
@@ -26,18 +25,35 @@ const ReplyItem: React.FC<ReplyItemProps> = ({ reply, onReply }) => {
const [liked, setLiked] = useState(false); const [liked, setLiked] = useState(false);
const [likeCount, setLikeCount] = useState(reply.likeCount); const [likeCount, setLikeCount] = useState(reply.likeCount);
const textColor = useColorModeValue('gray.800', 'gray.100'); // 深色主题颜色HeroUI 风格)
const mutedColor = useColorModeValue('gray.500', 'gray.400'); const textColor = 'gray.100';
const replyBg = useColorModeValue('gray.100', 'gray.700'); const mutedColor = 'gray.400';
const replyBg = 'rgba(255, 255, 255, 0.05)';
// 格式化时间 // 格式化时间 - 将 UTC 时间转换为北京时间
const formatTime = (dateStr: string) => { const formatTime = (dateStr: string) => {
return formatDistanceToNow(new Date(dateStr), { const date = new Date(dateStr);
// 后端存储的是 UTC 时间,需要加 8 小时转换为北京时间
const beijingDate = new Date(date.getTime() + 8 * 60 * 60 * 1000);
return formatDistanceToNow(beijingDate, {
addSuffix: true, addSuffix: true,
locale: zhCN, locale: zhCN,
}); });
}; };
// 解析内容中的 Markdown 图片
const parseContent = (content: string) => {
let html = content;
// 匹配 ![alt](url) 格式,支持 base64 data URL
html = html.replace(
/!\[([^\]]*)\]\(([^)]+)\)/g,
'<img src="$2" alt="$1" style="max-width: 100%; border-radius: 8px; margin: 8px 0;" />'
);
// 将换行转换为 <br>
html = html.replace(/\n/g, '<br />');
return html;
};
// 点赞 // 点赞
const handleLike = () => { const handleLike = () => {
setLiked(!liked); setLiked(!liked);
@@ -53,13 +69,14 @@ const ReplyItem: React.FC<ReplyItemProps> = ({ reply, onReply }) => {
name={reply.authorName} name={reply.authorName}
src={reply.authorAvatar} src={reply.authorAvatar}
mr={3} mr={3}
bg="linear-gradient(135deg, rgba(139, 92, 246, 0.6), rgba(59, 130, 246, 0.6))"
/> />
{/* 内容 */} {/* 内容 */}
<Box flex={1}> <Box flex={1}>
{/* 作者和时间 */} {/* 作者和时间 */}
<HStack spacing={2} mb={1}> <HStack spacing={2} mb={1}>
<Text fontWeight="semibold" fontSize="sm"> <Text fontWeight="semibold" fontSize="sm" color="purple.300">
{reply.authorName} {reply.authorName}
</Text> </Text>
<Text fontSize="xs" color={mutedColor}> <Text fontSize="xs" color={mutedColor}>
@@ -68,9 +85,9 @@ const ReplyItem: React.FC<ReplyItemProps> = ({ reply, onReply }) => {
{reply.isSolution && ( {reply.isSolution && (
<Text <Text
fontSize="xs" fontSize="xs"
color="green.500" color="green.400"
fontWeight="semibold" fontWeight="semibold"
bg="green.50" bg="rgba(34, 197, 94, 0.15)"
px={2} px={2}
py={0.5} py={0.5}
borderRadius="full" borderRadius="full"
@@ -96,11 +113,14 @@ const ReplyItem: React.FC<ReplyItemProps> = ({ reply, onReply }) => {
)} )}
{/* 回复内容 */} {/* 回复内容 */}
<Text <Box
color={textColor} color={textColor}
fontSize="sm" fontSize="sm"
lineHeight="1.6" lineHeight="1.6"
dangerouslySetInnerHTML={{ __html: reply.contentHtml || reply.content }} dangerouslySetInnerHTML={{ __html: parseContent(reply.contentHtml || reply.content) }}
sx={{
'img': { maxW: '100%', borderRadius: 'md', my: 2 },
}}
/> />
{/* 操作栏 */} {/* 操作栏 */}
@@ -110,7 +130,9 @@ const ReplyItem: React.FC<ReplyItemProps> = ({ reply, onReply }) => {
icon={<MdThumbUp />} icon={<MdThumbUp />}
size="xs" size="xs"
variant={liked ? 'solid' : 'ghost'} variant={liked ? 'solid' : 'ghost'}
colorScheme={liked ? 'blue' : 'gray'} colorScheme={liked ? 'purple' : 'gray'}
color={liked ? 'white' : 'gray.400'}
_hover={{ bg: 'whiteAlpha.100' }}
onClick={handleLike} onClick={handleLike}
/> />
<Text fontSize="xs" color={mutedColor}> <Text fontSize="xs" color={mutedColor}>
@@ -122,6 +144,8 @@ const ReplyItem: React.FC<ReplyItemProps> = ({ reply, onReply }) => {
icon={<MdReply />} icon={<MdReply />}
size="xs" size="xs"
variant="ghost" variant="ghost"
color="gray.400"
_hover={{ bg: 'whiteAlpha.100', color: 'white' }}
onClick={onReply} onClick={onReply}
/> />
@@ -130,6 +154,8 @@ const ReplyItem: React.FC<ReplyItemProps> = ({ reply, onReply }) => {
icon={<MdMoreVert />} icon={<MdMoreVert />}
size="xs" size="xs"
variant="ghost" variant="ghost"
color="gray.400"
_hover={{ bg: 'whiteAlpha.100', color: 'white' }}
/> />
</HStack> </HStack>
</Box> </Box>