Files
vf_react/src/views/Community/index.js
zdl 92d6751529 feat: 在 DynamicNewsCard 头部集成搜索和筛选功能
功能新增:
- 将 UnifiedSearchBox 组件集成到 DynamicNewsCard 的 CardHeader 中
- 实现 DynamicNewsCard 和 EventTimelineCard 共享筛选状态
- 用户可在动态新闻区域直接进行搜索和筛选操作

组件修改:
- DynamicNewsCard.js:
  * 导入 UnifiedSearchBox 组件
  * 添加 filters, popularKeywords, onSearch, onSearchFocus 等 props
  * 在 CardHeader 内部渲染搜索框(标题下方,mt={4})
- Community/index.js:
  * 向 DynamicNewsCard 传递筛选状态和回调函数
  * filters 和 popularKeywords 数据传递
  * updateFilters 和 scrollToTimeline 回调传递

布局结构:
CardHeader
├─ 第一行:标题、徽章、更新时间
└─ 第二行:UnifiedSearchBox(搜索框 + 热门概念 + 筛选器)

状态管理:
- 使用共享的 filters 状态(来自 useEventFilters hook)
- 搜索操作通过 updateFilters 回调同步到父组件
- 两个组件的筛选条件保持一致

用户体验提升:
- 用户无需滚动到页面底部即可进行搜索
- 动态新闻区域功能更完整和独立
- 搜索结果在两个组件间同步显示

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 12:49:58 +08:00

230 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/views/Community/index.js
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import {
fetchPopularKeywords,
fetchHotEvents,
fetchDynamicNews,
selectDynamicNewsWithLoading
} from '../../store/slices/communityDataSlice';
import {
Box,
Container,
useColorModeValue,
} from '@chakra-ui/react';
// 导入组件
import EventTimelineCard from './components/EventTimelineCard';
import DynamicNewsCard from './components/DynamicNewsCard';
import MarketReviewCard from './components/MarketReviewCard';
import HotEventsSection from './components/HotEventsSection';
import EventModals from './components/EventModals';
// 导入自定义 Hooks
import { useEventData } from './hooks/useEventData';
import { useEventFilters } from './hooks/useEventFilters';
import { useCommunityEvents } from './hooks/useCommunityEvents';
// 导入时间工具函数
import {
getCurrentTradingTimeRange,
getMarketReviewTimeRange,
filterEventsByTimeRange
} from '../../utils/tradingTimeUtils';
import { logger } from '../../utils/logger';
import { useNotification } from '../../contexts/NotificationContext';
import { usePostHogTrack } from '../../hooks/usePostHogRedux';
import { RETENTION_EVENTS } from '../../lib/constants';
// 导航栏已由 MainLayout 提供,无需在此导入
const Community = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const { track } = usePostHogTrack(); // PostHog 追踪(保留用于兼容)
// Redux状态
const { popularKeywords, hotEvents } = useSelector(state => state.communityData);
const {
data: dynamicNewsEvents,
loading: dynamicNewsLoading,
error: dynamicNewsError,
pagination: dynamicNewsPagination
} = useSelector(selectDynamicNewsWithLoading);
// Chakra UI hooks
const bgColor = useColorModeValue('gray.50', 'gray.900');
// Ref用于滚动到实时事件时间轴
const eventTimelineRef = useRef(null);
const hasScrolledRef = useRef(false); // 标记是否已滚动
// ⚡ 通知权限引导
const { showCommunityGuide } = useNotification();
// Modal/Drawer状态
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,
onEventClick: (event) => setSelectedEventForStock(event),
eventTimelineRef
});
const { events, pagination, loading, lastUpdateTime } = useEventData(filters);
// 计算市场复盘的时间范围和过滤后的事件
const marketReviewData = useMemo(() => {
const timeRange = getMarketReviewTimeRange();
const filteredEvents = filterEventsByTimeRange(events, timeRange.startTime, timeRange.endTime);
logger.debug('Community', '市场复盘时间范围', {
description: timeRange.description,
rangeType: timeRange.rangeType,
eventCount: filteredEvents.length
});
return {
events: filteredEvents,
timeRange
};
}, [events]);
// 加载热门关键词、热点事件和动态新闻使用Redux
useEffect(() => {
dispatch(fetchPopularKeywords());
dispatch(fetchHotEvents());
dispatch(fetchDynamicNews());
}, [dispatch]);
// 每5分钟刷新一次动态新闻
useEffect(() => {
const interval = setInterval(() => {
dispatch(fetchDynamicNews());
}, 5 * 60 * 1000);
return () => clearInterval(interval);
}, [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(() => {
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]);
// ⚡ 首次访问社区时,延迟显示权限引导
useEffect(() => {
if (showCommunityGuide) {
const timer = setTimeout(() => {
logger.info('Community', '显示社区权限引导');
showCommunityGuide();
}, 5000); // 延迟 5 秒,让用户先浏览页面
return () => clearTimeout(timer);
}
}, [showCommunityGuide]); // 只在组件挂载时执行一次
// ⚡ 滚动到实时事件区域(由搜索框聚焦触发)
const scrollToTimeline = useCallback(() => {
if (!hasScrolledRef.current && eventTimelineRef.current) {
eventTimelineRef.current.scrollIntoView({
behavior: 'smooth', // 平滑滚动动画
block: 'start', // 元素顶部对齐视口顶部,标题正好可见
inline: 'nearest' // 水平方向最小滚动
});
hasScrolledRef.current = true; // 标记已滚动
logger.debug('Community', '用户触发搜索,滚动到实时事件时间轴');
}
}, []);
return (
<Box minH="100vh" bg={bgColor}>
{/* 主内容区域 */}
<Container maxW="container.xl" pt={6} pb={8}>
{/* 热点事件区域 */}
<HotEventsSection events={hotEvents} />
{/* 实时要闻·动态追踪 - 横向滚动 */}
<DynamicNewsCard
mt={6}
events={dynamicNewsEvents}
loading={dynamicNewsLoading}
pagination={dynamicNewsPagination}
filters={filters}
popularKeywords={popularKeywords}
lastUpdateTime={lastUpdateTime}
onSearch={updateFilters}
onSearchFocus={scrollToTimeline}
onEventClick={handleEventClick}
onViewDetail={handleViewDetail}
/>
{/* 市场复盘 - 左右布局 */}
{/* <MarketReviewCard
mt={6}
events={marketReviewData.events}
loading={loading}
lastUpdateTime={lastUpdateTime}
onEventClick={handleEventClick}
onViewDetail={handleViewDetail}
onToggleFollow={() => {}}
/> */}
{/* 实时事件 - 原纵向列表 */}
{/* <EventTimelineCard
ref={eventTimelineRef}
mt={6}
events={events}
loading={loading}
pagination={pagination}
filters={filters}
popularKeywords={popularKeywords}
lastUpdateTime={lastUpdateTime}
onSearch={updateFilters}
onSearchFocus={scrollToTimeline}
onPageChange={handlePageChange}
onEventClick={handleEventClick}
onViewDetail={handleViewDetail}
/> */}
</Container>
{/* 事件弹窗 */}
<EventModals
eventModalState={{
isOpen: !!selectedEvent,
onClose: () => setSelectedEvent(null),
event: selectedEvent,
onEventClose: () => setSelectedEvent(null)
}}
stockDrawerState={{
visible: !!selectedEventForStock,
event: selectedEventForStock,
onClose: () => setSelectedEventForStock(null)
}}
/>
</Box>
);
};
export default Community;