diff --git a/src/views/StockOverview/hooks/useStockOverviewEvents.js b/src/views/StockOverview/hooks/useStockOverviewEvents.js new file mode 100644 index 00000000..871f8a5b --- /dev/null +++ b/src/views/StockOverview/hooks/useStockOverviewEvents.js @@ -0,0 +1,236 @@ +// src/views/StockOverview/hooks/useStockOverviewEvents.js +// 个股中心页面事件追踪 Hook + +import { useCallback, useEffect } from 'react'; +import { usePostHogTrack } from '../../../hooks/usePostHogRedux'; +import { RETENTION_EVENTS } from '../../../lib/constants'; +import { logger } from '../../../utils/logger'; + +/** + * 个股中心事件追踪 Hook + * @param {Object} options - 配置选项 + * @param {Function} options.navigate - 路由导航函数 + * @returns {Object} 事件追踪处理函数集合 + */ +export const useStockOverviewEvents = ({ navigate } = {}) => { + const { track } = usePostHogTrack(); + + // 🎯 页面浏览事件 - 页面加载时触发 + useEffect(() => { + track(RETENTION_EVENTS.STOCK_OVERVIEW_VIEWED, { + timestamp: new Date().toISOString(), + }); + logger.debug('useStockOverviewEvents', '📊 Stock Overview Page Viewed'); + }, [track]); + + /** + * 追踪市场统计数据查看 + * @param {Object} stats - 市场统计数据 + */ + const trackMarketStatsViewed = useCallback((stats) => { + if (!stats) return; + + track(RETENTION_EVENTS.STOCK_LIST_VIEWED, { + total_market_cap: stats.total_market_cap, + total_volume: stats.total_volume, + rising_stocks: stats.rising_count, + falling_stocks: stats.falling_count, + data_date: stats.date, + }); + + logger.debug('useStockOverviewEvents', '📈 Market Statistics Viewed', stats); + }, [track]); + + /** + * 追踪股票搜索开始 + */ + const trackSearchInitiated = useCallback(() => { + track(RETENTION_EVENTS.SEARCH_INITIATED, { + context: 'stock_overview', + }); + + logger.debug('useStockOverviewEvents', '🔍 Search Initiated'); + }, [track]); + + /** + * 追踪股票搜索查询 + * @param {string} query - 搜索查询词 + * @param {number} resultCount - 搜索结果数量 + */ + const trackStockSearched = useCallback((query, resultCount = 0) => { + if (!query) return; + + track(RETENTION_EVENTS.STOCK_SEARCHED, { + query, + result_count: resultCount, + has_results: resultCount > 0, + }); + + // 如果没有搜索结果,额外追踪 + if (resultCount === 0) { + track(RETENTION_EVENTS.SEARCH_NO_RESULTS, { + query, + context: 'stock_overview', + }); + } + + logger.debug('useStockOverviewEvents', '🔍 Stock Searched', { + query, + resultCount, + }); + }, [track]); + + /** + * 追踪搜索结果点击 + * @param {Object} stock - 被点击的股票对象 + * @param {number} position - 在搜索结果中的位置 + */ + const trackSearchResultClicked = useCallback((stock, position = 0) => { + track(RETENTION_EVENTS.SEARCH_RESULT_CLICKED, { + stock_code: stock.code, + stock_name: stock.name, + exchange: stock.exchange, + position, + context: 'stock_overview', + }); + + logger.debug('useStockOverviewEvents', '🎯 Search Result Clicked', { + stock: stock.code, + position, + }); + }, [track]); + + /** + * 追踪概念卡片点击 + * @param {Object} concept - 概念对象 + * @param {number} rank - 在列表中的排名 + */ + const trackConceptClicked = useCallback((concept, rank = 0) => { + track(RETENTION_EVENTS.CONCEPT_CLICKED, { + concept_name: concept.name, + concept_code: concept.code, + change_percent: concept.change_percent, + stock_count: concept.stock_count, + rank, + source: 'daily_hot_concepts', + }); + + logger.debug('useStockOverviewEvents', '🔥 Concept Clicked', { + concept: concept.name, + rank, + }); + }, [track]); + + /** + * 追踪概念下的股票标签点击 + * @param {Object} stock - 股票对象 + * @param {string} conceptName - 所属概念名称 + */ + const trackConceptStockClicked = useCallback((stock, conceptName) => { + track(RETENTION_EVENTS.CONCEPT_STOCK_CLICKED, { + stock_code: stock.code, + stock_name: stock.name, + concept_name: conceptName, + source: 'daily_hot_concepts_tag', + }); + + logger.debug('useStockOverviewEvents', '🏷️ Concept Stock Tag Clicked', { + stock: stock.code, + concept: conceptName, + }); + }, [track]); + + /** + * 追踪热力图中股票点击 + * @param {Object} stock - 被点击的股票对象 + * @param {string} marketCapRange - 市值区间 + */ + const trackHeatmapStockClicked = useCallback((stock, marketCapRange = '') => { + track(RETENTION_EVENTS.STOCK_CLICKED, { + stock_code: stock.code, + stock_name: stock.name, + change_percent: stock.change_percent, + market_cap_range: marketCapRange, + source: 'market_heatmap', + }); + + logger.debug('useStockOverviewEvents', '📊 Heatmap Stock Clicked', { + stock: stock.code, + marketCapRange, + }); + }, [track]); + + /** + * 追踪股票详情查看 + * @param {string} stockCode - 股票代码 + * @param {string} source - 来源(search/concept/heatmap) + */ + const trackStockDetailViewed = useCallback((stockCode, source = 'unknown') => { + track(RETENTION_EVENTS.STOCK_DETAIL_VIEWED, { + stock_code: stockCode, + source: `stock_overview_${source}`, + }); + + logger.debug('useStockOverviewEvents', '👁️ Stock Detail Viewed', { + stockCode, + source, + }); + + // 导航到公司详情页 + if (navigate) { + navigate(`/company/${stockCode}`); + } + }, [track, navigate]); + + /** + * 追踪概念详情查看 + * @param {string} conceptCode - 概念代码 + */ + const trackConceptDetailViewed = useCallback((conceptCode) => { + track(RETENTION_EVENTS.CONCEPT_DETAIL_VIEWED, { + concept_code: conceptCode, + source: 'stock_overview_daily_hot', + }); + + logger.debug('useStockOverviewEvents', '🎯 Concept Detail Viewed', { + conceptCode, + }); + + // 导航到概念详情页 + if (navigate) { + navigate(`/concept-detail/${conceptCode}`); + } + }, [track, navigate]); + + /** + * 追踪日期选择变化 + * @param {string} newDate - 新选择的日期 + * @param {string} previousDate - 之前的日期 + */ + const trackDateChanged = useCallback((newDate, previousDate = null) => { + track(RETENTION_EVENTS.SEARCH_FILTER_APPLIED, { + filter_type: 'date', + filter_value: newDate, + previous_value: previousDate, + context: 'stock_overview', + }); + + logger.debug('useStockOverviewEvents', '📅 Date Changed', { + newDate, + previousDate, + }); + }, [track]); + + return { + trackMarketStatsViewed, + trackSearchInitiated, + trackStockSearched, + trackSearchResultClicked, + trackConceptClicked, + trackConceptStockClicked, + trackHeatmapStockClicked, + trackStockDetailViewed, + trackConceptDetailViewed, + trackDateChanged, + }; +}; diff --git a/src/views/StockOverview/index.js b/src/views/StockOverview/index.js index f2a968e7..0237059e 100644 --- a/src/views/StockOverview/index.js +++ b/src/views/StockOverview/index.js @@ -61,6 +61,7 @@ import { BsGraphUp, BsLightningFill } from 'react-icons/bs'; import { keyframes } from '@emotion/react'; import * as echarts from 'echarts'; import { logger } from '../../utils/logger'; +import { useStockOverviewEvents } from './hooks/useStockOverviewEvents'; // Navigation bar now provided by MainLayout // import HomeNavbar from '../../components/Navbars/HomeNavbar'; @@ -83,6 +84,20 @@ const StockOverview = () => { const heatmapRef = useRef(null); const heatmapChart = useRef(null); + // 🎯 事件追踪 Hook + const { + trackMarketStatsViewed, + trackSearchInitiated, + trackStockSearched, + trackSearchResultClicked, + trackConceptClicked, + trackConceptStockClicked, + trackHeatmapStockClicked, + trackStockDetailViewed, + trackConceptDetailViewed, + trackDateChanged, + } = useStockOverviewEvents({ navigate }); + // 状态管理 const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState([]); @@ -141,11 +156,18 @@ const StockOverview = () => { }); if (data.success) { - setSearchResults(data.data || []); + const results = data.data || []; + setSearchResults(results); setShowResults(true); + + // 🎯 追踪搜索查询 + trackStockSearched(query, results.length); } else { logger.warn('StockOverview', '搜索失败', data.error || '请稍后重试', { query }); // ❌ 移除搜索失败 toast(非关键操作) + + // 🎯 追踪搜索无结果 + trackStockSearched(query, 0); } } catch (error) { logger.error('StockOverview', 'searchStocks', error, { query }); @@ -219,18 +241,23 @@ const StockOverview = () => { const data = await response.json(); if (data.success) { - setMarketStats(prevStats => ({ + const newStats = { ...data.summary, // 保留之前从 heatmap 接口获取的上涨/下跌家数 rising_count: prevStats?.rising_count, - falling_count: prevStats?.falling_count - })); + falling_count: prevStats?.falling_count, + date: data.trade_date + }; + setMarketStats(newStats); setAvailableDates(data.available_dates || []); if (!selectedDate) setSelectedDate(data.trade_date); logger.debug('StockOverview', '市场统计数据加载成功', { date: data.trade_date, availableDatesCount: data.available_dates?.length || 0 }); + + // 🎯 追踪市场统计数据查看 + trackMarketStatsViewed(newStats); } } catch (error) { logger.error('StockOverview', 'fetchMarketStats', error, { date }); @@ -403,6 +430,16 @@ const StockOverview = () => { heatmapChart.current.on('click', function(params) { // 只有点击个股(有code的节点)才跳转 if (params.data && params.data.code && !params.data.children) { + const stock = { + code: params.data.code, + name: params.data.name, + change_percent: params.data.change + }; + const marketCapRange = getMarketCapRange(params.data.value); + + // 🎯 追踪热力图股票点击 + trackHeatmapStockClicked(stock, marketCapRange); + navigate(`/company?scode=${params.data.code}`); } }); @@ -412,7 +449,7 @@ const StockOverview = () => { }); // ❌ 移除热力图渲染失败 toast(非关键操作) } - }, [colorMode, goldColor, navigate]); // ✅ 移除 toast 依赖 + }, [colorMode, goldColor, navigate, trackHeatmapStockClicked]); // ✅ 添加追踪函数依赖 // 获取市值区间 const getMarketCapRange = (cap) => { @@ -427,6 +464,12 @@ const StockOverview = () => { const handleSearchChange = (e) => { const value = e.target.value; setSearchQuery(value); + + // 🎯 追踪搜索开始(首次输入时) + if (value && !searchQuery) { + trackSearchInitiated(); + } + debounceSearch(value); }; @@ -438,19 +481,30 @@ const StockOverview = () => { }; // 选择股票 - const handleSelectStock = (stock) => { + const handleSelectStock = (stock, index = 0) => { + // 🎯 追踪搜索结果点击 + trackSearchResultClicked(stock, index); + navigate(`/company?scode=${stock.stock_code}`); handleClearSearch(); }; // 查看概念详情(模仿概念中心:打开对应HTML页) - const handleConceptClick = (conceptId, conceptName) => { - const htmlPath = `/htmls/${conceptName}.html`; + const handleConceptClick = (concept, rank = 0) => { + // 🎯 追踪概念点击 + trackConceptClicked(concept, rank); + + const htmlPath = `/htmls/${concept.concept_name}.html`; window.open(htmlPath, '_blank'); }; // 处理日期选择 const handleDateChange = (date) => { + const previousDate = selectedDate; + + // 🎯 追踪日期变化 + trackDateChanged(date, previousDate); + setSelectedDate(date); setIsCalendarOpen(false); // 重新获取数据 @@ -661,7 +715,7 @@ const StockOverview = () => { p={4} cursor="pointer" _hover={{ bg: hoverBg }} - onClick={() => handleSelectStock(stock)} + onClick={() => handleSelectStock(stock, index)} borderBottomWidth={index < searchResults.length - 1 ? "1px" : "0"} borderColor={borderColor} > @@ -880,7 +934,7 @@ const StockOverview = () => { }} transition="all 0.3s" cursor="pointer" - onClick={() => handleConceptClick(concept.concept_id, concept.concept_name)} + onClick={() => handleConceptClick(concept, index)} position="relative" overflow="hidden" > @@ -951,6 +1005,13 @@ const StockOverview = () => { cursor="pointer" onClick={(e) => { e.stopPropagation(); + + // 🎯 追踪概念下的股票标签点击 + trackConceptStockClicked({ + code: stock.stock_code, + name: stock.stock_name + }, concept.concept_name); + navigate(`/company?scode=${stock.stock_code}`); }} > @@ -969,7 +1030,7 @@ const StockOverview = () => { rightIcon={} onClick={(e) => { e.stopPropagation(); - handleConceptClick(concept.concept_id, concept.concept_name); + handleConceptClick(concept, index); }} > 查看详情