update pay function

This commit is contained in:
2025-11-23 18:11:48 +08:00
parent b582de9bc2
commit 7b3907a3bd
9 changed files with 3229 additions and 91 deletions

View File

@@ -0,0 +1,532 @@
/**
* 预测话题详情页
* 展示预测市场的完整信息、交易、评论等
*/
import React, { useState, useEffect } from 'react';
import {
Box,
Container,
Heading,
Text,
Button,
HStack,
VStack,
Flex,
Badge,
Avatar,
Icon,
Progress,
Divider,
useDisclosure,
useToast,
SimpleGrid,
} from '@chakra-ui/react';
import {
TrendingUp,
TrendingDown,
Crown,
Users,
Clock,
DollarSign,
ShoppingCart,
ArrowLeftRight,
CheckCircle2,
} from 'lucide-react';
import { useParams, useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import { forumColors } from '@theme/forumTheme';
import { getTopic } from '@services/predictionMarketService';
import { getUserAccount } from '@services/creditSystemService';
import { useAuth } from '@contexts/AuthContext';
import TradeModal from './components/TradeModal';
const MotionBox = motion(Box);
const PredictionTopicDetail = () => {
const { topicId } = useParams();
const navigate = useNavigate();
const toast = useToast();
const { user } = useAuth();
// 状态
const [topic, setTopic] = useState(null);
const [userAccount, setUserAccount] = useState(null);
const [tradeMode, setTradeMode] = useState('buy');
// 模态框
const {
isOpen: isTradeModalOpen,
onOpen: onTradeModalOpen,
onClose: onTradeModalClose,
} = useDisclosure();
// 加载话题数据
useEffect(() => {
const loadTopic = () => {
const topicData = getTopic(topicId);
if (topicData) {
setTopic(topicData);
} else {
toast({
title: '话题不存在',
status: 'error',
duration: 3000,
});
navigate('/value-forum');
}
};
loadTopic();
if (user) {
setUserAccount(getUserAccount(user.id));
}
}, [topicId, user]);
// 打开交易弹窗
const handleOpenTrade = (mode) => {
if (!user) {
toast({
title: '请先登录',
status: 'warning',
duration: 3000,
});
return;
}
setTradeMode(mode);
onTradeModalOpen();
};
// 交易成功回调
const handleTradeSuccess = () => {
// 刷新话题数据
setTopic(getTopic(topicId));
setUserAccount(getUserAccount(user.id));
};
if (!topic) {
return null;
}
// 获取选项数据
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;
// 格式化时间
const formatTime = (dateString) => {
const date = new Date(dateString);
const now = new Date();
const diff = date - now;
const days = Math.floor(diff / 86400000);
const hours = Math.floor(diff / 3600000);
if (days > 0) return `${days}天后`;
if (hours > 0) return `${hours}小时后`;
return '即将截止';
};
return (
<Box minH="100vh" bg={forumColors.background.main} pt="80px" pb="20">
<Container maxW="container.xl">
{/* 头部:返回按钮 */}
<Button
variant="ghost"
size="sm"
onClick={() => navigate('/value-forum')}
mb="6"
color={forumColors.text.secondary}
_hover={{ bg: forumColors.background.hover }}
>
返回论坛
</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
bg={forumColors.gradients.goldSubtle}
px="6"
py="4"
borderBottom="1px solid"
borderColor={forumColors.border.default}
>
<HStack justify="space-between">
<Badge
bg={forumColors.primary[500]}
color="white"
px="3"
py="1"
borderRadius="full"
fontSize="sm"
>
{topic.category}
</Badge>
<HStack spacing="3">
<Icon as={Clock} boxSize="16px" color={forumColors.text.secondary} />
<Text fontSize="sm" color={forumColors.text.secondary}>
{formatTime(topic.deadline)} 截止
</Text>
</HStack>
</HStack>
<Heading
as="h1"
fontSize="2xl"
fontWeight="bold"
color={forumColors.text.primary}
mt="4"
>
{topic.title}
</Heading>
<Text fontSize="md" color={forumColors.text.secondary} mt="3">
{topic.description}
</Text>
{/* 作者信息 */}
<HStack mt="4" spacing="3">
<Avatar
size="sm"
name={topic.author_name}
src={topic.author_avatar}
bg={forumColors.gradients.goldPrimary}
/>
<VStack align="start" spacing="0">
<Text fontSize="sm" fontWeight="600" color={forumColors.text.primary}>
{topic.author_name}
</Text>
<Text fontSize="xs" color={forumColors.text.tertiary}>
发起者
</Text>
</VStack>
</HStack>
</Box>
{/* 市场数据 */}
<Box p="6">
<SimpleGrid columns={{ base: 1, md: 2 }} spacing="6">
{/* Yes 方 */}
<Box
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="xl"
p="6"
position="relative"
>
{yesData.lord_id && (
<HStack
position="absolute"
top="3"
right="3"
spacing="1"
bg="yellow.400"
px="2"
py="1"
borderRadius="full"
>
<Icon as={Crown} boxSize="12px" color="white" />
<Text fontSize="xs" fontWeight="bold" color="white">
领主
</Text>
</HStack>
)}
<VStack align="start" spacing="4">
<HStack spacing="2">
<Icon as={TrendingUp} boxSize="20px" color="green.500" />
<Text fontSize="lg" fontWeight="700" color="green.600">
看涨 / Yes
</Text>
</HStack>
<VStack align="start" spacing="1">
<Text fontSize="xs" color={forumColors.text.secondary}>
当前价格
</Text>
<HStack spacing="1">
<Text fontSize="3xl" fontWeight="bold" color="green.600">
{Math.round(yesData.current_price)}
</Text>
<Text fontSize="sm" color={forumColors.text.secondary}>
积分/
</Text>
</HStack>
</VStack>
<Divider />
<HStack justify="space-between" w="full">
<Text fontSize="sm" color={forumColors.text.secondary}>
总份额
</Text>
<Text fontSize="md" fontWeight="600" color="green.600">
{yesData.total_shares}
</Text>
</HStack>
<HStack justify="space-between" w="full">
<Text fontSize="sm" color={forumColors.text.secondary}>
市场占比
</Text>
<Text fontSize="md" fontWeight="600" color="green.600">
{yesPercent.toFixed(1)}%
</Text>
</HStack>
</VStack>
</Box>
{/* No 方 */}
<Box
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="xl"
p="6"
position="relative"
>
{noData.lord_id && (
<HStack
position="absolute"
top="3"
right="3"
spacing="1"
bg="yellow.400"
px="2"
py="1"
borderRadius="full"
>
<Icon as={Crown} boxSize="12px" color="white" />
<Text fontSize="xs" fontWeight="bold" color="white">
领主
</Text>
</HStack>
)}
<VStack align="start" spacing="4">
<HStack spacing="2">
<Icon as={TrendingDown} boxSize="20px" color="red.500" />
<Text fontSize="lg" fontWeight="700" color="red.600">
看跌 / No
</Text>
</HStack>
<VStack align="start" spacing="1">
<Text fontSize="xs" color={forumColors.text.secondary}>
当前价格
</Text>
<HStack spacing="1">
<Text fontSize="3xl" fontWeight="bold" color="red.600">
{Math.round(noData.current_price)}
</Text>
<Text fontSize="sm" color={forumColors.text.secondary}>
积分/
</Text>
</HStack>
</VStack>
<Divider />
<HStack justify="space-between" w="full">
<Text fontSize="sm" color={forumColors.text.secondary}>
总份额
</Text>
<Text fontSize="md" fontWeight="600" color="red.600">
{noData.total_shares}
</Text>
</HStack>
<HStack justify="space-between" w="full">
<Text fontSize="sm" color={forumColors.text.secondary}>
市场占比
</Text>
<Text fontSize="md" fontWeight="600" color="red.600">
{noPercent.toFixed(1)}%
</Text>
</HStack>
</VStack>
</Box>
</SimpleGrid>
{/* 市场情绪进度条 */}
<Box mt="6">
<Flex justify="space-between" mb="2">
<Text fontSize="sm" fontWeight="600" color={forumColors.text.primary}>
市场情绪分布
</Text>
<Text fontSize="sm" color={forumColors.text.secondary}>
{yesPercent.toFixed(1)}% vs {noPercent.toFixed(1)}%
</Text>
</Flex>
<Progress
value={yesPercent}
size="lg"
borderRadius="full"
bg="red.200"
sx={{
'& > div': {
bg: 'linear-gradient(90deg, #48BB78 0%, #38A169 100%)',
},
}}
/>
</Box>
</Box>
</MotionBox>
</VStack>
</Box>
{/* 右侧:操作面板 */}
<Box gridColumn={{ base: '1', lg: '3' }}>
<VStack spacing="6" align="stretch" position="sticky" top="90px">
{/* 奖池信息 */}
<Box
bg={forumColors.background.card}
borderRadius="xl"
border="1px solid"
borderColor={forumColors.border.gold}
p="6"
>
<VStack spacing="4" align="stretch">
<HStack justify="center" spacing="2">
<Icon as={DollarSign} boxSize="24px" color={forumColors.primary[500]} />
<Text fontSize="sm" fontWeight="600" color={forumColors.text.secondary}>
当前奖池
</Text>
</HStack>
<Text
fontSize="4xl"
fontWeight="bold"
color={forumColors.primary[500]}
textAlign="center"
>
{topic.total_pool}
</Text>
<Text fontSize="sm" color={forumColors.text.secondary} textAlign="center">
积分
</Text>
<Divider />
<HStack justify="space-between" fontSize="sm">
<Text color={forumColors.text.secondary}>参与人数</Text>
<HStack spacing="1">
<Icon as={Users} boxSize="14px" />
<Text fontWeight="600">{topic.stats.unique_traders.size}</Text>
</HStack>
</HStack>
<HStack justify="space-between" fontSize="sm">
<Text color={forumColors.text.secondary}>总交易量</Text>
<Text fontWeight="600">{Math.round(topic.stats.total_volume)}</Text>
</HStack>
</VStack>
</Box>
{/* 交易按钮 */}
{topic.status === 'active' && (
<VStack spacing="3">
<Button
leftIcon={<ShoppingCart size={18} />}
bg={forumColors.gradients.goldPrimary}
color={forumColors.background.main}
size="lg"
w="full"
fontWeight="bold"
onClick={() => handleOpenTrade('buy')}
_hover={{
transform: 'translateY(-2px)',
boxShadow: forumColors.shadows.goldHover,
}}
_active={{ transform: 'translateY(0)' }}
>
购买席位
</Button>
<Button
leftIcon={<ArrowLeftRight size={18} />}
variant="outline"
borderColor={forumColors.border.default}
color={forumColors.text.primary}
size="lg"
w="full"
fontWeight="bold"
onClick={() => handleOpenTrade('sell')}
_hover={{
bg: forumColors.background.hover,
borderColor: forumColors.border.gold,
}}
>
卖出席位
</Button>
</VStack>
)}
{/* 用户余额 */}
{user && userAccount && (
<Box
bg={forumColors.background.hover}
borderRadius="lg"
p="4"
border="1px solid"
borderColor={forumColors.border.default}
>
<VStack spacing="2" align="stretch" fontSize="sm">
<HStack justify="space-between">
<Text color={forumColors.text.secondary}>可用余额</Text>
<Text fontWeight="600" color={forumColors.text.primary}>
{userAccount.balance} 积分
</Text>
</HStack>
<HStack justify="space-between">
<Text color={forumColors.text.secondary}>冻结积分</Text>
<Text fontWeight="600" color={forumColors.text.primary}>
{userAccount.frozen} 积分
</Text>
</HStack>
</VStack>
</Box>
)}
</VStack>
</Box>
</SimpleGrid>
</Container>
{/* 交易模态框 */}
<TradeModal
isOpen={isTradeModalOpen}
onClose={onTradeModalClose}
topic={topic}
mode={tradeMode}
onTradeSuccess={handleTradeSuccess}
/>
</Box>
);
};
export default PredictionTopicDetail;