401 lines
12 KiB
JavaScript
401 lines
12 KiB
JavaScript
// 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>
|
||
);
|
||
} |