feat: 日志优化
This commit is contained in:
@@ -355,7 +355,7 @@ export default function AuthFormContent() {
|
|||||||
window.location.href = response.auth_url;
|
window.location.href = response.auth_url;
|
||||||
}, 500);
|
}, 500);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('微信H5登录失败:', error);
|
logger.error('AuthFormContent', 'handleWechatH5Login', error);
|
||||||
toast({
|
toast({
|
||||||
title: "跳转失败",
|
title: "跳转失败",
|
||||||
description: error.message || "请稍后重试",
|
description: error.message || "请稍后重试",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Modal, Spin, Descriptions, Tag, List, Badge, Empty, Input, Button, message } from 'antd';
|
import { Modal, Spin, Descriptions, Tag, List, Badge, Empty, Input, Button, message } from 'antd';
|
||||||
import { eventService } from '../../../services/eventService';
|
import { eventService } from '../../../services/eventService';
|
||||||
|
import { logger } from '../../../utils/logger';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
const EventDetailModal = ({ visible, event, onClose }) => {
|
const EventDetailModal = ({ visible, event, onClose }) => {
|
||||||
@@ -22,7 +23,9 @@ const EventDetailModal = ({ visible, event, onClose }) => {
|
|||||||
setEventDetail(response.data);
|
setEventDetail(response.data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load event detail:', error);
|
logger.error('EventDetailModal', 'loadEventDetail', error, {
|
||||||
|
eventId: event?.id
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -39,7 +42,9 @@ const EventDetailModal = ({ visible, event, onClose }) => {
|
|||||||
setComments(result.data || []);
|
setComments(result.data || []);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load comments:', error);
|
logger.error('EventDetailModal', 'loadComments', error, {
|
||||||
|
eventId: event?.id
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setCommentsLoading(false);
|
setCommentsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useSubscription } from '../../../hooks/useSubscription';
|
|||||||
import SubscriptionUpgradeModal from '../../../components/SubscriptionUpgradeModal';
|
import SubscriptionUpgradeModal from '../../../components/SubscriptionUpgradeModal';
|
||||||
import CitationMark from '../../../components/Citation/CitationMark';
|
import CitationMark from '../../../components/Citation/CitationMark';
|
||||||
import { processCitationData } from '../../../utils/citationUtils';
|
import { processCitationData } from '../../../utils/citationUtils';
|
||||||
|
import { logger } from '../../../utils/logger';
|
||||||
import './InvestmentCalendar.css';
|
import './InvestmentCalendar.css';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
@@ -56,7 +57,10 @@ const InvestmentCalendar = () => {
|
|||||||
setEventCounts(response.data);
|
setEventCounts(response.data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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);
|
setSelectedDateEvents(response.data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load date events:', error);
|
logger.error('InvestmentCalendar', 'loadDateEvents', error, {
|
||||||
|
dateStr: date.format('YYYY-MM-DD')
|
||||||
|
});
|
||||||
setSelectedDateEvents([]);
|
setSelectedDateEvents([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -109,13 +115,15 @@ const InvestmentCalendar = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to load quote for ${code}:`, err);
|
logger.error('InvestmentCalendar', 'loadStockQuotes.fetchQuote', err, { code });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setStockQuotes(quotes);
|
setStockQuotes(quotes);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load stock quotes:', error);
|
logger.error('InvestmentCalendar', 'loadStockQuotes', error, {
|
||||||
|
stockCount: stocks.length
|
||||||
|
});
|
||||||
message.error('加载股票行情失败');
|
message.error('加载股票行情失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -239,7 +247,7 @@ const InvestmentCalendar = () => {
|
|||||||
message.error(response.error || '操作失败');
|
message.error(response.error || '操作失败');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('关注操作失败:', error);
|
logger.error('InvestmentCalendar', 'handleFollowToggle', error, { eventId });
|
||||||
message.error('操作失败,请重试');
|
message.error('操作失败,请重试');
|
||||||
} finally {
|
} finally {
|
||||||
setFollowingIds(prev => prev.filter(id => id !== eventId));
|
setFollowingIds(prev => prev.filter(id => id !== eventId));
|
||||||
@@ -272,7 +280,10 @@ const InvestmentCalendar = () => {
|
|||||||
message.error(data.error || '添加失败');
|
message.error(data.error || '添加失败');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`添加${stock[1]}(${stockCode})到自选失败:`, error);
|
logger.error('InvestmentCalendar', 'addSingleToWatchlist', error, {
|
||||||
|
stockCode,
|
||||||
|
stockName: stock[1]
|
||||||
|
});
|
||||||
message.error('添加失败,请重试');
|
message.error('添加失败,请重试');
|
||||||
} finally {
|
} finally {
|
||||||
setAddingToWatchlist(prev => ({ ...prev, [stockCode]: false }));
|
setAddingToWatchlist(prev => ({ ...prev, [stockCode]: false }));
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import {
|
|||||||
FaInfoCircle
|
FaInfoCircle
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { stockService } from '../../../services/eventService';
|
import { stockService } from '../../../services/eventService';
|
||||||
|
import { logger } from '../../../utils/logger';
|
||||||
|
|
||||||
const HistoricalEvents = ({
|
const HistoricalEvents = ({
|
||||||
events = [],
|
events = [],
|
||||||
@@ -99,7 +100,10 @@ const HistoricalEvents = ({
|
|||||||
[event.id]: response.data || []
|
[event.id]: response.data || []
|
||||||
}));
|
}));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载事件股票失败:', err);
|
logger.error('HistoricalEvents', 'showEventStocks', err, {
|
||||||
|
eventId: event.id,
|
||||||
|
eventTitle: event.title
|
||||||
|
});
|
||||||
setEventStocks(prev => ({
|
setEventStocks(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[event.id]: []
|
[event.id]: []
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ import ReactECharts from 'echarts-for-react';
|
|||||||
// 导入导航栏组件
|
// 导入导航栏组件
|
||||||
import HomeNavbar from '../../../components/Navbars/HomeNavbar';
|
import HomeNavbar from '../../../components/Navbars/HomeNavbar';
|
||||||
|
|
||||||
|
import { logger } from '../../../utils/logger';
|
||||||
|
|
||||||
// 板块关联TOP10数据计算
|
// 板块关联TOP10数据计算
|
||||||
function getSectorRelationTop10(sectorData) {
|
function getSectorRelationTop10(sectorData) {
|
||||||
// 股票代码 -> 所属板块集合
|
// 股票代码 -> 所属板块集合
|
||||||
@@ -191,7 +193,7 @@ const limitAnalyseService = {
|
|||||||
throw new Error('接口返回内容不是有效的 JSON,实际返回:' + text.slice(0, 100));
|
throw new Error('接口返回内容不是有效的 JSON,实际返回:' + text.slice(0, 100));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching available dates:', error);
|
logger.error('LimitAnalyse', 'getAvailableDates', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -202,7 +204,7 @@ const limitAnalyseService = {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data; // 修正:直接返回整个对象
|
return data; // 修正:直接返回整个对象
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching analysis data:', error);
|
logger.error('LimitAnalyse', 'getAnalysisData', error, { date });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -213,7 +215,7 @@ const limitAnalyseService = {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data.data || [];
|
return data.data || [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching sector data:', error);
|
logger.error('LimitAnalyse', 'getSectorData', error, { date });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -224,7 +226,7 @@ const limitAnalyseService = {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data.data || [];
|
return data.data || [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching word cloud data:', error);
|
logger.error('LimitAnalyse', 'getWordCloudData', error, { date });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -241,7 +243,7 @@ const limitAnalyseService = {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data.data;
|
return data.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error exporting data:', error);
|
logger.error('LimitAnalyse', 'exportData', error, { date, exportType });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
import { FaEye, FaExternalLinkAlt, FaChartLine, FaCalendarAlt } from 'react-icons/fa';
|
import { FaEye, FaExternalLinkAlt, FaChartLine, FaCalendarAlt } from 'react-icons/fa';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import tradingDayUtils from '../../../utils/tradingDayUtils'; // 引入交易日工具
|
import tradingDayUtils from '../../../utils/tradingDayUtils'; // 引入交易日工具
|
||||||
|
import { logger } from '../../../utils/logger';
|
||||||
|
|
||||||
// API配置
|
// API配置
|
||||||
const API_BASE_URL = process.env.NODE_ENV === 'production' ? '/concept-api' : 'https://valuefrontier.cn/concept-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 }) => {
|
const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoading, error: externalError }) => {
|
||||||
// 调试:检查 Icon 组件是否可用
|
// 调试:检查 Icon 组件是否可用
|
||||||
if (typeof Icon === 'undefined') {
|
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>;
|
return <div>组件加载错误:Icon 组件未定义</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +314,10 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
|
|||||||
} else if (moment.isMoment(tradeDate)) {
|
} else if (moment.isMoment(tradeDate)) {
|
||||||
formattedTradeDate = tradeDate.format('YYYY-MM-DD');
|
formattedTradeDate = tradeDate.format('YYYY-MM-DD');
|
||||||
} else {
|
} else {
|
||||||
console.warn('Invalid tradeDate format:', tradeDate, typeof tradeDate);
|
logger.warn('RelatedConcepts', '无效的交易日期格式', {
|
||||||
|
tradeDate,
|
||||||
|
tradeDateType: typeof tradeDate
|
||||||
|
});
|
||||||
formattedTradeDate = moment().format('YYYY-MM-DD');
|
formattedTradeDate = moment().format('YYYY-MM-DD');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +329,7 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
|
|||||||
trade_date: formattedTradeDate
|
trade_date: formattedTradeDate
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Searching concepts with:', requestBody);
|
logger.debug('RelatedConcepts', '搜索概念', requestBody);
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/search`, {
|
const response = await fetch(`${API_BASE_URL}/search`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -338,11 +344,16 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
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)) {
|
if (!validateConceptData(data)) {
|
||||||
console.warn('Invalid concept data format:', data);
|
logger.warn('RelatedConcepts', '概念数据格式无效', {
|
||||||
|
hasData: !!data
|
||||||
|
});
|
||||||
setConcepts([]);
|
setConcepts([]);
|
||||||
setError('返回的数据格式无效');
|
setError('返回的数据格式无效');
|
||||||
return;
|
return;
|
||||||
@@ -359,10 +370,16 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
|
|||||||
setEffectiveTradingDate(data.data.trade_date || formattedTradeDate);
|
setEffectiveTradingDate(data.data.trade_date || formattedTradeDate);
|
||||||
} else {
|
} else {
|
||||||
setConcepts([]);
|
setConcepts([]);
|
||||||
console.warn('No concepts found in response');
|
logger.warn('RelatedConcepts', '响应中未找到概念数据', {
|
||||||
|
hasResults: !!data.results,
|
||||||
|
hasDataConcepts: !!(data.data?.concepts)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to search concepts:', err);
|
logger.error('RelatedConcepts', 'searchConcepts', err, {
|
||||||
|
title,
|
||||||
|
tradeDate: formattedTradeDate
|
||||||
|
});
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
setConcepts([]);
|
setConcepts([]);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -387,13 +404,20 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
|
|||||||
} else if (typeof eventTime === 'number') {
|
} else if (typeof eventTime === 'number') {
|
||||||
eventMoment = moment(eventTime);
|
eventMoment = moment(eventTime);
|
||||||
} else {
|
} else {
|
||||||
console.warn('Unknown eventTime format:', eventTime, typeof eventTime);
|
logger.warn('RelatedConcepts', '未知的事件时间格式', {
|
||||||
|
eventTime,
|
||||||
|
eventTimeType: typeof eventTime,
|
||||||
|
eventId
|
||||||
|
});
|
||||||
eventMoment = moment();
|
eventMoment = moment();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保moment对象有效
|
// 确保moment对象有效
|
||||||
if (!eventMoment.isValid()) {
|
if (!eventMoment.isValid()) {
|
||||||
console.warn('Invalid eventTime:', eventTime);
|
logger.warn('RelatedConcepts', '无效的事件时间', {
|
||||||
|
eventTime,
|
||||||
|
eventId
|
||||||
|
});
|
||||||
eventMoment = moment();
|
eventMoment = moment();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,17 +434,25 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
|
|||||||
} else if (nextTradingDay instanceof Date) {
|
} else if (nextTradingDay instanceof Date) {
|
||||||
formattedDate = moment(nextTradingDay).format('YYYY-MM-DD');
|
formattedDate = moment(nextTradingDay).format('YYYY-MM-DD');
|
||||||
} else {
|
} else {
|
||||||
console.warn('tradingDayUtils.getNextTradingDay returned invalid format:', nextTradingDay);
|
logger.warn('RelatedConcepts', '交易日工具返回了无效格式', {
|
||||||
|
nextTradingDay,
|
||||||
|
eventId
|
||||||
|
});
|
||||||
formattedDate = eventMoment.add(1, 'day').format('YYYY-MM-DD');
|
formattedDate = eventMoment.add(1, 'day').format('YYYY-MM-DD');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 降级处理:简单地加一天(不考虑周末和节假日)
|
// 降级处理:简单地加一天(不考虑周末和节假日)
|
||||||
console.warn('tradingDayUtils.getNextTradingDay not available, using simple date addition');
|
logger.warn('RelatedConcepts', '交易日工具不可用,使用简单日期加法', {
|
||||||
|
eventId
|
||||||
|
});
|
||||||
formattedDate = eventMoment.add(1, 'day').format('YYYY-MM-DD');
|
formattedDate = eventMoment.add(1, 'day').format('YYYY-MM-DD');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to format event time:', e);
|
logger.error('RelatedConcepts', 'formatEventTime', e, {
|
||||||
|
eventTime,
|
||||||
|
eventId
|
||||||
|
});
|
||||||
// 使用当前交易日作为fallback
|
// 使用当前交易日作为fallback
|
||||||
if (tradingDayUtils && tradingDayUtils.getCurrentTradingDay) {
|
if (tradingDayUtils && tradingDayUtils.getCurrentTradingDay) {
|
||||||
const currentTradingDay = tradingDayUtils.getCurrentTradingDay();
|
const currentTradingDay = tradingDayUtils.getCurrentTradingDay();
|
||||||
@@ -430,7 +462,10 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
|
|||||||
} else if (currentTradingDay instanceof Date) {
|
} else if (currentTradingDay instanceof Date) {
|
||||||
formattedDate = moment(currentTradingDay).format('YYYY-MM-DD');
|
formattedDate = moment(currentTradingDay).format('YYYY-MM-DD');
|
||||||
} else {
|
} else {
|
||||||
console.warn('tradingDayUtils.getCurrentTradingDay returned invalid format:', currentTradingDay);
|
logger.warn('RelatedConcepts', '当前交易日工具返回了无效格式', {
|
||||||
|
currentTradingDay,
|
||||||
|
eventId
|
||||||
|
});
|
||||||
formattedDate = moment().format('YYYY-MM-DD');
|
formattedDate = moment().format('YYYY-MM-DD');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -440,7 +475,9 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
|
|||||||
|
|
||||||
searchConcepts(eventTitle, formattedDate);
|
searchConcepts(eventTitle, formattedDate);
|
||||||
} else if (!eventTitle) {
|
} else if (!eventTitle) {
|
||||||
console.warn('No event title provided for concept search');
|
logger.warn('RelatedConcepts', '未提供事件标题,无法搜索概念', {
|
||||||
|
eventId
|
||||||
|
});
|
||||||
setConcepts([]);
|
setConcepts([]);
|
||||||
}
|
}
|
||||||
}, [eventTitle, eventTime]);
|
}, [eventTitle, eventTime]);
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import * as echarts from 'echarts';
|
|||||||
import StockChartModal from '../../../components/StockChart/StockChartModal';
|
import StockChartModal from '../../../components/StockChart/StockChartModal';
|
||||||
|
|
||||||
import { eventService, stockService } from '../../../services/eventService';
|
import { eventService, stockService } from '../../../services/eventService';
|
||||||
|
import { logger } from '../../../utils/logger';
|
||||||
|
|
||||||
const RelatedStocks = ({
|
const RelatedStocks = ({
|
||||||
eventId,
|
eventId,
|
||||||
@@ -102,14 +103,24 @@ const RelatedStocks = ({
|
|||||||
setQuotesLoading(true);
|
setQuotesLoading(true);
|
||||||
const codes = stocksData.map(stock => stock.stock_code);
|
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);
|
const response = await stockService.getQuotes(codes, eventTime);
|
||||||
console.log('股票报价响应:', response);
|
logger.debug('RelatedStocks', '股票报价响应', {
|
||||||
|
hasResponse: !!response,
|
||||||
|
quotesCount: response ? Object.keys(response).length : 0
|
||||||
|
});
|
||||||
|
|
||||||
setQuotes(response || {});
|
setQuotes(response || {});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取股票报价失败:', err);
|
logger.error('RelatedStocks', 'fetchQuotes', err, {
|
||||||
|
stockCount: stocksData.length,
|
||||||
|
eventTime
|
||||||
|
});
|
||||||
toast({
|
toast({
|
||||||
title: '获取股票报价失败',
|
title: '获取股票报价失败',
|
||||||
description: err.message,
|
description: err.message,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { InfoIcon, ViewIcon } from '@chakra-ui/icons';
|
|||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
import { eventService } from '../../../services/eventService';
|
import { eventService } from '../../../services/eventService';
|
||||||
import CitedContent from '../../../components/Citation/CitedContent';
|
import CitedContent from '../../../components/Citation/CitedContent';
|
||||||
|
import { logger } from '../../../utils/logger';
|
||||||
|
|
||||||
// 节点样式配置 - 完全复刻Flask版本
|
// 节点样式配置 - 完全复刻Flask版本
|
||||||
const NODE_STYLES = {
|
const NODE_STYLES = {
|
||||||
@@ -67,40 +68,39 @@ const NODE_TYPE_LABELS = {
|
|||||||
|
|
||||||
// 过滤孤立节点 - 完全复刻Flask版本
|
// 过滤孤立节点 - 完全复刻Flask版本
|
||||||
function filterIsolatedNodes(nodes, edges) {
|
function filterIsolatedNodes(nodes, edges) {
|
||||||
console.log('开始过滤孤立节点');
|
logger.debug('TransmissionChain', '开始过滤孤立节点', {
|
||||||
console.log('输入节点:', nodes);
|
nodesCount: nodes?.length,
|
||||||
console.log('输入边:', edges);
|
edgesCount: edges?.length
|
||||||
|
});
|
||||||
|
|
||||||
if (!nodes || !edges) {
|
if (!nodes || !edges) {
|
||||||
console.log('节点或边数据为空');
|
logger.debug('TransmissionChain', '节点或边数据为空');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectedNodeIds = new Set();
|
const connectedNodeIds = new Set();
|
||||||
edges.forEach(edge => {
|
edges.forEach(edge => {
|
||||||
console.log('处理边:', edge, '从', edge.source, '到', edge.target);
|
|
||||||
connectedNodeIds.add(String(edge.source));
|
connectedNodeIds.add(String(edge.source));
|
||||||
connectedNodeIds.add(String(edge.target));
|
connectedNodeIds.add(String(edge.target));
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('连接的节点ID集合:', connectedNodeIds);
|
|
||||||
|
|
||||||
// 如果图中只有一个节点且是主事件,也显示它
|
// 如果图中只有一个节点且是主事件,也显示它
|
||||||
const mainEventNode = nodes.find(n => n.extra?.is_main_event);
|
const mainEventNode = nodes.find(n => n.extra?.is_main_event);
|
||||||
console.log('主事件节点:', mainEventNode);
|
|
||||||
|
|
||||||
if (mainEventNode) {
|
if (mainEventNode) {
|
||||||
connectedNodeIds.add(String(mainEventNode.id));
|
connectedNodeIds.add(String(mainEventNode.id));
|
||||||
console.log('添加主事件节点ID:', String(mainEventNode.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredNodes = nodes.filter(node => {
|
const filteredNodes = nodes.filter(node => {
|
||||||
const shouldKeep = connectedNodeIds.has(String(node.id));
|
return connectedNodeIds.has(String(node.id));
|
||||||
console.log(`节点 ${node.name}(${node.id}[${String(node.id)}]): ${shouldKeep ? '保留' : '过滤'}`);
|
});
|
||||||
return shouldKeep;
|
|
||||||
|
logger.debug('TransmissionChain', '过滤完成', {
|
||||||
|
originalCount: nodes.length,
|
||||||
|
filteredCount: filteredNodes.length,
|
||||||
|
connectedNodesCount: connectedNodeIds.size
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('过滤后的节点:', filteredNodes);
|
|
||||||
return filteredNodes;
|
return filteredNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,29 +146,33 @@ function calculateEdgeWidth(strength) {
|
|||||||
|
|
||||||
// 力导向图配置 - 完全复刻Flask版本
|
// 力导向图配置 - 完全复刻Flask版本
|
||||||
function getGraphOption(data) {
|
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) {
|
if (!data || !data.nodes || !data.edges || data.nodes.length === 0) {
|
||||||
console.log('数据为空或无效');
|
logger.debug('TransmissionChain', '数据为空或无效');
|
||||||
return {
|
return {
|
||||||
title: { text: '暂无传导链数据', left: 'center', top: 'center' },
|
title: { text: '暂无传导链数据', left: 'center', top: 'center' },
|
||||||
graphic: { type: 'text', left: 'center', top: '60%', style: { text: '当前事件暂无传导链分析数据', fontSize: 14 } }
|
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);
|
const filteredNodes = filterIsolatedNodes(data.nodes, data.edges);
|
||||||
console.log('过滤后节点数:', filteredNodes.length);
|
|
||||||
console.log('过滤后的节点:', filteredNodes);
|
|
||||||
|
|
||||||
// 进一步过滤:不显示事件类型的节点
|
// 进一步过滤:不显示事件类型的节点
|
||||||
const nonEventNodes = filteredNodes.filter(node => node.extra?.node_type !== 'event');
|
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) {
|
if (nonEventNodes.length === 0) {
|
||||||
console.log('过滤后没有有效节点');
|
logger.debug('TransmissionChain', '过滤后没有有效节点');
|
||||||
return {
|
return {
|
||||||
title: { text: '暂无有效节点数据', left: 'center', top: 'center' },
|
title: { text: '暂无有效节点数据', left: 'center', top: 'center' },
|
||||||
graphic: { type: 'text', left: 'center', top: '60%', style: { text: '当前事件的传导链节点均为孤立节点', fontSize: 14 } }
|
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 }
|
itemStyle: { color: NODE_STYLES[type]?.color || NODE_STYLES.other.color }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('节点类别:', categories);
|
|
||||||
|
|
||||||
// 构建图表节点数据 - 完全复刻Flask版本样式(排除事件节点)
|
// 构建图表节点数据 - 完全复刻Flask版本样式(排除事件节点)
|
||||||
const chartNodes = nonEventNodes.map(node => {
|
const chartNodes = nonEventNodes.map(node => {
|
||||||
const nodeType = node.extra?.node_type || 'other';
|
const nodeType = node.extra?.node_type || 'other';
|
||||||
const nodeStyle = NODE_STYLES[nodeType] || NODE_STYLES['other'];
|
const nodeStyle = NODE_STYLES[nodeType] || NODE_STYLES['other'];
|
||||||
const connectionCount = calculateNodeConnections(node.id, data.edges);
|
const connectionCount = calculateNodeConnections(node.id, data.edges);
|
||||||
|
|
||||||
console.log(`节点 ${node.name} (${node.id}): 类型=${nodeType}, 连接数=${connectionCount}`);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: String(node.id),
|
id: String(node.id),
|
||||||
name: node.name,
|
name: node.name,
|
||||||
@@ -433,33 +433,40 @@ const TransmissionChainAnalysis = ({ eventId }) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
console.log('开始加载传导链数据,eventId:', eventId);
|
logger.debug('TransmissionChain', '开始加载传导链数据', { eventId });
|
||||||
const [graphRes, sankeyRes] = await Promise.all([
|
const [graphRes, sankeyRes] = await Promise.all([
|
||||||
eventService.getTransmissionChainAnalysis(eventId),
|
eventService.getTransmissionChainAnalysis(eventId),
|
||||||
eventService.getSankeyData(eventId)
|
eventService.getSankeyData(eventId)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
console.log('传导链数据API响应:', graphRes);
|
logger.debug('TransmissionChain', 'API响应', {
|
||||||
console.log('桑基图数据API响应:', sankeyRes);
|
graphSuccess: graphRes.success,
|
||||||
|
graphNodesCount: graphRes.data?.nodes?.length,
|
||||||
|
graphEdgesCount: graphRes.data?.edges?.length,
|
||||||
|
sankeySuccess: sankeyRes.success
|
||||||
|
});
|
||||||
|
|
||||||
if (graphRes.success && graphRes.data) {
|
if (graphRes.success && graphRes.data) {
|
||||||
console.log('传导链节点数据:', graphRes.data.nodes);
|
|
||||||
console.log('传导链边数据:', graphRes.data.edges);
|
|
||||||
setGraphData(graphRes.data);
|
setGraphData(graphRes.data);
|
||||||
} else {
|
} else {
|
||||||
console.log('传导链数据加载失败:', graphRes);
|
logger.warn('TransmissionChain', '传导链数据加载失败', {
|
||||||
|
success: graphRes.success,
|
||||||
|
eventId
|
||||||
|
});
|
||||||
setGraphData(null);
|
setGraphData(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sankeyRes.success && sankeyRes.data) {
|
if (sankeyRes.success && sankeyRes.data) {
|
||||||
console.log('桑基图数据:', sankeyRes.data);
|
|
||||||
setSankeyData(sankeyRes.data);
|
setSankeyData(sankeyRes.data);
|
||||||
} else {
|
} else {
|
||||||
console.log('桑基图数据加载失败:', sankeyRes);
|
logger.warn('TransmissionChain', '桑基图数据加载失败', {
|
||||||
|
success: sankeyRes.success,
|
||||||
|
eventId
|
||||||
|
});
|
||||||
setSankeyData(null);
|
setSankeyData(null);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('传导链数据加载异常:', e);
|
logger.error('TransmissionChain', 'fetchData', e, { eventId });
|
||||||
setError('加载传导链数据失败');
|
setError('加载传导链数据失败');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -509,23 +516,32 @@ const TransmissionChainAnalysis = ({ eventId }) => {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
return result.data;
|
return result.data;
|
||||||
} else {
|
} else {
|
||||||
console.error('获取节点详情失败:', result.message);
|
logger.error('TransmissionChain', 'getChainNodeDetail', new Error(result.message), {
|
||||||
|
nodeId,
|
||||||
|
eventId
|
||||||
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('API调用异常:', error);
|
logger.error('TransmissionChain', 'getChainNodeDetail', error, {
|
||||||
|
nodeId,
|
||||||
|
eventId
|
||||||
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 力导向图节点点击事件
|
// 力导向图节点点击事件
|
||||||
const handleGraphNodeClick = async (params) => {
|
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) {
|
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));
|
const clickedNode = graphData.nodes.find(n => String(n.id) === String(params.data.id));
|
||||||
if (clickedNode) {
|
if (clickedNode) {
|
||||||
@@ -541,32 +557,30 @@ const TransmissionChainAnalysis = ({ eventId }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取详细节点信息(包括parents和children)
|
// 获取详细节点信息(包括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);
|
const detail = await getChainNodeDetail(params.data.id);
|
||||||
console.log('获取到的节点详情:', detail);
|
|
||||||
setNodeDetail(detail);
|
setNodeDetail(detail);
|
||||||
|
|
||||||
// 打开弹窗
|
// 打开弹窗
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果点击的是空白区域,也尝试查找最近的节点
|
|
||||||
else if (params.componentType === 'series' && !params.data) {
|
|
||||||
console.log('点击了图表空白区域');
|
|
||||||
// 这里可以添加点击空白区域的处理逻辑
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 桑基图节点点击事件
|
// 桑基图节点点击事件
|
||||||
const handleSankeyNodeClick = async (params) => {
|
const handleSankeyNodeClick = async (params) => {
|
||||||
if (params.dataType === 'node' && params.data && params.data.name) {
|
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) {
|
if (graphData && graphData.nodes) {
|
||||||
const clickedNode = graphData.nodes.find(n => n.name === params.data.name);
|
const clickedNode = graphData.nodes.find(n => n.name === params.data.name);
|
||||||
if (clickedNode) {
|
if (clickedNode) {
|
||||||
console.log('找到对应节点:', clickedNode);
|
|
||||||
setSelectedNode(clickedNode);
|
setSelectedNode(clickedNode);
|
||||||
|
|
||||||
// 计算传导路径
|
// 计算传导路径
|
||||||
@@ -579,15 +593,19 @@ const TransmissionChainAnalysis = ({ eventId }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取详细节点信息(包括parents和children)
|
// 获取详细节点信息(包括parents和children)
|
||||||
console.log('开始获取桑基图节点详情,节点ID:', clickedNode.id);
|
logger.debug('TransmissionChain', '获取桑基图节点详情', {
|
||||||
|
nodeId: clickedNode.id,
|
||||||
|
nodeName: clickedNode.name
|
||||||
|
});
|
||||||
const detail = await getChainNodeDetail(clickedNode.id);
|
const detail = await getChainNodeDetail(clickedNode.id);
|
||||||
console.log('获取到的桑基图节点详情:', detail);
|
|
||||||
setNodeDetail(detail);
|
setNodeDetail(detail);
|
||||||
|
|
||||||
// 打开弹窗
|
// 打开弹窗
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
} else {
|
} else {
|
||||||
console.log('未找到对应的节点数据');
|
logger.warn('TransmissionChain', '未找到对应的节点数据', {
|
||||||
|
nodeName: params.data.name
|
||||||
|
});
|
||||||
// 创建一个临时节点信息用于显示
|
// 创建一个临时节点信息用于显示
|
||||||
const tempNode = {
|
const tempNode = {
|
||||||
id: params.data.name,
|
id: params.data.name,
|
||||||
@@ -714,15 +732,7 @@ const TransmissionChainAnalysis = ({ eventId }) => {
|
|||||||
option={graphData ? getGraphOption(graphData) : {}}
|
option={graphData ? getGraphOption(graphData) : {}}
|
||||||
style={{ height: '100%', width: '100%' }}
|
style={{ height: '100%', width: '100%' }}
|
||||||
onEvents={{
|
onEvents={{
|
||||||
click: handleGraphNodeClick,
|
click: handleGraphNodeClick
|
||||||
// 添加更多事件以提高点击敏感性
|
|
||||||
mouseover: (params) => {
|
|
||||||
console.log('鼠标悬停:', params);
|
|
||||||
// 可以在这里添加悬停效果
|
|
||||||
},
|
|
||||||
mouseout: (params) => {
|
|
||||||
// 鼠标离开的处理
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
opts={{
|
opts={{
|
||||||
renderer: 'canvas',
|
renderer: 'canvas',
|
||||||
@@ -740,15 +750,7 @@ const TransmissionChainAnalysis = ({ eventId }) => {
|
|||||||
option={sankeyData ? getSankeyOption(sankeyData) : {}}
|
option={sankeyData ? getSankeyOption(sankeyData) : {}}
|
||||||
style={{ height: '100%', width: '100%' }}
|
style={{ height: '100%', width: '100%' }}
|
||||||
onEvents={{
|
onEvents={{
|
||||||
click: handleSankeyNodeClick,
|
click: handleSankeyNodeClick
|
||||||
// 添加更多事件以提高点击敏感性
|
|
||||||
mouseover: (params) => {
|
|
||||||
console.log('桑基图鼠标悬停:', params);
|
|
||||||
// 可以在这里添加悬停效果
|
|
||||||
},
|
|
||||||
mouseout: (params) => {
|
|
||||||
// 鼠标离开的处理
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
opts={{
|
opts={{
|
||||||
renderer: 'canvas',
|
renderer: 'canvas',
|
||||||
|
|||||||
Reference in New Issue
Block a user