feat: 任务 1: 集成 TradingSimulation 追踪事件任务 2: 传递 tradingEvents 到子组件

This commit is contained in:
zdl
2025-10-29 14:24:39 +08:00
parent 8632e40c94
commit e5ab99bae6
5 changed files with 139 additions and 16 deletions

View File

@@ -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';

View File

@@ -64,12 +64,13 @@ 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();
@@ -78,6 +79,23 @@ export default function PositionsList({ positions, account, onSellStock }) {
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,

View File

@@ -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', {

View File

@@ -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);
@@ -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 ? '买入失败' : '卖出失败',

View File

@@ -283,7 +283,12 @@ export default function TradingSimulation() {
{/* 主要功能区域 - 放在上面 */}
<Tabs
index={activeTab}
onChange={setActiveTab}
onChange={(index) => {
setActiveTab(index);
// 🎯 追踪 Tab 切换
const tabNames = ['trading', 'holdings', 'history', 'margin'];
tradingEvents.trackTabClicked(tabNames[index]);
}}
variant="soft-rounded"
colorScheme="blue"
size="lg"
@@ -303,6 +308,7 @@ export default function TradingSimulation() {
onBuyStock={buyStock}
onSellStock={sellStock}
searchStocks={searchStocks}
tradingEvents={tradingEvents}
/>
</TabPanel>
@@ -312,6 +318,7 @@ export default function TradingSimulation() {
positions={positions}
account={account}
onSellStock={sellStock}
tradingEvents={tradingEvents}
/>
</TabPanel>
@@ -320,6 +327,7 @@ export default function TradingSimulation() {
<TradingHistory
history={tradingHistory}
onCancelOrder={cancelOrder}
tradingEvents={tradingEvents}
/>
</TabPanel>
@@ -341,7 +349,7 @@ export default function TradingSimulation() {
<Heading size="lg" mb={4} color={useColorModeValue('gray.700', 'white')}>
📊 账户统计分析
</Heading>
<AccountOverview account={account} />
<AccountOverview account={account} tradingEvents={tradingEvents} />
</Box>
{/* 资产走势图表 - 只在有数据时显示 */}