You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
3.9 KiB
157 lines
3.9 KiB
|
1 month ago
|
/**
|
||
|
|
* 轮播图组件
|
||
|
|
*
|
||
|
|
* 展示首页轮播图,支持自动播放和手动滑动
|
||
|
|
* 使用真实数据
|
||
|
|
*/
|
||
|
|
|
||
|
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||
|
|
import {
|
||
|
|
View,
|
||
|
|
Image,
|
||
|
|
TouchableOpacity,
|
||
|
|
ScrollView,
|
||
|
|
NativeScrollEvent,
|
||
|
|
NativeSyntheticEvent,
|
||
|
|
ActivityIndicator,
|
||
|
|
Dimensions,
|
||
|
|
Alert,
|
||
|
|
} from 'react-native';
|
||
|
|
import Colors from '@/constants/Colors';
|
||
|
|
// import type { Banner } from '@/types/home';
|
||
|
|
import { styles } from './styles';
|
||
|
|
import useMsgStore from '@/stores/msgStore';
|
||
|
|
|
||
|
|
|
||
|
|
interface BannerSwiperProps {
|
||
|
|
theme: 'light' | 'dark';
|
||
|
|
}
|
||
|
|
|
||
|
|
const { width } = Dimensions.get('window');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 轮播图组件
|
||
|
|
*/
|
||
|
|
export default function BannerSwiper({ theme }: BannerSwiperProps) {
|
||
|
|
const s = styles[theme];
|
||
|
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
const scrollViewRef = useRef<ScrollView>(null);
|
||
|
|
const autoPlayTimerRef = useRef<any>(null);
|
||
|
|
const { homeBanner } = useMsgStore();
|
||
|
|
|
||
|
|
|
||
|
|
// 加载轮播图数据
|
||
|
|
useEffect(() => {
|
||
|
|
// 如果有传入的 banners 数据,直接使用
|
||
|
|
if (homeBanner.length > 0) {
|
||
|
|
setLoading(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// 如果没有数据,保持 loading 状态显示骨架屏
|
||
|
|
}, [homeBanner]);
|
||
|
|
|
||
|
|
// 处理 Banner 点击
|
||
|
|
const onBannerPress = useCallback((banner: Record<string, any>) => {
|
||
|
|
Alert.alert('轮播图', `点击了: ${banner.title || banner.id}`);
|
||
|
|
// 这里可以添加导航逻辑
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
// 处理滚动事件
|
||
|
|
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||
|
|
const contentOffsetX = event.nativeEvent.contentOffset.x;
|
||
|
|
const index = Math.round(contentOffsetX / (width - 24));
|
||
|
|
setCurrentIndex(Math.min(index, homeBanner.length - 1));
|
||
|
|
};
|
||
|
|
|
||
|
|
// 启动自动播放
|
||
|
|
const startAutoPlay = useCallback(() => {
|
||
|
|
if (homeBanner.length <= 1) return;
|
||
|
|
autoPlayTimerRef.current = setInterval(() => {
|
||
|
|
setCurrentIndex((prev) => {
|
||
|
|
const nextIndex = (prev + 1) % homeBanner.length;
|
||
|
|
scrollViewRef.current?.scrollTo({
|
||
|
|
x: nextIndex * (width - 24),
|
||
|
|
animated: true,
|
||
|
|
});
|
||
|
|
return nextIndex;
|
||
|
|
});
|
||
|
|
}, 5000);
|
||
|
|
}, [homeBanner.length]);
|
||
|
|
|
||
|
|
// 停止自动播放
|
||
|
|
const stopAutoPlay = useCallback(() => {
|
||
|
|
if (autoPlayTimerRef.current) {
|
||
|
|
clearInterval(autoPlayTimerRef.current);
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
// 自动播放
|
||
|
|
useEffect(() => {
|
||
|
|
if (!loading && homeBanner.length > 0) {
|
||
|
|
startAutoPlay();
|
||
|
|
}
|
||
|
|
return () => stopAutoPlay();
|
||
|
|
}, [loading, homeBanner.length, startAutoPlay, stopAutoPlay]);
|
||
|
|
|
||
|
|
// 骨架屏 - 加载中显示占位符
|
||
|
|
if (loading || homeBanner.length === 0) {
|
||
|
|
return (
|
||
|
|
<View style={s.container}>
|
||
|
|
<View
|
||
|
|
style={[
|
||
|
|
s.image,
|
||
|
|
{
|
||
|
|
backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0',
|
||
|
|
},
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
</View>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<View style={s.container}>
|
||
|
|
<ScrollView
|
||
|
|
ref={scrollViewRef}
|
||
|
|
style={s.scrollView}
|
||
|
|
horizontal
|
||
|
|
pagingEnabled
|
||
|
|
scrollEventThrottle={16}
|
||
|
|
onScroll={handleScroll}
|
||
|
|
showsHorizontalScrollIndicator={false}
|
||
|
|
onMomentumScrollBegin={stopAutoPlay}
|
||
|
|
onMomentumScrollEnd={startAutoPlay}
|
||
|
|
>
|
||
|
|
{homeBanner.map((banner) => (
|
||
|
|
<TouchableOpacity
|
||
|
|
key={banner.id}
|
||
|
|
onPress={() => onBannerPress(banner)}
|
||
|
|
activeOpacity={0.9}
|
||
|
|
>
|
||
|
|
<Image
|
||
|
|
source={{ uri: banner.subject }}
|
||
|
|
style={s.image}
|
||
|
|
resizeMode="cover"
|
||
|
|
/>
|
||
|
|
</TouchableOpacity>
|
||
|
|
))}
|
||
|
|
</ScrollView>
|
||
|
|
|
||
|
|
{/* 指示器 */}
|
||
|
|
<View style={s.indicatorContainer}>
|
||
|
|
{homeBanner.map((_, index) => (
|
||
|
|
<View
|
||
|
|
key={index}
|
||
|
|
style={[
|
||
|
|
s.indicator,
|
||
|
|
index === currentIndex && s.indicatorActive,
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
))}
|
||
|
|
</View>
|
||
|
|
</View>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|