feat: 首页更新

This commit is contained in:
2025-11-13 16:47:10 +08:00
parent 9ef9233797
commit 54bf84b19b
1244 changed files with 3507 additions and 951 deletions

View File

@@ -0,0 +1,231 @@
/**
* 游戏子菜单组件
* 基于 xinyong-web 的 SubGameCategoryMenu 组件重建
* 功能包括:
* - 水平/竖直菜单切换
* - 菜单项滚动和自动定位
* - 弹窗选择菜单
* - 主题适配
*/
import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
import {
View,
Text,
ScrollView,
TouchableOpacity,
Modal,
FlatList,
Dimensions,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useColorScheme, useThemeColors } from '@/theme';
import { styles } from './styles';
import { useGameMainMenus, useGameMenuTabs } from '@/hooks/useGameMenus';
import { Image } from '@/components';
const { width } = Dimensions.get('window');
interface GameSubMenusProps {
vertical?: boolean;
onSubMenuChange?: (menuKey: string) => void;
}
/**
* 游戏子菜单组件
*/
export default function GameSubMenus({ vertical = false, onSubMenuChange }: GameSubMenusProps) {
const colorScheme = useColorScheme();
const s = styles[colorScheme];
const themeColors = useThemeColors();
// 获取主菜单数据
const gameMenus = useGameMainMenus(colorScheme);
const { activeMainMenuTab, activeSubMenuTab, setActiveSubMenuTab } = useGameMenuTabs();
// 获取当前选中的主菜单
const currentMenu = useMemo(() => {
return gameMenus.find((menu) => menu.key === activeMainMenuTab);
}, [gameMenus, activeMainMenuTab]);
// 获取当前选中的子菜单
const subMenus = useMemo((): Record<string, any>[] => {
return currentMenu?.children || [];
}, [currentMenu]);
// 弹窗状态
const [showPopup, setShowPopup] = useState(false);
// 滚动视图引用
const scrollViewRef = useRef<ScrollView>(null);
// 当前选中的子菜单索引
const selectedSubMenuIndex = useMemo(() => {
return subMenus.findIndex((menu) => menu.key === activeSubMenuTab);
}, [subMenus, activeSubMenuTab]);
// 处理子菜单选择
const handleSubMenuPress = useCallback(
(menuKey: string) => {
setActiveSubMenuTab(menuKey);
onSubMenuChange?.(menuKey);
},
[setActiveSubMenuTab, onSubMenuChange]
);
// 处理打开弹窗
const handleOpenPopup = useCallback(() => {
setShowPopup(true);
}, []);
// 处理关闭弹窗
const handleClosePopup = useCallback(() => {
setShowPopup(false);
}, []);
// 处理弹窗中的菜单选择
const handlePopupMenuSelect = useCallback(
(menuKey: string) => {
handleSubMenuPress(menuKey);
handleClosePopup();
},
[handleSubMenuPress, handleClosePopup]
);
// 自动滚动到选中项
useEffect(() => {
if (selectedSubMenuIndex >= 0 && !vertical) {
const scrollPosition = selectedSubMenuIndex * 112; // 100 (width) + 12 (margin)
scrollViewRef.current?.scrollTo({
x: scrollPosition,
animated: true,
});
}
}, [selectedSubMenuIndex, vertical]);
// 如果没有子菜单,返回空
if (subMenus.length === 0) {
return null;
}
// 渲染菜单项
const renderMenuItem = (item: any, isActive: boolean) => (
<TouchableOpacity
style={[s.menuItem, vertical && s.menuItemVertical, isActive && s.menuItemActive]}
onPress={() => handleSubMenuPress(item.key)}
activeOpacity={0.7}
>
{item.colorImgSrc && (
<Image
source={item.colorImgSrc}
style={[s.menuIcon]}
adaptiveMode="height"
autoMeasure={true}
/>
)}
<Text style={[s.menuText, isActive && s.menuTextActive]} numberOfLines={2}>
{item.name}
</Text>
</TouchableOpacity>
);
// 水平布局
if (!vertical) {
return (
<View style={s.horizontalContainer}>
<ScrollView
ref={scrollViewRef}
horizontal
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
style={s.horizontalScrollView}
>
{subMenus.map((item) => (
<View key={item.key}>{renderMenuItem(item, item.key === activeSubMenuTab)}</View>
))}
{/* 更多菜单按钮 */}
<TouchableOpacity
style={[s.menuItem, { marginLeft: 6 }]}
onPress={handleOpenPopup}
activeOpacity={0.7}
>
<Ionicons name="list" size={32} color={themeColors.text + '80'} />
<Text style={s.menuText}></Text>
</TouchableOpacity>
</ScrollView>
</View>
);
}
// 竖直布局
return (
<View style={s.verticalContainer}>
<ScrollView
ref={scrollViewRef}
showsVerticalScrollIndicator={false}
scrollEventThrottle={16}
style={s.verticalScrollView}
>
{subMenus.map((item) => (
<View key={item.key}>{renderMenuItem(item, item.key === activeSubMenuTab)}</View>
))}
</ScrollView>
{/* 弹窗 */}
<Modal
visible={showPopup}
transparent
animationType="slide"
onRequestClose={handleClosePopup}
>
<View style={s.modalOverlay}>
<TouchableOpacity style={{ flex: 1 }} activeOpacity={1} onPress={handleClosePopup} />
<View style={s.modalContent}>
{/* 弹窗头部 */}
<View style={s.modalHeader}>
<Text style={s.modalTitle}></Text>
<TouchableOpacity style={s.modalCloseButton} onPress={handleClosePopup}>
<Ionicons name="close" size={24} color={themeColors.text} />
</TouchableOpacity>
</View>
{/* 弹窗内容 - 网格布局 */}
<FlatList
data={subMenus}
renderItem={({ item }) => (
<TouchableOpacity
style={[s.modalGridItem, item.key === activeSubMenuTab && s.modalGridItemActive]}
onPress={() => handlePopupMenuSelect(item.key)}
activeOpacity={0.7}
>
{item.colorImgSrc && (
<Image
source={item.colorImgSrc}
style={s.modalGridIcon}
resizeMode="contain"
adaptiveMode="height"
autoMeasure={true}
/>
)}
<Text
style={[
s.modalGridText,
item.key === activeSubMenuTab && s.modalGridTextActive,
]}
numberOfLines={2}
>
{item.name}
</Text>
</TouchableOpacity>
)}
keyExtractor={(item) => item.key}
numColumns={2}
scrollEnabled={false}
contentContainerStyle={s.modalGrid}
/>
</View>
</View>
</Modal>
</View>
);
}

