feat: Company - 5个事件(页面浏览、股票搜索、Tab 切换、自选股管理)
This commit is contained in:
103
src/views/Company/hooks/useCompanyEvents.js
Normal file
103
src/views/Company/hooks/useCompanyEvents.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
// src/views/Company/hooks/useCompanyEvents.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 {string} options.stockCode - 当前股票代码
|
||||||
|
* @returns {Object} 事件追踪处理函数集合
|
||||||
|
*/
|
||||||
|
export const useCompanyEvents = ({ stockCode } = {}) => {
|
||||||
|
const { track } = usePostHogTrack();
|
||||||
|
|
||||||
|
// 🎯 页面浏览事件 - 页面加载时触发
|
||||||
|
useEffect(() => {
|
||||||
|
track(RETENTION_EVENTS.COMPANY_PAGE_VIEWED, {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
stock_code: stockCode || null,
|
||||||
|
});
|
||||||
|
logger.debug('useCompanyEvents', '📊 Company Page Viewed', { stockCode });
|
||||||
|
}, [track, stockCode]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪股票搜索/切换
|
||||||
|
* @param {string} newStockCode - 新的股票代码
|
||||||
|
* @param {string} previousStockCode - 之前的股票代码
|
||||||
|
*/
|
||||||
|
const trackStockSearched = useCallback((newStockCode, previousStockCode = null) => {
|
||||||
|
if (!newStockCode) return;
|
||||||
|
|
||||||
|
track(RETENTION_EVENTS.STOCK_SEARCHED, {
|
||||||
|
query: newStockCode,
|
||||||
|
stock_code: newStockCode,
|
||||||
|
previous_stock_code: previousStockCode,
|
||||||
|
context: 'company_page',
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useCompanyEvents', '🔍 Stock Searched', {
|
||||||
|
newStockCode,
|
||||||
|
previousStockCode,
|
||||||
|
});
|
||||||
|
}, [track]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪 Tab 切换
|
||||||
|
* @param {number} tabIndex - Tab 索引 (0: 公司概览, 1: 股票行情, 2: 财务全景, 3: 盈利预测)
|
||||||
|
* @param {string} tabName - Tab 名称
|
||||||
|
* @param {number} previousTabIndex - 之前的 Tab 索引
|
||||||
|
*/
|
||||||
|
const trackTabChanged = useCallback((tabIndex, tabName, previousTabIndex = null) => {
|
||||||
|
track(RETENTION_EVENTS.TAB_CHANGED, {
|
||||||
|
tab_index: tabIndex,
|
||||||
|
tab_name: tabName,
|
||||||
|
previous_tab_index: previousTabIndex,
|
||||||
|
stock_code: stockCode,
|
||||||
|
context: 'company_page',
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useCompanyEvents', '🔄 Tab Changed', {
|
||||||
|
tabIndex,
|
||||||
|
tabName,
|
||||||
|
previousTabIndex,
|
||||||
|
stockCode,
|
||||||
|
});
|
||||||
|
}, [track, stockCode]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪加入自选股
|
||||||
|
* @param {string} stock_code - 股票代码
|
||||||
|
*/
|
||||||
|
const trackWatchlistAdded = useCallback((stock_code) => {
|
||||||
|
track(RETENTION_EVENTS.WATCHLIST_ADDED, {
|
||||||
|
stock_code,
|
||||||
|
source: 'company_page',
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useCompanyEvents', '⭐ Watchlist Added', { stock_code });
|
||||||
|
}, [track]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪移除自选股
|
||||||
|
* @param {string} stock_code - 股票代码
|
||||||
|
*/
|
||||||
|
const trackWatchlistRemoved = useCallback((stock_code) => {
|
||||||
|
track(RETENTION_EVENTS.WATCHLIST_REMOVED, {
|
||||||
|
stock_code,
|
||||||
|
source: 'company_page',
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useCompanyEvents', '❌ Watchlist Removed', { stock_code });
|
||||||
|
}, [track]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
trackStockSearched,
|
||||||
|
trackTabChanged,
|
||||||
|
trackWatchlistAdded,
|
||||||
|
trackWatchlistRemoved,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -34,6 +34,8 @@ import FinancialPanorama from './FinancialPanorama';
|
|||||||
import ForecastReport from './ForecastReport';
|
import ForecastReport from './ForecastReport';
|
||||||
import MarketDataView from './MarketDataView';
|
import MarketDataView from './MarketDataView';
|
||||||
import CompanyOverview from './CompanyOverview';
|
import CompanyOverview from './CompanyOverview';
|
||||||
|
// 导入 PostHog 追踪 Hook
|
||||||
|
import { useCompanyEvents } from './hooks/useCompanyEvents';
|
||||||
|
|
||||||
const CompanyIndex = () => {
|
const CompanyIndex = () => {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@@ -42,7 +44,18 @@ const CompanyIndex = () => {
|
|||||||
const { colorMode, toggleColorMode } = useColorMode();
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
|
|
||||||
|
// 🎯 PostHog 事件追踪
|
||||||
|
const {
|
||||||
|
trackStockSearched,
|
||||||
|
trackTabChanged,
|
||||||
|
trackWatchlistAdded,
|
||||||
|
trackWatchlistRemoved,
|
||||||
|
} = useCompanyEvents({ stockCode });
|
||||||
|
|
||||||
|
// Tab 索引状态(用于追踪 Tab 切换)
|
||||||
|
const [currentTabIndex, setCurrentTabIndex] = useState(0);
|
||||||
|
|
||||||
const bgColor = useColorModeValue('white', 'gray.800');
|
const bgColor = useColorModeValue('white', 'gray.800');
|
||||||
const tabBg = useColorModeValue('gray.50', 'gray.700');
|
const tabBg = useColorModeValue('gray.50', 'gray.700');
|
||||||
const activeBg = useColorModeValue('blue.500', 'blue.400');
|
const activeBg = useColorModeValue('blue.500', 'blue.400');
|
||||||
@@ -86,6 +99,9 @@ const CompanyIndex = () => {
|
|||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
if (inputCode && inputCode !== stockCode) {
|
if (inputCode && inputCode !== stockCode) {
|
||||||
|
// 🎯 追踪股票搜索
|
||||||
|
trackStockSearched(inputCode, stockCode);
|
||||||
|
|
||||||
setStockCode(inputCode);
|
setStockCode(inputCode);
|
||||||
setSearchParams({ scode: inputCode });
|
setSearchParams({ scode: inputCode });
|
||||||
}
|
}
|
||||||
@@ -123,6 +139,10 @@ const CompanyIndex = () => {
|
|||||||
|
|
||||||
logger.api.response('DELETE', url, resp.status);
|
logger.api.response('DELETE', url, resp.status);
|
||||||
if (!resp.ok) throw new Error('删除失败');
|
if (!resp.ok) throw new Error('删除失败');
|
||||||
|
|
||||||
|
// 🎯 追踪移除自选
|
||||||
|
trackWatchlistRemoved(stockCode);
|
||||||
|
|
||||||
setIsInWatchlist(false);
|
setIsInWatchlist(false);
|
||||||
toast({ title: '已从自选移除', status: 'info', duration: 1500 });
|
toast({ title: '已从自选移除', status: 'info', duration: 1500 });
|
||||||
} else {
|
} else {
|
||||||
@@ -140,6 +160,10 @@ const CompanyIndex = () => {
|
|||||||
|
|
||||||
logger.api.response('POST', url, resp.status);
|
logger.api.response('POST', url, resp.status);
|
||||||
if (!resp.ok) throw new Error('添加失败');
|
if (!resp.ok) throw new Error('添加失败');
|
||||||
|
|
||||||
|
// 🎯 追踪加入自选
|
||||||
|
trackWatchlistAdded(stockCode);
|
||||||
|
|
||||||
setIsInWatchlist(true);
|
setIsInWatchlist(true);
|
||||||
toast({ title: '已加入自选', status: 'success', duration: 1500 });
|
toast({ title: '已加入自选', status: 'success', duration: 1500 });
|
||||||
}
|
}
|
||||||
@@ -226,7 +250,18 @@ const CompanyIndex = () => {
|
|||||||
{/* 数据展示区域 */}
|
{/* 数据展示区域 */}
|
||||||
<Card bg={bgColor} shadow="lg">
|
<Card bg={bgColor} shadow="lg">
|
||||||
<CardBody p={0}>
|
<CardBody p={0}>
|
||||||
<Tabs variant="soft-rounded" colorScheme="blue" size="lg">
|
<Tabs
|
||||||
|
variant="soft-rounded"
|
||||||
|
colorScheme="blue"
|
||||||
|
size="lg"
|
||||||
|
index={currentTabIndex}
|
||||||
|
onChange={(index) => {
|
||||||
|
const tabNames = ['公司概览', '股票行情', '财务全景', '盈利预测'];
|
||||||
|
// 🎯 追踪 Tab 切换
|
||||||
|
trackTabChanged(index, tabNames[index], currentTabIndex);
|
||||||
|
setCurrentTabIndex(index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TabList p={4} bg={tabBg}>
|
<TabList p={4} bg={tabBg}>
|
||||||
<Tab
|
<Tab
|
||||||
_selected={{
|
_selected={{
|
||||||
|
|||||||
Reference in New Issue
Block a user