feat: 首页更新
@@ -3,7 +3,7 @@ import FontAwesome from '@expo/vector-icons/FontAwesome';
|
||||
import { Link, Tabs } from 'expo-router';
|
||||
import { Pressable } from 'react-native';
|
||||
|
||||
import Colors from '@/constants/Colors';
|
||||
import { Colors } from '@/theme';
|
||||
import { useColorScheme, useClientOnlyValue } from '@/hooks';
|
||||
|
||||
// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
|
||||
|
||||
@@ -40,7 +40,6 @@ import {
|
||||
useTheme,
|
||||
useLanguage,
|
||||
useHapticsEnabled,
|
||||
useSettingsActions,
|
||||
} from '@/stores';
|
||||
import { useTenantLoad, useTenantInfo } from '@/stores/tenantStore';
|
||||
|
||||
@@ -72,7 +71,7 @@ export default function DemoScreen() {
|
||||
const theme = useTheme();
|
||||
const language = useLanguage();
|
||||
const hapticsEnabled = useHapticsEnabled();
|
||||
const { setTheme, setLanguage, setHapticsEnabled } = useSettingsActions();
|
||||
const { setTheme, setLanguage, setHapticsEnabled } = useSettingsStore();
|
||||
// const setTheme = useSettingsStore((state) => state.setTheme);
|
||||
// const setLanguage = useSettingsStore((state) => state.setLanguage);
|
||||
// const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { Stack } from 'expo-router';
|
||||
import HomeScreen from '@/pages/HomeScreen';
|
||||
|
||||
export default function TabOneScreen() {
|
||||
export default function TabHoneScreen() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { StyleSheet, ScrollView, TouchableOpacity, View, Text, useColorScheme as useSystemColorScheme } from 'react-native';
|
||||
import { Stack } from 'expo-router';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useTheme, useSettingsActions } from '@/stores';
|
||||
import { useTheme, useSettingsStore } from '@/stores';
|
||||
import { useHaptics } from '@/hooks';
|
||||
import Colors from '@/constants/Colors';
|
||||
import { Colors } from '@/theme';
|
||||
|
||||
export default function ThemeTestScreen() {
|
||||
const currentTheme = useTheme();
|
||||
const { setTheme } = useSettingsActions();
|
||||
const { setTheme } = useSettingsStore();
|
||||
const haptics = useHaptics();
|
||||
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 { Text, View } from './Themed';
|
||||
|
||||
import Colors from '@/constants/Colors';
|
||||
import { Colors } from '@/theme';
|
||||
|
||||
export default function EditScreenInfo({ path }: { path: string }) {
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -7,12 +7,12 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, View, Text, TouchableOpacity, ScrollView } from 'react-native';
|
||||
import { ThemedText, ThemedView, useThemeColor } from './Themed';
|
||||
import { useTheme, useSettingsActions } from '@/stores';
|
||||
import { useTheme, useSettingsStore } from '@/stores';
|
||||
import { useHaptics } from '@/hooks';
|
||||
|
||||
export function ThemeDemo() {
|
||||
const theme = useTheme();
|
||||
const { setTheme } = useSettingsActions();
|
||||
const { setTheme } = useSettingsStore();
|
||||
const haptics = useHaptics();
|
||||
|
||||
// 获取主题颜色
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
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';
|
||||
|
||||
type ThemeProps = {
|
||||
|
||||
@@ -24,3 +24,6 @@ export {
|
||||
|
||||
// Client-only value (for SSR/Web compatibility)
|
||||
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 { GameMainTypesEnum, defaultHomeGameTabMenus, gameMainTypesMap } from '@/constants/game';
|
||||
import { ThemeEnum } from '@/constants/theme';
|
||||
import { forEach, cloneDeep, map, filter } from 'lodash-es';
|
||||
import { useIsLoggedIn } from '@/stores/userStore';
|
||||
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 = [
|
||||
@@ -78,10 +88,68 @@ export const useGameMainMenus = (theme: ThemeEnum) => {
|
||||
],
|
||||
(item) => ({
|
||||
...item,
|
||||
// 为了在 React Native 中正确加载本地图片,使用 require 的方式
|
||||
// 这里保留原始的 icon 名称,在组件中使用 require 加载
|
||||
icon: item.icon,
|
||||
key: `${item.key}`,
|
||||
})
|
||||
);
|
||||
) as GameMenu[];
|
||||
}, [theme, isLogin, menuSort, gameBigClass]);
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -7,43 +7,44 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useColorScheme as useSystemColorScheme } from 'react-native';
|
||||
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 读取用户设置的主题
|
||||
* 支持 'light' | 'dark' | 'auto' 三种模式
|
||||
* 支持 'light' | 'dark' | 'orange' | 'auto' 四种模式
|
||||
*/
|
||||
export function useColorScheme(): 'light' | 'dark' {
|
||||
export function useColorScheme(): ThemeEnum {
|
||||
const userTheme = useThemeStore();
|
||||
const systemTheme = useSystemColorScheme();
|
||||
|
||||
// 如果用户选择了 '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 中定义的颜色名称
|
||||
* @returns 当前主题对应的颜色值
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* 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(
|
||||
props: { light?: string; dark?: string },
|
||||
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
|
||||
props: { light?: string; dark?: string; orange?: string },
|
||||
colorName: keyof typeof Colors.light & keyof typeof Colors.dark & keyof typeof Colors.orange
|
||||
): string {
|
||||
const theme = useColorScheme();
|
||||
const colorFromProps = props[theme];
|
||||
@@ -83,7 +84,7 @@ export function useThemeColors() {
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { theme, colors, isDark } = useThemeInfo();
|
||||
* const { theme, colors, isDark, isLight, isOrange } = useThemeInfo();
|
||||
* ```
|
||||
*/
|
||||
export function useThemeInfo() {
|
||||
@@ -93,8 +94,9 @@ export function useThemeInfo() {
|
||||
return useMemo(() => ({
|
||||
theme,
|
||||
colors,
|
||||
isDark: theme === 'dark',
|
||||
isLight: theme === 'light',
|
||||
isDark: theme === ThemeEnum.DARK,
|
||||
isLight: theme === ThemeEnum.LIGHT,
|
||||
isOrange: theme === ThemeEnum.ORANGE,
|
||||
}), [theme, colors]);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"react-dom": "19.1.0",
|
||||
"react-hook-form": "^7.66.0",
|
||||
"react-native": "0.81.5",
|
||||
"react-native-linear-gradient": "^2.8.3",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-reanimated": "~4.1.1",
|
||||
"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';
|
||||
@@ -17,23 +16,22 @@ import {
|
||||
Dimensions,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import Colors from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks';
|
||||
// import type { Banner } from '@/types/home';
|
||||
import { styles } from './styles';
|
||||
import useMsgStore from '@/stores/msgStore';
|
||||
|
||||
|
||||
interface BannerSwiperProps {
|
||||
theme: 'light' | 'dark';
|
||||
}
|
||||
interface BannerSwiperProps {}
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
/**
|
||||
* 轮播图组件
|
||||
*/
|
||||
export default function BannerSwiper({ theme }: BannerSwiperProps) {
|
||||
const s = styles[theme];
|
||||
export default function BannerSwiper({}: BannerSwiperProps) {
|
||||
const colorScheme = useColorScheme();
|
||||
const s = styles[colorScheme];
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const scrollViewRef = useRef<ScrollView>(null);
|
||||
@@ -102,7 +100,7 @@ export default function BannerSwiper({ theme }: BannerSwiperProps) {
|
||||
style={[
|
||||
s.image,
|
||||
{
|
||||
backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0',
|
||||
backgroundColor: colorScheme === 'dark' ? '#333' : '#e0e0e0',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from 'react-native';
|
||||
import { useColorScheme } from '@/hooks';
|
||||
import { createThemeStyles } from '@/theme';
|
||||
import Colors from '@/constants/Colors';
|
||||
import { Colors } from '@/theme';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
Animated,
|
||||
} from 'react-native';
|
||||
import { createThemeStyles } from '@/theme';
|
||||
import Colors from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks';
|
||||
import { mockNavItems } from '@/services/mockHomeService';
|
||||
import type { NavItem } from '@/types/home';
|
||||
|
||||
@@ -66,7 +66,6 @@ const styles = createThemeStyles((colors) => ({
|
||||
}));
|
||||
|
||||
interface FastFootNavProps {
|
||||
theme: 'light' | 'dark';
|
||||
items?: NavItem[];
|
||||
onTabPress?: (tabId: string, action: string) => void;
|
||||
}
|
||||
@@ -74,8 +73,9 @@ interface FastFootNavProps {
|
||||
/**
|
||||
* 快速底部导航组件
|
||||
*/
|
||||
export default function FastFootNav({ theme, items: propItems, onTabPress }: FastFootNavProps) {
|
||||
const s = styles[theme];
|
||||
export default function FastFootNav({ items: propItems, onTabPress }: FastFootNavProps) {
|
||||
const colorScheme = useColorScheme();
|
||||
const s = styles[colorScheme];
|
||||
const [items, setItems] = useState<NavItem[]>(propItems || []);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
|
||||
|
||||
@@ -5,19 +5,37 @@
|
||||
*/
|
||||
|
||||
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 { styles } from './styles';
|
||||
import { useGameMainMenus, useMenuDataLoaded } from '@/hooks/useGameMenus';
|
||||
import { useGameMainMenus, useMenuDataLoaded, useSelectedCategory } from '@/hooks/useGameMenus';
|
||||
// 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 {
|
||||
theme: ThemeEnum;
|
||||
selectedCategory?: string;
|
||||
onCategorySelect?: (categoryId: string) => void;
|
||||
topHeight?: number;
|
||||
showSubMenus?: boolean;
|
||||
}
|
||||
@@ -26,16 +44,17 @@ interface GameMainMenuProps {
|
||||
* 游戏分类菜单组件
|
||||
*/
|
||||
export default function GameMainMenu({
|
||||
theme,
|
||||
selectedCategory = '103',
|
||||
onCategorySelect,
|
||||
topHeight = 0,
|
||||
showSubMenus = true,
|
||||
}: GameMainMenuProps) {
|
||||
const theme = useColorScheme();
|
||||
const s = styles[theme];
|
||||
const scrollViewRef = useRef<ScrollView>(null);
|
||||
const gameMenus = useGameMainMenus(theme);
|
||||
|
||||
// 从 hook 获取选中分类和更新方法
|
||||
const { selectedCategory, setSelectedCategory } = useSelectedCategory();
|
||||
|
||||
// 检查数据加载完成
|
||||
const isDataLoaded = useMenuDataLoaded();
|
||||
|
||||
@@ -55,9 +74,12 @@ export default function GameMainMenu({
|
||||
}, [selectedIndex]);
|
||||
|
||||
// 使用 useCallback 稳定 onPress 回调
|
||||
const handleCategoryPress = useCallback((categoryKey: string) => {
|
||||
onCategorySelect?.(categoryKey);
|
||||
}, [onCategorySelect]);
|
||||
const handleCategoryPress = useCallback(
|
||||
(categoryKey: string) => {
|
||||
setSelectedCategory(categoryKey);
|
||||
},
|
||||
[setSelectedCategory]
|
||||
);
|
||||
|
||||
// 骨架屏 - 显示加载中的占位符
|
||||
const renderSkeleton = () => (
|
||||
@@ -68,16 +90,16 @@ export default function GameMainMenu({
|
||||
showsHorizontalScrollIndicator={false}
|
||||
scrollEventThrottle={16}
|
||||
>
|
||||
{[1, 2, 3, 4, 5].map((index) => (
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<View
|
||||
key={`skeleton-${index}`}
|
||||
style={[s.categoryItem, { backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0' }]}
|
||||
style={[s.menuItem, { backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0' }]}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
|
||||
console.log('isDataLoaded', isDataLoaded);
|
||||
// 如果动态数据还未加载,显示骨架屏
|
||||
if (!isDataLoaded) {
|
||||
return renderSkeleton();
|
||||
@@ -92,20 +114,105 @@ export default function GameMainMenu({
|
||||
showsHorizontalScrollIndicator={false}
|
||||
scrollEventThrottle={16}
|
||||
>
|
||||
{gameMenus.map((menu) => (
|
||||
{gameMenus.map((menu) => {
|
||||
// 处理图片源 - 优先使用 logo(URL),其次使用 icon(本地资源)
|
||||
let imageSource: any = null;
|
||||
|
||||
if (menu.logo) {
|
||||
// logo 是 URL,直接使用
|
||||
imageSource = { uri: menu.logo };
|
||||
} else if (menu.icon) {
|
||||
// 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={[s.menuItem, selectedCategory === menu.key && s.menuItemActive]}
|
||||
style={webStyle}
|
||||
onPress={() => handleCategoryPress(menu.key)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text
|
||||
style={[s.menuText, selectedCategory === menu.key && s.menuTextActive]}
|
||||
>
|
||||
{menu.icon || '🎮'} {menu.name}
|
||||
</Text>
|
||||
{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}
|
||||
>
|
||||
{menuContent}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createThemeStyles } from '@/theme';
|
||||
import { createThemeStyles, createResponsiveThemeStyles } from '@/theme';
|
||||
import { Dimensions } from 'react-native';
|
||||
|
||||
// const { width } = Dimensions.get('window');
|
||||
@@ -10,7 +10,7 @@ import { Dimensions } from 'react-native';
|
||||
export const styles = createThemeStyles((colors) => ({
|
||||
container: {
|
||||
backgroundColor: colors.background,
|
||||
paddingVertical: 10,
|
||||
paddingTop: 5,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.border,
|
||||
},
|
||||
@@ -18,11 +18,11 @@ export const styles = createThemeStyles((colors) => ({
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
menuItem: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 5,
|
||||
marginRight: 8,
|
||||
borderRadius: 22,
|
||||
backgroundColor: colors.backgroundSecondary,
|
||||
borderRadius: 0,
|
||||
backgroundColor: 'transparent',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
elevation: 1,
|
||||
@@ -30,18 +30,41 @@ export const styles = createThemeStyles((colors) => ({
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 2,
|
||||
borderBottomColor: 'transparent',
|
||||
borderBottomWidth: 2,
|
||||
},
|
||||
menuItemActive: {
|
||||
backgroundColor: colors.primary,
|
||||
// backgroundColor: `${colors.tint}15`, // 主题色 + 20% 透明度
|
||||
elevation: 2,
|
||||
shadowOpacity: 0.15,
|
||||
borderBottomColor: colors.tint,
|
||||
borderRadius: 0,
|
||||
},
|
||||
menuText: {
|
||||
fontSize: 13,
|
||||
fontSize: 14,
|
||||
color: colors.text,
|
||||
fontWeight: '600',
|
||||
},
|
||||
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';
|
||||
import { useColorScheme } from '@/hooks';
|
||||
import { createThemeStyles } from '@/theme';
|
||||
import Colors from '@/constants/Colors';
|
||||
import { Colors } from '@/theme';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
@@ -88,7 +88,6 @@ const styles = createThemeStyles((colors) => ({
|
||||
}));
|
||||
|
||||
interface HeaderProps {
|
||||
theme?: 'light' | 'dark';
|
||||
onSearch?: (keyword: string) => void;
|
||||
onMessagePress?: () => void;
|
||||
onUserPress?: () => void;
|
||||
@@ -99,16 +98,14 @@ interface HeaderProps {
|
||||
* Header 组件
|
||||
*/
|
||||
export default function Header({
|
||||
theme = 'light',
|
||||
onSearch,
|
||||
onMessagePress,
|
||||
onUserPress,
|
||||
unreadCount = 0,
|
||||
}: HeaderProps) {
|
||||
const colorScheme = useColorScheme();
|
||||
const actualTheme = theme === 'light' || theme === 'dark' ? theme : colorScheme;
|
||||
const s = styles[actualTheme];
|
||||
const colors = Colors[actualTheme];
|
||||
const s = styles[colorScheme];
|
||||
const colors = Colors[colorScheme];
|
||||
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
Image,
|
||||
} from 'react-native';
|
||||
import { createThemeStyles } from '@/theme';
|
||||
import Colors from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks';
|
||||
import { mockHighPrizeGames } from '@/services/mockHomeService';
|
||||
import type { HighPrizeGame as HighPrizeGameType } from '@/types/home';
|
||||
|
||||
@@ -85,7 +85,6 @@ const styles = createThemeStyles((colors) => ({
|
||||
}));
|
||||
|
||||
interface HighPrizeGameProps {
|
||||
theme: 'light' | 'dark';
|
||||
games?: HighPrizeGameType[];
|
||||
onGamePress?: (game: HighPrizeGameType) => void;
|
||||
}
|
||||
@@ -93,8 +92,9 @@ interface HighPrizeGameProps {
|
||||
/**
|
||||
* 高奖金游戏组件
|
||||
*/
|
||||
export default function HighPrizeGame({ theme, games: propGames, onGamePress }: HighPrizeGameProps) {
|
||||
const s = styles[theme];
|
||||
export default function HighPrizeGame({ games: propGames, onGamePress }: HighPrizeGameProps) {
|
||||
const colorScheme = useColorScheme();
|
||||
const s = styles[colorScheme];
|
||||
const [games, setGames] = useState<HighPrizeGameType[]>(propGames || []);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
ActivityIndicator,
|
||||
Dimensions,
|
||||
} from 'react-native';
|
||||
import { createThemeStyles } from '@/theme';
|
||||
import Colors from '@/constants/Colors';
|
||||
import { createThemeStyles, useColorScheme, useThemeInfo } from '@/theme';
|
||||
import { useSelectedCategory } from '@/hooks/useGameMenus';
|
||||
import { getMockGamesByCategory } from '@/services/mockHomeService';
|
||||
import type { Game } from '@/types/home';
|
||||
|
||||
@@ -100,9 +100,7 @@ const styles = createThemeStyles((colors) => ({
|
||||
}));
|
||||
|
||||
interface LobbyProps {
|
||||
theme: 'light' | 'dark';
|
||||
games?: Game[];
|
||||
selectedCategory?: number;
|
||||
onGamePress?: (game: Game) => void;
|
||||
topHeight?: number;
|
||||
}
|
||||
@@ -111,13 +109,14 @@ interface LobbyProps {
|
||||
* 游戏大厅组件
|
||||
*/
|
||||
export default function Lobby({
|
||||
theme,
|
||||
games: propGames,
|
||||
selectedCategory = 0,
|
||||
onGamePress,
|
||||
topHeight = 0,
|
||||
}: 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 [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -179,7 +178,7 @@ export default function Lobby({
|
||||
if (loading) {
|
||||
return (
|
||||
<View style={s.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={Colors[theme].primary} />
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
Dimensions,
|
||||
} from 'react-native';
|
||||
import { createThemeStyles } from '@/theme';
|
||||
import Colors from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks';
|
||||
import { mockNotices } from '@/services/mockHomeService';
|
||||
import type { Notice } from '@/types/home';
|
||||
|
||||
@@ -66,7 +66,6 @@ const styles = createThemeStyles((colors) => ({
|
||||
}));
|
||||
|
||||
interface NoticeBarProps {
|
||||
theme: 'light' | 'dark';
|
||||
notices?: Notice[];
|
||||
onNoticePress?: (notice: Notice) => void;
|
||||
}
|
||||
@@ -74,8 +73,9 @@ interface NoticeBarProps {
|
||||
/**
|
||||
* 公告栏组件
|
||||
*/
|
||||
export default function NoticeBar({ theme, notices: propNotices, onNoticePress }: NoticeBarProps) {
|
||||
const s = styles[theme];
|
||||
export default function NoticeBar({ notices: propNotices, onNoticePress }: NoticeBarProps) {
|
||||
const colorScheme = useColorScheme();
|
||||
const s = styles[colorScheme];
|
||||
const [notices, setNotices] = useState<Notice[]>(propNotices || []);
|
||||
const [currentNotice, setCurrentNotice] = useState(0);
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import React from 'react';
|
||||
import BannerSwiperComponent from './BannerSwiper';
|
||||
import NoticeBarComponent from './NoticeBar';
|
||||
import GameCategoryMenuComponent from './GameCategoryMenu';
|
||||
import GameMainMenusComponent from './GameMainMenus';
|
||||
import LobbyComponent from './Lobby';
|
||||
import HighPrizeGameComponent from './HighPrizeGame';
|
||||
import FastFootNavComponent from './FastFootNav';
|
||||
@@ -17,7 +17,7 @@ import BottomTabsComponent from './BottomTabs';
|
||||
// 使用 React.memo 优化组件性能,避免不必要的重新渲染
|
||||
export const BannerSwiper = React.memo(BannerSwiperComponent);
|
||||
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 HighPrizeGame = React.memo(HighPrizeGameComponent);
|
||||
export const FastFootNav = React.memo(FastFootNavComponent);
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
/**
|
||||
* 首页主容器组件
|
||||
*
|
||||
* 支持浅色/深色主题,包含完整的首页功能:
|
||||
* - Header(搜索、用户信息)
|
||||
* - 轮播图
|
||||
* - 游戏分类菜单
|
||||
* - 游戏大厅
|
||||
* - 公告栏
|
||||
* - 高奖金游戏(深色主题)
|
||||
* - 快速导航(深色主题)
|
||||
* - BottomTabs(底部导航)
|
||||
* 完整首页容器
|
||||
* 包含 Header、内容区域、BottomTabs
|
||||
* 支持主题切换和真实数据
|
||||
*/
|
||||
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { ScrollView, StyleSheet, RefreshControl } from 'react-native';
|
||||
import { useColorScheme } from '@/hooks';
|
||||
import { createThemeStyles } from '@/theme';
|
||||
import Colors from '@/constants/Colors';
|
||||
import HomeScreenComplete from './HomeScreenComplete';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { View, ScrollView, RefreshControl, Alert } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { createThemeStyles, useColorScheme, useThemeInfo } from '@/theme';
|
||||
import {
|
||||
Header,
|
||||
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,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
scrollView: {
|
||||
contentContainer: {
|
||||
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);
|
||||
// 模拟刷新延迟
|
||||
setTimeout(() => {
|
||||
setRefreshing(false);
|
||||
}, 1000);
|
||||
/**
|
||||
* 完整首页容器
|
||||
*/
|
||||
export default function HomePage() {
|
||||
const colorScheme = useColorScheme();
|
||||
const s = styles[colorScheme];
|
||||
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 (
|
||||
<HomeScreenComplete
|
||||
theme={theme}
|
||||
isDarkTheme={theme === 'dark'}
|
||||
<>
|
||||
<GameMainMenus />
|
||||
<BannerSwiper />
|
||||
<NoticeBar />
|
||||
<HighPrizeGame onGamePress={handleGamePress} />
|
||||
<Lobby onGamePress={handleGamePress} />
|
||||
<FastFootNav onTabPress={handleTabPress} />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
// 浅色主题布局
|
||||
return (
|
||||
<>
|
||||
<BannerSwiper />
|
||||
<NoticeBar />
|
||||
<GameMainMenus />
|
||||
<Lobby onGamePress={handleGamePress} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={s.container}>
|
||||
{/* Header */}
|
||||
<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:
|
||||
specifier: 0.81.5
|
||||
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:
|
||||
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)
|
||||
@@ -2985,6 +2988,12 @@ packages:
|
||||
react: '*'
|
||||
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:
|
||||
resolution: {integrity: sha512-eaIH5bUQjJ/mYm4AkI6caaiyc7BcHDwX6CqNDi6RIxfxfWxROsHpll1oBuwn/cFvknvA8uEAkqLk/vzVihI3AQ==}
|
||||
peerDependencies:
|
||||
@@ -7208,6 +7217,11 @@ snapshots:
|
||||
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):
|
||||
dependencies:
|
||||
'@callstack/react-theme-provider': 3.0.9(react@19.1.0)
|
||||
|
||||
@@ -224,7 +224,7 @@ export const getMockHomePageData = () => {
|
||||
/**
|
||||
* 获取 Mock 游戏列表(支持分类过滤)
|
||||
*/
|
||||
export const getMockGamesByCategory = (categoryId: number) => {
|
||||
export const getMockGamesByCategory = (categoryId: string | number) => {
|
||||
if (categoryId === 0) {
|
||||
return mockGames; // 推荐分类返回所有游戏
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ interface State {
|
||||
gamesTryPlayIds: number[];
|
||||
smallClassGames: Record<string, any>;
|
||||
gameBigClass: Record<string, any>;
|
||||
selectedCategory: string; // 当前选中的游戏分类
|
||||
}
|
||||
|
||||
// 操作
|
||||
@@ -38,6 +39,7 @@ interface Actions {
|
||||
setHomeHotGames: (data: Record<string, any>[]) => void;
|
||||
setSmallClassGame: (data: Record<string, any>) => void;
|
||||
setGameBigClass: (data: Record<string, any>) => void;
|
||||
setSelectedCategory: (categoryId: string) => void; // 设置选中的游戏分类
|
||||
// requestHomePageData: (data?: Record<string, any>) => Promise<any>;
|
||||
}
|
||||
|
||||
@@ -58,6 +60,7 @@ const useGameStore = create<State & Actions>()((set, get) => ({
|
||||
gamesTryPlayIds: [], // 试玩游戏id列表
|
||||
smallClassGames: {},
|
||||
gameBigClass: {},
|
||||
selectedCategory: '103', // 默认选中推荐分类
|
||||
|
||||
|
||||
// 保存首页数据
|
||||
@@ -157,6 +160,12 @@ const useGameStore = create<State & Actions>()((set, get) => ({
|
||||
[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 恢复状态的函数
|
||||
|
||||
@@ -8,7 +8,6 @@ export {
|
||||
useUser,
|
||||
useIsLoggedIn,
|
||||
useToken,
|
||||
useUserActions,
|
||||
restoreUserState,
|
||||
} from './userStore';
|
||||
export type { User } from './userStore';
|
||||
@@ -21,7 +20,6 @@ export {
|
||||
useNotificationsEnabled,
|
||||
useSoundEnabled,
|
||||
useHapticsEnabled,
|
||||
useSettingsActions,
|
||||
restoreSettingsState,
|
||||
} 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 {
|
||||
const stored = await storageManager.local.getItem(STORAGE_KEYS.SETTINGS_STORE);
|
||||
if (stored) {
|
||||
const state = JSON.parse(stored);
|
||||
useSettingsStore.setState(state);
|
||||
useSettingsStore.setState(stored);
|
||||
if (__DEV__) {
|
||||
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);
|
||||
|
||||
// 获取设置操作方法
|
||||
// 使用 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 避免每次渲染都返回新对象
|
||||
export const useUserActions = () =>
|
||||
useUserStore(
|
||||
useShallow((state) => ({
|
||||
setUser: state.setUser,
|
||||
setToken: state.setToken,
|
||||
login: state.login,
|
||||
logout: state.logout,
|
||||
updateUser: state.updateUser,
|
||||
}))
|
||||
);
|
||||
// export const useUserActions = () =>
|
||||
// useUserStore(
|
||||
// useShallow((state) => ({
|
||||
// setUser: state.setUser,
|
||||
// setToken: state.setToken,
|
||||
// login: state.login,
|
||||
// logout: state.logout,
|
||||
// updateUser: state.updateUser,
|
||||
// }))
|
||||
// );
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
* 可以通过 settingsStore 切换主题
|
||||
*/
|
||||
|
||||
const tintColorLight = '#007AFF';
|
||||
const tintColorDark = '#0A84FF';
|
||||
const tintColorLight = '#10c8e3';
|
||||
const tintColorDark = '#ffd69f';
|
||||
const tintColorOrange = '#bd9534';
|
||||
|
||||
export default {
|
||||
light: {
|
||||
@@ -71,7 +72,7 @@ export default {
|
||||
backgroundTertiary: '#E5E5E5',
|
||||
|
||||
// 主题色
|
||||
tint: tintColorLight,
|
||||
tint: tintColorOrange,
|
||||
primary: '#007AFF',
|
||||
secondary: '#5856D6',
|
||||
success: '#34C759',
|
||||
@@ -85,7 +86,7 @@ export default {
|
||||
|
||||
// Tab 图标
|
||||
tabIconDefault: '#8E8E93',
|
||||
tabIconSelected: tintColorLight,
|
||||
tabIconSelected: tintColorOrange,
|
||||
|
||||
// 卡片
|
||||
card: '#FFFFFF',
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
// 导出颜色配置
|
||||
export { default as Colors } from '@/constants/Colors';
|
||||
export { default as Colors } from './Colors';
|
||||
|
||||
// 导出主题 Hooks
|
||||
export {
|
||||
@@ -23,6 +23,9 @@ export {
|
||||
View as ThemeView,
|
||||
} from '@/components/Themed';
|
||||
|
||||
// 导出主题类型
|
||||
export { ThemeEnum } from '@/constants/theme';
|
||||
|
||||
export type {
|
||||
ThemedTextProps,
|
||||
ThemedViewProps,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { StyleSheet, TextStyle, ViewStyle } from 'react-native';
|
||||
import Colors from '@/constants/Colors';
|
||||
import { Colors } from '@/theme';
|
||||
import { ThemeEnum } from '@/constants/theme';
|
||||
|
||||
/**
|
||||
@@ -53,7 +53,7 @@ export function createThemeStyles<T extends StyleSheet.NamedStyles<T>>(
|
||||
return {
|
||||
light: StyleSheet.create(createStyles(Colors.light)),
|
||||
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 {
|
||||
light: StyleSheet.create(lightStyles),
|
||||
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';
|
||||
|
||||
/**
|
||||
|
||||