feat: Concept 页面 - 9个事件搜索、筛选、概念交互、个股查看、时间轴、视图切换
新建文件:
- src/views/Concept/hooks/useConceptEvents.js (203行)
- 提供8个追踪函数
- 页面浏览自动追踪
- 完整的事件属性定义
修改文件:
- src/views/Concept/index.js
- 添加 useConceptEvents Hook
- 集成追踪到9个关键函数:
i. handleSearch - 搜索查询
ii. handleSortChange - 排序变化
iii. handleDateChange - 日期变化
iv. handlePageChange - 翻页
v. handleConceptClick - 概念点击(传递位置)
vi. handleViewStocks - 查看个股
vii. handleViewContent - 历史时间轴
viii. 视图切换按钮 - 网格/列表切换
ix. ConceptCard/ConceptListItem - 位置追踪
追踪事件: 9个
1. CONCEPT_CENTER_VIEWED - 页面浏览
2. SEARCH_QUERY_SUBMITTED - 搜索查询
3. SEARCH_FILTER_APPLIED - 筛选(sort/date)
4. CONCEPT_CLICKED - 概念点击(含位置)
5. CONCEPT_STOCKS_VIEWED - 查看个股
6. CONCEPT_STOCK_CLICKED - 股票点击
7. CONCEPT_TIMELINE_VIEWED - 历史时间轴
8. NEWS_LIST_VIEWED - 翻页(复用)
9. VIEW_MODE_CHANGED - 视图切换
This commit is contained in:
@@ -90,6 +90,8 @@ import { useSubscription } from '../../hooks/useSubscription';
|
||||
import SubscriptionUpgradeModal from '../../components/SubscriptionUpgradeModal';
|
||||
// 导入市场服务
|
||||
import { marketService } from '../../services/marketService';
|
||||
// 导入 PostHog 追踪 Hook
|
||||
import { useConceptEvents } from './hooks/useConceptEvents';
|
||||
|
||||
const API_BASE_URL = process.env.NODE_ENV === 'production'
|
||||
? '/concept-api'
|
||||
@@ -129,6 +131,18 @@ const ConceptCenter = () => {
|
||||
const navigate = useNavigate();
|
||||
const toast = useToast();
|
||||
|
||||
// 🎯 PostHog 事件追踪
|
||||
const {
|
||||
trackConceptSearched,
|
||||
trackFilterApplied,
|
||||
trackConceptClicked,
|
||||
trackConceptStocksViewed,
|
||||
trackConceptStockClicked,
|
||||
trackConceptTimelineViewed,
|
||||
trackPageChange,
|
||||
trackViewModeChanged,
|
||||
} = useConceptEvents({ navigate });
|
||||
|
||||
// 订阅权限管理
|
||||
const { hasFeatureAccess, getUpgradeRecommendation } = useSubscription();
|
||||
const [upgradeModalOpen, setUpgradeModalOpen] = useState(false);
|
||||
@@ -192,6 +206,9 @@ const ConceptCenter = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 🎯 追踪历史时间轴查看
|
||||
trackConceptTimelineViewed(conceptName, conceptId);
|
||||
|
||||
setSelectedConceptForContent(conceptName);
|
||||
setSelectedConceptId(conceptId);
|
||||
setIsTimelineModalOpen(true);
|
||||
@@ -318,8 +335,14 @@ const ConceptCenter = () => {
|
||||
setSortBy('change_pct');
|
||||
}
|
||||
|
||||
// 🎯 追踪搜索查询(在fetchConcepts后追踪结果数量)
|
||||
updateUrlParams({ q: searchQuery, page: 1, sort: newSortBy });
|
||||
fetchConcepts(searchQuery, 1, selectedDate, newSortBy);
|
||||
fetchConcepts(searchQuery, 1, selectedDate, newSortBy).then(() => {
|
||||
if (searchQuery && searchQuery.trim() !== '') {
|
||||
// 使用当前 concepts.length 作为结果数量
|
||||
setTimeout(() => trackConceptSearched(searchQuery, concepts.length), 100);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 处理Enter键搜索
|
||||
@@ -331,6 +354,11 @@ const ConceptCenter = () => {
|
||||
|
||||
// 处理排序变化
|
||||
const handleSortChange = (value) => {
|
||||
const previousSort = sortBy;
|
||||
|
||||
// 🎯 追踪排序变化
|
||||
trackFilterApplied('sort', value, previousSort);
|
||||
|
||||
setSortBy(value);
|
||||
setCurrentPage(1);
|
||||
updateUrlParams({ sort: value, page: 1 });
|
||||
@@ -340,6 +368,11 @@ const ConceptCenter = () => {
|
||||
// 处理日期变化
|
||||
const handleDateChange = (e) => {
|
||||
const date = new Date(e.target.value);
|
||||
const previousDate = selectedDate ? selectedDate.toISOString().split('T')[0] : null;
|
||||
|
||||
// 🎯 追踪日期变化
|
||||
trackFilterApplied('date', e.target.value, previousDate);
|
||||
|
||||
setSelectedDate(date);
|
||||
setCurrentPage(1);
|
||||
updateUrlParams({ date: e.target.value, page: 1 });
|
||||
@@ -359,6 +392,9 @@ const ConceptCenter = () => {
|
||||
|
||||
// 处理页码变化
|
||||
const handlePageChange = (page) => {
|
||||
// 🎯 追踪翻页
|
||||
trackPageChange(page, { sort: sortBy, q: searchQuery, date: selectedDate?.toISOString().split('T')[0] });
|
||||
|
||||
setCurrentPage(page);
|
||||
updateUrlParams({ page });
|
||||
fetchConcepts(searchQuery, page, selectedDate, sortBy);
|
||||
@@ -366,7 +402,12 @@ const ConceptCenter = () => {
|
||||
};
|
||||
|
||||
// 处理概念点击
|
||||
const handleConceptClick = (conceptId, conceptName) => {
|
||||
const handleConceptClick = (conceptId, conceptName, concept = null, position = 0) => {
|
||||
// 🎯 追踪概念点击
|
||||
if (concept) {
|
||||
trackConceptClicked(concept, position);
|
||||
}
|
||||
|
||||
const htmlPath = `https://valuefrontier.cn/htmls/${encodeURIComponent(conceptName)}.html`;
|
||||
window.open(htmlPath, '_blank');
|
||||
};
|
||||
@@ -433,6 +474,9 @@ const ConceptCenter = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 🎯 追踪查看个股
|
||||
trackConceptStocksViewed(concept.concept, concept.stocks?.length || 0);
|
||||
|
||||
setSelectedConceptStocks(concept.stocks || []);
|
||||
setSelectedConceptName(concept.concept);
|
||||
setStockMarketData({}); // 清空之前的数据
|
||||
@@ -649,7 +693,7 @@ const ConceptCenter = () => {
|
||||
}, []);
|
||||
|
||||
// 概念卡片组件 - 优化版
|
||||
const ConceptCard = ({ concept }) => {
|
||||
const ConceptCard = ({ concept, position = 0 }) => {
|
||||
const changePercent = concept.price_info?.avg_change_pct;
|
||||
const changeColor = getChangeColor(changePercent);
|
||||
const hasChange = changePercent !== null && changePercent !== undefined;
|
||||
@@ -657,7 +701,7 @@ const ConceptCenter = () => {
|
||||
return (
|
||||
<Card
|
||||
cursor="pointer"
|
||||
onClick={() => handleConceptClick(concept.concept_id, concept.concept)}
|
||||
onClick={() => handleConceptClick(concept.concept_id, concept.concept, concept, position)}
|
||||
bg="white"
|
||||
borderWidth="1px"
|
||||
borderColor="gray.200"
|
||||
@@ -857,7 +901,7 @@ const ConceptCenter = () => {
|
||||
};
|
||||
|
||||
// 概念列表项组件 - 列表视图
|
||||
const ConceptListItem = ({ concept }) => {
|
||||
const ConceptListItem = ({ concept, position = 0 }) => {
|
||||
const changePercent = concept.price_info?.avg_change_pct;
|
||||
const changeColor = getChangeColor(changePercent);
|
||||
const hasChange = changePercent !== null && changePercent !== undefined;
|
||||
@@ -865,7 +909,7 @@ const ConceptCenter = () => {
|
||||
return (
|
||||
<Card
|
||||
cursor="pointer"
|
||||
onClick={() => handleConceptClick(concept.concept_id, concept.concept)}
|
||||
onClick={() => handleConceptClick(concept.concept_id, concept.concept, concept, position)}
|
||||
bg="white"
|
||||
borderWidth="1px"
|
||||
borderColor="gray.200"
|
||||
@@ -1361,7 +1405,12 @@ const ConceptCenter = () => {
|
||||
<ButtonGroup size="sm" isAttached variant="outline">
|
||||
<IconButton
|
||||
icon={<FaThLarge />}
|
||||
onClick={() => setViewMode('grid')}
|
||||
onClick={() => {
|
||||
if (viewMode !== 'grid') {
|
||||
trackViewModeChanged('grid', viewMode);
|
||||
setViewMode('grid');
|
||||
}
|
||||
}}
|
||||
bg={viewMode === 'grid' ? 'purple.500' : 'transparent'}
|
||||
color={viewMode === 'grid' ? 'white' : 'purple.500'}
|
||||
borderColor="purple.500"
|
||||
@@ -1370,7 +1419,12 @@ const ConceptCenter = () => {
|
||||
/>
|
||||
<IconButton
|
||||
icon={<FaList />}
|
||||
onClick={() => setViewMode('list')}
|
||||
onClick={() => {
|
||||
if (viewMode !== 'list') {
|
||||
trackViewModeChanged('list', viewMode);
|
||||
setViewMode('list');
|
||||
}
|
||||
}}
|
||||
bg={viewMode === 'list' ? 'purple.500' : 'transparent'}
|
||||
color={viewMode === 'list' ? 'white' : 'purple.500'}
|
||||
borderColor="purple.500"
|
||||
@@ -1404,16 +1458,16 @@ const ConceptCenter = () => {
|
||||
<>
|
||||
{viewMode === 'grid' ? (
|
||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={6} className="concept-grid">
|
||||
{concepts.map((concept) => (
|
||||
{concepts.map((concept, index) => (
|
||||
<Box key={concept.concept_id} className="concept-item" role="group">
|
||||
<ConceptCard concept={concept} />
|
||||
<ConceptCard concept={concept} position={index} />
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
) : (
|
||||
<VStack spacing={4} align="stretch" className="concept-list">
|
||||
{concepts.map((concept) => (
|
||||
<ConceptListItem key={concept.concept_id} concept={concept} />
|
||||
{concepts.map((concept, index) => (
|
||||
<ConceptListItem key={concept.concept_id} concept={concept} position={index} />
|
||||
))}
|
||||
</VStack>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user