Files
vf_react/src/views/Community/components/DynamicNewsCard/EventScrollList.js
2025-11-03 17:21:07 +08:00

273 lines
8.2 KiB
JavaScript

// src/views/Community/components/DynamicNewsCard/EventScrollList.js
// 横向滚动事件列表组件
import React, { useRef } from 'react';
import {
Box,
Flex,
Grid,
IconButton,
Button,
ButtonGroup,
Center,
VStack,
Spinner,
Text,
useColorModeValue
} from '@chakra-ui/react';
import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons';
import DynamicNewsEventCard from '../EventCard/DynamicNewsEventCard';
import PaginationControl from './PaginationControl';
/**
* 事件列表组件 - 支持两种展示模式
* @param {Array} events - 当前页的事件列表(服务端已分页)
* @param {Object} selectedEvent - 当前选中的事件
* @param {Function} onEventSelect - 事件选择回调
* @param {string} borderColor - 边框颜色
* @param {number} currentPage - 当前页码
* @param {number} totalPages - 总页数(由服务端返回)
* @param {Function} onPageChange - 页码改变回调
* @param {boolean} loading - 加载状态
* @param {string} mode - 展示模式:'carousel'(单排轮播)| 'grid'(双排网格)
* @param {Function} onModeChange - 模式切换回调
*/
const EventScrollList = ({
events,
selectedEvent,
onEventSelect,
borderColor,
currentPage,
totalPages,
onPageChange,
loading = false,
mode = 'carousel',
onModeChange
}) => {
const scrollContainerRef = useRef(null);
// 时间轴样式配置
const getTimelineBoxStyle = () => {
return {
bg: useColorModeValue('gray.50', 'gray.700'),
borderColor: useColorModeValue('gray.400', 'gray.500'),
borderWidth: '2px',
textColor: useColorModeValue('blue.600', 'blue.400'),
boxShadow: 'sm',
};
};
return (
<Box>
{/* 顶部控制栏:模式切换按钮(左)+ 分页控制器(右) */}
<Flex justify="space-between" align="center" 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
currentPage={currentPage}
totalPages={totalPages}
onPageChange={onPageChange}
/>
)}
</Flex>
{/* 横向滚动区域 */}
<Box position="relative">
{/* 左侧翻页按钮 - 上一页 */}
{currentPage > 1 && (
<IconButton
icon={<ChevronLeftIcon boxSize={6} color="blue.500" />}
position="absolute"
left="0"
top="50%"
transform="translateY(-50%)"
zIndex={2}
onClick={() => onPageChange(currentPage - 1)}
variant="ghost"
size="md"
w="40px"
h="40px"
minW="40px"
borderRadius="full"
bg={useColorModeValue('rgba(255, 255, 255, 0.9)', 'rgba(0, 0, 0, 0.6)')}
boxShadow="0 2px 8px rgba(0, 0, 0, 0.15)"
_hover={{
bg: useColorModeValue('rgba(255, 255, 255, 1)', 'rgba(0, 0, 0, 0.8)'),
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.2)',
transform: 'translateY(-50%) scale(1.05)'
}}
aria-label="上一页"
title="上一页"
/>
)}
{/* 右侧翻页按钮 - 下一页 */}
{currentPage < totalPages && (
<IconButton
icon={<ChevronRightIcon boxSize={6} color="blue.500" />}
position="absolute"
right="0"
top="50%"
transform="translateY(-50%)"
zIndex={2}
onClick={() => onPageChange(currentPage + 1)}
variant="ghost"
size="md"
w="40px"
h="40px"
minW="40px"
borderRadius="full"
bg={useColorModeValue('rgba(255, 255, 255, 0.9)', 'rgba(0, 0, 0, 0.6)')}
boxShadow="0 2px 8px rgba(0, 0, 0, 0.15)"
_hover={{
bg: useColorModeValue('rgba(255, 255, 255, 1)', 'rgba(0, 0, 0, 0.8)'),
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.2)',
transform: 'translateY(-50%) scale(1.05)'
}}
aria-label="下一页"
title="下一页"
/>
)}
{/* 事件卡片容器 */}
<Box
ref={scrollContainerRef}
overflowX={mode === 'carousel' ? 'auto' : 'hidden'}
overflowY="hidden"
pt={0}
pb={4}
px={2}
position="relative"
css={mode === 'carousel' ? {
'&::-webkit-scrollbar': {
height: '8px',
},
'&::-webkit-scrollbar-track': {
background: useColorModeValue('#f1f1f1', '#2D3748'),
borderRadius: '10px',
},
'&::-webkit-scrollbar-thumb': {
background: useColorModeValue('#888', '#4A5568'),
borderRadius: '10px',
},
'&::-webkit-scrollbar-thumb:hover': {
background: useColorModeValue('#555', '#718096'),
},
scrollBehavior: 'smooth',
WebkitOverflowScrolling: 'touch',
} : {}}
>
{/* 加载遮罩 */}
{loading && (
<Center
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
bg={useColorModeValue('whiteAlpha.800', 'blackAlpha.700')}
backdropFilter="blur(2px)"
zIndex={10}
borderRadius="md"
>
<VStack>
<Spinner size="lg" color="blue.500" thickness="3px" />
<Text fontSize="sm" color={useColorModeValue('gray.600', 'gray.300')}>
加载中...
</Text>
</VStack>
</Center>
)}
{/* 模式1: 单排轮播模式 */}
{mode === 'carousel' && (
<Flex gap={4}>
{events.map((event, index) => (
<Box
key={event.id}
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"
>
{events.map((event, index) => (
<Box key={event.id}>
<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>
))}
</Grid>
)}
</Box>
</Box>
</Box>
);
};
export default EventScrollList;