// src/views/TradingSimulation/components/TradingPanel.js - 交易面板组件(现代化版本) import React, { useState, useRef, useEffect } from 'react'; import { logger } from '../../../utils/logger'; import { Box, Card, CardHeader, CardBody, Heading, Text, Input, Button, Select, NumberInput, NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper, FormControl, FormLabel, FormErrorMessage, VStack, HStack, Grid, GridItem, Tabs, TabList, TabPanels, Tab, TabPanel, Badge, Alert, AlertIcon, AlertDescription, Stat, StatLabel, StatNumber, StatHelpText, useToast, useColorModeValue, Spinner, Table, Thead, Tbody, Tr, Th, Td, Icon, InputGroup, InputLeftElement, Flex } from '@chakra-ui/react'; import { FiSearch, FiTrendingUp, FiTrendingDown, FiDollarSign, FiZap, FiTarget } from 'react-icons/fi'; // 导入现有的高质量组件 import IconBox from '../../../components/Icons/IconBox'; export default function TradingPanel({ account, onBuyStock, onSellStock, searchStocks }) { const [activeTab, setActiveTab] = useState(0); // 0: 买入, 1: 卖出 const [searchTerm, setSearchTerm] = useState(''); const [selectedStock, setSelectedStock] = useState(null); const [quantity, setQuantity] = useState(100); const [orderType, setOrderType] = useState('MARKET'); const [limitPrice, setLimitPrice] = useState(''); const [isLoading, setIsLoading] = useState(false); const [errors, setErrors] = useState({}); const [filteredStocks, setFilteredStocks] = useState([]); const [showStockList, setShowStockList] = useState(false); const toast = useToast(); const searchInputRef = useRef(); const cardBg = useColorModeValue('white', 'gray.800'); const borderColor = useColorModeValue('gray.200', 'gray.600'); const textColor = useColorModeValue('gray.700', 'white'); const secondaryColor = useColorModeValue('gray.500', 'gray.400'); const headerBgGradient = useColorModeValue('linear(to-r, blue.500, purple.600)', 'linear(to-r, blue.500, purple.600)'); const stockRowHoverBg = useColorModeValue('gray.50', 'gray.700'); const selectedInfoBg = useColorModeValue('blue.50', 'blue.900'); const calcCardBg = useColorModeValue('gray.50', 'gray.700'); // 搜索股票 useEffect(() => { const searchDebounced = setTimeout(async () => { if (searchTerm.length >= 2) { try { const results = await searchStocks(searchTerm); // 转换为组件需要的格式 const formattedResults = results.map(stock => [ stock.stock_code, { name: stock.stock_name, price: stock.current_price || 0, // 使用后端返回的真实价格 change: 0, // 暂时设为0,可以后续计算 changePercent: 0 } ]); setFilteredStocks(formattedResults); setShowStockList(true); } catch (error) { logger.error('TradingPanel', 'handleStockSearch', error, { searchTerm }); setFilteredStocks([]); setShowStockList(false); } } else { setFilteredStocks([]); setShowStockList(false); } }, 300); // 300ms 防抖 return () => clearTimeout(searchDebounced); }, [searchTerm, searchStocks]); // 选择股票 const handleSelectStock = (code, stock) => { setSelectedStock({ code, ...stock }); setSearchTerm(`${code} ${stock.name}`); setShowStockList(false); setLimitPrice(stock.price.toString()); }; // 清除选择 const clearSelection = () => { setSelectedStock(null); setSearchTerm(''); setShowStockList(false); setLimitPrice(''); setErrors({}); }; // 验证表单 const validateForm = () => { const newErrors = {}; if (!selectedStock) { newErrors.stock = '请选择股票'; } if (!quantity || quantity <= 0) { newErrors.quantity = '请输入有效的买入数量'; } if (quantity % 100 !== 0) { newErrors.quantity = '股票数量必须是100的倍数'; } if (orderType === 'LIMIT' && (!limitPrice || parseFloat(limitPrice) <= 0)) { newErrors.limitPrice = '请输入有效的限价'; } // 买入资金检查 if (activeTab === 0 && selectedStock) { const price = orderType === 'LIMIT' ? parseFloat(limitPrice) : selectedStock.price; const totalCost = price * quantity; const commission = Math.max(totalCost * 0.0003, 5); const totalAmount = totalCost + commission; if (totalAmount > account.availableCash) { newErrors.funds = '可用资金不足'; } } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // 执行交易 const handleTrade = async () => { if (!validateForm()) return; setIsLoading(true); try { let result; if (activeTab === 0) { // 买入 result = await onBuyStock( selectedStock.code, quantity, orderType, orderType === 'LIMIT' ? parseFloat(limitPrice) : null ); } else { // 卖出 result = await onSellStock( selectedStock.code, quantity, orderType, orderType === 'LIMIT' ? parseFloat(limitPrice) : null ); } if (result.success) { logger.info('TradingPanel', `${activeTab === 0 ? '买入' : '卖出'}成功`, { orderId: result.orderId, stockCode: selectedStock.code, quantity, orderType }); // ✅ 保留交易成功toast(关键用户操作反馈) toast({ title: activeTab === 0 ? '买入成功' : '卖出成功', description: `订单号: ${result.orderId}`, status: 'success', duration: 3000, isClosable: true, }); // 重置表单 setQuantity(100); clearSelection(); } } catch (error) { logger.error('TradingPanel', `${activeTab === 0 ? '买入' : '卖出'}失败`, error, { stockCode: selectedStock?.code, quantity, orderType }); // ✅ 保留交易失败toast(关键用户操作错误反馈) toast({ title: activeTab === 0 ? '买入失败' : '卖出失败', description: error.message, status: 'error', duration: 5000, isClosable: true, }); } finally { setIsLoading(false); } }; // 计算交易金额 const calculateTradeAmount = () => { if (!selectedStock || !quantity) return { totalCost: 0, commission: 0, totalAmount: 0 }; const price = orderType === 'LIMIT' ? parseFloat(limitPrice) || 0 : selectedStock.price; const totalCost = price * quantity; const commission = Math.max(totalCost * 0.0003, 5); const stampTax = activeTab === 1 ? totalCost * 0.001 : 0; // 卖出印花税 const totalAmount = activeTab === 0 ? totalCost + commission : totalCost - commission - stampTax; return { totalCost, commission, stampTax, totalAmount }; }; const { totalCost, commission, stampTax, totalAmount } = calculateTradeAmount(); // 格式化货币 const formatCurrency = (amount) => { return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY', minimumFractionDigits: 2 }).format(amount); }; return ( {/* 现代化标题栏 */} } /> 智能交易面板 快速下单,实时成交 7×24小时 买入 卖出 {/* 现代化股票搜索 */} 选择股票 setSearchTerm(e.target.value)} borderRadius="xl" border="2px solid" borderColor="gray.200" _focus={{ borderColor: "blue.400", boxShadow: "0 0 0 3px rgba(66, 153, 225, 0.1)" }} _hover={{ borderColor: "gray.300" }} /> {showStockList && filteredStocks.length > 0 && ( {filteredStocks.map(([code, stock]) => ( handleSelectStock(code, stock)} > ))}
代码 名称 价格 涨跌
{code} {stock.name} {(stock.price || 0).toFixed(2)} = 0 ? 'green.500' : 'red.500'}> {(stock.change || 0) >= 0 ? '+' : ''}{(stock.change || 0).toFixed(2)} ({(stock.changePercent || 0).toFixed(2)}%)
)}
{errors.stock && {errors.stock}}
{/* 选中股票信息 */} {selectedStock && ( 股票代码 {selectedStock.code} 股票名称 {selectedStock.name} 当前价格 ¥{(selectedStock.price || 0).toFixed(2)} 涨跌幅 = 0 ? 'green.500' : 'red.500'} > {(selectedStock.change || 0) >= 0 ? '+' : ''}{(selectedStock.changePercent || 0).toFixed(2)}% )} {/* 交易参数 */} 买入数量(股) setQuantity(parseInt(value) || 0)} min={100} step={100} > {errors.quantity && {errors.quantity}} 订单类型 {/* 限价输入 */} {orderType === 'LIMIT' && ( 限价价格(元) setLimitPrice(value)} precision={2} min={0} > {errors.limitPrice && {errors.limitPrice}} )} {/* 交易金额计算 */} {selectedStock && quantity > 0 && ( 交易金额 {formatCurrency(totalCost)} 手续费 {formatCurrency(commission)} 所需资金 {formatCurrency(totalAmount)} {/* 资金检查 */} {errors.funds && ( {errors.funds} )} )} {/* 现代化操作按钮 */}
{/* 卖出面板 - 与买入类似,但需要检查持仓 */} 卖出功能与买入类似,需要检查持仓数量 {/* 这里可以复制买入的逻辑,但需要增加持仓检查 */} 卖出功能请在"我的持仓"页面中进行操作
); }