feat: Retention(留存)分析
1. 最受欢迎的功能
- 哪些功能用户使用最频繁?
- 新闻、事件、个股、模拟盘的使用对比
2. 用户行为路径
- 用户从哪里进入?
- 在每个页面停留多久?
- 从哪个环节流失?
3. 内容偏好
- 什么类型的新闻最受欢迎?
- 用户关注哪些行业?
- 哪些事件获得最多关注?
Revenue(收入)转化
1. 付费转化漏斗
个人中心查看 →
自选股/关注事件使用 →
订阅页面查看 →
升级按钮点击 →
(付费转化)
2. 模拟盘转化分析
模拟盘进入 →
搜索股票 →
下单操作 →
持续使用 →
(付费转化)
This commit is contained in:
@@ -17,6 +17,7 @@ import EventModals from './components/EventModals';
|
|||||||
// 导入自定义 Hooks
|
// 导入自定义 Hooks
|
||||||
import { useEventData } from './hooks/useEventData';
|
import { useEventData } from './hooks/useEventData';
|
||||||
import { useEventFilters } from './hooks/useEventFilters';
|
import { useEventFilters } from './hooks/useEventFilters';
|
||||||
|
import { useCommunityEvents } from './hooks/useCommunityEvents';
|
||||||
|
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
import { useNotification } from '../../contexts/NotificationContext';
|
import { useNotification } from '../../contexts/NotificationContext';
|
||||||
@@ -28,7 +29,7 @@ import { RETENTION_EVENTS } from '../../lib/constants';
|
|||||||
const Community = () => {
|
const Community = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { track } = usePostHogTrack(); // PostHog 追踪
|
const { track } = usePostHogTrack(); // PostHog 追踪(保留用于兼容)
|
||||||
|
|
||||||
// Redux状态
|
// Redux状态
|
||||||
const { popularKeywords, hotEvents } = useSelector(state => state.communityData);
|
const { popularKeywords, hotEvents } = useSelector(state => state.communityData);
|
||||||
@@ -47,6 +48,9 @@ const Community = () => {
|
|||||||
const [selectedEvent, setSelectedEvent] = useState(null);
|
const [selectedEvent, setSelectedEvent] = useState(null);
|
||||||
const [selectedEventForStock, setSelectedEventForStock] = useState(null);
|
const [selectedEventForStock, setSelectedEventForStock] = useState(null);
|
||||||
|
|
||||||
|
// 🎯 初始化Community埋点Hook
|
||||||
|
const communityEvents = useCommunityEvents({ navigate });
|
||||||
|
|
||||||
// 自定义 Hooks
|
// 自定义 Hooks
|
||||||
const { filters, updateFilters, handlePageChange, handleEventClick, handleViewDetail } = useEventFilters({
|
const { filters, updateFilters, handlePageChange, handleEventClick, handleViewDetail } = useEventFilters({
|
||||||
navigate,
|
navigate,
|
||||||
@@ -63,13 +67,26 @@ const Community = () => {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
// 🎯 PostHog 追踪:页面浏览
|
// 🎯 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(() => {
|
useEffect(() => {
|
||||||
track(RETENTION_EVENTS.COMMUNITY_PAGE_VIEWED, {
|
if (events && events.length > 0 && !loading) {
|
||||||
timestamp: new Date().toISOString(),
|
communityEvents.trackNewsListViewed({
|
||||||
has_hot_events: hotEvents && hotEvents.length > 0,
|
totalCount: pagination?.total || events.length,
|
||||||
has_keywords: popularKeywords && popularKeywords.length > 0,
|
sortBy: filters.sort,
|
||||||
|
importance: filters.importance,
|
||||||
|
dateRange: filters.date_range,
|
||||||
|
industryFilter: filters.industry_code,
|
||||||
});
|
});
|
||||||
}, [track]); // 只在组件挂载时执行一次
|
}
|
||||||
|
}, [events, loading, pagination, filters, communityEvents]);
|
||||||
|
|
||||||
// ⚡ 首次访问社区时,延迟显示权限引导
|
// ⚡ 首次访问社区时,延迟显示权限引导
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
import { getApiBase } from '../../utils/apiConfig';
|
import { getApiBase } from '../../utils/apiConfig';
|
||||||
|
import { useDashboardEvents } from '../../hooks/useDashboardEvents';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Flex,
|
Flex,
|
||||||
@@ -72,6 +73,12 @@ export default function CenterDashboard() {
|
|||||||
const userId = user?.id;
|
const userId = user?.id;
|
||||||
const prevUserIdRef = React.useRef(userId);
|
const prevUserIdRef = React.useRef(userId);
|
||||||
|
|
||||||
|
// 🎯 初始化Dashboard埋点Hook
|
||||||
|
const dashboardEvents = useDashboardEvents({
|
||||||
|
pageType: 'center',
|
||||||
|
navigate
|
||||||
|
});
|
||||||
|
|
||||||
// 颜色主题
|
// 颜色主题
|
||||||
const textColor = useColorModeValue('gray.700', 'white');
|
const textColor = useColorModeValue('gray.700', 'white');
|
||||||
const borderColor = useColorModeValue('gray.200', 'gray.600');
|
const borderColor = useColorModeValue('gray.200', 'gray.600');
|
||||||
@@ -101,14 +108,33 @@ export default function CenterDashboard() {
|
|||||||
const je = await e.json();
|
const je = await e.json();
|
||||||
const jc = await c.json();
|
const jc = await c.json();
|
||||||
if (jw.success) {
|
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) {
|
if (jw.data && jw.data.length > 0) {
|
||||||
loadRealtimeQuotes();
|
loadRealtimeQuotes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (je.success) setFollowingEvents(Array.isArray(je.data) ? je.data : []);
|
if (je.success) {
|
||||||
if (jc.success) setEventComments(Array.isArray(jc.data) ? jc.data : []);
|
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) {
|
} catch (err) {
|
||||||
logger.error('Center', 'loadData', err, {
|
logger.error('Center', 'loadData', err, {
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ import TransmissionChainAnalysis from './components/TransmissionChainAnalysis';
|
|||||||
import { eventService } from '../../services/eventService';
|
import { eventService } from '../../services/eventService';
|
||||||
import { debugEventService } from '../../utils/debugEventService';
|
import { debugEventService } from '../../utils/debugEventService';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
|
import { useEventDetailEvents } from './hooks/useEventDetailEvents';
|
||||||
|
|
||||||
// 临时调试代码 - 生产环境测试后请删除
|
// 临时调试代码 - 生产环境测试后请删除
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -348,6 +349,15 @@ const EventDetail = () => {
|
|||||||
const [postsLoading, setPostsLoading] = useState(false);
|
const [postsLoading, setPostsLoading] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
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 [newPostContent, setNewPostContent] = useState('');
|
||||||
const [newPostTitle, setNewPostTitle] = useState('');
|
const [newPostTitle, setNewPostTitle] = useState('');
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
@@ -380,9 +390,11 @@ const EventDetail = () => {
|
|||||||
setEventData(eventResponse.data);
|
setEventData(eventResponse.data);
|
||||||
|
|
||||||
// 总是尝试加载相关股票(权限在组件内部检查)
|
// 总是尝试加载相关股票(权限在组件内部检查)
|
||||||
|
let stocksCount = 0;
|
||||||
try {
|
try {
|
||||||
const stocksResponse = await eventService.getRelatedStocks(actualEventId);
|
const stocksResponse = await eventService.getRelatedStocks(actualEventId);
|
||||||
setRelatedStocks(stocksResponse.data || []);
|
setRelatedStocks(stocksResponse.data || []);
|
||||||
|
stocksCount = stocksResponse.data?.length || 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn('EventDetail', '加载相关股票失败', { eventId: actualEventId, error: e.message });
|
logger.warn('EventDetail', '加载相关股票失败', { eventId: actualEventId, error: e.message });
|
||||||
setRelatedStocks([]);
|
setRelatedStocks([]);
|
||||||
@@ -399,13 +411,25 @@ const EventDetail = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 历史事件所有用户都可以访问,但免费用户只看到前2条
|
// 历史事件所有用户都可以访问,但免费用户只看到前2条
|
||||||
|
let timelineCount = 0;
|
||||||
try {
|
try {
|
||||||
const eventsResponse = await eventService.getHistoricalEvents(actualEventId);
|
const eventsResponse = await eventService.getHistoricalEvents(actualEventId);
|
||||||
setHistoricalEvents(eventsResponse.data || []);
|
setHistoricalEvents(eventsResponse.data || []);
|
||||||
|
timelineCount = eventsResponse.data?.length || 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn('EventDetail', '历史事件加载失败', { eventId: actualEventId, error: e.message });
|
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) {
|
} catch (err) {
|
||||||
logger.error('EventDetail', 'loadEventData', err, { eventId: actualEventId });
|
logger.error('EventDetail', 'loadEventData', err, { eventId: actualEventId });
|
||||||
setError(err.message || '加载事件数据失败');
|
setError(err.message || '加载事件数据失败');
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import LineChart from '../../components/Charts/LineChart';
|
|||||||
|
|
||||||
// 模拟盘账户管理 Hook
|
// 模拟盘账户管理 Hook
|
||||||
import { useTradingAccount } from './hooks/useTradingAccount';
|
import { useTradingAccount } from './hooks/useTradingAccount';
|
||||||
|
import { useTradingSimulationEvents } from './hooks/useTradingSimulationEvents';
|
||||||
|
|
||||||
export default function TradingSimulation() {
|
export default function TradingSimulation() {
|
||||||
// ========== 1. 所有 Hooks 必须放在最顶部,不能有任何条件判断 ==========
|
// ========== 1. 所有 Hooks 必须放在最顶部,不能有任何条件判断 ==========
|
||||||
@@ -76,6 +77,15 @@ export default function TradingSimulation() {
|
|||||||
getAssetHistory
|
getAssetHistory
|
||||||
} = useTradingAccount();
|
} = useTradingAccount();
|
||||||
|
|
||||||
|
// 🎯 初始化模拟盘埋点Hook(传入账户信息)
|
||||||
|
const tradingEvents = useTradingSimulationEvents({
|
||||||
|
portfolio: account ? {
|
||||||
|
totalValue: account.total_assets,
|
||||||
|
availableCash: account.available_cash,
|
||||||
|
holdingsCount: positions?.length || 0
|
||||||
|
} : null
|
||||||
|
});
|
||||||
|
|
||||||
// 所有的 useColorModeValue 也必须在顶部
|
// 所有的 useColorModeValue 也必须在顶部
|
||||||
const bgColor = useColorModeValue('gray.50', 'gray.900');
|
const bgColor = useColorModeValue('gray.50', 'gray.900');
|
||||||
const cardBg = useColorModeValue('white', 'gray.800');
|
const cardBg = useColorModeValue('white', 'gray.800');
|
||||||
|
|||||||
Reference in New Issue
Block a user