Files
vf_react/src/views/TradingSimulation/index.js
2025-10-20 13:58:07 +08:00

401 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/views/TradingSimulation/index.js - 模拟盘系统主页面
import React, { useState, useEffect } from 'react';
import {
Box,
Container,
Flex,
Grid,
GridItem,
Tab,
Tabs,
TabList,
TabPanels,
TabPanel,
Card,
CardHeader,
CardBody,
Heading,
Text,
Button,
Badge,
VStack,
HStack,
Stat,
StatLabel,
StatNumber,
StatHelpText,
StatArrow,
useColorModeValue,
Spinner,
Alert,
AlertIcon,
AlertTitle,
AlertDescription,
Link,
} from '@chakra-ui/react';
import { useAuth } from '../../contexts/AuthContext';
import { logger } from '../../utils/logger';
// 导入子组件
import AccountOverview from './components/AccountOverview';
import TradingPanel from './components/TradingPanel';
import PositionsList from './components/PositionsList';
import TradingHistory from './components/TradingHistory';
import MarginTrading from './components/MarginTrading';
// 导入现有的高质量组件
import LineChart from '../../components/Charts/LineChart';
// 导航栏已由 MainLayout 提供,无需在此导入
// 模拟盘账户管理 Hook
import { useTradingAccount } from './hooks/useTradingAccount';
export default function TradingSimulation() {
// ========== 1. 所有 Hooks 必须放在最顶部,不能有任何条件判断 ==========
const { user, isAuthenticated } = useAuth();
const [activeTab, setActiveTab] = useState(0);
const [assetHistory, setAssetHistory] = useState([]); // 移到这里!
// 使用模拟账户管理 Hook
const {
account,
positions,
tradingHistory,
isLoading,
error,
buyStock,
sellStock,
cancelOrder,
refreshAccount,
searchStocks,
getAssetHistory
} = useTradingAccount();
// 所有的 useColorModeValue 也必须在顶部
const bgColor = useColorModeValue('gray.50', 'gray.900');
const cardBg = useColorModeValue('white', 'gray.800');
const xAxisLabelColor = useColorModeValue('#718096', '#A0AEC0');
const yAxisLabelColor = useColorModeValue('#718096', '#A0AEC0');
const gridBorderColor = useColorModeValue('#E2E8F0', '#4A5568');
// ========== 2. 所有 useEffect 也必须在条件返回之前 ==========
useEffect(() => {
// 调试模式:即使没有登录也加载模拟数据
refreshAccount();
}, [refreshAccount]);
// 调试:观察认证状态变化
useEffect(() => {
logger.debug('TradingSimulation', '组件挂载,认证状态检查', {
isAuthenticated,
userId: user?.id,
userName: user?.name
});
}, [isAuthenticated, user?.id]); // 只依赖 user.id,避免无限循环
// 获取资产历史数据的 useEffect
useEffect(() => {
if (account) {
getAssetHistory(30).then(data => {
setAssetHistory(data || []);
logger.debug('TradingSimulation', '资产历史数据加载成功', {
accountId: account.id,
dataPoints: data?.length || 0
});
}).catch(err => {
logger.error('TradingSimulation', 'getAssetHistory', err, {
accountId: account?.id,
days: 30
});
setAssetHistory([]);
});
}
}, [account, getAssetHistory]);
// ========== 3. 数据处理和计算(不是 Hooks可以放在这里==========
// 准备资产走势图表数据(使用真实数据,安全处理)
const hasAssetData = Array.isArray(assetHistory) && assetHistory.length > 0;
const assetTrendData = hasAssetData ? [{
name: "总资产",
data: assetHistory.map(item => {
// 安全地获取数据避免undefined错误
if (!item) return 0;
return item.closing_assets || item.total_assets || 0;
})
}] : [];
const assetTrendOptions = hasAssetData ? {
chart: {
toolbar: { show: false },
height: 350
},
tooltip: {
theme: "dark",
y: {
formatter: function(val) {
return '¥' + (val || 0).toLocaleString()
}
}
},
dataLabels: {
enabled: false,
},
stroke: {
curve: "smooth",
width: 3
},
xaxis: {
type: "datetime",
categories: assetHistory.map(item => {
// 安全地获取日期
if (!item) return '';
return item.date || '';
}),
labels: {
style: {
colors: xAxisLabelColor,
fontSize: "12px",
},
},
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
yaxis: {
labels: {
style: {
colors: yAxisLabelColor,
fontSize: "12px",
},
formatter: function(val) {
return '¥' + ((val || 0) / 10000).toFixed(1) + 'w'
}
},
},
legend: {
show: false,
},
grid: {
strokeDashArray: 5,
borderColor: gridBorderColor
},
fill: {
type: "gradient",
gradient: {
shade: "light",
type: "vertical",
shadeIntensity: 0.5,
gradientToColors: undefined,
inverseColors: true,
opacityFrom: 0.8,
opacityTo: 0,
stops: [],
},
colors: [account?.totalProfit >= 0 ? "#48BB78" : "#F56565"],
},
colors: [account?.totalProfit >= 0 ? "#48BB78" : "#F56565"],
} : {};
// ========== 4. 现在可以安全地进行条件返回了 ==========
if (isLoading) {
return (
<Container maxW="7xl" py={8}>
<Flex justify="center" align="center" minH="400px">
<VStack spacing={4}>
<Spinner size="xl" color="blue.500" />
<Text>正在加载模拟盘数据...</Text>
</VStack>
</Flex>
</Container>
);
}
if (error) {
return (
<Container maxW="7xl" py={8}>
<Alert status="error">
<AlertIcon />
<AlertTitle>加载失败</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
</Container>
);
}
// ========== 5. 主要渲染逻辑 ==========
return (
<Box minH="100vh" bg={bgColor}>
{/* 导航栏已由 MainLayout 提供 */}
<Container maxW="7xl" py={8}>
{!isAuthenticated ? (
<Alert status="warning">
<AlertIcon />
<AlertTitle>需要登录</AlertTitle>
<AlertDescription>
请先登录以访问模拟盘系统
</AlertDescription>
</Alert>
) : (
<VStack spacing={8} align="stretch">
{/* 现代化页面标题 */}
<Box textAlign="center" py={6}>
<Heading
size="2xl"
bgGradient="linear(to-r, blue.400, purple.500)"
bgClip="text"
fontWeight="extrabold"
mb={3}
>
智能模拟交易平台
</Heading>
<Text fontSize="xl" color="gray.600" maxW="2xl" mx="auto">
体验真实交易环境提升投资技能零风险练手
</Text>
</Box>
{/* 主要功能区域 - 放在上面 */}
<Tabs
index={activeTab}
onChange={setActiveTab}
variant="soft-rounded"
colorScheme="blue"
size="lg"
>
<TabList bg={cardBg} p={2} borderRadius="xl" shadow="sm">
<Tab fontWeight="bold">💹 交易面板</Tab>
<Tab fontWeight="bold">📊 我的持仓</Tab>
<Tab fontWeight="bold">📋 交易历史</Tab>
<Tab fontWeight="bold">💰 融资融券</Tab>
</TabList>
<TabPanels>
{/* 交易面板 */}
<TabPanel px={0}>
<TradingPanel
account={account}
onBuyStock={buyStock}
onSellStock={sellStock}
searchStocks={searchStocks}
/>
</TabPanel>
{/* 我的持仓 */}
<TabPanel px={0}>
<PositionsList
positions={positions}
account={account}
onSellStock={sellStock}
/>
</TabPanel>
{/* 交易历史 */}
<TabPanel px={0}>
<TradingHistory
history={tradingHistory}
onCancelOrder={cancelOrder}
/>
</TabPanel>
{/* 融资融券 */}
<TabPanel px={0}>
<MarginTrading
account={account}
onMarginBuy={buyStock}
onShortSell={sellStock}
/>
</TabPanel>
</TabPanels>
</Tabs>
{/* 统计数据区域 - 放在下面 */}
<VStack spacing={6} align="stretch">
{/* 账户概览统计 */}
<Box>
<Heading size="lg" mb={4} color={useColorModeValue('gray.700', 'white')}>
📊 账户统计分析
</Heading>
<AccountOverview account={account} />
</Box>
{/* 资产走势图表 - 只在有数据时显示 */}
{hasAssetData && (
<Card>
<CardHeader>
<HStack justify="space-between">
<Text fontSize="lg" fontWeight="bold" color={useColorModeValue('gray.700', 'white')}>
📈 资产走势分析
</Text>
<Badge colorScheme="purple" variant="outline">
{assetHistory.length}
</Badge>
</HStack>
</CardHeader>
<CardBody>
<Box h="350px">
<LineChart
chartData={assetTrendData}
chartOptions={assetTrendOptions}
/>
</Box>
</CardBody>
</Card>
)}
{/* 无数据提示 */}
{!hasAssetData && account && (
<Card>
<CardBody>
<VStack spacing={4} py={8}>
<Text fontSize="lg" color="gray.500">📊 暂无历史数据</Text>
<Text fontSize="sm" color="gray.400" textAlign="center">
开始交易后这里将显示您的资产走势图表和详细统计分析
</Text>
</VStack>
</CardBody>
</Card>
)}
</VStack>
{/* 风险提示 */}
<Alert status="info" variant="left-accent">
<AlertIcon />
<Box>
<AlertTitle>风险提示</AlertTitle>
<AlertDescription>
本系统为模拟交易所有数据仅供学习和练习使用不构成实际投资建议
模拟盘起始资金为100万元人民币
</AlertDescription>
</Box>
</Alert>
</VStack>
)}
</Container>
{/* Footer区域 */}
<Box bg={useColorModeValue('gray.100', 'gray.800')} py={6} mt={8}>
<Container maxW="7xl">
<VStack spacing={2}>
<Text color="gray.500" fontSize="sm">
© 2024 价值前沿. 保留所有权利.
</Text>
<HStack spacing={4} fontSize="xs" color="gray.400">
<Link
href="https://beian.mps.gov.cn/#/query/webSearch?code=11010802046286"
isExternal
_hover={{ color: 'gray.600' }}
>
京公网安备11010802046286号
</Link>
<Text>京ICP备2025107343号-1</Text>
</HStack>
</VStack>
</Container>
</Box>
</Box>
);
}