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.
113 lines
3.1 KiB
113 lines
3.1 KiB
|
1 month ago
|
/**
|
||
|
|
* 游戏分类菜单组件
|
||
|
|
*
|
||
|
|
* 展示游戏分类,支持切换,使用真实数据
|
||
|
|
*/
|
||
|
|
|
||
|
|
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>
|
||
|
|
);
|
||
|
|
}
|