feat: 首页更新

This commit is contained in:
2025-11-12 00:13:26 +08:00
parent b48cce06f4
commit 9ef9233797
46 changed files with 660 additions and 369 deletions

View File

@@ -5,19 +5,37 @@
*/
import React, { useRef, useEffect, useState, useCallback, useMemo } from 'react';
import { View, Text, ScrollView, TouchableOpacity, Animated } from 'react-native';
import { View, Text, ScrollView, TouchableOpacity, Animated, Image, Platform } from 'react-native';
// import type { GameCategory } from '@/types/home';
import { styles } from './styles';
import { useGameMainMenus, useMenuDataLoaded } from '@/hooks/useGameMenus';
import { styles } from './styles';
import { useGameMainMenus, useMenuDataLoaded, useSelectedCategory } from '@/hooks/useGameMenus';
// import useGameStore from '@/stores/gameStore';
import { ThemeEnum } from '@/constants/theme';
// 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 {
theme: ThemeEnum;
selectedCategory?: string;
onCategorySelect?: (categoryId: string) => void;
topHeight?: number;
showSubMenus?: boolean;
}
@@ -26,16 +44,17 @@ interface GameMainMenuProps {
* 游戏分类菜单组件
*/
export default function GameMainMenu({
theme,
selectedCategory = '103',
onCategorySelect,
topHeight = 0,
showSubMenus = true,
}: GameMainMenuProps) {
const theme = useColorScheme();
const s = styles[theme];
const scrollViewRef = useRef<ScrollView>(null);
const gameMenus = useGameMainMenus(theme);
// 从 hook 获取选中分类和更新方法
const { selectedCategory, setSelectedCategory } = useSelectedCategory();
// 检查数据加载完成
const isDataLoaded = useMenuDataLoaded();
@@ -55,9 +74,12 @@ export default function GameMainMenu({
}, [selectedIndex]);
// 使用 useCallback 稳定 onPress 回调
const handleCategoryPress = useCallback((categoryKey: string) => {
onCategorySelect?.(categoryKey);
}, [onCategorySelect]);
const handleCategoryPress = useCallback(
(categoryKey: string) => {
setSelectedCategory(categoryKey);
},
[setSelectedCategory]
);
// 骨架屏 - 显示加载中的占位符
const renderSkeleton = () => (
@@ -68,16 +90,16 @@ export default function GameMainMenu({
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
>
{[1, 2, 3, 4, 5].map((index) => (
{Array.from({ length: 6 }).map((_, index) => (
<View
key={`skeleton-${index}`}
style={[s.categoryItem, { backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0' }]}
style={[s.menuItem, { backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0' }]}
/>
))}
</ScrollView>
</View>
);
console.log('isDataLoaded', isDataLoaded);
// 如果动态数据还未加载,显示骨架屏
if (!isDataLoaded) {
return renderSkeleton();
@@ -92,20 +114,105 @@ export default function GameMainMenu({
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]}
{gameMenus.map((menu) => {
// 处理图片源 - 优先使用 logoURL其次使用 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}
>
{menu.icon || '🎮'} {menu.name}
</Text>
</TouchableOpacity>
))}
{menuContent}
</TouchableOpacity>
);
})}
</ScrollView>
</View>
);

View File

@@ -1,4 +1,4 @@
import { createThemeStyles } from '@/theme';
import { createThemeStyles, createResponsiveThemeStyles } from '@/theme';
import { Dimensions } from 'react-native';
// const { width } = Dimensions.get('window');
@@ -10,7 +10,7 @@ import { Dimensions } from 'react-native';
export const styles = createThemeStyles((colors) => ({
container: {
backgroundColor: colors.background,
paddingVertical: 10,
paddingTop: 5,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
@@ -18,11 +18,11 @@ export const styles = createThemeStyles((colors) => ({
paddingHorizontal: 12,
},
menuItem: {
paddingHorizontal: 16,
paddingVertical: 10,
paddingHorizontal: 12,
paddingVertical: 5,
marginRight: 8,
borderRadius: 22,
backgroundColor: colors.backgroundSecondary,
borderRadius: 0,
backgroundColor: 'transparent',
justifyContent: 'center',
alignItems: 'center',
elevation: 1,
@@ -30,18 +30,41 @@ export const styles = createThemeStyles((colors) => ({
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
borderBottomColor: 'transparent',
borderBottomWidth: 2,
},
menuItemActive: {
backgroundColor: colors.primary,
// backgroundColor: `${colors.tint}15`, // 主题色 + 20% 透明度
elevation: 2,
shadowOpacity: 0.15,
borderBottomColor: colors.tint,
borderRadius: 0,
},
menuText: {
fontSize: 13,
fontSize: 14,
color: colors.text,
fontWeight: '600',
},
menuTextActive: {
color: '#FFFFFF',
color: colors.tint,
},
menuIcon: {
width: 30,
height: 30,
marginBottom: 4,
},
}));
export const themeStyles = createResponsiveThemeStyles({
menuItemActive: {
backgroundColor: '',
},
}, {
menuItemActive: {
backgroundColor: '',
},
}, {
menuItemActive: {
backgroundColor: '',
},
});