feat: 日志优化

This commit is contained in:
zdl
2025-10-18 12:12:02 +08:00
parent 87b77af187
commit 4ebb17190f
8 changed files with 206 additions and 134 deletions

View File

@@ -355,7 +355,7 @@ export default function AuthFormContent() {
window.location.href = response.auth_url;
}, 500);
} catch (error) {
console.error('微信H5登录失败:', error);
logger.error('AuthFormContent', 'handleWechatH5Login', error);
toast({
title: "跳转失败",
description: error.message || "请稍后重试",

View File

@@ -2,6 +2,7 @@
import React, { useState, useEffect } from 'react';
import { Modal, Spin, Descriptions, Tag, List, Badge, Empty, Input, Button, message } from 'antd';
import { eventService } from '../../../services/eventService';
import { logger } from '../../../utils/logger';
import moment from 'moment';
const EventDetailModal = ({ visible, event, onClose }) => {
@@ -22,7 +23,9 @@ const EventDetailModal = ({ visible, event, onClose }) => {
setEventDetail(response.data);
}
} catch (error) {
console.error('Failed to load event detail:', error);
logger.error('EventDetailModal', 'loadEventDetail', error, {
eventId: event?.id
});
} finally {
setLoading(false);
}
@@ -39,7 +42,9 @@ const EventDetailModal = ({ visible, event, onClose }) => {
setComments(result.data || []);
}
} catch (error) {
console.error('Failed to load comments:', error);
logger.error('EventDetailModal', 'loadComments', error, {
eventId: event?.id
});
} finally {
setCommentsLoading(false);
}

View File

@@ -16,6 +16,7 @@ import { useSubscription } from '../../../hooks/useSubscription';
import SubscriptionUpgradeModal from '../../../components/SubscriptionUpgradeModal';
import CitationMark from '../../../components/Citation/CitationMark';
import { processCitationData } from '../../../utils/citationUtils';
import { logger } from '../../../utils/logger';
import './InvestmentCalendar.css';
const { TabPane } = Tabs;
@@ -56,7 +57,10 @@ const InvestmentCalendar = () => {
setEventCounts(response.data);
}
} catch (error) {
console.error('Failed to load calendar event counts:', error);
logger.error('InvestmentCalendar', 'loadEventCounts', error, {
year: date.year(),
month: date.month() + 1
});
}
};
@@ -71,7 +75,9 @@ const InvestmentCalendar = () => {
setSelectedDateEvents(response.data);
}
} catch (error) {
console.error('Failed to load date events:', error);
logger.error('InvestmentCalendar', 'loadDateEvents', error, {
dateStr: date.format('YYYY-MM-DD')
});
setSelectedDateEvents([]);
} finally {
setLoading(false);
@@ -109,13 +115,15 @@ const InvestmentCalendar = () => {
}
}
} catch (err) {
console.error(`Failed to load quote for ${code}:`, err);
logger.error('InvestmentCalendar', 'loadStockQuotes.fetchQuote', err, { code });
}
}
setStockQuotes(quotes);
} catch (error) {
console.error('Failed to load stock quotes:', error);
logger.error('InvestmentCalendar', 'loadStockQuotes', error, {
stockCount: stocks.length
});
message.error('加载股票行情失败');
}
};
@@ -239,7 +247,7 @@ const InvestmentCalendar = () => {
message.error(response.error || '操作失败');
}
} catch (error) {
console.error('关注操作失败:', error);
logger.error('InvestmentCalendar', 'handleFollowToggle', error, { eventId });
message.error('操作失败,请重试');
} finally {
setFollowingIds(prev => prev.filter(id => id !== eventId));
@@ -272,7 +280,10 @@ const InvestmentCalendar = () => {
message.error(data.error || '添加失败');
}
} catch (error) {
console.error(`添加${stock[1]}(${stockCode})到自选失败:`, error);
logger.error('InvestmentCalendar', 'addSingleToWatchlist', error, {
stockCode,
stockName: stock[1]
});
message.error('添加失败,请重试');
} finally {
setAddingToWatchlist(prev => ({ ...prev, [stockCode]: false }));

View File

@@ -46,6 +46,7 @@ import {
FaInfoCircle
} from 'react-icons/fa';
import { stockService } from '../../../services/eventService';
import { logger } from '../../../utils/logger';
const HistoricalEvents = ({
events = [],
@@ -99,7 +100,10 @@ const HistoricalEvents = ({
[event.id]: response.data || []
}));
} catch (err) {
console.error('加载事件股票失败:', err);
logger.error('HistoricalEvents', 'showEventStocks', err, {
eventId: event.id,
eventTitle: event.title
});
setEventStocks(prev => ({
...prev,
[event.id]: []

View File

@@ -64,6 +64,8 @@ import ReactECharts from 'echarts-for-react';
// 导入导航栏组件
import HomeNavbar from '../../../components/Navbars/HomeNavbar';
import { logger } from '../../../utils/logger';
// 板块关联TOP10数据计算
function getSectorRelationTop10(sectorData) {
// 股票代码 -> 所属板块集合
@@ -191,7 +193,7 @@ const limitAnalyseService = {
throw new Error('接口返回内容不是有效的 JSON实际返回' + text.slice(0, 100));
}
} catch (error) {
console.error('Error fetching available dates:', error);
logger.error('LimitAnalyse', 'getAvailableDates', error);
throw error;
}
},
@@ -202,7 +204,7 @@ const limitAnalyseService = {
const data = await response.json();
return data; // 修正:直接返回整个对象
} catch (error) {
console.error('Error fetching analysis data:', error);
logger.error('LimitAnalyse', 'getAnalysisData', error, { date });
throw error;
}
},
@@ -213,7 +215,7 @@ const limitAnalyseService = {
const data = await response.json();
return data.data || [];
} catch (error) {
console.error('Error fetching sector data:', error);
logger.error('LimitAnalyse', 'getSectorData', error, { date });
throw error;
}
},
@@ -224,7 +226,7 @@ const limitAnalyseService = {
const data = await response.json();
return data.data || [];
} catch (error) {
console.error('Error fetching word cloud data:', error);
logger.error('LimitAnalyse', 'getWordCloudData', error, { date });
throw error;
}
},
@@ -241,7 +243,7 @@ const limitAnalyseService = {
const data = await response.json();
return data.data;
} catch (error) {
console.error('Error exporting data:', error);
logger.error('LimitAnalyse', 'exportData', error, { date, exportType });
throw error;
}
},

