diff --git a/src/views/Community/components/DynamicNewsCard.js b/src/views/Community/components/DynamicNewsCard.js index 2d5406cd..a2c568cc 100644 --- a/src/views/Community/components/DynamicNewsCard.js +++ b/src/views/Community/components/DynamicNewsCard.js @@ -72,8 +72,8 @@ const DynamicNewsCard = forwardRef(({ // 发起 Redux action 获取新页面数据 dispatch(fetchDynamicNews({ page: newPage, per_page: pageSize })); - // 重置选中事件(等新数据加载后自动选中第一个) - setSelectedEvent(null); + // 保持当前选中事件,避免详情面板消失导致页面抖动 + // 新数据加载完成后,useEffect 会自动选中第一个事件 }; return ( @@ -112,27 +112,8 @@ const DynamicNewsCard = forwardRef(({ {/* 主体内容 */} - {/* Loading 状态 */} - {loading && ( -
- - - 正在加载最新事件... - -
- )} - - {/* Empty 状态 */} - {!loading && (!events || events.length === 0) && ( -
- - 暂无事件数据 - -
- )} - - {/* 横向滚动事件列表 */} - {!loading && events && events.length > 0 && ( + {/* 横向滚动事件列表 - 始终渲染(除非为空) */} + {events && events.length > 0 ? ( + ) : !loading ? ( + /* Empty 状态 - 只在非加载且无数据时显示 */ +
+ + 暂无事件数据 + +
+ ) : ( + /* 首次加载状态 */ +
+ + + 正在加载最新事件... + +
)} - {/* 详情面板 */} - {!loading && events && events.length > 0 && ( + {/* 详情面板 - 始终显示(如果有选中事件) */} + {events && events.length > 0 && selectedEvent && ( diff --git a/src/views/Community/components/DynamicNewsCard/EventScrollList.js b/src/views/Community/components/DynamicNewsCard/EventScrollList.js index 78dafbc7..413e2256 100644 --- a/src/views/Community/components/DynamicNewsCard/EventScrollList.js +++ b/src/views/Community/components/DynamicNewsCard/EventScrollList.js @@ -6,6 +6,10 @@ import { Box, Flex, IconButton, + Center, + VStack, + Spinner, + Text, useColorModeValue } from '@chakra-ui/react'; import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons'; @@ -21,6 +25,7 @@ import PaginationControl from './PaginationControl'; * @param {number} currentPage - 当前页码 * @param {number} totalPages - 总页数(由服务端返回) * @param {Function} onPageChange - 页码改变回调 + * @param {boolean} loading - 加载状态 */ const EventScrollList = ({ events, @@ -29,7 +34,8 @@ const EventScrollList = ({ borderColor, currentPage, totalPages, - onPageChange + onPageChange, + loading = false }) => { const scrollContainerRef = useRef(null); const [showLeftArrow, setShowLeftArrow] = useState(false); @@ -89,17 +95,6 @@ const EventScrollList = ({ return ( - {/* 分页控制器 - 右上角 */} - {totalPages > 1 && ( - - - - )} - {/* 横向滚动区域 */} {/* 左侧滚动按钮 */} @@ -149,6 +144,7 @@ const EventScrollList = ({ py={4} px={2} onScroll={handleScroll} + position="relative" css={{ '&::-webkit-scrollbar': { height: '8px', @@ -170,6 +166,29 @@ const EventScrollList = ({ WebkitOverflowScrolling: 'touch', }} > + {/* 加载遮罩 */} + {loading && ( +
+ + + + 加载中... + + +
+ )} + + {/* 事件卡片列表 */} {events.map((event, index) => ( + + {/* 分页控制器 - 右下角 */} + {totalPages > 1 && ( + + + + )}
); }; diff --git a/src/views/Community/hooks/useEventFilters.js b/src/views/Community/hooks/useEventFilters.js index e65401fd..aec3fdd8 100644 --- a/src/views/Community/hooks/useEventFilters.js +++ b/src/views/Community/hooks/useEventFilters.js @@ -102,17 +102,7 @@ export const useEventFilters = ({ navigate, onEventClick, eventTimelineRef } = { // 保持现有筛选条件,只更新页码 updateFilters({ ...filters, page }); - - // 滚动到实时事件时间轴(平滑滚动) - if (eventTimelineRef && eventTimelineRef.current) { - setTimeout(() => { - eventTimelineRef.current.scrollIntoView({ - behavior: 'smooth', // 平滑滚动 - block: 'start' // 滚动到元素顶部 - }); - }, 100); // 延迟100ms,确保DOM更新 - } - }, [filters, updateFilters, eventTimelineRef, track]); + }, [filters, updateFilters, track]); // 处理事件点击 const handleEventClick = useCallback((event) => { diff --git a/src/views/Community/index.js b/src/views/Community/index.js index 474fd2de..3ab1b193 100644 --- a/src/views/Community/index.js +++ b/src/views/Community/index.js @@ -60,6 +60,7 @@ const Community = () => { // Ref:用于滚动到实时事件时间轴 const eventTimelineRef = useRef(null); const hasScrolledRef = useRef(false); // 标记是否已滚动 + const containerRef = useRef(null); // 用于首次滚动到内容区域 // ⚡ 通知权限引导 const { showCommunityGuide } = useNotification(); @@ -145,6 +146,23 @@ const Community = () => { } }, [showCommunityGuide]); // 只在组件挂载时执行一次 + // ⚡ 首次进入页面时滚动到内容区域(考虑导航栏高度) + useEffect(() => { + // 延迟执行,确保DOM已完全渲染 + const timer = setTimeout(() => { + if (containerRef.current) { + // 滚动到容器顶部,自动考虑导航栏的高度 + containerRef.current.scrollIntoView({ + behavior: 'auto', + block: 'start', + inline: 'nearest' + }); + } + }, 0); + + return () => clearTimeout(timer); + }, []); // 空依赖数组,只在组件挂载时执行一次 + // ⚡ 滚动到实时事件区域(由搜索框聚焦触发) const scrollToTimeline = useCallback(() => { if (!hasScrolledRef.current && eventTimelineRef.current) { @@ -161,7 +179,7 @@ const Community = () => { return ( {/* 主内容区域 */} - + {/* 热点事件区域 */} diff --git a/src/views/EventDetail/index.js b/src/views/EventDetail/index.js index 52bd6f2f..0f4910b0 100644 --- a/src/views/EventDetail/index.js +++ b/src/views/EventDetail/index.js @@ -358,6 +358,9 @@ const EventDetail = () => { const { user } = useAuth(); const { hasFeatureAccess, getUpgradeRecommendation } = useSubscription(); + // 滚动位置管理 + const scrollPositionRef = useRef(0); + // State hooks const [eventData, setEventData] = useState(null); const [relatedStocks, setRelatedStocks] = useState([]); @@ -399,6 +402,16 @@ const EventDetail = () => { const actualEventId = getEventIdFromPath(); + // 保存当前滚动位置 + const saveScrollPosition = () => { + scrollPositionRef.current = window.scrollY || window.pageYOffset; + }; + + // 恢复滚动位置 + const restoreScrollPosition = () => { + window.scrollTo(0, scrollPositionRef.current); + }; + const loadEventData = async () => { try { setLoading(true); @@ -540,8 +553,19 @@ const EventDetail = () => { // Effect hook - must be called after all state hooks useEffect(() => { if (actualEventId) { + // 保存当前滚动位置 + saveScrollPosition(); + loadEventData(); loadPosts(); + + // 数据加载完成后恢复滚动位置 + // 使用 setTimeout 确保 DOM 已更新 + const timer = setTimeout(() => { + restoreScrollPosition(); + }, 100); + + return () => clearTimeout(timer); } else { setError('无效的事件ID'); setLoading(false);