344 lines
9.9 KiB
JavaScript
344 lines
9.9 KiB
JavaScript
/**
|
||
* 预测话题卡片组件
|
||
* 展示预测市场的话题概览
|
||
*/
|
||
|
||
import React, { useMemo, useState, useEffect } from 'react';
|
||
import {
|
||
Box,
|
||
Text,
|
||
HStack,
|
||
VStack,
|
||
Badge,
|
||
Progress,
|
||
Flex,
|
||
Avatar,
|
||
Icon,
|
||
} from '@chakra-ui/react';
|
||
import { motion } from 'framer-motion';
|
||
import {
|
||
TrendingUp,
|
||
TrendingDown,
|
||
Crown,
|
||
Users,
|
||
Coins,
|
||
Zap,
|
||
Lock,
|
||
CheckCircle2,
|
||
} from 'lucide-react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import { forumColors } from '@theme/forumTheme';
|
||
import CountdownTimer, { useCountdown } from './CountdownTimer';
|
||
|
||
const MotionBox = motion(Box);
|
||
|
||
const PredictionTopicCard = ({ topic }) => {
|
||
const navigate = useNavigate();
|
||
|
||
// 使用倒计时 Hook 获取实时状态
|
||
const timeLeft = useCountdown(topic.deadline);
|
||
|
||
// 处理卡片点击
|
||
const handleCardClick = () => {
|
||
navigate(`/value-forum/prediction/${topic.id}`);
|
||
};
|
||
|
||
// 格式化数字
|
||
const formatNumber = (num) => {
|
||
if (num >= 10000) return `${(num / 10000).toFixed(1)}万`;
|
||
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`;
|
||
return num;
|
||
};
|
||
|
||
// 计算实际状态(基于截止时间自动判断)
|
||
const [effectiveStatus, setEffectiveStatus] = useState(topic.status);
|
||
|
||
useEffect(() => {
|
||
// 如果已经是结算状态,保持不变
|
||
if (topic.status === 'settled') {
|
||
setEffectiveStatus('settled');
|
||
return;
|
||
}
|
||
// 如果已过截止时间,自动变为已截止
|
||
if (timeLeft.expired && topic.status === 'active') {
|
||
setEffectiveStatus('trading_closed');
|
||
return;
|
||
}
|
||
setEffectiveStatus(topic.status);
|
||
}, [topic.status, timeLeft.expired]);
|
||
|
||
// 获取选项数据
|
||
const yesData = topic.positions?.yes || { total_shares: 0, current_price: 500, lord_id: null };
|
||
const noData = topic.positions?.no || { total_shares: 0, current_price: 500, lord_id: null };
|
||
|
||
// 计算总份额
|
||
const totalShares = yesData.total_shares + noData.total_shares;
|
||
|
||
// 计算百分比
|
||
const yesPercent = totalShares > 0 ? (yesData.total_shares / totalShares) * 100 : 50;
|
||
const noPercent = totalShares > 0 ? (noData.total_shares / totalShares) * 100 : 50;
|
||
|
||
// 状态颜色(使用 effectiveStatus)
|
||
const statusColorMap = {
|
||
active: forumColors.success[500],
|
||
trading_closed: forumColors.warning[500],
|
||
settled: forumColors.text.secondary,
|
||
};
|
||
|
||
const statusLabelMap = {
|
||
active: '交易中',
|
||
trading_closed: '等待结算',
|
||
settled: '已结算',
|
||
};
|
||
|
||
const statusIconMap = {
|
||
active: Zap,
|
||
trading_closed: Lock,
|
||
settled: CheckCircle2,
|
||
};
|
||
|
||
return (
|
||
<MotionBox
|
||
bg={forumColors.background.card}
|
||
borderRadius="xl"
|
||
overflow="hidden"
|
||
border="2px solid"
|
||
borderColor={forumColors.border.default}
|
||
cursor="pointer"
|
||
onClick={handleCardClick}
|
||
whileHover={{ y: -8, scale: 1.02 }}
|
||
transition={{ duration: 0.3 }}
|
||
_hover={{
|
||
borderColor: forumColors.border.gold,
|
||
boxShadow: forumColors.shadows.gold,
|
||
}}
|
||
>
|
||
{/* 头部:状态标识 */}
|
||
<Box
|
||
bg={forumColors.gradients.goldSubtle}
|
||
px="4"
|
||
py="2"
|
||
borderBottom="1px solid"
|
||
borderColor={forumColors.border.default}
|
||
>
|
||
<Flex justify="space-between" align="center">
|
||
<HStack spacing="2">
|
||
<Icon as={statusIconMap[effectiveStatus]} boxSize="16px" color={statusColorMap[effectiveStatus]} />
|
||
<Text fontSize="xs" fontWeight="bold" color={statusColorMap[effectiveStatus]}>
|
||
{statusLabelMap[effectiveStatus]}
|
||
</Text>
|
||
</HStack>
|
||
|
||
<Badge
|
||
bg={statusColorMap[effectiveStatus]}
|
||
color="white"
|
||
px="3"
|
||
py="1"
|
||
borderRadius="full"
|
||
fontSize="xs"
|
||
fontWeight="bold"
|
||
>
|
||
{effectiveStatus === 'active' ? '可交易' : effectiveStatus === 'trading_closed' ? '待结算' : '已完成'}
|
||
</Badge>
|
||
</Flex>
|
||
</Box>
|
||
|
||
{/* 内容区域 */}
|
||
<VStack align="stretch" p="5" spacing="4">
|
||
{/* 话题标题 */}
|
||
<Text
|
||
fontSize="lg"
|
||
fontWeight="700"
|
||
color={forumColors.text.primary}
|
||
noOfLines={2}
|
||
lineHeight="1.4"
|
||
>
|
||
{topic.title}
|
||
</Text>
|
||
|
||
{/* 描述 */}
|
||
{topic.description && (
|
||
<Text
|
||
fontSize="sm"
|
||
color={forumColors.text.secondary}
|
||
noOfLines={2}
|
||
lineHeight="1.6"
|
||
>
|
||
{topic.description}
|
||
</Text>
|
||
)}
|
||
|
||
{/* 双向价格卡片 */}
|
||
<HStack spacing="3" w="full">
|
||
{/* Yes 方 */}
|
||
<Box
|
||
flex="1"
|
||
bg="linear-gradient(135deg, rgba(72, 187, 120, 0.1) 0%, rgba(72, 187, 120, 0.05) 100%)"
|
||
border="2px solid"
|
||
borderColor="green.400"
|
||
borderRadius="lg"
|
||
p="3"
|
||
position="relative"
|
||
overflow="hidden"
|
||
>
|
||
{/* 领主徽章 */}
|
||
{yesData.lord_id && (
|
||
<Icon
|
||
as={Crown}
|
||
position="absolute"
|
||
top="2"
|
||
right="2"
|
||
boxSize="16px"
|
||
color="yellow.400"
|
||
/>
|
||
)}
|
||
|
||
<VStack spacing="1" align="start">
|
||
<HStack spacing="1">
|
||
<Icon as={TrendingUp} boxSize="14px" color="green.500" />
|
||
<Text fontSize="xs" fontWeight="600" color="green.600">
|
||
看涨 / Yes
|
||
</Text>
|
||
</HStack>
|
||
|
||
<Text fontSize="2xl" fontWeight="bold" color="green.600">
|
||
{Math.round(yesData.current_price)}
|
||
<Text as="span" fontSize="xs" ml="1">
|
||
积分
|
||
</Text>
|
||
</Text>
|
||
|
||
<Text fontSize="xs" color={forumColors.text.secondary}>
|
||
{yesData.total_shares}份 · {yesPercent.toFixed(0)}%
|
||
</Text>
|
||
</VStack>
|
||
</Box>
|
||
|
||
{/* No 方 */}
|
||
<Box
|
||
flex="1"
|
||
bg="linear-gradient(135deg, rgba(245, 101, 101, 0.1) 0%, rgba(245, 101, 101, 0.05) 100%)"
|
||
border="2px solid"
|
||
borderColor="red.400"
|
||
borderRadius="lg"
|
||
p="3"
|
||
position="relative"
|
||
overflow="hidden"
|
||
>
|
||
{/* 领主徽章 */}
|
||
{noData.lord_id && (
|
||
<Icon
|
||
as={Crown}
|
||
position="absolute"
|
||
top="2"
|
||
right="2"
|
||
boxSize="16px"
|
||
color="yellow.400"
|
||
/>
|
||
)}
|
||
|
||
<VStack spacing="1" align="start">
|
||
<HStack spacing="1">
|
||
<Icon as={TrendingDown} boxSize="14px" color="red.500" />
|
||
<Text fontSize="xs" fontWeight="600" color="red.600">
|
||
看跌 / No
|
||
</Text>
|
||
</HStack>
|
||
|
||
<Text fontSize="2xl" fontWeight="bold" color="red.600">
|
||
{Math.round(noData.current_price)}
|
||
<Text as="span" fontSize="xs" ml="1">
|
||
积分
|
||
</Text>
|
||
</Text>
|
||
|
||
<Text fontSize="xs" color={forumColors.text.secondary}>
|
||
{noData.total_shares}份 · {noPercent.toFixed(0)}%
|
||
</Text>
|
||
</VStack>
|
||
</Box>
|
||
</HStack>
|
||
|
||
{/* 市场情绪进度条 */}
|
||
<Box>
|
||
<Flex justify="space-between" mb="1">
|
||
<Text fontSize="xs" color={forumColors.text.tertiary}>
|
||
市场情绪
|
||
</Text>
|
||
<Text fontSize="xs" color={forumColors.text.tertiary}>
|
||
{yesPercent.toFixed(0)}% vs {noPercent.toFixed(0)}%
|
||
</Text>
|
||
</Flex>
|
||
<Progress
|
||
value={yesPercent}
|
||
size="sm"
|
||
borderRadius="full"
|
||
bg="red.100"
|
||
sx={{
|
||
'& > div': {
|
||
bg: 'linear-gradient(90deg, #48BB78 0%, #38A169 100%)',
|
||
},
|
||
}}
|
||
/>
|
||
</Box>
|
||
|
||
{/* 奖池和数据 */}
|
||
<HStack spacing="4" fontSize="sm" color={forumColors.text.secondary} flexWrap="wrap">
|
||
<HStack spacing="1">
|
||
<Icon as={Coins} boxSize="16px" color={forumColors.primary[500]} />
|
||
<Text fontWeight="600" color={forumColors.primary[500]}>
|
||
{formatNumber(topic.total_pool)}
|
||
</Text>
|
||
<Text fontSize="xs">奖池</Text>
|
||
</HStack>
|
||
|
||
<HStack spacing="1">
|
||
<Icon as={Users} boxSize="16px" />
|
||
<Text>{topic.participants_count || topic.stats?.unique_traders?.size || 0}人</Text>
|
||
</HStack>
|
||
|
||
{/* 使用新的倒计时组件 */}
|
||
<CountdownTimer
|
||
deadline={topic.deadline}
|
||
status={effectiveStatus}
|
||
size="sm"
|
||
showIcon={true}
|
||
/>
|
||
</HStack>
|
||
|
||
{/* 底部:作者信息 */}
|
||
<Flex justify="space-between" align="center" pt="2" borderTop="1px solid" borderColor={forumColors.border.default}>
|
||
<HStack spacing="2">
|
||
<Avatar
|
||
size="xs"
|
||
name={topic.author_name}
|
||
src={topic.author_avatar}
|
||
bg={forumColors.gradients.goldPrimary}
|
||
color={forumColors.background.main}
|
||
/>
|
||
<Text fontSize="xs" color={forumColors.text.tertiary}>
|
||
{topic.author_name}
|
||
</Text>
|
||
</HStack>
|
||
|
||
{/* 分类标签 */}
|
||
{topic.category && (
|
||
<Badge
|
||
bg={forumColors.background.hover}
|
||
color={forumColors.text.primary}
|
||
px="2"
|
||
py="1"
|
||
borderRadius="md"
|
||
fontSize="xs"
|
||
>
|
||
{topic.category}
|
||
</Badge>
|
||
)}
|
||
</Flex>
|
||
</VStack>
|
||
</MotionBox>
|
||
);
|
||
};
|
||
|
||
export default PredictionTopicCard;
|