From 32121c416e81c8785678dfe0de1de7670bf9d92b Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Sat, 18 Oct 2025 09:03:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=20TradingSimulation?= =?UTF-8?q?=20=E5=92=8C=20Dashboard=20=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/InvestmentCalendarChakra.js | 37 ++++++++------ .../components/InvestmentPlansAndReviews.js | 49 +++++++++++-------- .../Dashboard/components/MyFutureEvents.js | 21 ++++---- .../components/PositionsList.js | 14 ++++++ .../components/TradingHistory.js | 3 ++ .../components/TradingPanel.js | 18 ++++++- .../hooks/useTradingAccount.js | 19 +++---- src/views/TradingSimulation/index.js | 18 +++++-- 8 files changed, 119 insertions(+), 60 deletions(-) diff --git a/src/views/Dashboard/components/InvestmentCalendarChakra.js b/src/views/Dashboard/components/InvestmentCalendarChakra.js index 247511e0..493fbb1d 100644 --- a/src/views/Dashboard/components/InvestmentCalendarChakra.js +++ b/src/views/Dashboard/components/InvestmentCalendarChakra.js @@ -54,6 +54,7 @@ import dayGridPlugin from '@fullcalendar/daygrid'; import interactionPlugin from '@fullcalendar/interaction'; import moment from 'moment'; import 'moment/locale/zh-cn'; +import { logger } from '../../../utils/logger'; import './InvestmentCalendar.css'; moment.locale('zh-cn'); @@ -86,10 +87,10 @@ export default function InvestmentCalendarChakra() { try { setLoading(true); const base = (process.env.NODE_ENV === 'production' ? '' : process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001'); - + // 直接加载用户相关的事件(投资计划 + 关注的未来事件) - const userResponse = await fetch(base + '/api/account/calendar/events', { - credentials: 'include' + const userResponse = await fetch(base + '/api/account/calendar/events', { + credentials: 'include' }); if (userResponse.ok) { @@ -110,20 +111,18 @@ export default function InvestmentCalendarChakra() { })); setEvents(allEvents); + logger.debug('InvestmentCalendar', '日历事件加载成功', { + count: allEvents.length + }); } } } catch (error) { - console.error('加载日历事件失败:', error); - toast({ - title: '加载失败', - description: '无法加载日历事件', - status: 'error', - duration: 3000, - }); + logger.error('InvestmentCalendar', 'loadEvents', error); + // ❌ 移除数据加载失败 toast(非关键操作) } finally { setLoading(false); } - }, [toast]); + }, []); // ✅ 移除 toast 依赖 useEffect(() => { loadEvents(); @@ -170,7 +169,7 @@ export default function InvestmentCalendarChakra() { const handleAddEvent = async () => { try { const base = (process.env.NODE_ENV === 'production' ? '' : process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001'); - + const eventData = { ...newEvent, event_date: (selectedDate ? selectedDate.format('YYYY-MM-DD') : moment().format('YYYY-MM-DD')), @@ -189,6 +188,10 @@ export default function InvestmentCalendarChakra() { if (response.ok) { const data = await response.json(); if (data.success) { + logger.info('InvestmentCalendar', '添加事件成功', { + eventTitle: eventData.title, + eventDate: eventData.event_date + }); toast({ title: '添加成功', description: '投资计划已添加', @@ -207,7 +210,9 @@ export default function InvestmentCalendarChakra() { } } } catch (error) { - console.error('添加事件失败:', error); + logger.error('InvestmentCalendar', 'handleAddEvent', error, { + eventTitle: newEvent?.title + }); toast({ title: '添加失败', description: '无法添加投资计划', @@ -220,6 +225,7 @@ export default function InvestmentCalendarChakra() { // 删除用户事件 const handleDeleteEvent = async (eventId) => { if (!eventId) { + logger.warn('InvestmentCalendar', '删除事件失败', '缺少事件 ID', { eventId }); toast({ title: '无法删除', description: '缺少事件 ID', @@ -230,13 +236,14 @@ export default function InvestmentCalendarChakra() { } try { const base = (process.env.NODE_ENV === 'production' ? '' : process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001'); - + const response = await fetch(base + `/api/account/calendar/events/${eventId}`, { method: 'DELETE', credentials: 'include', }); if (response.ok) { + logger.info('InvestmentCalendar', '删除事件成功', { eventId }); toast({ title: '删除成功', status: 'success', @@ -245,7 +252,7 @@ export default function InvestmentCalendarChakra() { loadEvents(); } } catch (error) { - console.error('删除事件失败:', error); + logger.error('InvestmentCalendar', 'handleDeleteEvent', error, { eventId }); toast({ title: '删除失败', status: 'error', diff --git a/src/views/Dashboard/components/InvestmentPlansAndReviews.js b/src/views/Dashboard/components/InvestmentPlansAndReviews.js index 570c872d..1a90516c 100644 --- a/src/views/Dashboard/components/InvestmentPlansAndReviews.js +++ b/src/views/Dashboard/components/InvestmentPlansAndReviews.js @@ -62,6 +62,7 @@ import { } from 'react-icons/fi'; import moment from 'moment'; import 'moment/locale/zh-cn'; +import { logger } from '../../../utils/logger'; moment.locale('zh-cn'); @@ -97,31 +98,30 @@ export default function InvestmentPlansAndReviews({ type = 'both' }) { try { setLoading(true); const base = (process.env.NODE_ENV === 'production' ? '' : process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001'); - - const response = await fetch(base + '/api/account/investment-plans', { - credentials: 'include' + + const response = await fetch(base + '/api/account/investment-plans', { + credentials: 'include' }); - + if (response.ok) { const data = await response.json(); if (data.success) { const allItems = data.data || []; setPlans(allItems.filter(item => item.type === 'plan')); setReviews(allItems.filter(item => item.type === 'review')); + logger.debug('InvestmentPlansAndReviews', '数据加载成功', { + plansCount: allItems.filter(item => item.type === 'plan').length, + reviewsCount: allItems.filter(item => item.type === 'review').length + }); } } } catch (error) { - console.error('加载数据失败:', error); - toast({ - title: '加载失败', - description: '无法加载投资计划和复盘记录', - status: 'error', - duration: 3000, - }); + logger.error('InvestmentPlansAndReviews', 'loadData', error); + // ❌ 移除数据加载失败 toast(非关键操作) } finally { setLoading(false); } - }, [toast]); + }, []); // ✅ 移除 toast 依赖 useEffect(() => { loadData(); @@ -154,13 +154,13 @@ export default function InvestmentPlansAndReviews({ type = 'both' }) { const handleSave = async () => { try { const base = (process.env.NODE_ENV === 'production' ? '' : process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001'); - - const url = editingItem + + const url = editingItem ? base + `/api/account/investment-plans/${editingItem.id}` : base + '/api/account/investment-plans'; - + const method = editingItem ? 'PUT' : 'POST'; - + const response = await fetch(url, { method, headers: { @@ -171,6 +171,11 @@ export default function InvestmentPlansAndReviews({ type = 'both' }) { }); if (response.ok) { + logger.info('InvestmentPlansAndReviews', `${editingItem ? '更新' : '创建'}成功`, { + itemId: editingItem?.id, + title: formData.title, + type: formData.type + }); toast({ title: editingItem ? '更新成功' : '创建成功', status: 'success', @@ -182,7 +187,10 @@ export default function InvestmentPlansAndReviews({ type = 'both' }) { throw new Error('保存失败'); } } catch (error) { - console.error('保存失败:', error); + logger.error('InvestmentPlansAndReviews', 'handleSave', error, { + itemId: editingItem?.id, + title: formData?.title + }); toast({ title: '保存失败', description: '无法保存数据', @@ -195,16 +203,17 @@ export default function InvestmentPlansAndReviews({ type = 'both' }) { // 删除数据 const handleDelete = async (id) => { if (!window.confirm('确定要删除吗?')) return; - + try { const base = (process.env.NODE_ENV === 'production' ? '' : process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001'); - + const response = await fetch(base + `/api/account/investment-plans/${id}`, { method: 'DELETE', credentials: 'include', }); if (response.ok) { + logger.info('InvestmentPlansAndReviews', '删除成功', { itemId: id }); toast({ title: '删除成功', status: 'success', @@ -213,7 +222,7 @@ export default function InvestmentPlansAndReviews({ type = 'both' }) { loadData(); } } catch (error) { - console.error('删除失败:', error); + logger.error('InvestmentPlansAndReviews', 'handleDelete', error, { itemId: id }); toast({ title: '删除失败', status: 'error', diff --git a/src/views/Dashboard/components/MyFutureEvents.js b/src/views/Dashboard/components/MyFutureEvents.js index d04b8a3d..5e47925e 100644 --- a/src/views/Dashboard/components/MyFutureEvents.js +++ b/src/views/Dashboard/components/MyFutureEvents.js @@ -28,6 +28,7 @@ import { FiAlertCircle, } from 'react-icons/fi'; import { eventService } from '../../../services/eventService'; +import { logger } from '../../../utils/logger'; import moment from 'moment'; export default function MyFutureEvents({ limit = 5 }) { @@ -49,24 +50,21 @@ export default function MyFutureEvents({ limit = 5 }) { const response = await eventService.calendar.getFollowingEvents(); if (response.success) { // 按时间排序,最近的在前 - const sortedEvents = (response.data || []).sort((a, b) => + const sortedEvents = (response.data || []).sort((a, b) => moment(a.calendar_time).valueOf() - moment(b.calendar_time).valueOf() ); setFutureEvents(sortedEvents); + logger.debug('MyFutureEvents', '未来事件加载成功', { + count: sortedEvents.length + }); } } catch (error) { - console.error('加载未来事件失败:', error); - toast({ - title: '加载失败', - description: '无法加载关注的未来事件', - status: 'error', - duration: 3000, - isClosable: true, - }); + logger.error('MyFutureEvents', 'loadFutureEvents', error); + // ❌ 移除数据加载失败 toast(非关键操作) } finally { setLoading(false); } - }, [toast]); + }, []); // ✅ 移除 toast 依赖 useEffect(() => { loadFutureEvents(); @@ -78,6 +76,7 @@ export default function MyFutureEvents({ limit = 5 }) { const response = await eventService.calendar.toggleFollow(eventId); if (response.success) { setFutureEvents(prev => prev.filter(event => event.id !== eventId)); + logger.info('MyFutureEvents', '取消关注成功', { eventId }); toast({ title: '取消关注成功', status: 'success', @@ -86,7 +85,7 @@ export default function MyFutureEvents({ limit = 5 }) { }); } } catch (error) { - console.error('取消关注失败:', error); + logger.error('MyFutureEvents', 'handleUnfollow', error, { eventId }); toast({ title: '操作失败', description: '取消关注失败,请重试', diff --git a/src/views/TradingSimulation/components/PositionsList.js b/src/views/TradingSimulation/components/PositionsList.js index c7486f18..8d27673f 100644 --- a/src/views/TradingSimulation/components/PositionsList.js +++ b/src/views/TradingSimulation/components/PositionsList.js @@ -54,6 +54,7 @@ import { FiTrendingUp, FiTrendingDown, FiMinus, FiBarChart2, FiPieChart } from ' import BarChart from '../../../components/Charts/BarChart'; import PieChart from '../../../components/Charts/PieChart'; import IconBox from '../../../components/Icons/IconBox'; +import { logger } from '../../../utils/logger'; // 计算涨跌幅的辅助函数 const calculateChange = (currentPrice, avgPrice) => { @@ -118,6 +119,13 @@ export default function PositionsList({ positions, account, onSellStock }) { ); if (result.success) { + logger.info('PositionsList', '卖出成功', { + stockCode: selectedPosition.stockCode, + stockName: selectedPosition.stockName, + quantity: sellQuantity, + orderType, + orderId: result.orderId + }); toast({ title: '卖出成功', description: `已卖出 ${selectedPosition.stockName} ${sellQuantity} 股`, @@ -128,6 +136,12 @@ export default function PositionsList({ positions, account, onSellStock }) { onClose(); } } catch (error) { + logger.error('PositionsList', 'handleSellConfirm', error, { + stockCode: selectedPosition?.stockCode, + stockName: selectedPosition?.stockName, + quantity: sellQuantity, + orderType + }); toast({ title: '卖出失败', description: error.message, diff --git a/src/views/TradingSimulation/components/TradingHistory.js b/src/views/TradingSimulation/components/TradingHistory.js index 41cf4fed..af1a9716 100644 --- a/src/views/TradingSimulation/components/TradingHistory.js +++ b/src/views/TradingSimulation/components/TradingHistory.js @@ -32,6 +32,7 @@ import { useToast } from '@chakra-ui/react'; import { FiSearch, FiFilter, FiClock, FiTrendingUp, FiTrendingDown } from 'react-icons/fi'; +import { logger } from '../../../utils/logger'; export default function TradingHistory({ history, onCancelOrder }) { const [filterType, setFilterType] = useState('ALL'); // ALL, BUY, SELL @@ -147,6 +148,7 @@ export default function TradingHistory({ history, onCancelOrder }) { const handleCancelOrder = async (orderId) => { try { await onCancelOrder(orderId); + logger.info('TradingHistory', '撤单成功', { orderId }); toast({ title: '撤单成功', description: `订单 ${orderId} 已撤销`, @@ -155,6 +157,7 @@ export default function TradingHistory({ history, onCancelOrder }) { isClosable: true, }); } catch (error) { + logger.error('TradingHistory', 'handleCancelOrder', error, { orderId }); toast({ title: '撤单失败', description: error.message, diff --git a/src/views/TradingSimulation/components/TradingPanel.js b/src/views/TradingSimulation/components/TradingPanel.js index cb742d63..f5f697e0 100644 --- a/src/views/TradingSimulation/components/TradingPanel.js +++ b/src/views/TradingSimulation/components/TradingPanel.js @@ -1,5 +1,6 @@ // src/views/TradingSimulation/components/TradingPanel.js - 交易面板组件(现代化版本) import React, { useState, useRef, useEffect } from 'react'; +import { logger } from '../../../utils/logger'; import { Box, Card, @@ -97,7 +98,7 @@ export default function TradingPanel({ account, onBuyStock, onSellStock, searchS setFilteredStocks(formattedResults); setShowStockList(true); } catch (error) { - console.error('搜索股票失败:', error); + logger.error('TradingPanel', 'handleStockSearch', error, { searchTerm }); setFilteredStocks([]); setShowStockList(false); } @@ -189,6 +190,14 @@ export default function TradingPanel({ account, onBuyStock, onSellStock, searchS } if (result.success) { + logger.info('TradingPanel', `${activeTab === 0 ? '买入' : '卖出'}成功`, { + orderId: result.orderId, + stockCode: selectedStock.code, + quantity, + orderType + }); + + // ✅ 保留交易成功toast(关键用户操作反馈) toast({ title: activeTab === 0 ? '买入成功' : '卖出成功', description: `订单号: ${result.orderId}`, @@ -202,6 +211,13 @@ export default function TradingPanel({ account, onBuyStock, onSellStock, searchS clearSelection(); } } catch (error) { + logger.error('TradingPanel', `${activeTab === 0 ? '买入' : '卖出'}失败`, error, { + stockCode: selectedStock?.code, + quantity, + orderType + }); + + // ✅ 保留交易失败toast(关键用户操作错误反馈) toast({ title: activeTab === 0 ? '买入失败' : '卖出失败', description: error.message, diff --git a/src/views/TradingSimulation/hooks/useTradingAccount.js b/src/views/TradingSimulation/hooks/useTradingAccount.js index 2fd155ef..4ef01ca9 100644 --- a/src/views/TradingSimulation/hooks/useTradingAccount.js +++ b/src/views/TradingSimulation/hooks/useTradingAccount.js @@ -1,6 +1,7 @@ // src/views/TradingSimulation/hooks/useTradingAccount.js - 模拟盘账户管理 Hook import { useState, useCallback } from 'react'; import { useAuth } from '../../../contexts/AuthContext'; +import { logger } from '../../../utils/logger'; // API 基础URL - 修复HTTPS混合内容问题 const API_BASE_URL = process.env.NODE_ENV === 'production' @@ -108,7 +109,7 @@ export function useTradingAccount() { const searchStocks = useCallback(async (keyword) => { // 调试模式:返回模拟数据 if (!user || user.id === 'demo') { - console.log('🔧 调试模式:模拟股票搜索', keyword); + logger.debug('useTradingAccount', '调试模式:模拟股票搜索', { keyword }); const mockStocks = [ { stock_code: '000001', @@ -147,7 +148,7 @@ export function useTradingAccount() { const response = await apiRequest(`/api/stocks/search?q=${encodeURIComponent(keyword)}&limit=10`); return response.data || []; } catch (error) { - console.error('搜索股票失败:', error); + logger.error('useTradingAccount', 'searchStocks', error, { keyword }); return []; } }, [user]); @@ -156,7 +157,7 @@ export function useTradingAccount() { const refreshAccount = useCallback(async () => { // 调试模式:使用模拟数据(因为后端API可能有CORS问题) if (!user || user.id === 'demo') { - console.log('🔧 调试模式:使用模拟账户数据'); + logger.debug('useTradingAccount', '调试模式:使用模拟账户数据', { userId: user?.id }); setAccount({ id: 'demo', accountName: '演示账户', @@ -198,7 +199,7 @@ export function useTradingAccount() { setTradingHistory(mapOrderData(ordersResponse.data || [])); } catch (err) { - console.error('刷新账户数据失败:', err); + logger.error('useTradingAccount', 'refreshAccount', err, { userId: user?.id }); setError('加载账户数据失败: ' + err.message); } finally { setIsLoading(false); @@ -213,7 +214,7 @@ export function useTradingAccount() { // 调试模式:模拟买入成功 if (!user || user.id === 'demo') { - console.log('🔧 调试模式:模拟买入', { stockCode, quantity, orderType }); + logger.debug('useTradingAccount', '调试模式:模拟买入', { stockCode, quantity, orderType }); return { success: true, orderId: 'demo_' + Date.now() }; } @@ -251,7 +252,7 @@ export function useTradingAccount() { // 调试模式:模拟卖出成功 if (!user || user.id === 'demo') { - console.log('🔧 调试模式:模拟卖出', { stockCode, quantity, orderType }); + logger.debug('useTradingAccount', '调试模式:模拟卖出', { stockCode, quantity, orderType }); return { success: true, orderId: 'demo_' + Date.now() }; } @@ -311,7 +312,7 @@ export function useTradingAccount() { const response = await apiRequest(`/api/simulation/transactions?${params.toString()}`); return response.data || []; } catch (error) { - console.error('获取交易记录失败:', error); + logger.error('useTradingAccount', 'getTransactions', error, options); return []; } }, []); @@ -334,7 +335,7 @@ export function useTradingAccount() { const response = await apiRequest(`/api/simulation/statistics?days=${days}`); return response.data?.daily_returns || []; } catch (error) { - console.error('获取资产历史失败:', error); + logger.error('useTradingAccount', 'getAssetHistory', error, { days, userId: user?.id }); return []; } }, [user]); @@ -359,7 +360,7 @@ export function useTradingAccount() { } return {}; } catch (error) { - console.error('获取股票行情失败:', error); + logger.error('useTradingAccount', 'getStockQuotes', error, { stockCodes }); return {}; } }, []); diff --git a/src/views/TradingSimulation/index.js b/src/views/TradingSimulation/index.js index b6585684..dfce7e60 100644 --- a/src/views/TradingSimulation/index.js +++ b/src/views/TradingSimulation/index.js @@ -34,6 +34,7 @@ import { Link, } from '@chakra-ui/react'; import { useAuth } from '../../contexts/AuthContext'; +import { logger } from '../../utils/logger'; // 导入子组件 import AccountOverview from './components/AccountOverview'; @@ -86,9 +87,11 @@ export default function TradingSimulation() { // 调试:观察认证状态变化 useEffect(() => { - try { - console.log('🔍 TradingSimulation mounted. isAuthenticated=', isAuthenticated, 'user=', user); - } catch {} + logger.debug('TradingSimulation', '组件挂载,认证状态检查', { + isAuthenticated, + userId: user?.id, + userName: user?.name + }); }, [isAuthenticated, user]); // 获取资产历史数据的 useEffect @@ -96,8 +99,15 @@ export default function TradingSimulation() { if (account) { getAssetHistory(30).then(data => { setAssetHistory(data || []); + logger.debug('TradingSimulation', '资产历史数据加载成功', { + accountId: account.id, + dataPoints: data?.length || 0 + }); }).catch(err => { - console.error('获取资产历史失败:', err); + logger.error('TradingSimulation', 'getAssetHistory', err, { + accountId: account?.id, + days: 30 + }); setAssetHistory([]); }); }