View File

@@ -0,0 +1,174 @@
/**
* GameSubMenus 组件样式
* 支持水平和竖直两种布局
*/
import { createThemeStyles } from '@/theme';
export const styles = createThemeStyles((colors) => ({
// ========== 容器样式 ==========
container: {
flex: 1,
backgroundColor: colors.background,
},
// ========== 水平布局样式 ==========
horizontalContainer: {
paddingVertical: 12,
paddingHorizontal: 8,
backgroundColor: colors.background,
},
horizontalScrollView: {
flexGrow: 0,
},
// ========== 竖直布局样式 ==========
verticalContainer: {
paddingHorizontal: 12,
paddingVertical: 12,
backgroundColor: colors.background,
},
verticalScrollView: {
flexGrow: 0,
},
// ========== 菜单项样式 ==========
menuItem: {
minWidth: 90,
height: 33,
marginHorizontal: 6,
paddingHorizontal: 8,
// paddingVertical: 8,
borderRadius: 6,
backgroundColor: '#f6f6f7',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
menuItemActive: {
backgroundColor: colors.primary,
},
menuItemVertical: {
width: 120,
height: 120,
marginBottom: 12,
marginHorizontal: 0,
},
menuIcon: {
height: 16,
width: 48,
// 宽度由 Image 组件根据 autoMeasure 自动计算
resizeMode: 'contain',
},
menuText: {
fontSize: 12,
fontWeight: '500',
color: colors.text + '80',
textAlign: 'center',
// marginTop: 4,
},
menuTextActive: {
color: '#fff',
fontWeight: '600',
},
// ========== 弹窗样式 ==========
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'flex-end',
},
modalContent: {
backgroundColor: colors.background,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
paddingTop: 16,
paddingBottom: 24,
maxHeight: '70%',
},
modalHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
paddingBottom: 12,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
modalTitle: {
fontSize: 16,
fontWeight: '600',
color: colors.text,
},
modalCloseButton: {
padding: 8,
},
modalGrid: {
paddingHorizontal: 12,
paddingVertical: 12,
},
modalGridItem: {
flex: 1,
margin: 6,
height: 120,
borderRadius: 12,
backgroundColor: colors.card,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
},
modalGridItemActive: {
backgroundColor: colors.primary,
},
modalGridIcon: {
// 只设置高度,宽度由 Image 组件根据 adaptiveMode="height" 自动计算
height: 60,
marginBottom: 8,
// 注意resizeMode 不应该在样式中,应该作为 Image 组件的 prop
},
modalGridText: {
fontSize: 12,
fontWeight: '500',
color: colors.text + '80',
textAlign: 'center',
},
modalGridTextActive: {
color: '#fff',
fontWeight: '600',
},
// ========== 加载状态 ==========
skeleton: {
backgroundColor: colors.card,
borderRadius: 12,
},
skeletonHorizontal: {
width: 100,
height: 96,
marginHorizontal: 6,
},
skeletonVertical: {
width: 120,
height: 120,
marginBottom: 12,
},
}));