diff --git a/src/views/ValueForum/PredictionTopicDetail.js b/src/views/ValueForum/PredictionTopicDetail.js
index 1b116970..60f3c512 100644
--- a/src/views/ValueForum/PredictionTopicDetail.js
+++ b/src/views/ValueForum/PredictionTopicDetail.js
@@ -3,7 +3,7 @@
* 展示预测市场的完整信息、交易、评论等
*/
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
import {
Box,
Heading,
@@ -20,17 +20,21 @@ import {
useDisclosure,
useToast,
SimpleGrid,
+ Alert,
+ AlertIcon,
} from '@chakra-ui/react';
import {
TrendingUp,
TrendingDown,
Crown,
Users,
- Clock,
Coins,
ShoppingCart,
ArrowLeftRight,
CheckCircle2,
+ Lock,
+ Gavel,
+ Trophy,
} from 'lucide-react';
import { useParams, useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
@@ -41,6 +45,8 @@ import { LAYOUT_SIZE } from '@/layouts/config/layoutConfig';
import TradeModal from './components/TradeModal';
import PredictionCommentSection from './components/PredictionCommentSection';
import CommentInvestModal from './components/CommentInvestModal';
+import SettleTopicModal from './components/SettleTopicModal';
+import CountdownTimer, { useCountdown } from './components/CountdownTimer';
const MotionBox = motion(Box);
@@ -70,6 +76,12 @@ const PredictionTopicDetail = () => {
onClose: onInvestModalClose,
} = useDisclosure();
+ const {
+ isOpen: isSettleModalOpen,
+ onOpen: onSettleModalOpen,
+ onClose: onSettleModalClose,
+ } = useDisclosure();
+
// 加载话题数据
useEffect(() => {
const loadTopic = async () => {
@@ -184,6 +196,47 @@ const PredictionTopicDetail = () => {
}
};
+ // 使用倒计时 Hook
+ const timeLeft = useCountdown(topic?.deadline);
+
+ // 计算实际状态(基于截止时间自动判断)
+ const effectiveStatus = useMemo(() => {
+ if (!topic) return 'active';
+ // 如果已经是结算状态,保持不变
+ if (topic.status === 'settled') return 'settled';
+ // 如果已过截止时间,自动变为已截止
+ if (timeLeft.expired && topic.status === 'active') return 'trading_closed';
+ return topic.status;
+ }, [topic?.status, timeLeft.expired]);
+
+ // 判断当前用户是否是作者
+ const isAuthor = useMemo(() => {
+ if (!user || !topic) return false;
+ return user.id === topic.author_id || user.username === topic.author_name;
+ }, [user, topic]);
+
+ // 判断是否可以结算(作者 + 已截止 + 未结算)
+ const canSettle = useMemo(() => {
+ return isAuthor && effectiveStatus === 'trading_closed';
+ }, [isAuthor, effectiveStatus]);
+
+ // 判断是否可以交易
+ const canTrade = useMemo(() => {
+ return effectiveStatus === 'active' && !isAuthor;
+ }, [effectiveStatus, isAuthor]);
+
+ // 结算成功回调
+ const handleSettleSuccess = async () => {
+ try {
+ const topicResponse = await getTopicDetail(topicId);
+ if (topicResponse.success) {
+ setTopic(topicResponse.data);
+ }
+ } catch (error) {
+ console.error('刷新数据失败:', error);
+ }
+ };
+
if (!topic) {
return null;
}
@@ -207,20 +260,19 @@ const PredictionTopicDetail = () => {
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 '即将截止';
+ // 获取结算结果显示
+ const getResultDisplay = () => {
+ if (topic.status !== 'settled' || !topic.result) return null;
+ const resultMap = {
+ yes: { label: '看涨方获胜', color: 'green', icon: TrendingUp },
+ no: { label: '看跌方获胜', color: 'red', icon: TrendingDown },
+ draw: { label: '平局', color: 'gray', icon: null },
+ };
+ return resultMap[topic.result];
};
+ const resultDisplay = getResultDisplay();
+
return (
{/* 头部:返回按钮 */}
@@ -258,26 +310,73 @@ const PredictionTopicDetail = () => {
borderBottom="1px solid"
borderColor={forumColors.border.default}
>
-
-
- {topic.category}
-
+
+
+
+ {topic.category}
+
-
-
-
- {formatTime(topic.deadline)} 截止
-
+ {/* 状态标识 */}
+
+
+ {effectiveStatus === 'active' ? '交易中' :
+ effectiveStatus === 'trading_closed' ? '等待结算' : '已结算'}
+
+
+ {/* 倒计时 */}
+
+ {/* 结算结果展示 */}
+ {resultDisplay && (
+
+
+
+ 结算结果:{resultDisplay.label}
+
+
+ )}
+
{
- {/* 交易按钮 */}
- {topic.status === 'active' && (
+ {/* 交易按钮 - 只在可交易时显示 */}
+ {canTrade && (
}
@@ -581,6 +680,104 @@ const PredictionTopicDetail = () => {
)}
+ {/* 作者提示 - 不能参与自己的话题 */}
+ {isAuthor && effectiveStatus === 'active' && (
+
+
+
+ 作为话题创建者,你不能参与自己发起的预测
+
+
+ )}
+
+ {/* 结算按钮 - 作者在截止后可见 */}
+ {canSettle && (
+
+
+
+
+
+ 交易已截止
+
+
+ 请根据实际结果进行结算
+
+
+
+
+ }
+ bg="linear-gradient(135deg, #DD6B20 0%, #C05621 100%)"
+ color="white"
+ size="lg"
+ w="full"
+ h={{ base: "12", md: "auto" }}
+ fontWeight="bold"
+ fontSize={{ base: "md", md: "lg" }}
+ onClick={onSettleModalOpen}
+ _hover={{
+ transform: 'translateY(-2px)',
+ boxShadow: '0 4px 12px rgba(221, 107, 32, 0.4)',
+ }}
+ _active={{ transform: 'translateY(0)' }}
+ >
+ 结算话题
+
+
+ )}
+
+ {/* 已结算状态提示 */}
+ {effectiveStatus === 'settled' && (
+
+
+
+ 话题已结算
+
+
+ 奖池已分配给获胜方
+
+
+ )}
+
+ {/* 等待结算状态提示(非作者) */}
+ {effectiveStatus === 'trading_closed' && !isAuthor && (
+
+
+
+ 交易已截止
+
+
+ 等待话题作者提交结算结果
+
+
+ )}
+
{/* 用户余额 */}
{user && userAccount && (
{
topic={topic}
onInvestSuccess={handleInvestSuccess}
/>
+
+ {/* 结算模态框 */}
+
);
};
diff --git a/src/views/ValueForum/components/CountdownTimer.js b/src/views/ValueForum/components/CountdownTimer.js
new file mode 100644
index 00000000..e66ee0cb
--- /dev/null
+++ b/src/views/ValueForum/components/CountdownTimer.js
@@ -0,0 +1,277 @@
+/**
+ * 倒计时组件
+ * 实时显示距离截止时间的倒计时,参考 Polymarket 设计
+ */
+
+import React, { useState, useEffect, useMemo } from 'react';
+import { HStack, Text, Box, Icon, Badge } from '@chakra-ui/react';
+import { Clock, AlertTriangle, CheckCircle2, Lock } from 'lucide-react';
+import { forumColors } from '@theme/forumTheme';
+
+/**
+ * 计算时间差
+ * @param {string|Date} deadline - 截止时间
+ * @returns {object} 时间差对象
+ */
+const calculateTimeLeft = (deadline) => {
+ const now = new Date();
+ const end = new Date(deadline);
+ const diff = end - now;
+
+ if (diff <= 0) {
+ return { expired: true, days: 0, hours: 0, minutes: 0, seconds: 0, totalMs: 0 };
+ }
+
+ return {
+ expired: false,
+ days: Math.floor(diff / (1000 * 60 * 60 * 24)),
+ hours: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
+ minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
+ seconds: Math.floor((diff % (1000 * 60)) / 1000),
+ totalMs: diff,
+ };
+};
+
+/**
+ * 倒计时 Hook
+ */
+export const useCountdown = (deadline) => {
+ const [timeLeft, setTimeLeft] = useState(() => calculateTimeLeft(deadline));
+
+ useEffect(() => {
+ // 已过期则不需要更新
+ if (timeLeft.expired) return;
+
+ // 根据剩余时间决定更新频率
+ const interval = timeLeft.totalMs < 60000 ? 1000 : 60000; // 小于1分钟时秒级更新
+
+ const timer = setInterval(() => {
+ const newTimeLeft = calculateTimeLeft(deadline);
+ setTimeLeft(newTimeLeft);
+
+ if (newTimeLeft.expired) {
+ clearInterval(timer);
+ }
+ }, interval);
+
+ return () => clearInterval(timer);
+ }, [deadline, timeLeft.expired, timeLeft.totalMs]);
+
+ return timeLeft;
+};
+
+/**
+ * 格式化倒计时显示
+ */
+const formatCountdown = (timeLeft) => {
+ if (timeLeft.expired) {
+ return '已截止';
+ }
+
+ if (timeLeft.days > 0) {
+ return `${timeLeft.days}天 ${timeLeft.hours}小时`;
+ }
+
+ if (timeLeft.hours > 0) {
+ return `${timeLeft.hours}小时 ${timeLeft.minutes}分`;
+ }
+
+ if (timeLeft.minutes > 0) {
+ return `${timeLeft.minutes}分 ${timeLeft.seconds}秒`;
+ }
+
+ return `${timeLeft.seconds}秒`;
+};
+
+/**
+ * 获取倒计时状态和颜色
+ */
+const getCountdownStatus = (timeLeft) => {
+ if (timeLeft.expired) {
+ return {
+ status: 'expired',
+ color: forumColors.text.secondary,
+ bgColor: forumColors.background.hover,
+ icon: Lock,
+ label: '已截止',
+ };
+ }
+
+ // 小于1小时
+ if (timeLeft.days === 0 && timeLeft.hours === 0) {
+ return {
+ status: 'critical',
+ color: '#E53E3E',
+ bgColor: 'rgba(229, 62, 62, 0.1)',
+ icon: AlertTriangle,
+ label: '即将截止',
+ };
+ }
+
+ // 小于24小时
+ if (timeLeft.days === 0) {
+ return {
+ status: 'warning',
+ color: '#DD6B20',
+ bgColor: 'rgba(221, 107, 32, 0.1)',
+ icon: Clock,
+ label: '今日截止',
+ };
+ }
+
+ // 小于3天
+ if (timeLeft.days < 3) {
+ return {
+ status: 'soon',
+ color: '#D69E2E',
+ bgColor: 'rgba(214, 158, 46, 0.1)',
+ icon: Clock,
+ label: '即将截止',
+ };
+ }
+
+ return {
+ status: 'normal',
+ color: forumColors.text.secondary,
+ bgColor: 'transparent',
+ icon: Clock,
+ label: '交易中',
+ };
+};
+
+/**
+ * 倒计时显示组件
+ */
+const CountdownTimer = ({
+ deadline,
+ status = 'active', // active, trading_closed, settled
+ size = 'md', // sm, md, lg
+ showIcon = true,
+ showLabel = false,
+ variant = 'inline', // inline, badge, card
+ onExpire,
+}) => {
+ const timeLeft = useCountdown(deadline);
+ const countdownStatus = getCountdownStatus(timeLeft);
+
+ // 当倒计时结束时触发回调
+ useEffect(() => {
+ if (timeLeft.expired && onExpire) {
+ onExpire();
+ }
+ }, [timeLeft.expired, onExpire]);
+
+ // 如果话题已结算,显示结算状态
+ if (status === 'settled') {
+ return (
+
+
+
+ 已结算
+
+
+ );
+ }
+
+ // 如果话题已截止(手动设置的状态)
+ if (status === 'trading_closed') {
+ return (
+
+
+
+ 等待结算
+
+
+ );
+ }
+
+ const sizeMap = {
+ sm: { fontSize: 'xs', iconSize: '12px', px: 2, py: 1 },
+ md: { fontSize: 'sm', iconSize: '14px', px: 3, py: 1 },
+ lg: { fontSize: 'md', iconSize: '16px', px: 4, py: 2 },
+ };
+
+ const sizeConfig = sizeMap[size];
+
+ // Badge 变体
+ if (variant === 'badge') {
+ return (
+
+ {showIcon && }
+ {showLabel && {countdownStatus.label}}
+ {formatCountdown(timeLeft)}
+
+ );
+ }
+
+ // Card 变体 - 更详细的显示
+ if (variant === 'card') {
+ return (
+
+
+
+
+
+ {timeLeft.expired ? '已截止' : '距离截止'}
+
+
+
+ {formatCountdown(timeLeft)}
+
+
+
+ {!timeLeft.expired && timeLeft.days === 0 && (
+
+ ⚠️ 交易即将关闭,请尽快完成交易
+
+ )}
+
+ );
+ }
+
+ // 默认 inline 变体
+ return (
+
+ {showIcon && (
+
+ )}
+ {showLabel && (
+
+ {countdownStatus.label}:
+
+ )}
+
+ {formatCountdown(timeLeft)}
+
+
+ );
+};
+
+export default CountdownTimer;
diff --git a/src/views/ValueForum/components/CreatePredictionModal.js b/src/views/ValueForum/components/CreatePredictionModal.js
index 7313c4e1..2544347a 100644
--- a/src/views/ValueForum/components/CreatePredictionModal.js
+++ b/src/views/ValueForum/components/CreatePredictionModal.js
@@ -3,7 +3,7 @@
* 用户可以发起新的预测市场话题
*/
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
import {
Modal,
ModalOverlay,
@@ -26,8 +26,15 @@ import {
Alert,
AlertIcon,
useToast,
+ RadioGroup,
+ Radio,
+ Tabs,
+ TabList,
+ Tab,
+ TabPanels,
+ TabPanel,
} from '@chakra-ui/react';
-import { Zap, Calendar, DollarSign } from 'lucide-react';
+import { Zap, Calendar, DollarSign, Clock, AlertTriangle } from 'lucide-react';
import { forumColors } from '@theme/forumTheme';
import { createTopic, getUserAccount } from '@services/predictionMarketService.api';
import { CREDIT_CONFIG } from '@services/creditSystemService';
@@ -43,7 +50,10 @@ const CreatePredictionModal = ({ isOpen, onClose, onTopicCreated }) => {
title: '',
description: '',
category: 'stock',
+ deadline_type: 'preset', // 'preset' 或 'custom'
deadline_days: 7,
+ custom_date: '',
+ custom_time: '15:00', // 默认下午3点(A股收盘时间)
});
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -70,6 +80,63 @@ const CreatePredictionModal = ({ isOpen, onClose, onTopicCreated }) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
+ // 计算截止时间
+ const calculatedDeadline = useMemo(() => {
+ if (formData.deadline_type === 'preset') {
+ const deadline = new Date();
+ deadline.setDate(deadline.getDate() + parseInt(formData.deadline_days));
+ deadline.setHours(15, 0, 0, 0); // 默认下午3点
+ return deadline;
+ } else {
+ if (!formData.custom_date) return null;
+ const [hours, minutes] = formData.custom_time.split(':');
+ const deadline = new Date(formData.custom_date);
+ deadline.setHours(parseInt(hours), parseInt(minutes), 0, 0);
+ return deadline;
+ }
+ }, [formData.deadline_type, formData.deadline_days, formData.custom_date, formData.custom_time]);
+
+ // 格式化截止时间显示
+ const formatDeadlineDisplay = (date) => {
+ if (!date) return '请选择日期';
+ const now = new Date();
+ const diff = date - now;
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
+ const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+
+ const dateStr = date.toLocaleDateString('zh-CN', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ weekday: 'short',
+ });
+ const timeStr = date.toLocaleTimeString('zh-CN', {
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+
+ if (days > 0) {
+ return `${dateStr} ${timeStr}(${days}天${hours}小时后)`;
+ } else if (hours > 0) {
+ return `${dateStr} ${timeStr}(${hours}小时后)`;
+ }
+ return `${dateStr} ${timeStr}`;
+ };
+
+ // 获取最小日期(明天)
+ const getMinDate = () => {
+ const tomorrow = new Date();
+ tomorrow.setDate(tomorrow.getDate() + 1);
+ return tomorrow.toISOString().split('T')[0];
+ };
+
+ // 获取最大日期(90天后)
+ const getMaxDate = () => {
+ const maxDate = new Date();
+ maxDate.setDate(maxDate.getDate() + 90);
+ return maxDate.toISOString().split('T')[0];
+ };
+
// 提交表单
const handleSubmit = async () => {
try {
@@ -105,9 +172,27 @@ const CreatePredictionModal = ({ isOpen, onClose, onTopicCreated }) => {
return;
}
- // 计算截止时间
- const deadline = new Date();
- deadline.setDate(deadline.getDate() + parseInt(formData.deadline_days));
+ // 验证截止时间
+ if (!calculatedDeadline) {
+ toast({
+ title: '请设置截止时间',
+ status: 'warning',
+ duration: 3000,
+ });
+ return;
+ }
+
+ // 检查截止时间是否在未来
+ if (calculatedDeadline <= new Date()) {
+ toast({
+ title: '截止时间必须在未来',
+ status: 'warning',
+ duration: 3000,
+ });
+ return;
+ }
+
+ const deadline = calculatedDeadline;
// 调用 API 创建话题
const response = await createTopic({
@@ -130,7 +215,10 @@ const CreatePredictionModal = ({ isOpen, onClose, onTopicCreated }) => {
title: '',
description: '',
category: 'stock',
+ deadline_type: 'preset',
deadline_days: 7,
+ custom_date: '',
+ custom_time: '15:00',
});
// 通知父组件
@@ -288,27 +376,175 @@ const CreatePredictionModal = ({ isOpen, onClose, onTopicCreated }) => {
交易截止时间
-
+
+
+
+ 截止时间:
+
+
+ {formatDeadlineDisplay(calculatedDeadline)}
+
+
+
+
- 截止后次日可提交结果进行结算
+ 截止后交易自动锁定,由作者提交结果进行结算
diff --git a/src/views/ValueForum/components/PredictionTopicCard.js b/src/views/ValueForum/components/PredictionTopicCard.js
index 561e186a..87078c74 100644
--- a/src/views/ValueForum/components/PredictionTopicCard.js
+++ b/src/views/ValueForum/components/PredictionTopicCard.js
@@ -3,7 +3,7 @@
* 展示预测市场的话题概览
*/
-import React from 'react';
+import React, { useMemo } from 'react';
import {
Box,
Text,
@@ -14,7 +14,6 @@ import {
Flex,
Avatar,
Icon,
- useColorModeValue,
} from '@chakra-ui/react';
import { motion } from 'framer-motion';
import {
@@ -22,18 +21,23 @@ import {
TrendingDown,
Crown,
Users,
- Clock,
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}`);
@@ -46,19 +50,14 @@ const PredictionTopicCard = ({ topic }) => {
return num;
};
- // 格式化时间
- 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 '即将截止';
- };
+ // 计算实际状态(基于截止时间自动判断)
+ const effectiveStatus = useMemo(() => {
+ // 如果已经是结算状态,保持不变
+ if (topic.status === 'settled') return 'settled';
+ // 如果已过截止时间,自动变为已截止
+ if (timeLeft.expired && topic.status === 'active') return 'trading_closed';
+ return topic.status;
+ }, [topic.status, timeLeft.expired]);
// 获取选项数据
const yesData = topic.positions?.yes || { total_shares: 0, current_price: 500, lord_id: null };
@@ -71,7 +70,7 @@ const PredictionTopicCard = ({ topic }) => {
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],
@@ -80,10 +79,16 @@ const PredictionTopicCard = ({ topic }) => {
const statusLabelMap = {
active: '交易中',
- trading_closed: '已截止',
+ trading_closed: '等待结算',
settled: '已结算',
};
+ const statusIconMap = {
+ active: Zap,
+ trading_closed: Lock,
+ settled: CheckCircle2,
+ };
+
return (
{
>
-
-
- 预测市场
+
+
+ {statusLabelMap[effectiveStatus]}
{
fontSize="xs"
fontWeight="bold"
>
- {statusLabelMap[topic.status]}
+ {effectiveStatus === 'active' ? '可交易' : effectiveStatus === 'trading_closed' ? '待结算' : '已完成'}
@@ -270,7 +275,7 @@ const PredictionTopicCard = ({ topic }) => {
{/* 奖池和数据 */}
-
+
@@ -281,13 +286,16 @@ const PredictionTopicCard = ({ topic }) => {
- {topic.stats?.unique_traders?.size || 0}人
+ {topic.participants_count || topic.stats?.unique_traders?.size || 0}人
-
-
- {formatTime(topic.deadline)}
-
+ {/* 使用新的倒计时组件 */}
+
{/* 底部:作者信息 */}
diff --git a/src/views/ValueForum/components/SettleTopicModal.js b/src/views/ValueForum/components/SettleTopicModal.js
new file mode 100644
index 00000000..edeb562b
--- /dev/null
+++ b/src/views/ValueForum/components/SettleTopicModal.js
@@ -0,0 +1,332 @@
+/**
+ * 话题结算模态框
+ * 允许话题创建者在截止后提交结算结果
+ */
+
+import React, { useState } from 'react';
+import {
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalHeader,
+ ModalBody,
+ ModalFooter,
+ ModalCloseButton,
+ Button,
+ VStack,
+ HStack,
+ Text,
+ Box,
+ Icon,
+ Alert,
+ AlertIcon,
+ useToast,
+ RadioGroup,
+ Radio,
+ Divider,
+} from '@chakra-ui/react';
+import { CheckCircle2, TrendingUp, TrendingDown, Scale, AlertTriangle, Coins } from 'lucide-react';
+import { forumColors } from '@theme/forumTheme';
+import { settleTopic } from '@services/predictionMarketService.api';
+import { GLASS_BLUR } from '@/constants/glassConfig';
+
+const SettleTopicModal = ({ isOpen, onClose, topic, onSettleSuccess }) => {
+ const toast = useToast();
+ const [selectedResult, setSelectedResult] = useState('');
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ // 获取选项数据
+ const yesData = {
+ total_shares: topic?.yes_total_shares || 0,
+ current_price: topic?.yes_price || 500,
+ };
+ const noData = {
+ total_shares: topic?.no_total_shares || 0,
+ current_price: topic?.no_price || 500,
+ };
+
+ 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 resultOptions = [
+ {
+ value: 'yes',
+ label: '看涨方获胜 (Yes)',
+ icon: TrendingUp,
+ color: 'green',
+ description: `${yesData.total_shares} 份持仓将获得奖池分红`,
+ poolShare: yesData.total_shares > 0 ? topic?.total_pool : 0,
+ },
+ {
+ value: 'no',
+ label: '看跌方获胜 (No)',
+ icon: TrendingDown,
+ color: 'red',
+ description: `${noData.total_shares} 份持仓将获得奖池分红`,
+ poolShare: noData.total_shares > 0 ? topic?.total_pool : 0,
+ },
+ {
+ value: 'draw',
+ label: '平局 / 无效',
+ icon: Scale,
+ color: 'gray',
+ description: '所有参与者按持仓比例退回积分',
+ poolShare: 0,
+ },
+ ];
+
+ const handleSubmit = async () => {
+ if (!selectedResult) {
+ toast({
+ title: '请选择结算结果',
+ status: 'warning',
+ duration: 3000,
+ });
+ return;
+ }
+
+ try {
+ setIsSubmitting(true);
+
+ const response = await settleTopic(topic.id, selectedResult);
+
+ if (response.success) {
+ toast({
+ title: '结算成功!',
+ description: `话题已结算,${resultOptions.find(o => o.value === selectedResult)?.label}`,
+ status: 'success',
+ duration: 5000,
+ });
+
+ if (onSettleSuccess) {
+ onSettleSuccess(response.data);
+ }
+
+ onClose();
+ }
+ } catch (error) {
+ console.error('结算失败:', error);
+ toast({
+ title: '结算失败',
+ description: error.message || '请稍后重试',
+ status: 'error',
+ duration: 3000,
+ });
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ if (!topic) return null;
+
+ return (
+
+
+
+
+
+
+ 结算预测话题
+
+
+
+
+
+
+ {/* 话题信息 */}
+
+
+ {topic.title}
+
+
+ {topic.description}
+
+
+
+ {/* 市场数据摘要 */}
+
+
+ 看涨方
+
+ {yesPercent.toFixed(0)}%
+
+
+ {yesData.total_shares} 份
+
+
+
+
+
+
+ 看跌方
+
+ {noPercent.toFixed(0)}%
+
+
+ {noData.total_shares} 份
+
+
+
+
+
+
+ 奖池
+
+
+
+ {topic.total_pool}
+
+
+ 积分
+
+
+
+ {/* 警告提示 */}
+
+
+
+
+ 结算操作不可撤销
+
+
+ 请确认预测结果后再提交,结算后将自动分配奖池给获胜方
+
+
+
+
+ {/* 结算选项 */}
+
+
+ 选择结算结果
+
+
+
+
+ {resultOptions.map((option) => (
+
+
+
+
+
+
+
+
+ {option.label}
+
+
+
+ {option.description}
+
+
+
+
+ {option.poolShare > 0 && (
+
+
+ 奖池分配
+
+
+ {option.poolShare} 积分
+
+
+ )}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ }
+ _hover={{
+ opacity: 0.9,
+ transform: 'translateY(-2px)',
+ }}
+ _active={{ transform: 'translateY(0)' }}
+ >
+ 确认结算
+
+
+
+
+
+ );
+};
+
+export default SettleTopicModal;