diff --git a/src/views/TradingSimulation/components/AccountOverview.js b/src/views/TradingSimulation/components/AccountOverview.js index 49fd2fc7..c677dc44 100644 --- a/src/views/TradingSimulation/components/AccountOverview.js +++ b/src/views/TradingSimulation/components/AccountOverview.js @@ -28,7 +28,9 @@ import { FiTrendingUp, FiTrendingDown, FiDollarSign, FiPieChart, FiTarget, FiAct import DonutChart from '../../../components/Charts/DonutChart'; import IconBox from '../../../components/Icons/IconBox'; -export default function AccountOverview({ account }) { +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'; diff --git a/src/views/TradingSimulation/components/PositionsList.js b/src/views/TradingSimulation/components/PositionsList.js index 8d27673f..cca36095 100644 --- a/src/views/TradingSimulation/components/PositionsList.js +++ b/src/views/TradingSimulation/components/PositionsList.js @@ -64,20 +64,38 @@ const calculateChange = (currentPrice, avgPrice) => { return { change, changePercent }; }; -export default function PositionsList({ positions, account, onSellStock }) { +export default function PositionsList({ positions, account, onSellStock, tradingEvents }) { const [selectedPosition, setSelectedPosition] = useState(null); const [sellQuantity, setSellQuantity] = useState(0); const [orderType, setOrderType] = useState('MARKET'); const [limitPrice, setLimitPrice] = useState(''); const [isLoading, setIsLoading] = useState(false); - + const [hasTracked, setHasTracked] = React.useState(false); + const { isOpen, onOpen, onClose } = useDisclosure(); const toast = useToast(); - + const cardBg = useColorModeValue('white', 'gray.800'); const textColor = useColorModeValue('gray.700', 'white'); const secondaryColor = useColorModeValue('gray.500', 'gray.400'); + // 🎯 追踪持仓查看 - 组件加载时触发一次 + React.useEffect(() => { + if (!hasTracked && positions && positions.length > 0 && tradingEvents && tradingEvents.trackSimulationHoldingsViewed) { + const totalMarketValue = positions.reduce((sum, pos) => sum + (pos.marketValue || pos.quantity * pos.currentPrice || 0), 0); + const totalCost = positions.reduce((sum, pos) => sum + (pos.totalCost || pos.quantity * pos.avgPrice || 0), 0); + const totalProfit = positions.reduce((sum, pos) => sum + (pos.profit || 0), 0); + + tradingEvents.trackSimulationHoldingsViewed({ + count: positions.length, + totalValue: totalMarketValue, + totalCost, + profitLoss: totalProfit, + }); + setHasTracked(true); + } + }, [positions, tradingEvents, hasTracked]); + // 格式化货币 const formatCurrency = (amount) => { return new Intl.NumberFormat('zh-CN', { @@ -102,6 +120,17 @@ export default function PositionsList({ positions, account, onSellStock }) { setSelectedPosition(position); setSellQuantity(position.availableQuantity); // 默认全部可卖数量 setLimitPrice(position.currentPrice?.toString() || position.avgPrice.toString()); + + // 🎯 追踪卖出按钮点击 + if (tradingEvents && tradingEvents.trackSellButtonClicked) { + tradingEvents.trackSellButtonClicked({ + stockCode: position.stockCode, + stockName: position.stockName, + quantity: position.quantity, + profitLoss: position.profit || 0, + }, 'holdings'); + } + onOpen(); }; @@ -110,6 +139,8 @@ export default function PositionsList({ positions, account, onSellStock }) { if (!selectedPosition || sellQuantity <= 0) return; setIsLoading(true); + const price = orderType === 'LIMIT' ? parseFloat(limitPrice) : selectedPosition.currentPrice || selectedPosition.avgPrice; + try { const result = await onSellStock( selectedPosition.stockCode, @@ -126,6 +157,20 @@ export default function PositionsList({ positions, account, onSellStock }) { orderType, orderId: result.orderId }); + + // 🎯 追踪卖出成功 + if (tradingEvents && tradingEvents.trackSimulationOrderPlaced) { + tradingEvents.trackSimulationOrderPlaced({ + stockCode: selectedPosition.stockCode, + stockName: selectedPosition.stockName, + direction: 'sell', + quantity: sellQuantity, + price, + orderType, + success: true, + }); + } + toast({ title: '卖出成功', description: `已卖出 ${selectedPosition.stockName} ${sellQuantity} 股`, @@ -142,6 +187,21 @@ export default function PositionsList({ positions, account, onSellStock }) { quantity: sellQuantity, orderType }); + + // 🎯 追踪卖出失败 + if (tradingEvents && tradingEvents.trackSimulationOrderPlaced) { + tradingEvents.trackSimulationOrderPlaced({ + stockCode: selectedPosition.stockCode, + stockName: selectedPosition.stockName, + direction: 'sell', + quantity: sellQuantity, + price, + orderType, + success: false, + errorMessage: error.message, + }); + } + toast({ title: '卖出失败', description: error.message, diff --git a/src/views/TradingSimulation/components/TradingHistory.js b/src/views/TradingSimulation/components/TradingHistory.js index af1a9716..31797bd2 100644 --- a/src/views/TradingSimulation/components/TradingHistory.js +++ b/src/views/TradingSimulation/components/TradingHistory.js @@ -34,18 +34,31 @@ import { import { FiSearch, FiFilter, FiClock, FiTrendingUp, FiTrendingDown } from 'react-icons/fi'; import { logger } from '../../../utils/logger'; -export default function TradingHistory({ history, onCancelOrder }) { +export default function TradingHistory({ history, onCancelOrder, tradingEvents }) { const [filterType, setFilterType] = useState('ALL'); // ALL, BUY, SELL const [filterStatus, setFilterStatus] = useState('ALL'); // ALL, FILLED, PENDING, CANCELLED const [searchTerm, setSearchTerm] = useState(''); const [sortBy, setSortBy] = useState('createdAt'); // createdAt, stockCode, amount const [sortOrder, setSortOrder] = useState('desc'); // desc, asc - + const [hasTracked, setHasTracked] = React.useState(false); + const toast = useToast(); const cardBg = useColorModeValue('white', 'gray.800'); const textColor = useColorModeValue('gray.700', 'white'); const secondaryColor = useColorModeValue('gray.500', 'gray.400'); + // 🎯 追踪历史记录查看 - 组件加载时触发一次 + React.useEffect(() => { + if (!hasTracked && history && history.length > 0 && tradingEvents && tradingEvents.trackSimulationHistoryViewed) { + tradingEvents.trackSimulationHistoryViewed({ + count: history.length, + filterBy: 'all', + dateRange: 'all', + }); + setHasTracked(true); + } + }, [history, tradingEvents, hasTracked]); + // 格式化货币 const formatCurrency = (amount) => { return new Intl.NumberFormat('zh-CN', { diff --git a/src/views/TradingSimulation/components/TradingPanel.js b/src/views/TradingSimulation/components/TradingPanel.js index f5f697e0..77cb2d87 100644 --- a/src/views/TradingSimulation/components/TradingPanel.js +++ b/src/views/TradingSimulation/components/TradingPanel.js @@ -55,7 +55,7 @@ import { FiSearch, FiTrendingUp, FiTrendingDown, FiDollarSign, FiZap, FiTarget } // 导入现有的高质量组件 import IconBox from '../../../components/Icons/IconBox'; -export default function TradingPanel({ account, onBuyStock, onSellStock, searchStocks }) { +export default function TradingPanel({ account, onBuyStock, onSellStock, searchStocks, tradingEvents }) { const [activeTab, setActiveTab] = useState(0); // 0: 买入, 1: 卖出 const [searchTerm, setSearchTerm] = useState(''); const [selectedStock, setSelectedStock] = useState(null); @@ -87,7 +87,7 @@ export default function TradingPanel({ account, onBuyStock, onSellStock, searchS const results = await searchStocks(searchTerm); // 转换为组件需要的格式 const formattedResults = results.map(stock => [ - stock.stock_code, + stock.stock_code, { name: stock.stock_name, price: stock.current_price || 0, // 使用后端返回的真实价格 @@ -97,10 +97,20 @@ export default function TradingPanel({ account, onBuyStock, onSellStock, searchS ]); setFilteredStocks(formattedResults); setShowStockList(true); + + // 🎯 追踪股票搜索 + if (tradingEvents && tradingEvents.trackSimulationStockSearched) { + tradingEvents.trackSimulationStockSearched(searchTerm, formattedResults.length); + } } catch (error) { logger.error('TradingPanel', 'handleStockSearch', error, { searchTerm }); setFilteredStocks([]); setShowStockList(false); + + // 🎯 追踪搜索无结果 + if (tradingEvents && tradingEvents.trackSimulationStockSearched) { + tradingEvents.trackSimulationStockSearched(searchTerm, 0); + } } } else { setFilteredStocks([]); @@ -109,7 +119,7 @@ export default function TradingPanel({ account, onBuyStock, onSellStock, searchS }, 300); // 300ms 防抖 return () => clearTimeout(searchDebounced); - }, [searchTerm, searchStocks]); + }, [searchTerm, searchStocks, tradingEvents]); // 选择股票 const handleSelectStock = (code, stock) => { @@ -169,6 +179,9 @@ export default function TradingPanel({ account, onBuyStock, onSellStock, searchS if (!validateForm()) return; setIsLoading(true); + const price = orderType === 'LIMIT' ? parseFloat(limitPrice) : selectedStock.price; + const direction = activeTab === 0 ? 'buy' : 'sell'; + try { let result; if (activeTab === 0) { @@ -197,6 +210,19 @@ export default function TradingPanel({ account, onBuyStock, onSellStock, searchS orderType }); + // 🎯 追踪下单成功 + if (tradingEvents && tradingEvents.trackSimulationOrderPlaced) { + tradingEvents.trackSimulationOrderPlaced({ + stockCode: selectedStock.code, + stockName: selectedStock.name, + direction, + quantity, + price, + orderType, + success: true, + }); + } + // ✅ 保留交易成功toast(关键用户操作反馈) toast({ title: activeTab === 0 ? '买入成功' : '卖出成功', @@ -217,6 +243,20 @@ export default function TradingPanel({ account, onBuyStock, onSellStock, searchS orderType }); + // 🎯 追踪下单失败 + if (tradingEvents && tradingEvents.trackSimulationOrderPlaced) { + tradingEvents.trackSimulationOrderPlaced({ + stockCode: selectedStock.code, + stockName: selectedStock.name, + direction, + quantity, + price, + orderType, + success: false, + errorMessage: error.message, + }); + } + // ✅ 保留交易失败toast(关键用户操作错误反馈) toast({ title: activeTab === 0 ? '买入失败' : '卖出失败', diff --git a/src/views/TradingSimulation/index.js b/src/views/TradingSimulation/index.js index a8a60eb7..31624c59 100644 --- a/src/views/TradingSimulation/index.js +++ b/src/views/TradingSimulation/index.js @@ -281,9 +281,14 @@ export default function TradingSimulation() { {/* 主要功能区域 - 放在上面 */} - { + setActiveTab(index); + // 🎯 追踪 Tab 切换 + const tabNames = ['trading', 'holdings', 'history', 'margin']; + tradingEvents.trackTabClicked(tabNames[index]); + }} variant="soft-rounded" colorScheme="blue" size="lg" @@ -298,28 +303,31 @@ export default function TradingSimulation() { {/* 交易面板 */} - {/* 我的持仓 */} - {/* 交易历史 */} - @@ -341,7 +349,7 @@ export default function TradingSimulation() { 📊 账户统计分析 - + {/* 资产走势图表 - 只在有数据时显示 */}