feat: 完成集成后,您可以在 PostHog 中分析:
- 用户搜索行为:搜索频率、热门搜索词、搜索成功率 - 概念关注度:哪些概念最受关注、点击排名分布 - 热力图使用情况:用户点击的股票市值分布、涨跌偏好 - 日期筛选模式:用户倾向查看哪些日期的数据 - 转化漏斗:从页面浏览 → 搜索 → 点击 → 详情的转化率
This commit is contained in:
236
src/views/StockOverview/hooks/useStockOverviewEvents.js
Normal file
236
src/views/StockOverview/hooks/useStockOverviewEvents.js
Normal file
@@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -61,6 +61,7 @@ import { BsGraphUp, BsLightningFill } from 'react-icons/bs';
|
|||||||
import { keyframes } from '@emotion/react';
|
import { keyframes } from '@emotion/react';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
|
import { useStockOverviewEvents } from './hooks/useStockOverviewEvents';
|
||||||
// Navigation bar now provided by MainLayout
|
// Navigation bar now provided by MainLayout
|
||||||
// import HomeNavbar from '../../components/Navbars/HomeNavbar';
|
// import HomeNavbar from '../../components/Navbars/HomeNavbar';
|
||||||
|
|
||||||
@@ -83,6 +84,20 @@ const StockOverview = () => {
|
|||||||
const heatmapRef = useRef(null);
|
const heatmapRef = useRef(null);
|
||||||
const heatmapChart = 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 [searchQuery, setSearchQuery] = useState('');
|
||||||
const [searchResults, setSearchResults] = useState([]);
|
const [searchResults, setSearchResults] = useState([]);
|
||||||
@@ -141,11 +156,18 @@ const StockOverview = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
setSearchResults(data.data || []);
|
const results = data.data || [];
|
||||||
|
setSearchResults(results);
|
||||||
setShowResults(true);
|
setShowResults(true);
|
||||||
|
|
||||||
|
// 🎯 追踪搜索查询
|
||||||
|
trackStockSearched(query, results.length);
|
||||||
} else {
|
} else {
|
||||||
logger.warn('StockOverview', '搜索失败', data.error || '请稍后重试', { query });
|
logger.warn('StockOverview', '搜索失败', data.error || '请稍后重试', { query });
|
||||||
// ❌ 移除搜索失败 toast(非关键操作)
|
// ❌ 移除搜索失败 toast(非关键操作)
|
||||||
|
|
||||||
|
// 🎯 追踪搜索无结果
|
||||||
|
trackStockSearched(query, 0);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('StockOverview', 'searchStocks', error, { query });
|
logger.error('StockOverview', 'searchStocks', error, { query });
|
||||||
@@ -219,18 +241,23 @@ const StockOverview = () => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
setMarketStats(prevStats => ({
|
const newStats = {
|
||||||
...data.summary,
|
...data.summary,
|
||||||
// 保留之前从 heatmap 接口获取的上涨/下跌家数
|
// 保留之前从 heatmap 接口获取的上涨/下跌家数
|
||||||
rising_count: prevStats?.rising_count,
|
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 || []);
|
setAvailableDates(data.available_dates || []);
|
||||||
if (!selectedDate) setSelectedDate(data.trade_date);
|
if (!selectedDate) setSelectedDate(data.trade_date);
|
||||||
logger.debug('StockOverview', '市场统计数据加载成功', {
|
logger.debug('StockOverview', '市场统计数据加载成功', {
|
||||||
date: data.trade_date,
|
date: data.trade_date,
|
||||||
availableDatesCount: data.available_dates?.length || 0
|
availableDatesCount: data.available_dates?.length || 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🎯 追踪市场统计数据查看
|
||||||
|
trackMarketStatsViewed(newStats);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('StockOverview', 'fetchMarketStats', error, { date });
|
logger.error('StockOverview', 'fetchMarketStats', error, { date });
|
||||||
@@ -403,6 +430,16 @@ const StockOverview = () => {
|
|||||||
heatmapChart.current.on('click', function(params) {
|
heatmapChart.current.on('click', function(params) {
|
||||||
// 只有点击个股(有code的节点)才跳转
|
// 只有点击个股(有code的节点)才跳转
|
||||||
if (params.data && params.data.code && !params.data.children) {
|
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}`);
|
navigate(`/company?scode=${params.data.code}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -412,7 +449,7 @@ const StockOverview = () => {
|
|||||||
});
|
});
|
||||||
// ❌ 移除热力图渲染失败 toast(非关键操作)
|
// ❌ 移除热力图渲染失败 toast(非关键操作)
|
||||||
}
|
}
|
||||||
}, [colorMode, goldColor, navigate]); // ✅ 移除 toast 依赖
|
}, [colorMode, goldColor, navigate, trackHeatmapStockClicked]); // ✅ 添加追踪函数依赖
|
||||||
|
|
||||||
// 获取市值区间
|
// 获取市值区间
|
||||||
const getMarketCapRange = (cap) => {
|
const getMarketCapRange = (cap) => {
|
||||||
@@ -427,6 +464,12 @@ const StockOverview = () => {
|
|||||||
const handleSearchChange = (e) => {
|
const handleSearchChange = (e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
setSearchQuery(value);
|
setSearchQuery(value);
|
||||||
|
|
||||||
|
// 🎯 追踪搜索开始(首次输入时)
|
||||||
|
if (value && !searchQuery) {
|
||||||
|
trackSearchInitiated();
|
||||||
|
}
|
||||||
|
|
||||||
debounceSearch(value);
|
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}`);
|
navigate(`/company?scode=${stock.stock_code}`);
|
||||||
handleClearSearch();
|
handleClearSearch();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查看概念详情(模仿概念中心:打开对应HTML页)
|
// 查看概念详情(模仿概念中心:打开对应HTML页)
|
||||||
const handleConceptClick = (conceptId, conceptName) => {
|
const handleConceptClick = (concept, rank = 0) => {
|
||||||
const htmlPath = `/htmls/${conceptName}.html`;
|
// 🎯 追踪概念点击
|
||||||
|
trackConceptClicked(concept, rank);
|
||||||
|
|
||||||
|
const htmlPath = `/htmls/${concept.concept_name}.html`;
|
||||||
window.open(htmlPath, '_blank');
|
window.open(htmlPath, '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理日期选择
|
// 处理日期选择
|
||||||
const handleDateChange = (date) => {
|
const handleDateChange = (date) => {
|
||||||
|
const previousDate = selectedDate;
|
||||||
|
|
||||||
|
// 🎯 追踪日期变化
|
||||||
|
trackDateChanged(date, previousDate);
|
||||||
|
|
||||||
setSelectedDate(date);
|
setSelectedDate(date);
|
||||||
setIsCalendarOpen(false);
|
setIsCalendarOpen(false);
|
||||||
// 重新获取数据
|
// 重新获取数据
|
||||||
@@ -661,7 +715,7 @@ const StockOverview = () => {
|
|||||||
p={4}
|
p={4}
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
_hover={{ bg: hoverBg }}
|
_hover={{ bg: hoverBg }}
|
||||||
onClick={() => handleSelectStock(stock)}
|
onClick={() => handleSelectStock(stock, index)}
|
||||||
borderBottomWidth={index < searchResults.length - 1 ? "1px" : "0"}
|
borderBottomWidth={index < searchResults.length - 1 ? "1px" : "0"}
|
||||||
borderColor={borderColor}
|
borderColor={borderColor}
|
||||||
>
|
>
|
||||||
@@ -880,7 +934,7 @@ const StockOverview = () => {
|
|||||||
}}
|
}}
|
||||||
transition="all 0.3s"
|
transition="all 0.3s"
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
onClick={() => handleConceptClick(concept.concept_id, concept.concept_name)}
|
onClick={() => handleConceptClick(concept, index)}
|
||||||
position="relative"
|
position="relative"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
>
|
>
|
||||||
@@ -951,6 +1005,13 @@ const StockOverview = () => {
|
|||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// 🎯 追踪概念下的股票标签点击
|
||||||
|
trackConceptStockClicked({
|
||||||
|
code: stock.stock_code,
|
||||||
|
name: stock.stock_name
|
||||||
|
}, concept.concept_name);
|
||||||
|
|
||||||
navigate(`/company?scode=${stock.stock_code}`);
|
navigate(`/company?scode=${stock.stock_code}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -969,7 +1030,7 @@ const StockOverview = () => {
|
|||||||
rightIcon={<FaChevronRight />}
|
rightIcon={<FaChevronRight />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleConceptClick(concept.concept_id, concept.concept_name);
|
handleConceptClick(concept, index);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
查看详情
|
查看详情
|
||||||
|
|||||||
Reference in New Issue
Block a user