From 78e4b8f696758e1871bb18692203c332ee546fa8 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Wed, 29 Oct 2025 11:48:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Retention=EF=BC=88=E7=95=99=E5=AD=98?= =?UTF-8?q?=EF=BC=89=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 最受欢迎的功能 - 哪些功能用户使用最频繁? - 新闻、事件、个股、模拟盘的使用对比 2. 用户行为路径 - 用户从哪里进入? - 在每个页面停留多久? - 从哪个环节流失? 3. 内容偏好 - 什么类型的新闻最受欢迎? - 用户关注哪些行业? - 哪些事件获得最多关注? Revenue(收入)转化 1. 付费转化漏斗 个人中心查看 → 自选股/关注事件使用 → 订阅页面查看 → 升级按钮点击 → (付费转化) 2. 模拟盘转化分析 模拟盘进入 → 搜索股票 → 下单操作 → 持续使用 → (付费转化) --- src/views/Community/index.js | 31 +++++++++++++++++++++------ src/views/Dashboard/Center.js | 32 +++++++++++++++++++++++++--- src/views/EventDetail/index.js | 24 +++++++++++++++++++++ src/views/TradingSimulation/index.js | 10 +++++++++ 4 files changed, 87 insertions(+), 10 deletions(-) diff --git a/src/views/Community/index.js b/src/views/Community/index.js index 16436fe1..0739f7d5 100644 --- a/src/views/Community/index.js +++ b/src/views/Community/index.js @@ -17,6 +17,7 @@ import EventModals from './components/EventModals'; // 导入自定义 Hooks import { useEventData } from './hooks/useEventData'; import { useEventFilters } from './hooks/useEventFilters'; +import { useCommunityEvents } from './hooks/useCommunityEvents'; import { logger } from '../../utils/logger'; import { useNotification } from '../../contexts/NotificationContext'; @@ -28,7 +29,7 @@ import { RETENTION_EVENTS } from '../../lib/constants'; const Community = () => { const navigate = useNavigate(); const dispatch = useDispatch(); - const { track } = usePostHogTrack(); // PostHog 追踪 + const { track } = usePostHogTrack(); // PostHog 追踪(保留用于兼容) // Redux状态 const { popularKeywords, hotEvents } = useSelector(state => state.communityData); @@ -47,6 +48,9 @@ const Community = () => { const [selectedEvent, setSelectedEvent] = useState(null); const [selectedEventForStock, setSelectedEventForStock] = useState(null); + // 🎯 初始化Community埋点Hook + const communityEvents = useCommunityEvents({ navigate }); + // 自定义 Hooks const { filters, updateFilters, handlePageChange, handleEventClick, handleViewDetail } = useEventFilters({ navigate, @@ -63,13 +67,26 @@ const Community = () => { }, [dispatch]); // 🎯 PostHog 追踪:页面浏览 + // useEffect(() => { + // track(RETENTION_EVENTS.COMMUNITY_PAGE_VIEWED, { + // timestamp: new Date().toISOString(), + // has_hot_events: hotEvents && hotEvents.length > 0, + // has_keywords: popularKeywords && popularKeywords.length > 0, + // }); + // }, [track]); // 只在组件挂载时执行一次 + + // 🎯 追踪新闻列表查看(当事件列表加载完成后) useEffect(() => { - track(RETENTION_EVENTS.COMMUNITY_PAGE_VIEWED, { - timestamp: new Date().toISOString(), - has_hot_events: hotEvents && hotEvents.length > 0, - has_keywords: popularKeywords && popularKeywords.length > 0, - }); - }, [track]); // 只在组件挂载时执行一次 + if (events && events.length > 0 && !loading) { + communityEvents.trackNewsListViewed({ + totalCount: pagination?.total || events.length, + sortBy: filters.sort, + importance: filters.importance, + dateRange: filters.date_range, + industryFilter: filters.industry_code, + }); + } + }, [events, loading, pagination, filters, communityEvents]); // ⚡ 首次访问社区时,延迟显示权限引导 useEffect(() => { diff --git a/src/views/Dashboard/Center.js b/src/views/Dashboard/Center.js index 3eca3e4a..43449784 100644 --- a/src/views/Dashboard/Center.js +++ b/src/views/Dashboard/Center.js @@ -2,6 +2,7 @@ import React, { useEffect, useState, useCallback } from 'react'; import { logger } from '../../utils/logger'; import { getApiBase } from '../../utils/apiConfig'; +import { useDashboardEvents } from '../../hooks/useDashboardEvents'; import { Box, Flex, @@ -72,6 +73,12 @@ export default function CenterDashboard() { const userId = user?.id; const prevUserIdRef = React.useRef(userId); + // 🎯 初始化Dashboard埋点Hook + const dashboardEvents = useDashboardEvents({ + pageType: 'center', + navigate + }); + // 颜色主题 const textColor = useColorModeValue('gray.700', 'white'); const borderColor = useColorModeValue('gray.200', 'gray.600'); @@ -101,14 +108,33 @@ export default function CenterDashboard() { const je = await e.json(); const jc = await c.json(); if (jw.success) { - setWatchlist(Array.isArray(jw.data) ? jw.data : []); + const watchlistData = Array.isArray(jw.data) ? jw.data : []; + setWatchlist(watchlistData); + + // 🎯 追踪自选股列表查看 + if (watchlistData.length > 0) { + dashboardEvents.trackWatchlistViewed(watchlistData.length, true); + } + // 加载实时行情 if (jw.data && jw.data.length > 0) { loadRealtimeQuotes(); } } - if (je.success) setFollowingEvents(Array.isArray(je.data) ? je.data : []); - if (jc.success) setEventComments(Array.isArray(jc.data) ? jc.data : []); + if (je.success) { + const eventsData = Array.isArray(je.data) ? je.data : []; + setFollowingEvents(eventsData); + + // 🎯 追踪关注的事件列表查看 + dashboardEvents.trackFollowingEventsViewed(eventsData.length); + } + if (jc.success) { + const commentsData = Array.isArray(jc.data) ? jc.data : []; + setEventComments(commentsData); + + // 🎯 追踪评论列表查看 + dashboardEvents.trackCommentsViewed(commentsData.length); + } } catch (err) { logger.error('Center', 'loadData', err, { userId, diff --git a/src/views/EventDetail/index.js b/src/views/EventDetail/index.js index 7c6ea891..2a5ddbc3 100644 --- a/src/views/EventDetail/index.js +++ b/src/views/EventDetail/index.js @@ -75,6 +75,7 @@ import TransmissionChainAnalysis from './components/TransmissionChainAnalysis'; import { eventService } from '../../services/eventService'; import { debugEventService } from '../../utils/debugEventService'; import { logger } from '../../utils/logger'; +import { useEventDetailEvents } from './hooks/useEventDetailEvents'; // 临时调试代码 - 生产环境测试后请删除 if (typeof window !== 'undefined') { @@ -348,6 +349,15 @@ const EventDetail = () => { const [postsLoading, setPostsLoading] = useState(false); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState(0); + + // 🎯 初始化事件详情埋点Hook(传入event对象) + const eventEvents = useEventDetailEvents({ + event: eventData ? { + id: eventData.id, + title: eventData.title, + importance: eventData.importance + } : null + }); const [newPostContent, setNewPostContent] = useState(''); const [newPostTitle, setNewPostTitle] = useState(''); const [submitting, setSubmitting] = useState(false); @@ -380,9 +390,11 @@ const EventDetail = () => { setEventData(eventResponse.data); // 总是尝试加载相关股票(权限在组件内部检查) + let stocksCount = 0; try { const stocksResponse = await eventService.getRelatedStocks(actualEventId); setRelatedStocks(stocksResponse.data || []); + stocksCount = stocksResponse.data?.length || 0; } catch (e) { logger.warn('EventDetail', '加载相关股票失败', { eventId: actualEventId, error: e.message }); setRelatedStocks([]); @@ -399,13 +411,25 @@ const EventDetail = () => { } // 历史事件所有用户都可以访问,但免费用户只看到前2条 + let timelineCount = 0; try { const eventsResponse = await eventService.getHistoricalEvents(actualEventId); setHistoricalEvents(eventsResponse.data || []); + timelineCount = eventsResponse.data?.length || 0; } catch (e) { logger.warn('EventDetail', '历史事件加载失败', { eventId: actualEventId, error: e.message }); } + // 🎯 追踪事件分析内容查看(数据加载完成后) + if (eventResponse.data && eventEvents) { + eventEvents.trackEventAnalysisViewed({ + type: 'overview', + relatedStockCount: stocksCount, + timelineEventCount: timelineCount, + marketImpact: eventResponse.data.market_impact + }); + } + } catch (err) { logger.error('EventDetail', 'loadEventData', err, { eventId: actualEventId }); setError(err.message || '加载事件数据失败'); diff --git a/src/views/TradingSimulation/index.js b/src/views/TradingSimulation/index.js index 22058ab9..a8a60eb7 100644 --- a/src/views/TradingSimulation/index.js +++ b/src/views/TradingSimulation/index.js @@ -49,6 +49,7 @@ import LineChart from '../../components/Charts/LineChart'; // 模拟盘账户管理 Hook import { useTradingAccount } from './hooks/useTradingAccount'; +import { useTradingSimulationEvents } from './hooks/useTradingSimulationEvents'; export default function TradingSimulation() { // ========== 1. 所有 Hooks 必须放在最顶部,不能有任何条件判断 ========== @@ -76,6 +77,15 @@ export default function TradingSimulation() { getAssetHistory } = useTradingAccount(); + // 🎯 初始化模拟盘埋点Hook(传入账户信息) + const tradingEvents = useTradingSimulationEvents({ + portfolio: account ? { + totalValue: account.total_assets, + availableCash: account.available_cash, + holdingsCount: positions?.length || 0 + } : null + }); + // 所有的 useColorModeValue 也必须在顶部 const bgColor = useColorModeValue('gray.50', 'gray.900'); const cardBg = useColorModeValue('white', 'gray.800');