feat: 首页更新
@@ -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/
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
BIN
assets/images/game/menu/blockThird.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/images/game/menu/chess.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
assets/images/game/menu/clock-solid.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/images/game/menu/clock-solid_dark.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
assets/images/game/menu/electronic.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/images/game/menu/fishing.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
assets/images/game/menu/home-star-solid.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/images/game/menu/live.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
assets/images/game/menu/lottery.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/images/game/menu/recommend.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
assets/images/game/menu/sports.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
assets/images/game/menu/trial.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
@@ -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
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
// 获取主题颜色
|
// 获取主题颜色
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -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]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// 处理图片源 - 优先使用 logo(URL),其次使用 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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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; // 推荐分类返回所有游戏
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 恢复状态的函数
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -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,
|
||||||
}))
|
// }))
|
||||||
);
|
// );
|
||||||
|
|||||||
@@ -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',
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* 提供主题相关的辅助函数
|
* 提供主题相关的辅助函数
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Colors from '@/constants/Colors';
|
import { Colors } from '@/theme';
|
||||||
import { ThemeEnum } from '@/constants/theme';
|
import { ThemeEnum } from '@/constants/theme';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||