View File

@@ -32,6 +32,7 @@ import {
import { FaEye, FaExternalLinkAlt, FaChartLine, FaCalendarAlt } from 'react-icons/fa';
import moment from 'moment';
import tradingDayUtils from '../../../utils/tradingDayUtils'; // 引入交易日工具
import { logger } from '../../../utils/logger';
// API配置
const API_BASE_URL = process.env.NODE_ENV === 'production' ? '/concept-api' : 'https://valuefrontier.cn/concept-api';
@@ -254,7 +255,9 @@ const ConceptCard = ({ concept, tradingDate, onViewDetails }) => {
const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoading, error: externalError }) => {
// 调试:检查 Icon 组件是否可用
if (typeof Icon === 'undefined') {
console.error('Icon component is not defined! Make sure @chakra-ui/react is properly imported.');
logger.error('RelatedConcepts', 'Icon组件检查', new Error('Icon组件未定义'), {
eventId
});
return <div>组件加载错误Icon 组件未定义</div>;
}
@@ -311,7 +314,10 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
} else if (moment.isMoment(tradeDate)) {
formattedTradeDate = tradeDate.format('YYYY-MM-DD');
} else {
console.warn('Invalid tradeDate format:', tradeDate, typeof tradeDate);
logger.warn('RelatedConcepts', '无效的交易日期格式', {
tradeDate,
tradeDateType: typeof tradeDate
});
formattedTradeDate = moment().format('YYYY-MM-DD');
}
@@ -323,7 +329,7 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
trade_date: formattedTradeDate
};
console.log('Searching concepts with:', requestBody);
logger.debug('RelatedConcepts', '搜索概念', requestBody);
const response = await fetch(`${API_BASE_URL}/search`, {
method: 'POST',
@@ -338,11 +344,16 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
}
const data = await response.json();
console.log('Concept search response:', data);
logger.debug('RelatedConcepts', '概念搜索响应', {
hasResults: !!data.results,
resultsCount: data.results?.length || 0
});
// 数据验证
if (!validateConceptData(data)) {
console.warn('Invalid concept data format:', data);
logger.warn('RelatedConcepts', '概念数据格式无效', {
hasData: !!data
});
setConcepts([]);
setError('返回的数据格式无效');
return;
@@ -359,10 +370,16 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
setEffectiveTradingDate(data.data.trade_date || formattedTradeDate);
} else {
setConcepts([]);
console.warn('No concepts found in response');
logger.warn('RelatedConcepts', '响应中未找到概念数据', {
hasResults: !!data.results,
hasDataConcepts: !!(data.data?.concepts)
});
}
} catch (err) {
console.error('Failed to search concepts:', err);
logger.error('RelatedConcepts', 'searchConcepts', err, {
title,
tradeDate: formattedTradeDate
});
setError(err.message);
setConcepts([]);
} finally {
@@ -387,13 +404,20 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
} else if (typeof eventTime === 'number') {
eventMoment = moment(eventTime);
} else {
console.warn('Unknown eventTime format:', eventTime, typeof eventTime);
logger.warn('RelatedConcepts', '未知的事件时间格式', {
eventTime,
eventTimeType: typeof eventTime,
eventId
});
eventMoment = moment();
}
// 确保moment对象有效
if (!eventMoment.isValid()) {
console.warn('Invalid eventTime:', eventTime);
logger.warn('RelatedConcepts', '无效的事件时间', {
eventTime,
eventId
});
eventMoment = moment();
}
@@ -410,17 +434,25 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
} else if (nextTradingDay instanceof Date) {
formattedDate = moment(nextTradingDay).format('YYYY-MM-DD');
} else {
console.warn('tradingDayUtils.getNextTradingDay returned invalid format:', nextTradingDay);
logger.warn('RelatedConcepts', '交易日工具返回了无效格式', {
nextTradingDay,
eventId
});
formattedDate = eventMoment.add(1, 'day').format('YYYY-MM-DD');
}
} else {
// 降级处理:简单地加一天(不考虑周末和节假日)
console.warn('tradingDayUtils.getNextTradingDay not available, using simple date addition');
logger.warn('RelatedConcepts', '交易日工具不可用,使用简单日期加法', {
eventId
});
formattedDate = eventMoment.add(1, 'day').format('YYYY-MM-DD');
}
}
} catch (e) {
console.error('Failed to format event time:', e);
logger.error('RelatedConcepts', 'formatEventTime', e, {
eventTime,
eventId
});
// 使用当前交易日作为fallback
if (tradingDayUtils && tradingDayUtils.getCurrentTradingDay) {
const currentTradingDay = tradingDayUtils.getCurrentTradingDay();
@@ -430,7 +462,10 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
} else if (currentTradingDay instanceof Date) {
formattedDate = moment(currentTradingDay).format('YYYY-MM-DD');
} else {
console.warn('tradingDayUtils.getCurrentTradingDay returned invalid format:', currentTradingDay);
logger.warn('RelatedConcepts', '当前交易日工具返回了无效格式', {
currentTradingDay,
eventId
});
formattedDate = moment().format('YYYY-MM-DD');
}
} else {
@@ -440,7 +475,9 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
searchConcepts(eventTitle, formattedDate);
} else if (!eventTitle) {
console.warn('No event title provided for concept search');
logger.warn('RelatedConcepts', '未提供事件标题,无法搜索概念', {
eventId
});
setConcepts([]);
}
}, [eventTitle, eventTime]);

