feat: 首页更新
This commit is contained in:
231
pages/HomeScreen/components/GameSubMenus/index.tsx
Normal file
231
pages/HomeScreen/components/GameSubMenus/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
174
pages/HomeScreen/components/GameSubMenus/styles.ts
Normal file
174
pages/HomeScreen/components/GameSubMenus/styles.ts
Normal 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,
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user