232 lines
6.7 KiB
TypeScript
232 lines
6.7 KiB
TypeScript
|
|
/**
|
|||
|
|
* 游戏子菜单组件
|
|||
|
|
* 基于 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>
|
|||
|
|
);
|
|||
|
|
}
|