215 lines
7.3 KiB
TypeScript
215 lines
7.3 KiB
TypeScript
/**
|
||
* 游戏分类菜单组件
|
||
*
|
||
* 展示游戏分类,支持切换,使用真实数据
|
||
*/
|
||
|
||
import React, { useRef, useEffect, useState, useCallback, useMemo } from 'react';
|
||
import { View, Text, ScrollView, TouchableOpacity, Animated, Image, Platform } from 'react-native';
|
||
// import type { GameCategory } from '@/types/home';
|
||
import { styles } from './styles';
|
||
import { useGameMainMenus, useMenuDataLoaded, useGameMenuTabs } from '@/hooks/useGameMenus';
|
||
// import useGameStore from '@/stores/gameStore';
|
||
// import { ThemeEnum } from '@/constants/theme';
|
||
import { Colors, useColorScheme } from '@/theme';
|
||
|
||
// 条件导入 LinearGradient - 仅在非 Web 平台使用
|
||
let LinearGradient: any = null;
|
||
if (Platform.OS !== 'web') {
|
||
LinearGradient = require('react-native-linear-gradient').default;
|
||
}
|
||
|
||
// 游戏菜单图片映射 - 使用 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'),
|
||
};
|
||
|
||
interface GameMainMenuProps {
|
||
topHeight?: number;
|
||
showSubMenus?: boolean;
|
||
}
|
||
|
||
/**
|
||
* 游戏分类菜单组件
|
||
*/
|
||
export default function GameMainMenu({ topHeight = 0, showSubMenus = true }: GameMainMenuProps) {
|
||
const theme = useColorScheme();
|
||
const s = styles[theme];
|
||
const scrollViewRef = useRef<ScrollView>(null);
|
||
const gameMenus = useGameMainMenus(theme);
|
||
|
||
// 从 hook 获取选中分类和更新方法
|
||
const { activeMainMenuTab, setActiveMainMenuTab } = useGameMenuTabs();
|
||
|
||
// 检查数据加载完成
|
||
const isDataLoaded = useMenuDataLoaded();
|
||
|
||
// 使用 useMemo 缓存找到的索引,避免每次都重新计算
|
||
const selectedIndex = useMemo(() => {
|
||
return gameMenus.findIndex((cat) => cat.key === activeMainMenuTab);
|
||
}, [activeMainMenuTab, gameMenus]);
|
||
|
||
// 当分类改变时,滚动到该分类
|
||
useEffect(() => {
|
||
if (selectedIndex >= 0) {
|
||
scrollViewRef.current?.scrollTo({
|
||
x: selectedIndex * 100,
|
||
animated: true,
|
||
});
|
||
}
|
||
}, [selectedIndex]);
|
||
|
||
// 使用 useCallback 稳定 onPress 回调
|
||
const handleCategoryPress = useCallback(
|
||
(categoryKey: string) => {
|
||
setActiveMainMenuTab(categoryKey);
|
||
},
|
||
[setActiveMainMenuTab]
|
||
);
|
||
|
||
// 骨架屏 - 显示加载中的占位符
|
||
const renderSkeleton = () => (
|
||
<View style={s.container}>
|
||
<ScrollView
|
||
style={s.scrollView}
|
||
horizontal
|
||
showsHorizontalScrollIndicator={false}
|
||
scrollEventThrottle={16}
|
||
>
|
||
{Array.from({ length: 6 }).map((_, index) => (
|
||
<View
|
||
key={`skeleton-${index}`}
|
||
style={[s.menuItem, { backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0' }]}
|
||
/>
|
||
))}
|
||
</ScrollView>
|
||
</View>
|
||
);
|
||
console.log('isDataLoaded', isDataLoaded);
|
||
// 如果动态数据还未加载,显示骨架屏
|
||
if (!isDataLoaded) {
|
||
return renderSkeleton();
|
||
}
|
||
|
||
return (
|
||
<View style={s.container}>
|
||
<ScrollView
|
||
ref={scrollViewRef}
|
||
style={s.scrollView}
|
||
horizontal
|
||
showsHorizontalScrollIndicator={false}
|
||
scrollEventThrottle={16}
|
||
>
|
||
{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 = activeMainMenuTab === 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}
|
||
>
|
||
{menuContent}
|
||
</TouchableOpacity>
|
||
);
|
||
})}
|
||
</ScrollView>
|
||
</View>
|
||
);
|
||
}
|