2025-11-11 18:48:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 游戏分类菜单组件
|
|
|
|
|
|
*
|
|
|
|
|
|
* 展示游戏分类,支持切换,使用真实数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useRef, useEffect, useState, useCallback, useMemo } from 'react';
|
2025-11-12 00:13:26 +08:00
|
|
|
|
import { View, Text, ScrollView, TouchableOpacity, Animated, Image, Platform } from 'react-native';
|
2025-11-11 18:48:54 +08:00
|
|
|
|
// import type { GameCategory } from '@/types/home';
|
2025-11-12 00:13:26 +08:00
|
|
|
|
import { styles } from './styles';
|
|
|
|
|
|
import { useGameMainMenus, useMenuDataLoaded, useSelectedCategory } from '@/hooks/useGameMenus';
|
2025-11-11 18:48:54 +08:00
|
|
|
|
// import useGameStore from '@/stores/gameStore';
|
2025-11-12 00:13:26 +08:00
|
|
|
|
// import { ThemeEnum } from '@/constants/theme';
|
|
|
|
|
|
import { Colors, useColorScheme } from '@/theme';
|
2025-11-11 18:48:54 +08:00
|
|
|
|
|
2025-11-12 00:13:26 +08:00
|
|
|
|
// 条件导入 LinearGradient - 仅在非 Web 平台使用
|
|
|
|
|
|
let LinearGradient: any = null;
|
|
|
|
|
|
if (Platform.OS !== 'web') {
|
|
|
|
|
|
LinearGradient = require('react-native-linear-gradient').default;
|
|
|
|
|
|
}
|
2025-11-11 18:48:54 +08:00
|
|
|
|
|
2025-11-12 00:13:26 +08:00
|
|
|
|
// 游戏菜单图片映射 - 使用 require 加载本地资源
|
|
|
|
|
|
const MENU_ICON_MAP: Record<string, any> = {
|
|
|
|
|
|
'recommend': require('../../../../assets/images/game/menu/recommend.png'),
|
|
|
|
|
|
'chess': require('../../../../assets/images/game/menu/chess.png'),
|
|
|
|
|
|
'electronic': require('../../../../assets/images/game/menu/electronic.png'),
|
|
|
|
|
|
'fishing': require('../../../../assets/images/game/menu/fishing.png'),
|
|
|
|
|
|
'lottery': require('../../../../assets/images/game/menu/lottery.png'),
|
|
|
|
|
|
'sports': require('../../../../assets/images/game/menu/sports.png'),
|
|
|
|
|
|
'trial': require('../../../../assets/images/game/menu/trial.png'),
|
|
|
|
|
|
'blockThird': require('../../../../assets/images/game/menu/blockThird.png'),
|
|
|
|
|
|
'clock-solid': require('../../../../assets/images/game/menu/clock-solid.png'),
|
|
|
|
|
|
'clock-solid_dark': require('../../../../assets/images/game/menu/clock-solid_dark.png'),
|
|
|
|
|
|
'home-star-solid': require('../../../../assets/images/game/menu/home-star-solid.png'),
|
|
|
|
|
|
'live': require('../../../../assets/images/game/menu/live.png'),
|
|
|
|
|
|
};
|
2025-11-11 18:48:54 +08:00
|
|
|
|
|
|
|
|
|
|
interface GameMainMenuProps {
|
|
|
|
|
|
topHeight?: number;
|
|
|
|
|
|
showSubMenus?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 游戏分类菜单组件
|
|
|
|
|
|
*/
|
|
|
|
|
|
export default function GameMainMenu({
|
|
|
|
|
|
topHeight = 0,
|
|
|
|
|
|
showSubMenus = true,
|
|
|
|
|
|
}: GameMainMenuProps) {
|
2025-11-12 00:13:26 +08:00
|
|
|
|
const theme = useColorScheme();
|
2025-11-11 18:48:54 +08:00
|
|
|
|
const s = styles[theme];
|
|
|
|
|
|
const scrollViewRef = useRef<ScrollView>(null);
|
|
|
|
|
|
const gameMenus = useGameMainMenus(theme);
|
|
|
|
|
|
|
2025-11-12 00:13:26 +08:00
|
|
|
|
// 从 hook 获取选中分类和更新方法
|
|
|
|
|
|
const { selectedCategory, setSelectedCategory } = useSelectedCategory();
|
|
|
|
|
|
|
2025-11-11 18:48:54 +08:00
|
|
|
|
// 检查数据加载完成
|
|
|
|
|
|
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 回调
|
2025-11-12 00:13:26 +08:00
|
|
|
|
const handleCategoryPress = useCallback(
|
|
|
|
|
|
(categoryKey: string) => {
|
|
|
|
|
|
setSelectedCategory(categoryKey);
|
|
|
|
|
|
},
|
|
|
|
|
|
[setSelectedCategory]
|
|
|
|
|
|
);
|
2025-11-11 18:48:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 骨架屏 - 显示加载中的占位符
|
|
|
|
|
|
const renderSkeleton = () => (
|
|
|
|
|
|
<View style={s.container}>
|
|
|
|
|
|
<ScrollView
|
|
|
|
|
|
style={s.scrollView}
|
|
|
|
|
|
horizontal
|
|
|
|
|
|
showsHorizontalScrollIndicator={false}
|
|
|
|
|
|
scrollEventThrottle={16}
|
|
|
|
|
|
>
|
2025-11-12 00:13:26 +08:00
|
|
|
|
{Array.from({ length: 6 }).map((_, index) => (
|
2025-11-11 18:48:54 +08:00
|
|
|
|
<View
|
|
|
|
|
|
key={`skeleton-${index}`}
|
2025-11-12 00:13:26 +08:00
|
|
|
|
style={[s.menuItem, { backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0' }]}
|
2025-11-11 18:48:54 +08:00
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ScrollView>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
);
|
2025-11-12 00:13:26 +08:00
|
|
|
|
console.log('isDataLoaded', isDataLoaded);
|
2025-11-11 18:48:54 +08:00
|
|
|
|
// 如果动态数据还未加载,显示骨架屏
|
|
|
|
|
|
if (!isDataLoaded) {
|
|
|
|
|
|
return renderSkeleton();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<View style={s.container}>
|
|
|
|
|
|
<ScrollView
|
|
|
|
|
|
ref={scrollViewRef}
|
|
|
|
|
|
style={s.scrollView}
|
|
|
|
|
|
horizontal
|
|
|
|
|
|
showsHorizontalScrollIndicator={false}
|
|
|
|
|
|
scrollEventThrottle={16}
|
|
|
|
|
|
>
|
2025-11-12 00:13:26 +08:00
|
|
|
|
{gameMenus.map((menu) => {
|
|
|
|
|
|
// 处理图片源 - 优先使用 logo(URL),其次使用 icon(本地资源)
|
|
|
|
|
|
let imageSource: any = null;
|
|
|
|
|
|
|
|
|
|
|
|
if (menu.logo) {
|
|
|
|
|
|
// logo 是 URL,直接使用
|
|
|
|
|
|
imageSource = { uri: menu.logo };
|
|
|
|
|
|
} else if (menu.icon) {
|
|
|
|
|
|
// icon 是本地资源名称,从映射表中获取
|
|
|
|
|
|
imageSource = MENU_ICON_MAP[menu.icon];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isActive = selectedCategory === menu.key;
|
|
|
|
|
|
const themeColors = Colors[theme];
|
|
|
|
|
|
|
|
|
|
|
|
// 获取渐变色 - 从主题色到透明
|
|
|
|
|
|
const gradientStart = `${themeColors.tint}40`; // 主题色 + 40% 透明度
|
|
|
|
|
|
const gradientEnd = `${themeColors.tint}00`; // 完全透明
|
|
|
|
|
|
|
|
|
|
|
|
const menuContent = (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{imageSource && (
|
|
|
|
|
|
<Image source={imageSource} style={s.menuIcon} resizeMode="contain" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
<Text style={[s.menuText, isActive && s.menuTextActive]}>
|
|
|
|
|
|
{menu.name}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是选中状态,使用 LinearGradient 包装(非 Web 平台)或 CSS 渐变(Web 平台)
|
|
|
|
|
|
if (isActive) {
|
|
|
|
|
|
// Web 平台使用 CSS 渐变
|
|
|
|
|
|
if (Platform.OS === 'web') {
|
|
|
|
|
|
const webStyle = {
|
|
|
|
|
|
...s.menuItem,
|
|
|
|
|
|
...s.menuItemActive,
|
|
|
|
|
|
background: `linear-gradient(to top, ${gradientStart}, ${gradientEnd})`,
|
|
|
|
|
|
backgroundColor: undefined, // 移除 backgroundColor,使用 background
|
|
|
|
|
|
} as any;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
key={menu.key}
|
|
|
|
|
|
style={webStyle}
|
|
|
|
|
|
onPress={() => handleCategoryPress(menu.key)}
|
|
|
|
|
|
activeOpacity={0.7}
|
|
|
|
|
|
>
|
|
|
|
|
|
{menuContent}
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 非 Web 平台使用 LinearGradient
|
|
|
|
|
|
if (LinearGradient) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<LinearGradient
|
|
|
|
|
|
key={menu.key}
|
|
|
|
|
|
colors={[gradientStart, gradientEnd]}
|
|
|
|
|
|
start={{ x: 0.5, y: 1 }} // 从下往上
|
|
|
|
|
|
end={{ x: 0.5, y: 0 }}
|
|
|
|
|
|
style={[s.menuItem, s.menuItemActive]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}
|
|
|
|
|
|
onPress={() => handleCategoryPress(menu.key)}
|
|
|
|
|
|
activeOpacity={0.7}
|
|
|
|
|
|
>
|
|
|
|
|
|
{menuContent}
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
</LinearGradient>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 备用方案:如果 LinearGradient 不可用,使用纯色
|
|
|
|
|
|
return (
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
key={menu.key}
|
|
|
|
|
|
style={[s.menuItem, s.menuItemActive]}
|
|
|
|
|
|
onPress={() => handleCategoryPress(menu.key)}
|
|
|
|
|
|
activeOpacity={0.7}
|
|
|
|
|
|
>
|
|
|
|
|
|
{menuContent}
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 未选中状态,使用普通 TouchableOpacity
|
|
|
|
|
|
return (
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
key={menu.key}
|
|
|
|
|
|
style={s.menuItem}
|
|
|
|
|
|
onPress={() => handleCategoryPress(menu.key)}
|
|
|
|
|
|
activeOpacity={0.7}
|
2025-11-11 18:48:54 +08:00
|
|
|
|
>
|
2025-11-12 00:13:26 +08:00
|
|
|
|
{menuContent}
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
2025-11-11 18:48:54 +08:00
|
|
|
|
</ScrollView>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|