Files
vf_react/src/views/TradingSimulation/components/AccountOverview.js
zdl bd787b1d8b refactor(TradingSimulation): 迁移 ApexCharts 图表到 ECharts
图表组件迁移:
  - AssetTrendChart: 资产走势折线图 → ECharts 面积图
  - AssetAllocationChart: 资产配置环形图 → ECharts 饼图
  - PositionDistributionChart: 持仓分布饼图 → ECharts 饼图
  - ProfitAnalysisChart: 盈亏分析柱状图 → ECharts 柱状图

  删除的 ApexCharts 组件:
  - src/components/Charts/LineChart.js
  - src/components/Charts/BarChart.js
  - src/components/Charts/PieChart.js
  - src/components/Charts/DonutChart.js

  技术改进:
  - 统一使用 ECharts 作为通用图表库
  - 新组件使用 TypeScript,类型安全
  - 为后续移除 apexcharts 依赖做准备
2025-12-24 12:06:26 +08:00

304 lines
11 KiB
JavaScript

// src/views/TradingSimulation/components/AccountOverview.js - 账户概览组件(现代化版本)
import React from 'react';
import {
Grid,
GridItem,
Stat,
StatLabel,
StatNumber,
StatHelpText,
StatArrow,
Box,
Text,
Badge,
VStack,
HStack,
Progress,
useColorModeValue,
Icon,
Flex,
SimpleGrid,
Card,
CardBody,
CardHeader
} from '@chakra-ui/react';
import { FiTrendingUp, FiTrendingDown, FiDollarSign, FiPieChart, FiTarget, FiActivity } from 'react-icons/fi';
// 导入图表组件
import { AssetAllocationChart } from './AssetAllocationChart';
import IconBox from '../../../components/Icons/IconBox';
export default function AccountOverview({ account, tradingEvents }) {
// tradingEvents 已传递,可用于将来添加的账户重置等功能
// 例如: tradingEvents.trackAccountReset(beforeResetData)
const textColor = useColorModeValue('gray.700', 'white');
const secondaryColor = useColorModeValue('gray.500', 'gray.400');
const profitColor = account?.totalProfit >= 0 ? 'green.500' : 'red.500';
const profitBg = useColorModeValue(
account?.totalProfit >= 0 ? 'green.50' : 'red.50',
account?.totalProfit >= 0 ? 'green.900' : 'red.900'
);
if (!account) {
return (
<Box p={4}>
<Text color={secondaryColor}>暂无账户数据</Text>
</Box>
);
}
// 安全地计算资产配置比例
const cashRatio = account?.totalAssets > 0 ? (account.availableCash / account.totalAssets) * 100 : 0;
const stockRatio = account?.totalAssets > 0 ? (account.marketValue / account.totalAssets) * 100 : 0;
// 格式化数字
const formatCurrency = (amount) => {
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(amount || 0);
};
const formatPercent = (percent) => {
return `${(percent || 0) >= 0 ? '+' : ''}${(percent || 0).toFixed(2)}%`;
};
return (
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing={6}>
{/* 左侧:主要资产数据 */}
<VStack spacing={6} align="stretch">
{/* 核心资产指标 */}
<SimpleGrid columns={{ base: 2, md: 2 }} spacing={4}>
{/* 总资产卡片 */}
<Card minH="120px">
<CardBody>
<Flex direction="row" align="center" justify="center" w="100%">
<Stat me="auto">
<StatLabel
fontSize="sm"
color={secondaryColor}
fontWeight="bold"
pb=".1rem"
>
总资产
</StatLabel>
<Flex>
<StatNumber fontSize="lg" color={textColor} fontWeight="bold">
{formatCurrency(account?.totalAssets)}
</StatNumber>
</Flex>
</Stat>
<IconBox
as="div"
h={"45px"}
w={"45px"}
bg="linear-gradient(90deg, #4481EB 0%, #04BEFE 100%)"
icon={<Icon as={FiDollarSign} color="white" w="24px" h="24px" />}
/>
</Flex>
</CardBody>
</Card>
{/* 总收益卡片 */}
<Card minH="120px">
<CardBody>
<Flex direction="row" align="center" justify="center" w="100%">
<Stat me="auto">
<StatLabel
fontSize="sm"
color={secondaryColor}
fontWeight="bold"
pb=".1rem"
>
总收益
</StatLabel>
<Flex align="center">
<StatNumber fontSize="lg" color={profitColor} fontWeight="bold">
{formatCurrency(account?.totalProfit)}
</StatNumber>
<StatArrow
type={(account?.totalProfit || 0) >= 0 ? 'increase' : 'decrease'}
color={profitColor}
ml={2}
/>
</Flex>
<StatHelpText color={profitColor} fontSize="sm" fontWeight="bold">
{formatPercent(account?.totalProfitPercent)}
</StatHelpText>
</Stat>
<IconBox
as="div"
h={"45px"}
w={"45px"}
bg={(account?.totalProfit || 0) >= 0
? "linear-gradient(90deg, #4FD1C7 0%, #81E6D9 100%)"
: "linear-gradient(90deg, #FEB2B2 0%, #F56565 100%)"
}
icon={
<Icon
as={(account?.totalProfit || 0) >= 0 ? FiTrendingUp : FiTrendingDown}
color="white"
w="24px"
h="24px"
/>
}
/>
</Flex>
</CardBody>
</Card>
{/* 可用资金卡片 */}
<Card minH="120px">
<CardBody>
<Flex direction="row" align="center" justify="center" w="100%">
<Stat me="auto">
<StatLabel
fontSize="sm"
color={secondaryColor}
fontWeight="bold"
pb=".1rem"
>
可用资金
</StatLabel>
<Flex>
<StatNumber fontSize="lg" color={textColor} fontWeight="bold">
{formatCurrency(account?.availableCash)}
</StatNumber>
</Flex>
<StatHelpText color={secondaryColor} fontSize="xs">
现金占比: {(cashRatio || 0).toFixed(1)}%
</StatHelpText>
</Stat>
<IconBox
as="div"
h={"45px"}
w={"45px"}
bg="linear-gradient(90deg, #FFE57F 0%, #FFB74D 100%)"
icon={<Icon as={FiTarget} color="white" w="24px" h="24px" />}
/>
</Flex>
</CardBody>
</Card>
{/* 持仓市值卡片 */}
<Card minH="120px">
<CardBody>
<Flex direction="row" align="center" justify="center" w="100%">
<Stat me="auto">
<StatLabel
fontSize="sm"
color={secondaryColor}
fontWeight="bold"
pb=".1rem"
>
持仓市值
</StatLabel>
<Flex>
<StatNumber fontSize="lg" color={textColor} fontWeight="bold">
{formatCurrency(account?.marketValue)}
</StatNumber>
</Flex>
<StatHelpText color={secondaryColor} fontSize="xs">
持仓占比: {(stockRatio || 0).toFixed(1)}%
</StatHelpText>
</Stat>
<IconBox
as="div"
h={"45px"}
w={"45px"}
bg="linear-gradient(90deg, #9F7AEA 0%, #805AD5 100%)"
icon={<Icon as={FiPieChart} color="white" w="24px" h="24px" />}
/>
</Flex>
</CardBody>
</Card>
</SimpleGrid>
{/* 风险等级和账户状态 */}
<Card>
<CardBody>
<VStack spacing={4}>
<HStack justify="space-between" w="full">
<HStack spacing={3}>
<IconBox
as="div"
h={"35px"}
w={"35px"}
bg="linear-gradient(90deg, #F093FB 0%, #F5576C 100%)"
icon={<Icon as={FiActivity} color="white" w="16px" h="16px" />}
/>
<VStack align="start" spacing={0}>
<Text fontSize="sm" fontWeight="bold" color={textColor}>账户状态</Text>
<Text fontSize="xs" color={secondaryColor}>风险等级: 中等</Text>
</VStack>
</HStack>
<Badge
colorScheme={(account?.totalProfit || 0) >= 0 ? 'green' : 'red'}
variant="solid"
borderRadius="full"
px={3}
py={1}
>
{(account?.totalProfit || 0) >= 0 ? '盈利中' : '亏损中'}
</Badge>
</HStack>
<HStack justify="space-between" w="full" fontSize="xs" color={secondaryColor}>
<Text>创建时间: {account?.createdAt ? new Date(account.createdAt).toLocaleDateString('zh-CN') : '-'}</Text>
<Text>最后更新: {account?.lastUpdated ? new Date(account.lastUpdated).toLocaleString('zh-CN') : '-'}</Text>
</HStack>
</VStack>
</CardBody>
</Card>
</VStack>
{/* 右侧:资产配置图表 */}
<Card>
<CardHeader>
<HStack justify="space-between" w="full">
<Text fontSize="lg" fontWeight="bold" color={textColor}>
资产配置
</Text>
<Badge colorScheme="blue" variant="outline">
实时更新
</Badge>
</HStack>
</CardHeader>
<CardBody>
<AssetAllocationChart
cashAmount={account?.availableCash || 0}
stockAmount={account?.marketValue || 0}
height={280}
/>
{/* 详细配置信息 */}
<VStack spacing={3} mt={4}>
<HStack justify="space-between" w="full">
<HStack spacing={2}>
<Box w={3} h={3} bg="blue.400" borderRadius="sm" />
<Text fontSize="sm" color={textColor}>现金资产</Text>
</HStack>
<Text fontSize="sm" fontWeight="bold" color={textColor}>
{formatCurrency(account?.availableCash)}
</Text>
</HStack>
<HStack justify="space-between" w="full">
<HStack spacing={2}>
<Box w={3} h={3} bg="green.400" borderRadius="sm" />
<Text fontSize="sm" color={textColor}>股票资产</Text>
</HStack>
<Text fontSize="sm" fontWeight="bold" color={textColor}>
{formatCurrency(account?.marketValue)}
</Text>
</HStack>
</VStack>
</CardBody>
</Card>
</SimpleGrid>
);
}