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

@@ -3,7 +3,7 @@ import FontAwesome from '@expo/vector-icons/FontAwesome';
import { Link, Tabs } from 'expo-router'; import { Link, Tabs } from 'expo-router';
import { Pressable } from 'react-native'; import { Pressable } from 'react-native';
import Colors from '@/constants/Colors'; import { Colors } from '@/theme';
import { useColorScheme, useClientOnlyValue } from '@/hooks'; import { useColorScheme, useClientOnlyValue } from '@/hooks';
// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/ // You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/

View File

@@ -40,7 +40,6 @@ import {
useTheme, useTheme,
useLanguage, useLanguage,
useHapticsEnabled, useHapticsEnabled,
useSettingsActions,
} from '@/stores'; } from '@/stores';
import { useTenantLoad, useTenantInfo } from '@/stores/tenantStore'; import { useTenantLoad, useTenantInfo } from '@/stores/tenantStore';
@@ -72,7 +71,7 @@ export default function DemoScreen() {
const theme = useTheme(); const theme = useTheme();
const language = useLanguage(); const language = useLanguage();
const hapticsEnabled = useHapticsEnabled(); const hapticsEnabled = useHapticsEnabled();
const { setTheme, setLanguage, setHapticsEnabled } = useSettingsActions(); const { setTheme, setLanguage, setHapticsEnabled } = useSettingsStore();
// const setTheme = useSettingsStore((state) => state.setTheme); // const setTheme = useSettingsStore((state) => state.setTheme);
// const setLanguage = useSettingsStore((state) => state.setLanguage); // const setLanguage = useSettingsStore((state) => state.setLanguage);
// const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled); // const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);

View File

@@ -8,7 +8,7 @@
import { Stack } from 'expo-router'; import { Stack } from 'expo-router';
import HomeScreen from '@/pages/HomeScreen'; import HomeScreen from '@/pages/HomeScreen';
export default function TabOneScreen() { export default function TabHoneScreen() {
return ( return (
<> <>
<Stack.Screen <Stack.Screen

View File

@@ -1,13 +1,13 @@
import { StyleSheet, ScrollView, TouchableOpacity, View, Text, useColorScheme as useSystemColorScheme } from 'react-native'; import { StyleSheet, ScrollView, TouchableOpacity, View, Text, useColorScheme as useSystemColorScheme } from 'react-native';
import { Stack } from 'expo-router'; import { Stack } from 'expo-router';
import { useState, useEffect, useMemo } from 'react'; import { useState, useEffect, useMemo } from 'react';
import { useTheme, useSettingsActions } from '@/stores'; import { useTheme, useSettingsStore } from '@/stores';
import { useHaptics } from '@/hooks'; import { useHaptics } from '@/hooks';
import Colors from '@/constants/Colors'; import { Colors } from '@/theme';
export default function ThemeTestScreen() { export default function ThemeTestScreen() {
const currentTheme = useTheme(); const currentTheme = useTheme();
const { setTheme } = useSettingsActions(); const { setTheme } = useSettingsStore();
const haptics = useHaptics(); const haptics = useHaptics();
const systemColorScheme = useSystemColorScheme(); const systemColorScheme = useSystemColorScheme();

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -5,7 +5,7 @@ import { ExternalLink } from './ExternalLink';
import { MonoText } from './StyledText'; import { MonoText } from './StyledText';
import { Text, View } from './Themed'; import { Text, View } from './Themed';
import Colors from '@/constants/Colors'; import { Colors } from '@/theme';
export default function EditScreenInfo({ path }: { path: string }) { export default function EditScreenInfo({ path }: { path: string }) {
return ( return (

165
components/Header/index.tsx Normal file
View File

@@ -0,0 +1,165 @@
/**
* 首页 Header 组件
* 包含搜索、用户信息、消息等功能
*/
import React, { useState, useCallback } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
TextInput,
Image,
Dimensions,
} from 'react-native';
import { createThemeStyles, useColorScheme, useThemeInfo } from '@/theme';
const { width } = Dimensions.get('window');
/**
* 创建主题样式
*/
const styles = createThemeStyles((colors) => ({
container: {
backgroundColor: colors.background,
paddingHorizontal: 12,
paddingVertical: 8,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 8,
},
logo: {
fontSize: 18,
fontWeight: '600',
color: colors.primary,
},
searchContainer: {
flex: 1,
marginHorizontal: 12,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.card,
borderRadius: 20,
paddingHorizontal: 12,
height: 36,
},
searchInput: {
flex: 1,
marginLeft: 8,
fontSize: 14,
color: colors.text,
},
searchPlaceholder: {
color: colors.text + '80',
},
iconButton: {
width: 36,
height: 36,
borderRadius: 18,
justifyContent: 'center',
alignItems: 'center',
marginLeft: 8,
},
badge: {
position: 'absolute',
top: -4,
right: -4,
backgroundColor: colors.primary,
borderRadius: 8,
minWidth: 16,
height: 16,
justifyContent: 'center',
alignItems: 'center',
},
badgeText: {
color: '#fff',
fontSize: 10,
fontWeight: '600',
textAlign: 'center',
},
}));
interface HeaderProps {
onSearch?: (keyword: string) => void;
onMessagePress?: () => void;
onUserPress?: () => void;
unreadCount?: number;
}
/**
* Header 组件
*/
export default function Header({
onSearch,
onMessagePress,
onUserPress,
unreadCount = 0,
}: HeaderProps) {
const theme = useColorScheme();
const s = styles[theme];
const { colors } = useThemeInfo();
const [searchText, setSearchText] = useState('');
const [isSearching, setIsSearching] = useState(false);
const handleSearch = useCallback(() => {
if (searchText.trim()) {
onSearch?.(searchText);
}
}, [searchText, onSearch]);
const handleClearSearch = useCallback(() => {
setSearchText('');
}, []);
return (
<View style={s.container}>
{/* 顶部栏 */}
<View style={s.header}>
{/* Logo */}
<Text style={s.logo}>🎮 </Text>
{/* 搜索框 */}
<View style={s.searchContainer}>
<Text style={{ color: colors.text + '60', fontSize: 16 }}>🔍</Text>
<TextInput
style={[s.searchInput, s.searchPlaceholder]}
placeholder="搜索游戏..."
placeholderTextColor={colors.text + '60'}
value={searchText}
onChangeText={setSearchText}
onSubmitEditing={handleSearch}
returnKeyType="search"
/>
{searchText ? (
<TouchableOpacity onPress={handleClearSearch}>
<Text style={{ fontSize: 16 }}></Text>
</TouchableOpacity>
) : null}
</View>
{/* 消息按钮 */}
<TouchableOpacity style={s.iconButton} onPress={onMessagePress} activeOpacity={0.7}>
<Text style={{ fontSize: 18 }}>💬</Text>
{unreadCount > 0 && (
<View style={s.badge}>
<Text style={s.badgeText}>{unreadCount > 99 ? '99+' : unreadCount}</Text>
</View>
)}
</TouchableOpacity>
{/* 用户按钮 */}
<TouchableOpacity style={s.iconButton} onPress={onUserPress} activeOpacity={0.7}>
<Text style={{ fontSize: 18 }}>👤</Text>
</TouchableOpacity>
</View>
</View>
);
}

View File

@@ -1,18 +1,18 @@
/** /**
* 主题演示组件 * 主题演示组件
* *
* 展示所有主题颜色和组件在不同主题下的效果 * 展示所有主题颜色和组件在不同主题下的效果
*/ */
import React from 'react'; import React from 'react';
import { StyleSheet, View, Text, TouchableOpacity, ScrollView } from 'react-native'; import { StyleSheet, View, Text, TouchableOpacity, ScrollView } from 'react-native';
import { ThemedText, ThemedView, useThemeColor } from './Themed'; import { ThemedText, ThemedView, useThemeColor } from './Themed';
import { useTheme, useSettingsActions } from '@/stores'; import { useTheme, useSettingsStore } from '@/stores';
import { useHaptics } from '@/hooks'; import { useHaptics } from '@/hooks';
export function ThemeDemo() { export function ThemeDemo() {
const theme = useTheme(); const theme = useTheme();
const { setTheme } = useSettingsActions(); const { setTheme } = useSettingsStore();
const haptics = useHaptics(); const haptics = useHaptics();
// 获取主题颜色 // 获取主题颜色

View File

@@ -7,7 +7,7 @@
import { Text as DefaultText, View as DefaultView, TextStyle } from 'react-native'; import { Text as DefaultText, View as DefaultView, TextStyle } from 'react-native';
import Colors from '@/constants/Colors'; import { Colors } from '@/theme';
import { useColorScheme } from '@/hooks/useTheme'; import { useColorScheme } from '@/hooks/useTheme';
type ThemeProps = { type ThemeProps = {

View File

@@ -24,3 +24,6 @@ export {
// Client-only value (for SSR/Web compatibility) // Client-only value (for SSR/Web compatibility)
export { useClientOnlyValue } from './useClientOnlyValue'; export { useClientOnlyValue } from './useClientOnlyValue';
// Game Menus
export { useGameMainMenus, useMenuDataLoaded, useSelectedCategory } from './useGameMenus';

View File

@@ -1,10 +1,20 @@
import { useEffect, useState, useMemo } from 'react'; import { useEffect, useState, useMemo, useCallback } from 'react';
import useGameStore from '@/stores/gameStore'; import useGameStore from '@/stores/gameStore';
import { GameMainTypesEnum, defaultHomeGameTabMenus, gameMainTypesMap } from '@/constants/game'; import { GameMainTypesEnum, defaultHomeGameTabMenus, gameMainTypesMap } from '@/constants/game';
import { ThemeEnum } from '@/constants/theme'; import { ThemeEnum } from '@/constants/theme';
import { forEach, cloneDeep, map, filter } from 'lodash-es'; import { forEach, cloneDeep, map, filter } from 'lodash-es';
import { useIsLoggedIn } from '@/stores/userStore'; import { useIsLoggedIn } from '@/stores/userStore';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import storageManager, { STORAGE_KEYS } from '@/utils/storageManager';
type GameMenu = {
name: string;
key: string;
icon?: string;
logo?: string;
children?: GameMenu[];
};
// 有子菜单的游戏类型 // 有子菜单的游戏类型
const hasSubGameMainTypes = [ const hasSubGameMainTypes = [
@@ -78,10 +88,68 @@ export const useGameMainMenus = (theme: ThemeEnum) => {
], ],
(item) => ({ (item) => ({
...item, ...item,
// 为了在 React Native 中正确加载本地图片,使用 require 的方式
// 这里保留原始的 icon 名称,在组件中使用 require 加载
icon: item.icon,
key: `${item.key}`, key: `${item.key}`,
}) })
); ) as GameMenu[];
}, [theme, isLogin, menuSort, gameBigClass]); }, [theme, isLogin, menuSort, gameBigClass]);
}; };
export const useMenuDataLoaded = () => useGameStore((state) => state.menuSort?.length > 0); export const useMenuDataLoaded = () => useGameStore((state) => state.menuSort?.length > 0);
/**
* 游戏分类选择 Hook统一管理
*
* 管理当前选中的游戏分类,支持:
* - 从 gameStore 获取当前选中分类
* - 更新选中分类并保存到 session storage
* - 页面刷新后恢复选中分类
*
* @returns {Object} 包含 selectedCategory 和 setSelectedCategory 的对象
*
* @example
* ```typescript
* const { selectedCategory, setSelectedCategory } = useSelectedCategory();
*
* // 获取当前选中分类
* console.log(selectedCategory); // '103'
*
* // 更新选中分类
* setSelectedCategory('1');
* ```
*/
export const useSelectedCategory = () => {
const selectedCategory = useGameStore((state) => state.selectedCategory);
const setSelectedCategoryInStore = useGameStore((state) => state.setSelectedCategory);
// 初始化时从 session storage 恢复选中分类
useEffect(() => {
const initializeSelectedCategory = async () => {
try {
const savedCategory = storageManager.session.getItem(STORAGE_KEYS.APP_ACTIVE_MAIN_MENU_TAB);
if (savedCategory) {
setSelectedCategoryInStore(savedCategory);
}
} catch (error) {
console.error('Failed to restore selected category:', error);
}
};
initializeSelectedCategory();
}, [setSelectedCategoryInStore]);
// 更新选中分类的回调函数
const setSelectedCategory = useCallback(
(categoryId: string) => {
setSelectedCategoryInStore(categoryId);
},
[setSelectedCategoryInStore]
);
return {
selectedCategory,
setSelectedCategory,
};
};

View File

@@ -1,49 +1,50 @@
/** /**
* 主题 Hooks * 主题 Hooks
* *
* 提供统一的主题访问接口 * 提供统一的主题访问接口
*/ */
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useColorScheme as useSystemColorScheme } from 'react-native'; import { useColorScheme as useSystemColorScheme } from 'react-native';
import { useTheme as useThemeStore } from '@/stores'; import { useTheme as useThemeStore } from '@/stores';
import Colors from '@/constants/Colors'; import { Colors } from '@/theme';
import { ThemeEnum } from '@/constants/theme';
/** /**
* 获取当前颜色方案light | dark * 获取当前颜色方案light | dark | orange
* *
* 从 settingsStore 读取用户设置的主题 * 从 settingsStore 读取用户设置的主题
* 支持 'light' | 'dark' | 'auto' 种模式 * 支持 'light' | 'dark' | 'orange' | 'auto' 种模式
*/ */
export function useColorScheme(): 'light' | 'dark' { export function useColorScheme(): ThemeEnum {
const userTheme = useThemeStore(); const userTheme = useThemeStore();
const systemTheme = useSystemColorScheme(); const systemTheme = useSystemColorScheme();
// 如果用户选择了 'auto',则使用系统主题 // 如果用户选择了 'auto',则使用系统主题
if (userTheme === 'auto') { if (userTheme === 'auto') {
return systemTheme === 'dark' ? 'dark' : 'light'; return systemTheme === 'dark' ? ThemeEnum.DARK : ThemeEnum.LIGHT;
} }
// 否则使用用户选择的主题 // 否则使用用户选择的主题
return userTheme; return userTheme as ThemeEnum;
} }
/** /**
* 获取主题颜色 * 获取主题颜色
* *
* @param props - 可选的自定义颜色 { light?: string; dark?: string } * @param props - 可选的自定义颜色 { light?: string; dark?: string; orange?: string }
* @param colorName - Colors 中定义的颜色名称 * @param colorName - Colors 中定义的颜色名称
* @returns 当前主题对应的颜色值 * @returns 当前主题对应的颜色值
* *
* @example * @example
* ```tsx * ```tsx
* const textColor = useThemeColor({}, 'text'); * const textColor = useThemeColor({}, 'text');
* const customColor = useThemeColor({ light: '#000', dark: '#fff' }, 'text'); * const customColor = useThemeColor({ light: '#000', dark: '#fff', orange: '#f90' }, 'text');
* ``` * ```
*/ */
export function useThemeColor( export function useThemeColor(
props: { light?: string; dark?: string }, props: { light?: string; dark?: string; orange?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark colorName: keyof typeof Colors.light & keyof typeof Colors.dark & keyof typeof Colors.orange
): string { ): string {
const theme = useColorScheme(); const theme = useColorScheme();
const colorFromProps = props[theme]; const colorFromProps = props[theme];
@@ -57,9 +58,9 @@ export function useThemeColor(
/** /**
* 获取完整的主题颜色对象 * 获取完整的主题颜色对象
* *
* @returns 当前主题的所有颜色配置 * @returns 当前主题的所有颜色配置
* *
* @example * @example
* ```tsx * ```tsx
* const colors = useThemeColors(); * const colors = useThemeColors();
@@ -70,7 +71,7 @@ export function useThemeColor(
*/ */
export function useThemeColors() { export function useThemeColors() {
const theme = useColorScheme(); const theme = useColorScheme();
return useMemo(() => { return useMemo(() => {
return Colors[theme]; return Colors[theme];
}, [theme]); }, [theme]);
@@ -78,23 +79,24 @@ export function useThemeColors() {
/** /**
* 获取主题相关的所有信息 * 获取主题相关的所有信息
* *
* @returns 主题信息对象 * @returns 主题信息对象
* *
* @example * @example
* ```tsx * ```tsx
* const { theme, colors, isDark } = useThemeInfo(); * const { theme, colors, isDark, isLight, isOrange } = useThemeInfo();
* ``` * ```
*/ */
export function useThemeInfo() { export function useThemeInfo() {
const theme = useColorScheme(); const theme = useColorScheme();
const colors = useThemeColors(); const colors = useThemeColors();
return useMemo(() => ({ return useMemo(() => ({
theme, theme,
colors, colors,
isDark: theme === 'dark', isDark: theme === ThemeEnum.DARK,
isLight: theme === 'light', isLight: theme === ThemeEnum.LIGHT,
isOrange: theme === ThemeEnum.ORANGE,
}), [theme, colors]); }), [theme, colors]);
} }

View File

@@ -37,6 +37,7 @@
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-hook-form": "^7.66.0", "react-hook-form": "^7.66.0",
"react-native": "0.81.5", "react-native": "0.81.5",
"react-native-linear-gradient": "^2.8.3",
"react-native-paper": "^5.14.5", "react-native-paper": "^5.14.5",
"react-native-reanimated": "~4.1.1", "react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0", "react-native-safe-area-context": "~5.6.0",

View File

@@ -1,180 +0,0 @@
/**
* 完整首页容器
* 包含 Header、内容区域、BottomTabs
* 支持主题切换和真实数据
*/
import React, { useState, useEffect, useCallback } from 'react';
import { View, ScrollView, RefreshControl, StyleSheet, SafeAreaView, Alert } from 'react-native';
import { useColorScheme } from '@/hooks';
import { createThemeStyles } from '@/theme';
import Colors from '@/constants/Colors';
import Header from './components/Header';
import BannerSwiper from './components/BannerSwiper';
import NoticeBar from './components/NoticeBar';
import GameMainMenus from './components/GameMainMenus';
import Lobby from './components/Lobby';
import HighPrizeGame from './components/HighPrizeGame';
import FastFootNav from './components/FastFootNav';
import { requestHomePageData } from '@/stores/gameStore';
import { useTenantLoad } from '@/stores/tenantStore';
import type {
Banner,
Notice,
GameCategory,
Game,
HighPrizeGame as HighPrizeGameType,
} from '@/types/home';
/**
* 创建主题样式
*/
const styles = createThemeStyles((colors) => ({
container: {
flex: 1,
backgroundColor: colors.background,
},
contentContainer: {
flex: 1,
},
scrollContent: {
paddingBottom: 20,
},
}));
interface HomeScreenCompleteProps {
theme?: 'light' | 'dark';
isDarkTheme?: boolean;
}
/**
* 完整首页容器
*/
export default function HomeScreenComplete({
theme = 'light',
isDarkTheme = false,
}: HomeScreenCompleteProps) {
const colorScheme = useColorScheme();
const actualTheme = theme === 'light' || theme === 'dark' ? theme : colorScheme;
const s = styles[actualTheme];
const [refreshing, setRefreshing] = useState(false);
const [selectedCategory, setSelectedCategory] = useState<number>(0);
const tenantLoad = useTenantLoad();
// 加载首页数据
const loadHomePageData = useCallback(async () => {
try {
await requestHomePageData();
} catch (error) {
console.error('加载首页数据失败:', error);
}
}, []);
// 初始化加载
useEffect(() => {
console.log('租户数据加载完成:', tenantLoad);
if (tenantLoad) {
loadHomePageData();
}
}, [loadHomePageData, tenantLoad]);
// 下拉刷新
const handleRefresh = useCallback(async () => {
setRefreshing(true);
try {
await loadHomePageData();
} finally {
setRefreshing(false);
}
}, [loadHomePageData]);
// 处理分类选择
const handleCategorySelect = useCallback((categoryId: number) => {
setSelectedCategory(categoryId);
// 这里可以根据分类过滤游戏
}, []);
// 处理游戏点击
const handleGamePress = useCallback((game: Game) => {
Alert.alert('游戏', `点击了: ${game.play_up_name}`);
// 这里可以添加打开游戏的逻辑
}, []);
// 处理底部 Tab 点击
const handleTabPress = useCallback((tabId: string, action: string) => {
Alert.alert('导航', `点击了: ${tabId}`);
// 这里可以添加导航逻辑
}, []);
// 处理搜索
const handleSearch = useCallback((keyword: string) => {
Alert.alert('搜索', `搜索关键词: ${keyword}`);
// 这里可以添加搜索逻辑
}, []);
// 根据主题选择要显示的组件
const renderContent = () => {
if (isDarkTheme || actualTheme === 'dark') {
// 深色主题布局
return (
<>
<GameMainMenus
theme={actualTheme}
selectedCategory={selectedCategory}
onCategorySelect={handleCategorySelect}
/>
<BannerSwiper theme={actualTheme} />
<NoticeBar theme={actualTheme} />
<HighPrizeGame theme={actualTheme} onGamePress={handleGamePress} />
<Lobby theme={actualTheme} onGamePress={handleGamePress} />
<FastFootNav theme={actualTheme} onTabPress={handleTabPress} />
</>
);
} else {
// 浅色主题布局
return (
<>
<BannerSwiper theme={actualTheme} />
<NoticeBar theme={actualTheme} />
<GameMainMenus
theme={actualTheme}
selectedCategory={selectedCategory}
onCategorySelect={handleCategorySelect}
/>
<Lobby theme={actualTheme} onGamePress={handleGamePress} />
</>
);
}
};
return (
<SafeAreaView style={s.container}>
{/* Header */}
<Header
theme={actualTheme}
onSearch={handleSearch}
onMessagePress={() => Alert.alert('消息', '消息功能')}
onUserPress={() => Alert.alert('用户', '用户中心')}
unreadCount={3}
/>
{/* 内容区域 */}
<View style={s.contentContainer}>
<ScrollView
style={s.contentContainer}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
tintColor={Colors[actualTheme].primary}
/>
}
showsVerticalScrollIndicator={false}
>
<View style={s.scrollContent}>{renderContent()}</View>
</ScrollView>
</View>
</SafeAreaView>
);
}

View File

@@ -2,7 +2,6 @@
* 轮播图组件 * 轮播图组件
* *
* 展示首页轮播图,支持自动播放和手动滑动 * 展示首页轮播图,支持自动播放和手动滑动
* 使用真实数据
*/ */
import React, { useState, useEffect, useRef, useCallback } from 'react'; import React, { useState, useEffect, useRef, useCallback } from 'react';
@@ -17,23 +16,22 @@ import {
Dimensions, Dimensions,
Alert, Alert,
} from 'react-native'; } from 'react-native';
import Colors from '@/constants/Colors'; import { useColorScheme } from '@/hooks';
// import type { Banner } from '@/types/home'; // import type { Banner } from '@/types/home';
import { styles } from './styles'; import { styles } from './styles';
import useMsgStore from '@/stores/msgStore'; import useMsgStore from '@/stores/msgStore';
interface BannerSwiperProps { interface BannerSwiperProps {}
theme: 'light' | 'dark';
}
const { width } = Dimensions.get('window'); const { width } = Dimensions.get('window');
/** /**
* 轮播图组件 * 轮播图组件
*/ */
export default function BannerSwiper({ theme }: BannerSwiperProps) { export default function BannerSwiper({}: BannerSwiperProps) {
const s = styles[theme]; const colorScheme = useColorScheme();
const s = styles[colorScheme];
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const scrollViewRef = useRef<ScrollView>(null); const scrollViewRef = useRef<ScrollView>(null);
@@ -102,7 +100,7 @@ export default function BannerSwiper({ theme }: BannerSwiperProps) {
style={[ style={[
s.image, s.image,
{ {
backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0', backgroundColor: colorScheme === 'dark' ? '#333' : '#e0e0e0',
}, },
]} ]}
/> />

View File

@@ -12,7 +12,7 @@ import {
} from 'react-native'; } from 'react-native';
import { useColorScheme } from '@/hooks'; import { useColorScheme } from '@/hooks';
import { createThemeStyles } from '@/theme'; import { createThemeStyles } from '@/theme';
import Colors from '@/constants/Colors'; import { Colors } from '@/theme';
const { width } = Dimensions.get('window'); const { width } = Dimensions.get('window');

View File

@@ -14,7 +14,7 @@ import {
Animated, Animated,
} from 'react-native'; } from 'react-native';
import { createThemeStyles } from '@/theme'; import { createThemeStyles } from '@/theme';
import Colors from '@/constants/Colors'; import { useColorScheme } from '@/hooks';
import { mockNavItems } from '@/services/mockHomeService'; import { mockNavItems } from '@/services/mockHomeService';
import type { NavItem } from '@/types/home'; import type { NavItem } from '@/types/home';
@@ -66,7 +66,6 @@ const styles = createThemeStyles((colors) => ({
})); }));
interface FastFootNavProps { interface FastFootNavProps {
theme: 'light' | 'dark';
items?: NavItem[]; items?: NavItem[];
onTabPress?: (tabId: string, action: string) => void; onTabPress?: (tabId: string, action: string) => void;
} }
@@ -74,8 +73,9 @@ interface FastFootNavProps {
/** /**
* 快速底部导航组件 * 快速底部导航组件
*/ */
export default function FastFootNav({ theme, items: propItems, onTabPress }: FastFootNavProps) { export default function FastFootNav({ items: propItems, onTabPress }: FastFootNavProps) {
const s = styles[theme]; const colorScheme = useColorScheme();
const s = styles[colorScheme];
const [items, setItems] = useState<NavItem[]>(propItems || []); const [items, setItems] = useState<NavItem[]>(propItems || []);
const [selectedId, setSelectedId] = useState<string | null>(null); const [selectedId, setSelectedId] = useState<string | null>(null);

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ import {
} from 'react-native'; } from 'react-native';
import { useColorScheme } from '@/hooks'; import { useColorScheme } from '@/hooks';
import { createThemeStyles } from '@/theme'; import { createThemeStyles } from '@/theme';
import Colors from '@/constants/Colors'; import { Colors } from '@/theme';
const { width } = Dimensions.get('window'); const { width } = Dimensions.get('window');
@@ -88,7 +88,6 @@ const styles = createThemeStyles((colors) => ({
})); }));
interface HeaderProps { interface HeaderProps {
theme?: 'light' | 'dark';
onSearch?: (keyword: string) => void; onSearch?: (keyword: string) => void;
onMessagePress?: () => void; onMessagePress?: () => void;
onUserPress?: () => void; onUserPress?: () => void;
@@ -99,16 +98,14 @@ interface HeaderProps {
* Header 组件 * Header 组件
*/ */
export default function Header({ export default function Header({
theme = 'light',
onSearch, onSearch,
onMessagePress, onMessagePress,
onUserPress, onUserPress,
unreadCount = 0, unreadCount = 0,
}: HeaderProps) { }: HeaderProps) {
const colorScheme = useColorScheme(); const colorScheme = useColorScheme();
const actualTheme = theme === 'light' || theme === 'dark' ? theme : colorScheme; const s = styles[colorScheme];
const s = styles[actualTheme]; const colors = Colors[colorScheme];
const colors = Colors[actualTheme];
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
const [isSearching, setIsSearching] = useState(false); const [isSearching, setIsSearching] = useState(false);

View File

@@ -15,7 +15,7 @@ import {
Image, Image,
} from 'react-native'; } from 'react-native';
import { createThemeStyles } from '@/theme'; import { createThemeStyles } from '@/theme';
import Colors from '@/constants/Colors'; import { useColorScheme } from '@/hooks';
import { mockHighPrizeGames } from '@/services/mockHomeService'; import { mockHighPrizeGames } from '@/services/mockHomeService';
import type { HighPrizeGame as HighPrizeGameType } from '@/types/home'; import type { HighPrizeGame as HighPrizeGameType } from '@/types/home';
@@ -85,7 +85,6 @@ const styles = createThemeStyles((colors) => ({
})); }));
interface HighPrizeGameProps { interface HighPrizeGameProps {
theme: 'light' | 'dark';
games?: HighPrizeGameType[]; games?: HighPrizeGameType[];
onGamePress?: (game: HighPrizeGameType) => void; onGamePress?: (game: HighPrizeGameType) => void;
} }
@@ -93,8 +92,9 @@ interface HighPrizeGameProps {
/** /**
* 高奖金游戏组件 * 高奖金游戏组件
*/ */
export default function HighPrizeGame({ theme, games: propGames, onGamePress }: HighPrizeGameProps) { export default function HighPrizeGame({ games: propGames, onGamePress }: HighPrizeGameProps) {
const s = styles[theme]; const colorScheme = useColorScheme();
const s = styles[colorScheme];
const [games, setGames] = useState<HighPrizeGameType[]>(propGames || []); const [games, setGames] = useState<HighPrizeGameType[]>(propGames || []);
const [selectedId, setSelectedId] = useState<string | null>(null); const [selectedId, setSelectedId] = useState<string | null>(null);

View File

@@ -15,8 +15,8 @@ import {
ActivityIndicator, ActivityIndicator,
Dimensions, Dimensions,
} from 'react-native'; } from 'react-native';
import { createThemeStyles } from '@/theme'; import { createThemeStyles, useColorScheme, useThemeInfo } from '@/theme';
import Colors from '@/constants/Colors'; import { useSelectedCategory } from '@/hooks/useGameMenus';
import { getMockGamesByCategory } from '@/services/mockHomeService'; import { getMockGamesByCategory } from '@/services/mockHomeService';
import type { Game } from '@/types/home'; import type { Game } from '@/types/home';
@@ -100,9 +100,7 @@ const styles = createThemeStyles((colors) => ({
})); }));
interface LobbyProps { interface LobbyProps {
theme: 'light' | 'dark';
games?: Game[]; games?: Game[];
selectedCategory?: number;
onGamePress?: (game: Game) => void; onGamePress?: (game: Game) => void;
topHeight?: number; topHeight?: number;
} }
@@ -111,13 +109,14 @@ interface LobbyProps {
* 游戏大厅组件 * 游戏大厅组件
*/ */
export default function Lobby({ export default function Lobby({
theme,
games: propGames, games: propGames,
selectedCategory = 0,
onGamePress, onGamePress,
topHeight = 0, topHeight = 0,
}: LobbyProps) { }: LobbyProps) {
const s = styles[theme]; const colorScheme = useColorScheme();
const s = styles[colorScheme];
const { colors } = useThemeInfo();
const { selectedCategory } = useSelectedCategory();
const [games, setGames] = useState<Game[]>(propGames || []); const [games, setGames] = useState<Game[]>(propGames || []);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -179,7 +178,7 @@ export default function Lobby({
if (loading) { if (loading) {
return ( return (
<View style={s.loadingContainer}> <View style={s.loadingContainer}>
<ActivityIndicator size="large" color={Colors[theme].primary} /> <ActivityIndicator size="large" color={colors.primary} />
</View> </View>
); );
} }

View File

@@ -14,7 +14,7 @@ import {
Dimensions, Dimensions,
} from 'react-native'; } from 'react-native';
import { createThemeStyles } from '@/theme'; import { createThemeStyles } from '@/theme';
import Colors from '@/constants/Colors'; import { useColorScheme } from '@/hooks';
import { mockNotices } from '@/services/mockHomeService'; import { mockNotices } from '@/services/mockHomeService';
import type { Notice } from '@/types/home'; import type { Notice } from '@/types/home';
@@ -66,7 +66,6 @@ const styles = createThemeStyles((colors) => ({
})); }));
interface NoticeBarProps { interface NoticeBarProps {
theme: 'light' | 'dark';
notices?: Notice[]; notices?: Notice[];
onNoticePress?: (notice: Notice) => void; onNoticePress?: (notice: Notice) => void;
} }
@@ -74,8 +73,9 @@ interface NoticeBarProps {
/** /**
* 公告栏组件 * 公告栏组件
*/ */
export default function NoticeBar({ theme, notices: propNotices, onNoticePress }: NoticeBarProps) { export default function NoticeBar({ notices: propNotices, onNoticePress }: NoticeBarProps) {
const s = styles[theme]; const colorScheme = useColorScheme();
const s = styles[colorScheme];
const [notices, setNotices] = useState<Notice[]>(propNotices || []); const [notices, setNotices] = useState<Notice[]>(propNotices || []);
const [currentNotice, setCurrentNotice] = useState(0); const [currentNotice, setCurrentNotice] = useState(0);
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(true);

View File

@@ -7,7 +7,7 @@
import React from 'react'; import React from 'react';
import BannerSwiperComponent from './BannerSwiper'; import BannerSwiperComponent from './BannerSwiper';
import NoticeBarComponent from './NoticeBar'; import NoticeBarComponent from './NoticeBar';
import GameCategoryMenuComponent from './GameCategoryMenu'; import GameMainMenusComponent from './GameMainMenus';
import LobbyComponent from './Lobby'; import LobbyComponent from './Lobby';
import HighPrizeGameComponent from './HighPrizeGame'; import HighPrizeGameComponent from './HighPrizeGame';
import FastFootNavComponent from './FastFootNav'; import FastFootNavComponent from './FastFootNav';
@@ -17,7 +17,7 @@ import BottomTabsComponent from './BottomTabs';
// 使用 React.memo 优化组件性能,避免不必要的重新渲染 // 使用 React.memo 优化组件性能,避免不必要的重新渲染
export const BannerSwiper = React.memo(BannerSwiperComponent); export const BannerSwiper = React.memo(BannerSwiperComponent);
export const NoticeBar = React.memo(NoticeBarComponent); export const NoticeBar = React.memo(NoticeBarComponent);
export const GameCategoryMenu = React.memo(GameCategoryMenuComponent); export const GameMainMenus = React.memo(GameMainMenusComponent);
export const Lobby = React.memo(LobbyComponent); export const Lobby = React.memo(LobbyComponent);
export const HighPrizeGame = React.memo(HighPrizeGameComponent); export const HighPrizeGame = React.memo(HighPrizeGameComponent);
export const FastFootNav = React.memo(FastFootNavComponent); export const FastFootNav = React.memo(FastFootNavComponent);

View File

@@ -1,23 +1,30 @@
/** /**
* 首页容器组件 * 完整首页容器
* * 包含 Header、内容区域、BottomTabs
* 支持浅色/深色主题,包含完整的首页功能: * 支持主题切换和真实数据
* - Header搜索、用户信息
* - 轮播图
* - 游戏分类菜单
* - 游戏大厅
* - 公告栏
* - 高奖金游戏(深色主题)
* - 快速导航(深色主题)
* - BottomTabs底部导航
*/ */
import React, { useState, useEffect, useCallback } from 'react';
import React, { useMemo, useCallback } from 'react'; import { View, ScrollView, RefreshControl, Alert } from 'react-native';
import { ScrollView, StyleSheet, RefreshControl } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useColorScheme } from '@/hooks'; import { createThemeStyles, useColorScheme, useThemeInfo } from '@/theme';
import { createThemeStyles } from '@/theme'; import {
import Colors from '@/constants/Colors'; Header,
import HomeScreenComplete from './HomeScreenComplete'; BannerSwiper,
NoticeBar,
GameMainMenus,
Lobby,
HighPrizeGame,
FastFootNav,
} from './components';
import { requestHomePageData } from '@/stores/gameStore';
import { useTenantLoad } from '@/stores/tenantStore';
import type {
Banner,
Notice,
GameCategory,
Game,
HighPrizeGame as HighPrizeGameType,
} from '@/types/home';
/** /**
* 创建主题样式 * 创建主题样式
@@ -27,33 +34,124 @@ const styles = createThemeStyles((colors) => ({
flex: 1, flex: 1,
backgroundColor: colors.background, backgroundColor: colors.background,
}, },
scrollView: { contentContainer: {
flex: 1, flex: 1,
}, },
scrollContent: {
paddingBottom: 20,
},
})); }));
/**
* 首页主容器组件
*/
export default function HomeScreen() {
const theme = useColorScheme();
const s = styles[theme];
const [refreshing, setRefreshing] = React.useState(false);
// 下拉刷新处理 /**
const onRefresh = useCallback(() => { * 完整首页容器
setRefreshing(true); */
// 模拟刷新延迟 export default function HomePage() {
setTimeout(() => { const colorScheme = useColorScheme();
setRefreshing(false); const s = styles[colorScheme];
}, 1000); const { isDark: isDarkTheme, colors } = useThemeInfo();
const [refreshing, setRefreshing] = useState(false);
const tenantLoad = useTenantLoad();
// 加载首页数据
const loadHomePageData = useCallback(async () => {
try {
await requestHomePageData();
} catch (error) {
console.error('加载首页数据失败:', error);
}
}, []); }, []);
// 初始化加载
useEffect(() => {
console.log('租户数据加载完成:', tenantLoad);
if (tenantLoad) {
loadHomePageData();
}
}, [loadHomePageData, tenantLoad]);
// 下拉刷新
const handleRefresh = useCallback(async () => {
setRefreshing(true);
try {
await loadHomePageData();
} finally {
setRefreshing(false);
}
}, [loadHomePageData]);
// 处理游戏点击
const handleGamePress = useCallback((game: Game) => {
Alert.alert('游戏', `点击了: ${game.play_up_name}`);
// 这里可以添加打开游戏的逻辑
}, []);
// 处理底部 Tab 点击
const handleTabPress = useCallback((tabId: string, action: string) => {
Alert.alert('导航', `点击了: ${tabId}`);
// 这里可以添加导航逻辑
}, []);
// 处理搜索
const handleSearch = useCallback((keyword: string) => {
Alert.alert('搜索', `搜索关键词: ${keyword}`);
// 这里可以添加搜索逻辑
}, []);
// 根据主题选择要显示的组件
const renderContent = () => {
if (isDarkTheme) {
// 深色主题布局
return (
<>
<GameMainMenus />
<BannerSwiper />
<NoticeBar />
<HighPrizeGame onGamePress={handleGamePress} />
<Lobby onGamePress={handleGamePress} />
<FastFootNav onTabPress={handleTabPress} />
</>
);
} else {
// 浅色主题布局
return (
<>
<BannerSwiper />
<NoticeBar />
<GameMainMenus />
<Lobby onGamePress={handleGamePress} />
</>
);
}
};
return ( return (
<HomeScreenComplete <SafeAreaView style={s.container}>
theme={theme} {/* Header */}
isDarkTheme={theme === 'dark'} <Header
/> onSearch={handleSearch}
onMessagePress={() => Alert.alert('消息', '消息功能')}
onUserPress={() => Alert.alert('用户', '用户中心')}
unreadCount={3}
/>
{/* 内容区域 */}
<View style={s.contentContainer}>
<ScrollView
style={s.contentContainer}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
tintColor={colors.primary}
/>
}
showsVerticalScrollIndicator={false}
>
<View style={s.scrollContent}>{renderContent()}</View>
</ScrollView>
</View>
</SafeAreaView>
); );
} }

14
pnpm-lock.yaml generated
View File

@@ -80,6 +80,9 @@ importers:
react-native: react-native:
specifier: 0.81.5 specifier: 0.81.5
version: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0) version: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
react-native-linear-gradient:
specifier: ^2.8.3
version: 2.8.3(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native-paper: react-native-paper:
specifier: ^5.14.5 specifier: ^5.14.5
version: 5.14.5(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) version: 5.14.5(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
@@ -2985,6 +2988,12 @@ packages:
react: '*' react: '*'
react-native: '*' react-native: '*'
react-native-linear-gradient@2.8.3:
resolution: {integrity: sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA==}
peerDependencies:
react: '*'
react-native: '*'
react-native-paper@5.14.5: react-native-paper@5.14.5:
resolution: {integrity: sha512-eaIH5bUQjJ/mYm4AkI6caaiyc7BcHDwX6CqNDi6RIxfxfWxROsHpll1oBuwn/cFvknvA8uEAkqLk/vzVihI3AQ==} resolution: {integrity: sha512-eaIH5bUQjJ/mYm4AkI6caaiyc7BcHDwX6CqNDi6RIxfxfWxROsHpll1oBuwn/cFvknvA8uEAkqLk/vzVihI3AQ==}
peerDependencies: peerDependencies:
@@ -7208,6 +7217,11 @@ snapshots:
react: 19.1.0 react: 19.1.0
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0) react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
react-native-linear-gradient@2.8.3(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
react-native-paper@5.14.5(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): react-native-paper@5.14.5(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
'@callstack/react-theme-provider': 3.0.9(react@19.1.0) '@callstack/react-theme-provider': 3.0.9(react@19.1.0)

View File

@@ -224,7 +224,7 @@ export const getMockHomePageData = () => {
/** /**
* 获取 Mock 游戏列表(支持分类过滤) * 获取 Mock 游戏列表(支持分类过滤)
*/ */
export const getMockGamesByCategory = (categoryId: number) => { export const getMockGamesByCategory = (categoryId: string | number) => {
if (categoryId === 0) { if (categoryId === 0) {
return mockGames; // 推荐分类返回所有游戏 return mockGames; // 推荐分类返回所有游戏
} }

View File

@@ -27,6 +27,7 @@ interface State {
gamesTryPlayIds: number[]; gamesTryPlayIds: number[];
smallClassGames: Record<string, any>; smallClassGames: Record<string, any>;
gameBigClass: Record<string, any>; gameBigClass: Record<string, any>;
selectedCategory: string; // 当前选中的游戏分类
} }
// 操作 // 操作
@@ -38,6 +39,7 @@ interface Actions {
setHomeHotGames: (data: Record<string, any>[]) => void; setHomeHotGames: (data: Record<string, any>[]) => void;
setSmallClassGame: (data: Record<string, any>) => void; setSmallClassGame: (data: Record<string, any>) => void;
setGameBigClass: (data: Record<string, any>) => void; setGameBigClass: (data: Record<string, any>) => void;
setSelectedCategory: (categoryId: string) => void; // 设置选中的游戏分类
// requestHomePageData: (data?: Record<string, any>) => Promise<any>; // requestHomePageData: (data?: Record<string, any>) => Promise<any>;
} }
@@ -58,6 +60,7 @@ const useGameStore = create<State & Actions>()((set, get) => ({
gamesTryPlayIds: [], // 试玩游戏id列表 gamesTryPlayIds: [], // 试玩游戏id列表
smallClassGames: {}, smallClassGames: {},
gameBigClass: {}, gameBigClass: {},
selectedCategory: '103', // 默认选中推荐分类
// 保存首页数据 // 保存首页数据
@@ -157,6 +160,12 @@ const useGameStore = create<State & Actions>()((set, get) => ({
[GameMainKeysEnum.BLOCK_THIRD]: groupByType?.[10] || [], [GameMainKeysEnum.BLOCK_THIRD]: groupByType?.[10] || [],
} }); } });
}, },
setSelectedCategory: (categoryId: string) => {
set({ selectedCategory: categoryId });
// 保存到 session storage页面刷新后仍然保留
storageManager.session.setItem(STORAGE_KEYS.APP_ACTIVE_MAIN_MENU_TAB, categoryId);
},
})); }));
// 从 AsyncStorage 恢复状态的函数 // 从 AsyncStorage 恢复状态的函数

View File

@@ -8,7 +8,6 @@ export {
useUser, useUser,
useIsLoggedIn, useIsLoggedIn,
useToken, useToken,
useUserActions,
restoreUserState, restoreUserState,
} from './userStore'; } from './userStore';
export type { User } from './userStore'; export type { User } from './userStore';
@@ -21,7 +20,6 @@ export {
useNotificationsEnabled, useNotificationsEnabled,
useSoundEnabled, useSoundEnabled,
useHapticsEnabled, useHapticsEnabled,
useSettingsActions,
restoreSettingsState, restoreSettingsState,
} from './settingsStore'; } from './settingsStore';
export type { Theme, Language } from './settingsStore'; export type { Theme, Language } from './settingsStore';

View File

@@ -10,7 +10,7 @@ import storageManager, { STORAGE_KEYS } from '@/utils/storageManager';
/** /**
* 主题类型 * 主题类型
*/ */
export type Theme = 'light' | 'dark' | 'auto'; export type Theme = 'light' | 'dark' | 'orange' | 'auto';
/** /**
* 语言类型 * 语言类型
@@ -121,8 +121,7 @@ export const restoreSettingsState = async () => {
try { try {
const stored = await storageManager.local.getItem(STORAGE_KEYS.SETTINGS_STORE); const stored = await storageManager.local.getItem(STORAGE_KEYS.SETTINGS_STORE);
if (stored) { if (stored) {
const state = JSON.parse(stored); useSettingsStore.setState(stored);
useSettingsStore.setState(state);
if (__DEV__) { if (__DEV__) {
console.log('✅ Settings state restored from storage'); console.log('✅ Settings state restored from storage');
} }
@@ -152,16 +151,3 @@ export const useSoundEnabled = () => useSettingsStore((state) => state.soundEnab
// 获取触觉反馈状态 // 获取触觉反馈状态
export const useHapticsEnabled = () => useSettingsStore((state) => state.hapticsEnabled); export const useHapticsEnabled = () => useSettingsStore((state) => state.hapticsEnabled);
// 获取设置操作方法
// 使用 useShallow 避免每次渲染都返回新对象
export const useSettingsActions = () =>
useSettingsStore(
useShallow((state) => ({
setTheme: state.setTheme,
setLanguage: state.setLanguage,
setNotificationsEnabled: state.setNotificationsEnabled,
setSoundEnabled: state.setSoundEnabled,
setHapticsEnabled: state.setHapticsEnabled,
resetSettings: state.resetSettings,
}))
);

View File

@@ -147,13 +147,13 @@ export const useToken = () => useUserStore((state) => state.token);
// 获取用户操作方法 // 获取用户操作方法
// 使用 useShallow 避免每次渲染都返回新对象 // 使用 useShallow 避免每次渲染都返回新对象
export const useUserActions = () => // export const useUserActions = () =>
useUserStore( // useUserStore(
useShallow((state) => ({ // useShallow((state) => ({
setUser: state.setUser, // setUser: state.setUser,
setToken: state.setToken, // setToken: state.setToken,
login: state.login, // login: state.login,
logout: state.logout, // logout: state.logout,
updateUser: state.updateUser, // updateUser: state.updateUser,
})) // }))
); // );

View File

@@ -4,8 +4,9 @@
* settingsStore * settingsStore
*/ */
const tintColorLight = '#007AFF'; const tintColorLight = '#10c8e3';
const tintColorDark = '#0A84FF'; const tintColorDark = '#ffd69f';
const tintColorOrange = '#bd9534';
export default { export default {
light: { light: {
@@ -71,7 +72,7 @@ export default {
backgroundTertiary: '#E5E5E5', backgroundTertiary: '#E5E5E5',
// 主题色 // 主题色
tint: tintColorLight, tint: tintColorOrange,
primary: '#007AFF', primary: '#007AFF',
secondary: '#5856D6', secondary: '#5856D6',
success: '#34C759', success: '#34C759',
@@ -85,7 +86,7 @@ export default {
// Tab 图标 // Tab 图标
tabIconDefault: '#8E8E93', tabIconDefault: '#8E8E93',
tabIconSelected: tintColorLight, tabIconSelected: tintColorOrange,
// 卡片 // 卡片
card: '#FFFFFF', card: '#FFFFFF',

View File

@@ -1,11 +1,11 @@
/** /**
* 主题系统统一导出 * 主题系统统一导出
* *
* 提供主题配置、工具函数和类型定义 * 提供主题配置、工具函数和类型定义
*/ */
// 导出颜色配置 // 导出颜色配置
export { default as Colors } from '@/constants/Colors'; export { default as Colors } from './Colors';
// 导出主题 Hooks // 导出主题 Hooks
export { export {
@@ -23,6 +23,9 @@ export {
View as ThemeView, View as ThemeView,
} from '@/components/Themed'; } from '@/components/Themed';
// 导出主题类型
export { ThemeEnum } from '@/constants/theme';
export type { export type {
ThemedTextProps, ThemedTextProps,
ThemedViewProps, ThemedViewProps,

View File

@@ -7,7 +7,7 @@
*/ */
import { StyleSheet, TextStyle, ViewStyle } from 'react-native'; import { StyleSheet, TextStyle, ViewStyle } from 'react-native';
import Colors from '@/constants/Colors'; import { Colors } from '@/theme';
import { ThemeEnum } from '@/constants/theme'; import { ThemeEnum } from '@/constants/theme';
/** /**
@@ -53,7 +53,7 @@ export function createThemeStyles<T extends StyleSheet.NamedStyles<T>>(
return { return {
light: StyleSheet.create(createStyles(Colors.light)), light: StyleSheet.create(createStyles(Colors.light)),
dark: StyleSheet.create(createStyles(Colors.dark)), dark: StyleSheet.create(createStyles(Colors.dark)),
orange: StyleSheet.create(createStyles(Colors.light)), orange: StyleSheet.create(createStyles(Colors.orange)),
}; };
} }
@@ -89,7 +89,7 @@ export function createResponsiveThemeStyles<T extends StyleSheet.NamedStyles<T>>
return { return {
light: StyleSheet.create(lightStyles), light: StyleSheet.create(lightStyles),
dark: StyleSheet.create(darkStyles), dark: StyleSheet.create(darkStyles),
orange: StyleSheet.create(lightStyles), orange: StyleSheet.create(orangeStyles),
}; };
} }

View File

@@ -4,7 +4,7 @@
* 提供主题相关的辅助函数 * 提供主题相关的辅助函数
*/ */
import Colors from '@/constants/Colors'; import { Colors } from '@/theme';
import { ThemeEnum } from '@/constants/theme'; import { ThemeEnum } from '@/constants/theme';
/** /**