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.
112 lines
3.1 KiB
112 lines
3.1 KiB
/** |
|
* 游戏分类菜单组件 |
|
* |
|
* 展示游戏分类,支持切换,使用真实数据 |
|
*/ |
|
|
|
import React, { useRef, useEffect, useState, useCallback, useMemo } from 'react'; |
|
import { View, Text, ScrollView, TouchableOpacity, Animated } from 'react-native'; |
|
// import type { GameCategory } from '@/types/home'; |
|
import { styles } from './styles'; |
|
import { useGameMainMenus, useMenuDataLoaded } from '@/hooks/useGameMenus'; |
|
// import useGameStore from '@/stores/gameStore'; |
|
import { ThemeEnum } from '@/constants/theme'; |
|
|
|
|
|
|
|
interface GameMainMenuProps { |
|
theme: ThemeEnum; |
|
selectedCategory?: string; |
|
onCategorySelect?: (categoryId: string) => void; |
|
topHeight?: number; |
|
showSubMenus?: boolean; |
|
} |
|
|
|
/** |
|
* 游戏分类菜单组件 |
|
*/ |
|
export default function GameMainMenu({ |
|
theme, |
|
selectedCategory = '103', |
|
onCategorySelect, |
|
topHeight = 0, |
|
showSubMenus = true, |
|
}: GameMainMenuProps) { |
|
const s = styles[theme]; |
|
const scrollViewRef = useRef<ScrollView>(null); |
|
const gameMenus = useGameMainMenus(theme); |
|
|
|
// 检查数据加载完成 |
|
const isDataLoaded = useMenuDataLoaded(); |
|
|
|
// 使用 useMemo 缓存找到的索引,避免每次都重新计算 |
|
const selectedIndex = useMemo(() => { |
|
return gameMenus.findIndex((cat) => cat.key === selectedCategory); |
|
}, [selectedCategory, gameMenus]); |
|
|
|
// 当分类改变时,滚动到该分类 |
|
useEffect(() => { |
|
if (selectedIndex >= 0) { |
|
scrollViewRef.current?.scrollTo({ |
|
x: selectedIndex * 100, |
|
animated: true, |
|
}); |
|
} |
|
}, [selectedIndex]); |
|
|
|
// 使用 useCallback 稳定 onPress 回调 |
|
const handleCategoryPress = useCallback((categoryKey: string) => { |
|
onCategorySelect?.(categoryKey); |
|
}, [onCategorySelect]); |
|
|
|
// 骨架屏 - 显示加载中的占位符 |
|
const renderSkeleton = () => ( |
|
<View style={s.container}> |
|
<ScrollView |
|
style={s.scrollView} |
|
horizontal |
|
showsHorizontalScrollIndicator={false} |
|
scrollEventThrottle={16} |
|
> |
|
{[1, 2, 3, 4, 5].map((index) => ( |
|
<View |
|
key={`skeleton-${index}`} |
|
style={[s.categoryItem, { backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0' }]} |
|
/> |
|
))} |
|
</ScrollView> |
|
</View> |
|
); |
|
|
|
// 如果动态数据还未加载,显示骨架屏 |
|
if (!isDataLoaded) { |
|
return renderSkeleton(); |
|
} |
|
|
|
return ( |
|
<View style={s.container}> |
|
<ScrollView |
|
ref={scrollViewRef} |
|
style={s.scrollView} |
|
horizontal |
|
showsHorizontalScrollIndicator={false} |
|
scrollEventThrottle={16} |
|
> |
|
{gameMenus.map((menu) => ( |
|
<TouchableOpacity |
|
key={menu.key} |
|
style={[s.menuItem, selectedCategory === menu.key && s.menuItemActive]} |
|
onPress={() => handleCategoryPress(menu.key)} |
|
activeOpacity={0.7} |
|
> |
|
<Text |
|
style={[s.menuText, selectedCategory === menu.key && s.menuTextActive]} |
|
> |
|
{menu.icon || '🎮'} {menu.name} |
|
</Text> |
|
</TouchableOpacity> |
|
))} |
|
</ScrollView> |
|
</View> |
|
); |
|
}
|
|
|