View File

@@ -51,6 +51,7 @@ import * as echarts from 'echarts';
import StockChartModal from '../../../components/StockChart/StockChartModal';
import { eventService, stockService } from '../../../services/eventService';
import { logger } from '../../../utils/logger';
const RelatedStocks = ({
eventId,
@@ -102,14 +103,24 @@ const RelatedStocks = ({
setQuotesLoading(true);
const codes = stocksData.map(stock => stock.stock_code);
console.log('获取股票报价,代码:', codes, '事件时间:', eventTime);
logger.debug('RelatedStocks', '获取股票报价', {
codes,
eventTime,
stockCount: codes.length
});
const response = await stockService.getQuotes(codes, eventTime);
console.log('股票报价响应:', response);
logger.debug('RelatedStocks', '股票报价响应', {
hasResponse: !!response,
quotesCount: response ? Object.keys(response).length : 0
});
setQuotes(response || {});
} catch (err) {
console.error('获取股票报价失败:', err);
logger.error('RelatedStocks', 'fetchQuotes', err, {
stockCount: stocksData.length,
eventTime
});
toast({
title: '获取股票报价失败',
description: err.message,

View File

@@ -34,6 +34,7 @@ import { InfoIcon, ViewIcon } from '@chakra-ui/icons';
import ReactECharts from 'echarts-for-react';
import { eventService } from '../../../services/eventService';
import CitedContent from '../../../components/Citation/CitedContent';
import { logger } from '../../../utils/logger';
// 节点样式配置 - 完全复刻Flask版本
const NODE_STYLES = {
@@ -67,40 +68,39 @@ const NODE_TYPE_LABELS = {
// 过滤孤立节点 - 完全复刻Flask版本
function filterIsolatedNodes(nodes, edges) {
console.log('开始过滤孤立节点');
console.log('输入节点:', nodes);
console.log('输入边:', edges);
logger.debug('TransmissionChain', '开始过滤孤立节点', {
nodesCount: nodes?.length,
edgesCount: edges?.length
});
if (!nodes || !edges) {
console.log('节点或边数据为空');
logger.debug('TransmissionChain', '节点或边数据为空');
return [];
}
const connectedNodeIds = new Set();
edges.forEach(edge => {
console.log('处理边:', edge, '从', edge.source, '到', edge.target);
connectedNodeIds.add(String(edge.source));
connectedNodeIds.add(String(edge.target));
});
console.log('连接的节点ID集合:', connectedNodeIds);
// 如果图中只有一个节点且是主事件,也显示它
const mainEventNode = nodes.find(n => n.extra?.is_main_event);
console.log('主事件节点:', mainEventNode);
if (mainEventNode) {
connectedNodeIds.add(String(mainEventNode.id));
console.log('添加主事件节点ID:', String(mainEventNode.id));
}
const filteredNodes = nodes.filter(node => {
const shouldKeep = connectedNodeIds.has(String(node.id));
console.log(`节点 ${node.name}(${node.id}[${String(node.id)}]): ${shouldKeep ? '保留' : '过滤'}`);
return shouldKeep;
return connectedNodeIds.has(String(node.id));
});
logger.debug('TransmissionChain', '过滤完成', {
originalCount: nodes.length,
filteredCount: filteredNodes.length,
connectedNodesCount: connectedNodeIds.size
});
console.log('过滤后的节点:', filteredNodes);
return filteredNodes;
}
@@ -146,29 +146,33 @@ function calculateEdgeWidth(strength) {
// 力导向图配置 - 完全复刻Flask版本
function getGraphOption(data) {
console.log('getGraphOption 被调用,输入数据:', data);
logger.debug('TransmissionChain', 'getGraphOption被调用', {
hasData: !!data,
nodesCount: data?.nodes?.length,
edgesCount: data?.edges?.length
});
if (!data || !data.nodes || !data.edges || data.nodes.length === 0) {
console.log('数据为空或无效');
logger.debug('TransmissionChain', '数据为空或无效');
return {
title: { text: '暂无传导链数据', left: 'center', top: 'center' },
graphic: { type: 'text', left: 'center', top: '60%', style: { text: '当前事件暂无传导链分析数据', fontSize: 14 } }
};
}
console.log('原始节点数:', data.nodes.length);
console.log('原始边数:', data.edges.length);
const filteredNodes = filterIsolatedNodes(data.nodes, data.edges);
console.log('过滤后节点数:', filteredNodes.length);
console.log('过滤后的节点:', filteredNodes);
// 进一步过滤:不显示事件类型的节点
const nonEventNodes = filteredNodes.filter(node => node.extra?.node_type !== 'event');
console.log('排除事件节点后:', nonEventNodes.length);
logger.debug('TransmissionChain', '节点过滤结果', {
originalCount: data.nodes.length,
filteredCount: filteredNodes.length,
nonEventCount: nonEventNodes.length
});
if (nonEventNodes.length === 0) {
console.log('过滤后没有有效节点');
logger.debug('TransmissionChain', '过滤后没有有效节点');
return {
title: { text: '暂无有效节点数据', left: 'center', top: 'center' },
graphic: { type: 'text', left: 'center', top: '60%', style: { text: '当前事件的传导链节点均为孤立节点', fontSize: 14 } }
@@ -181,16 +185,12 @@ function getGraphOption(data) {
itemStyle: { color: NODE_STYLES[type]?.color || NODE_STYLES.other.color }
}));
console.log('节点类别:', categories);
// 构建图表节点数据 - 完全复刻Flask版本样式排除事件节点
const chartNodes = nonEventNodes.map(node => {
const nodeType = node.extra?.node_type || 'other';
const nodeStyle = NODE_STYLES[nodeType] || NODE_STYLES['other'];
const connectionCount = calculateNodeConnections(node.id, data.edges);
console.log(`节点 ${node.name} (${node.id}): 类型=${nodeType}, 连接数=${connectionCount}`);
return {
id: String(node.id),
name: node.name,
@@ -433,33 +433,40 @@ const TransmissionChainAnalysis = ({ eventId }) => {
setLoading(true);
setError(null);
try {
console.log('开始加载传导链数据eventId:', eventId);
logger.debug('TransmissionChain', '开始加载传导链数据', { eventId });
const [graphRes, sankeyRes] = await Promise.all([
eventService.getTransmissionChainAnalysis(eventId),
eventService.getSankeyData(eventId)
]);
console.log('传导链数据API响应:', graphRes);
console.log('桑基图数据API响应:', sankeyRes);
logger.debug('TransmissionChain', 'API响应', {
graphSuccess: graphRes.success,
graphNodesCount: graphRes.data?.nodes?.length,
graphEdgesCount: graphRes.data?.edges?.length,
sankeySuccess: sankeyRes.success
});
if (graphRes.success && graphRes.data) {
console.log('传导链节点数据:', graphRes.data.nodes);
console.log('传导链边数据:', graphRes.data.edges);
setGraphData(graphRes.data);
} else {
console.log('传导链数据加载失败:', graphRes);
logger.warn('TransmissionChain', '传导链数据加载失败', {
success: graphRes.success,
eventId
});
setGraphData(null);
}
if (sankeyRes.success && sankeyRes.data) {
console.log('桑基图数据:', sankeyRes.data);
setSankeyData(sankeyRes.data);
} else {
console.log('桑基图数据加载失败:', sankeyRes);
logger.warn('TransmissionChain', '桑基图数据加载失败', {
success: sankeyRes.success,
eventId
});
setSankeyData(null);
}
} catch (e) {
console.error('传导链数据加载异常:', e);
logger.error('TransmissionChain', 'fetchData', e, { eventId });
setError('加载传导链数据失败');
} finally {
setLoading(false);
@@ -509,23 +516,32 @@ const TransmissionChainAnalysis = ({ eventId }) => {
if (result.success) {
return result.data;
} else {
console.error('获取节点详情失败:', result.message);
logger.error('TransmissionChain', 'getChainNodeDetail', new Error(result.message), {
nodeId,
eventId
});
return null;
}
} catch (error) {
console.error('API调用异常:', error);
logger.error('TransmissionChain', 'getChainNodeDetail', error, {
nodeId,
eventId
});
return null;
}
}
// 力导向图节点点击事件
const handleGraphNodeClick = async (params) => {
console.log('点击事件详情:', params);
logger.debug('TransmissionChain', '图表节点点击', {
dataType: params.dataType,
componentType: params.componentType,
hasData: !!params.data,
nodeId: params.data?.id
});
// 处理节点点击(包括节点本体和标签)
if ((params.dataType === 'node' || params.componentType === 'series') && params.data && params.data.id) {
console.log('点击图表节点:', params.data.id, 'dataType:', params.dataType, 'componentType:', params.componentType);
// 获取基本节点信息
const clickedNode = graphData.nodes.find(n => String(n.id) === String(params.data.id));
if (clickedNode) {
@@ -541,32 +557,30 @@ const TransmissionChainAnalysis = ({ eventId }) => {
}
// 获取详细节点信息包括parents和children
console.log('开始获取节点详情节点ID:', params.data.id);
logger.debug('TransmissionChain', '获取节点详情', {
nodeId: params.data.id,
nodeName: clickedNode.name
});
const detail = await getChainNodeDetail(params.data.id);
console.log('获取到的节点详情:', detail);
setNodeDetail(detail);
// 打开弹窗
setIsModalOpen(true);
}
}
// 如果点击的是空白区域,也尝试查找最近的节点
else if (params.componentType === 'series' && !params.data) {
console.log('点击了图表空白区域');
// 这里可以添加点击空白区域的处理逻辑
}
};
// 桑基图节点点击事件
const handleSankeyNodeClick = async (params) => {
if (params.dataType === 'node' && params.data && params.data.name) {
console.log('点击桑基图节点:', params.data.name);
logger.debug('TransmissionChain', '桑基图节点点击', {
nodeName: params.data.name
});
// 通过名称在原始数据中查找对应的节点
if (graphData && graphData.nodes) {
const clickedNode = graphData.nodes.find(n => n.name === params.data.name);
if (clickedNode) {
console.log('找到对应节点:', clickedNode);
setSelectedNode(clickedNode);
// 计算传导路径
@@ -579,15 +593,19 @@ const TransmissionChainAnalysis = ({ eventId }) => {
}
// 获取详细节点信息包括parents和children
console.log('开始获取桑基图节点详情节点ID:', clickedNode.id);
logger.debug('TransmissionChain', '获取桑基图节点详情', {
nodeId: clickedNode.id,
nodeName: clickedNode.name
});
const detail = await getChainNodeDetail(clickedNode.id);
console.log('获取到的桑基图节点详情:', detail);
setNodeDetail(detail);
// 打开弹窗
setIsModalOpen(true);
} else {
console.log('未找到对应的节点数据');
logger.warn('TransmissionChain', '未找到对应的节点数据', {
nodeName: params.data.name
});
// 创建一个临时节点信息用于显示
const tempNode = {
id: params.data.name,
@@ -714,15 +732,7 @@ const TransmissionChainAnalysis = ({ eventId }) => {
option={graphData ? getGraphOption(graphData) : {}}
style={{ height: '100%', width: '100%' }}
onEvents={{
click: handleGraphNodeClick,
// 添加更多事件以提高点击敏感性
mouseover: (params) => {
console.log('鼠标悬停:', params);
// 可以在这里添加悬停效果
},
mouseout: (params) => {
// 鼠标离开的处理
}
click: handleGraphNodeClick
}}
opts={{
renderer: 'canvas',
@@ -740,15 +750,7 @@ const TransmissionChainAnalysis = ({ eventId }) => {
option={sankeyData ? getSankeyOption(sankeyData) : {}}
style={{ height: '100%', width: '100%' }}
onEvents={{
click: handleSankeyNodeClick,
// 添加更多事件以提高点击敏感性
mouseover: (params) => {
console.log('桑基图鼠标悬停:', params);
// 可以在这里添加悬停效果
},
mouseout: (params) => {
// 鼠标离开的处理
}
click: handleSankeyNodeClick
}}
opts={{
renderer: 'canvas',