feat: 单排/双排列表模式切换

This commit is contained in:
zdl
2025-11-03 17:21:07 +08:00
parent c208ba36b7
commit 8ebfad9992
3 changed files with 123 additions and 45 deletions

View File

@@ -25,7 +25,7 @@ import UnifiedSearchBox from './UnifiedSearchBox';
import { fetchDynamicNews } from '../../../store/slices/communityDataSlice'; import { fetchDynamicNews } from '../../../store/slices/communityDataSlice';
/** /**
* 实时要闻·动态追踪 - 横向滚动卡片组件 * 实时要闻·动态追踪 - 事件展示卡片组件
* @param {Array} events - 事件列表 * @param {Array} events - 事件列表
* @param {boolean} loading - 加载状态 * @param {boolean} loading - 加载状态
* @param {Object} pagination - 分页信息 { page, per_page, total, total_pages } * @param {Object} pagination - 分页信息 { page, per_page, total, total_pages }
@@ -36,6 +36,7 @@ import { fetchDynamicNews } from '../../../store/slices/communityDataSlice';
* @param {Function} onSearchFocus - 搜索框获得焦点回调 * @param {Function} onSearchFocus - 搜索框获得焦点回调
* @param {Function} onEventClick - 事件点击回调 * @param {Function} onEventClick - 事件点击回调
* @param {Function} onViewDetail - 查看详情回调 * @param {Function} onViewDetail - 查看详情回调
* @param {string} mode - 展示模式:'carousel'单排轮播5个| 'grid'双排网格10个
* @param {Object} ref - 用于滚动的ref * @param {Object} ref - 用于滚动的ref
*/ */
const DynamicNewsCard = forwardRef(({ const DynamicNewsCard = forwardRef(({
@@ -55,11 +56,25 @@ const DynamicNewsCard = forwardRef(({
const cardBg = useColorModeValue('white', 'gray.800'); const cardBg = useColorModeValue('white', 'gray.800');
const borderColor = useColorModeValue('gray.200', 'gray.700'); const borderColor = useColorModeValue('gray.200', 'gray.700');
const [selectedEvent, setSelectedEvent] = useState(null); const [selectedEvent, setSelectedEvent] = useState(null);
const [mode, setMode] = useState('carousel'); // 'carousel' 或 'grid',默认单排
const pageSize = 5; // 每页显示5个事件 // 根据模式决定每页显示数量
const pageSize = mode === 'carousel' ? 5 : 10; // carousel: 5个, grid: 10个
const currentPage = pagination.page || 1; const currentPage = pagination.page || 1;
const totalPages = pagination.total_pages || 1; const totalPages = pagination.total_pages || 1;
// 模式切换处理
const handleModeToggle = (newMode) => {
if (newMode !== mode) {
setMode(newMode);
// 切换模式时重置到第1页并重新请求数据
const newPageSize = newMode === 'carousel' ? 5 : 10;
dispatch(fetchDynamicNews({ page: 1, per_page: newPageSize }));
// 清除当前选中的事件
setSelectedEvent(null);
}
};
// 默认选中第一个事件 // 默认选中第一个事件
useEffect(() => { useEffect(() => {
if (events && events.length > 0 && !selectedEvent) { if (events && events.length > 0 && !selectedEvent) {
@@ -123,6 +138,8 @@ const DynamicNewsCard = forwardRef(({
totalPages={totalPages} totalPages={totalPages}
onPageChange={handlePageChange} onPageChange={handlePageChange}
loading={loading} loading={loading}
mode={mode}
onModeChange={handleModeToggle}
/> />
) : !loading ? ( ) : !loading ? (
/* Empty 状态 - 只在非加载且无数据时显示 */ /* Empty 状态 - 只在非加载且无数据时显示 */

View File

@@ -5,7 +5,10 @@ import React, { useRef } from 'react';
import { import {
Box, Box,
Flex, Flex,
Grid,
IconButton, IconButton,
Button,
ButtonGroup,
Center, Center,
VStack, VStack,
Spinner, Spinner,
@@ -17,7 +20,7 @@ import DynamicNewsEventCard from '../EventCard/DynamicNewsEventCard';
import PaginationControl from './PaginationControl'; import PaginationControl from './PaginationControl';
/** /**
* 横向滚动事件列表组件 * 事件列表组件 - 支持两种展示模式
* @param {Array} events - 当前页的事件列表(服务端已分页) * @param {Array} events - 当前页的事件列表(服务端已分页)
* @param {Object} selectedEvent - 当前选中的事件 * @param {Object} selectedEvent - 当前选中的事件
* @param {Function} onEventSelect - 事件选择回调 * @param {Function} onEventSelect - 事件选择回调
@@ -26,6 +29,8 @@ import PaginationControl from './PaginationControl';
* @param {number} totalPages - 总页数(由服务端返回) * @param {number} totalPages - 总页数(由服务端返回)
* @param {Function} onPageChange - 页码改变回调 * @param {Function} onPageChange - 页码改变回调
* @param {boolean} loading - 加载状态 * @param {boolean} loading - 加载状态
* @param {string} mode - 展示模式:'carousel'(单排轮播)| 'grid'(双排网格)
* @param {Function} onModeChange - 模式切换回调
*/ */
const EventScrollList = ({ const EventScrollList = ({
events, events,
@@ -35,7 +40,9 @@ const EventScrollList = ({
currentPage, currentPage,
totalPages, totalPages,
onPageChange, onPageChange,
loading = false loading = false,
mode = 'carousel',
onModeChange
}) => { }) => {
const scrollContainerRef = useRef(null); const scrollContainerRef = useRef(null);
@@ -52,16 +59,35 @@ const EventScrollList = ({
return ( return (
<Box> <Box>
{/* 分页控制器 - 右上角相对定位 */} {/* 顶部控制栏:模式切换按钮(左)+ 分页控制器(右) */}
{totalPages > 1 && ( <Flex justify="space-between" align="center" mb={2}>
<Flex justify="flex-end" p={0} m={0} mb={2}> {/* 模式切换按钮 */}
<ButtonGroup size="sm" isAttached>
<Button
onClick={() => onModeChange('carousel')}
colorScheme="blue"
variant={mode === 'carousel' ? 'solid' : 'outline'}
>
单排
</Button>
<Button
onClick={() => onModeChange('grid')}
colorScheme="blue"
variant={mode === 'grid' ? 'solid' : 'outline'}
>
双排
</Button>
</ButtonGroup>
{/* 分页控制器 */}
{totalPages > 1 && (
<PaginationControl <PaginationControl
currentPage={currentPage} currentPage={currentPage}
totalPages={totalPages} totalPages={totalPages}
onPageChange={onPageChange} onPageChange={onPageChange}
/> />
</Flex> )}
)} </Flex>
{/* 横向滚动区域 */} {/* 横向滚动区域 */}
<Box position="relative"> <Box position="relative">
@@ -122,17 +148,16 @@ const EventScrollList = ({
/> />
)} )}
{/* 横向滚动容器 */} {/* 事件卡片容器 */}
<Flex <Box
ref={scrollContainerRef} ref={scrollContainerRef}
overflowX="auto" overflowX={mode === 'carousel' ? 'auto' : 'hidden'}
overflowY="hidden" overflowY="hidden"
gap={4}
pt={0} pt={0}
pb={4} pb={4}
px={2} px={2}
position="relative" position="relative"
css={{ css={mode === 'carousel' ? {
'&::-webkit-scrollbar': { '&::-webkit-scrollbar': {
height: '8px', height: '8px',
}, },
@@ -147,11 +172,9 @@ const EventScrollList = ({
'&::-webkit-scrollbar-thumb:hover': { '&::-webkit-scrollbar-thumb:hover': {
background: useColorModeValue('#555', '#718096'), background: useColorModeValue('#555', '#718096'),
}, },
// 平滑滚动
scrollBehavior: 'smooth', scrollBehavior: 'smooth',
// 触摸设备优化
WebkitOverflowScrolling: 'touch', WebkitOverflowScrolling: 'touch',
}} } : {}}
> >
{/* 加载遮罩 */} {/* 加载遮罩 */}
{loading && ( {loading && (
@@ -175,35 +198,72 @@ const EventScrollList = ({
</Center> </Center>
)} )}
{/* 事件卡片列表 */} {/* 模式1: 单排轮播模式 */}
{events.map((event, index) => ( {mode === 'carousel' && (
<Box <Flex gap={4}>
key={event.id} {events.map((event, index) => (
minW="calc((100% - 64px) / 5)" <Box
maxW="calc((100% - 64px) / 5)" key={event.id}
flexShrink={0} minW="calc((100% - 64px) / 5)"
maxW="calc((100% - 64px) / 5)"
flexShrink={0}
>
<DynamicNewsEventCard
event={event}
index={index}
isFollowing={false}
followerCount={event.follower_count || 0}
isSelected={selectedEvent?.id === event.id}
onEventClick={(clickedEvent) => {
onEventSelect(clickedEvent);
}}
onTitleClick={(e) => {
e.preventDefault();
e.stopPropagation();
onEventSelect(event);
}}
onToggleFollow={() => {}}
timelineStyle={getTimelineBoxStyle()}
borderColor={borderColor}
/>
</Box>
))}
</Flex>
)}
{/* 模式2: 双排网格模式 */}
{mode === 'grid' && (
<Grid
templateRows="repeat(2, 1fr)"
templateColumns="repeat(5, 1fr)"
gap={4}
autoFlow="column"
> >
<DynamicNewsEventCard {events.map((event, index) => (
event={event} <Box key={event.id}>
index={index} <DynamicNewsEventCard
isFollowing={false} event={event}
followerCount={event.follower_count || 0} index={index}
isSelected={selectedEvent?.id === event.id} isFollowing={false}
onEventClick={(clickedEvent) => { followerCount={event.follower_count || 0}
onEventSelect(clickedEvent); isSelected={selectedEvent?.id === event.id}
}} onEventClick={(clickedEvent) => {
onTitleClick={(e) => { onEventSelect(clickedEvent);
e.preventDefault(); }}
e.stopPropagation(); onTitleClick={(e) => {
onEventSelect(event); e.preventDefault();
}} e.stopPropagation();
onToggleFollow={() => {}} onEventSelect(event);
timelineStyle={getTimelineBoxStyle()} }}
borderColor={borderColor} onToggleFollow={() => {}}
/> timelineStyle={getTimelineBoxStyle()}
</Box> borderColor={borderColor}
))} />
</Flex> </Box>
))}
</Grid>
)}
</Box>
</Box> </Box>
</Box> </Box>
); );

View File

@@ -196,6 +196,7 @@ const Community = () => {
onSearchFocus={scrollToTimeline} onSearchFocus={scrollToTimeline}
onEventClick={handleEventClick} onEventClick={handleEventClick}
onViewDetail={handleViewDetail} onViewDetail={handleViewDetail}
mode="grid"
/> />
{/* 市场复盘 - 左右布局 */} {/* 市场复盘 - 左右布局 */}