Compare commits

..

No commits in common. '9ef9233797b4574f83915e1ceb47b7dde1e6d520' and '230191f181d9da56a4d178553f337c0f34b83ed5' have entirely different histories.

  1. 26
      app/(tabs)/_layout.tsx
  2. 25
      app/(tabs)/demo.tsx
  3. 200
      app/(tabs)/index.tsx
  4. 4
      app/_layout.tsx
  5. 6
      app/theme-test.tsx
  6. BIN
      assets/images/game/menu/blockThird.png
  7. BIN
      assets/images/game/menu/chess.png
  8. BIN
      assets/images/game/menu/clock-solid.png
  9. BIN
      assets/images/game/menu/clock-solid_dark.png
  10. BIN
      assets/images/game/menu/electronic.png
  11. BIN
      assets/images/game/menu/fishing.png
  12. BIN
      assets/images/game/menu/home-star-solid.png
  13. BIN
      assets/images/game/menu/live.png
  14. BIN
      assets/images/game/menu/lottery.png
  15. BIN
      assets/images/game/menu/recommend.png
  16. BIN
      assets/images/game/menu/sports.png
  17. BIN
      assets/images/game/menu/trial.png
  18. 2
      components/EditScreenInfo.tsx
  19. 165
      components/Header/index.tsx
  20. 4
      components/ThemeDemo.tsx
  21. 2
      components/Themed.tsx
  22. 56
      constants/Colors.ts
  23. 182
      constants/game.ts
  24. 6
      constants/theme.ts
  25. 8
      constants/user.ts
  26. 3
      hooks/index.ts
  27. 155
      hooks/useGameMenus.ts
  28. 28
      hooks/useTheme.ts
  29. 1
      package.json
  30. 154
      pages/HomeScreen/components/BannerSwiper/index.tsx
  31. 61
      pages/HomeScreen/components/BannerSwiper/styles.ts
  32. 126
      pages/HomeScreen/components/BottomTabs.tsx
  33. 143
      pages/HomeScreen/components/FastFootNav.tsx
  34. 219
      pages/HomeScreen/components/GameMainMenus/index.tsx
  35. 70
      pages/HomeScreen/components/GameMainMenus/styles.ts
  36. 177
      pages/HomeScreen/components/Header.tsx
  37. 164
      pages/HomeScreen/components/HighPrizeGame.tsx
  38. 208
      pages/HomeScreen/components/Lobby.tsx
  39. 171
      pages/HomeScreen/components/NoticeBar.tsx
  40. 26
      pages/HomeScreen/components/index.ts
  41. 157
      pages/HomeScreen/index.tsx
  42. 1
      pages/index.ts
  43. 14
      pnpm-lock.yaml
  44. 2
      scripts/proxy-server.js
  45. 37
      services/gameService.ts
  46. 1
      services/index.ts
  47. 233
      services/mockHomeService.ts
  48. 4
      services/tenantService.ts
  49. 267
      stores/gameStore.ts
  50. 12
      stores/index.ts
  51. 72
      stores/msgStore.ts
  52. 34
      stores/settingsStore.ts
  53. 108
      stores/tenantStore.ts
  54. 52
      stores/userStore.ts
  55. 5
      theme/index.ts
  56. 18
      theme/styles.ts
  57. 61
      theme/utils.ts
  58. 121
      types/home.ts
  59. 11
      utils/index.ts
  60. 6
      utils/network/api.ts
  61. 69
      utils/network/helper.ts
  62. 220
      utils/sessionStorage.ts
  63. 184
      utils/storage.ts
  64. 377
      utils/storageManager.ts

26
app/(tabs)/_layout.tsx

@ -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 '@/theme';
import Colors from '@/constants/Colors';
import { useColorScheme, useClientOnlyValue } from '@/hooks';
// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
@ -29,28 +29,42 @@ export default function TabLayout() {
<Tabs.Screen
name="index"
options={{
title: '首页',
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
title: 'Tab One',
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
headerRight: () => (
<Link href="/modal" asChild>
<Pressable>
{({ pressed }) => (
<FontAwesome
name="info-circle"
size={25}
color={Colors[colorScheme ?? 'light'].text}
style={{ marginRight: 15, opacity: pressed ? 0.5 : 1 }}
/>
)}
</Pressable>
</Link>
),
}}
/>
<Tabs.Screen
name="two"
options={{
title: '充值',
title: 'Tab Two',
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
}}
/>
<Tabs.Screen
name="demo"
options={{
title: '活动',
title: '完整示例',
tabBarIcon: ({ color }) => <TabBarIcon name="rocket" color={color} />,
}}
/>
<Tabs.Screen
name="paper"
options={{
title: '我的',
title: 'Paper UI',
tabBarIcon: ({ color }) => <TabBarIcon name="paint-brush" color={color} />,
}}
/>

25
app/(tabs)/demo.tsx

@ -24,8 +24,10 @@ import { useRouter } from 'expo-router';
// 工具函数
import {
storageManager,
Storage,
STORAGE_KEYS,
SessionStorage,
SESSION_KEYS,
formatDate,
formatRelativeTime,
formatChatTime
@ -40,13 +42,18 @@ import {
useTheme,
useLanguage,
useHapticsEnabled,
useSettingsActions,
useTenantStates,
useTenantInfo,
} from '@/stores';
import { useTenantLoad, useTenantInfo } from '@/stores/tenantStore';
// 验证规则
import { loginSchema } from '@/schemas';
import type { LoginFormData } from '@/schemas';
// API 服务
import { authService } from '@/services';
// 自定义 Hooks
import { useDebounce, useThrottle, useHaptics } from '@/hooks';
@ -64,14 +71,14 @@ export default function DemoScreen() {
const login = useUserStore((state) => state.login);
const logout = useUserStore((state) => state.logout);
const tenantLoad = useTenantLoad();
const { tenantLoad } = useTenantStates();
const tenantInfo = useTenantInfo();
// 设置状态
const theme = useTheme();
const language = useLanguage();
const hapticsEnabled = useHapticsEnabled();
const { setTheme, setLanguage, setHapticsEnabled } = useSettingsStore();
const { setTheme, setLanguage, setHapticsEnabled } = useSettingsActions();
// const setTheme = useSettingsStore((state) => state.setTheme);
// const setLanguage = useSettingsStore((state) => state.setLanguage);
// const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);
@ -182,7 +189,7 @@ export default function DemoScreen() {
counter,
};
storageManager.session.setItem(STORAGE_KEYS.USER_PREFERENCES, testData);
await Storage.setObject(STORAGE_KEYS.USER_PREFERENCES, testData);
haptics.success();
Alert.alert('成功', '数据已保存到本地存储');
} catch (error) {
@ -194,7 +201,7 @@ export default function DemoScreen() {
const handleLoadFromStorage = async () => {
try {
haptics.light();
const data = storageManager.session.getItem(STORAGE_KEYS.USER_PREFERENCES);
const data = await Storage.getObject<any>(STORAGE_KEYS.USER_PREFERENCES);
if (data) {
setStorageValue(JSON.stringify(data, null, 2));
@ -222,7 +229,7 @@ export default function DemoScreen() {
counter: Math.floor(Math.random() * 100),
};
storageManager.session.setItem(STORAGE_KEYS.FORM_DRAFT, testData);
SessionStorage.setObject(SESSION_KEYS.FORM_DRAFT, testData);
haptics.success();
Alert.alert('成功', '数据已保存到会话存储(应用重启后会丢失)');
} catch (error) {
@ -234,7 +241,7 @@ export default function DemoScreen() {
const handleLoadFromSession = () => {
try {
haptics.light();
const data = storageManager.session.getItem(STORAGE_KEYS.FORM_DRAFT);
const data = SessionStorage.getObject<any>(SESSION_KEYS.FORM_DRAFT);
if (data) {
setSessionValue(JSON.stringify(data, null, 2));
@ -252,7 +259,7 @@ export default function DemoScreen() {
const handleClearSession = () => {
try {
haptics.light();
storageManager.session.clear();
SessionStorage.clear();
setSessionValue('');
haptics.success();
Alert.alert('成功', '会话存储已清空');

200
app/(tabs)/index.tsx

@ -1,23 +1,187 @@
/**
* -
*
* xinyong-web
* /
*/
import { useState, useEffect } from 'react';
import { StyleSheet, TouchableOpacity, Alert, ActivityIndicator } from 'react-native';
import * as Updates from 'expo-updates';
import { Stack } from 'expo-router';
import HomeScreen from '@/pages/HomeScreen';
import { Text, View } from '@/components/Themed';
export default function TabOneScreen() {
const [isChecking, setIsChecking] = useState(false);
const [updateInfo, setUpdateInfo] = useState<string>('');
const checkForUpdates = async () => {
if (__DEV__) {
Alert.alert('提示', '开发模式下无法检查更新,请使用生产构建测试热更新功能');
return;
}
setIsChecking(true);
setUpdateInfo('正在检查更新...');
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
setUpdateInfo('发现新版本,正在下载...');
await Updates.fetchUpdateAsync();
Alert.alert('更新完成', '新版本已下载完成,是否立即重启应用?', [
{
text: '稍后',
style: 'cancel',
onPress: () => setUpdateInfo('更新已下载,稍后重启应用即可应用'),
},
{
text: '立即重启',
onPress: async () => {
await Updates.reloadAsync();
},
},
]);
} else {
setUpdateInfo('当前已是最新版本');
}
} catch (error) {
setUpdateInfo('检查更新失败: ' + (error as Error).message);
Alert.alert('错误', '检查更新失败,请稍后重试');
} finally {
setIsChecking(false);
}
};
const getUpdateInfo = () => {
const { isEmbeddedLaunch, isEmergencyLaunch, updateId, channel, runtimeVersion } =
Updates.useUpdates();
return `
运行模式: ${__DEV__ ? '开发模式' : '生产模式'}
是否为内嵌启动: ${isEmbeddedLaunch ? '是' : '否'}
是否为紧急启动: ${isEmergencyLaunch ? '是' : '否'}
ID: ${updateId || '无'}
更新通道: ${channel || '无'}
运行时版本: ${runtimeVersion || '无'}
`.trim();
};
useEffect(() => {
console.log('=== TabOneScreen 组件已渲染 ===');
}, []);
export default function TabHoneScreen() {
return (
<>
<Stack.Screen
options={{
title: '首页',
headerShown: false,
}}
/>
<HomeScreen />
</>
<View style={styles.container}>
<Text style={styles.title}>🚀 </Text>
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
<View style={styles.infoContainer}>
<Text style={styles.infoTitle}></Text>
<Text style={styles.infoText}>{getUpdateInfo()}</Text>
</View>
<TouchableOpacity
style={[styles.button, isChecking && styles.buttonDisabled]}
onPress={checkForUpdates}
disabled={isChecking}
>
{isChecking ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}></Text>
)}
</TouchableOpacity>
{updateInfo ? (
<View style={styles.updateInfoContainer}>
<Text style={styles.updateInfoText}>{updateInfo}</Text>
</View>
) : null}
<View style={styles.instructionsContainer}>
<Text style={styles.instructionsTitle}>📝 使</Text>
<Text style={styles.instructionsText}>
1. 使 EAS Build {'\n'}
2. eas update {'\n'}
3. "检查更新"{'\n'}
4.
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 10,
},
separator: {
marginVertical: 20,
height: 1,
width: '80%',
},
infoContainer: {
backgroundColor: 'rgba(0, 122, 255, 0.1)',
padding: 15,
borderRadius: 10,
marginBottom: 20,
width: '100%',
},
infoTitle: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 10,
},
infoText: {
fontSize: 12,
fontFamily: 'monospace',
lineHeight: 18,
},
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 30,
paddingVertical: 15,
borderRadius: 10,
marginBottom: 20,
minWidth: 200,
alignItems: 'center',
},
buttonDisabled: {
backgroundColor: '#999',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
updateInfoContainer: {
backgroundColor: 'rgba(52, 199, 89, 0.1)',
padding: 15,
borderRadius: 10,
marginBottom: 20,
width: '100%',
},
updateInfoText: {
fontSize: 14,
textAlign: 'center',
},
instructionsContainer: {
backgroundColor: 'rgba(255, 149, 0, 0.1)',
padding: 15,
borderRadius: 10,
width: '100%',
},
instructionsTitle: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 10,
},
instructionsText: {
fontSize: 13,
lineHeight: 20,
},
});

4
app/_layout.tsx

@ -12,8 +12,7 @@ import { PaperProvider, MD3DarkTheme, MD3LightTheme } from 'react-native-paper';
// ✅ 从 hooks 目录导入
import { useColorScheme } from '@/hooks';
// ✅ 从 stores 目录导入
import { restoreUserState, restoreSettingsState } from '@/stores';
import { requestTenantInfo } from '@/stores/tenantStore';
import { restoreUserState, restoreSettingsState, useTenantActions } from '@/stores';
export {
// Catch any errors thrown by the Layout component.
@ -33,6 +32,7 @@ export default function RootLayout() {
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
...FontAwesome.font,
});
const { requestTenantInfo } = useTenantActions();
// Expo Router uses Error Boundaries to catch errors in the navigation tree.
useEffect(() => {

6
app/theme-test.tsx

@ -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, useSettingsStore } from '@/stores';
import { useTheme, useSettingsActions } from '@/stores';
import { useHaptics } from '@/hooks';
import { Colors } from '@/theme';
import Colors from '@/constants/Colors';
export default function ThemeTestScreen() {
const currentTheme = useTheme();
const { setTheme } = useSettingsStore();
const { setTheme } = useSettingsActions();
const haptics = useHaptics();
const systemColorScheme = useSystemColorScheme();

BIN
assets/images/game/menu/blockThird.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

BIN
assets/images/game/menu/chess.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

BIN
assets/images/game/menu/clock-solid.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

BIN
assets/images/game/menu/clock-solid_dark.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

BIN
assets/images/game/menu/electronic.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

BIN
assets/images/game/menu/fishing.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

BIN
assets/images/game/menu/home-star-solid.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

BIN
assets/images/game/menu/live.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

BIN
assets/images/game/menu/lottery.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/images/game/menu/recommend.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

BIN
assets/images/game/menu/sports.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

BIN
assets/images/game/menu/trial.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

2
components/EditScreenInfo.tsx

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

165
components/Header/index.tsx

@ -1,165 +0,0 @@
/**
* 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>
);
}

4
components/ThemeDemo.tsx

@ -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, useSettingsStore } from '@/stores';
import { useTheme, useSettingsActions } from '@/stores';
import { useHaptics } from '@/hooks';
export function ThemeDemo() {
const theme = useTheme();
const { setTheme } = useSettingsStore();
const { setTheme } = useSettingsActions();
const haptics = useHaptics();
// 获取主题颜色

2
components/Themed.tsx

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

56
theme/Colors/index.ts → constants/Colors.ts

@ -1,12 +1,12 @@
/**
*
*
* light dark
* settingsStore
*/
const tintColorLight = '#10c8e3';
const tintColorDark = '#ffd69f';
const tintColorOrange = '#bd9534';
const tintColorLight = '#007AFF';
const tintColorDark = '#0A84FF';
export default {
light: {
@ -59,56 +59,6 @@ export default {
// 覆盖层
overlay: 'rgba(0, 0, 0, 0.5)',
},
orange: {
// 文本颜色
text: '#000000',
textSecondary: '#666666',
textTertiary: '#999999',
textInverse: '#FFFFFF',
// 背景颜色
background: '#FFFFFF',
backgroundSecondary: '#F5F5F5',
backgroundTertiary: '#E5E5E5',
// 主题色
tint: tintColorOrange,
primary: '#007AFF',
secondary: '#5856D6',
success: '#34C759',
warning: '#FF9500',
error: '#FF3B30',
info: '#5AC8FA',
// 边框颜色
border: '#E5E5E5',
borderSecondary: '#D1D1D6',
// Tab 图标
tabIconDefault: '#8E8E93',
tabIconSelected: tintColorOrange,
// 卡片
card: '#FFFFFF',
cardShadow: 'rgba(0, 0, 0, 0.1)',
// 输入框
inputBackground: '#FFFFFF',
inputBorder: '#D1D1D6',
inputPlaceholder: '#C7C7CC',
// 按钮
buttonPrimary: '#007AFF',
buttonSecondary: '#5856D6',
buttonDisabled: '#E5E5E5',
buttonText: '#FFFFFF',
// 分隔线
separator: '#E5E5E5',
// 覆盖层
overlay: 'rgba(0, 0, 0, 0.5)',
},
dark: {
// 文本颜色
text: '#FFFFFF',

182
constants/game.ts

@ -1,182 +0,0 @@
import { filter, toNumber, isNaN, forEach } from 'lodash-es';
// 1:棋牌 2:彩票 3:电子 4:捕鱼 5:真人 6:体育 7:红包 8:原创 10: 区块链
export enum GameMainTypesEnum {
CHESS = 1,
LOTTERY = 2,
ELECTRONIC = 3,
FISHING = 4,
LIVE = 5,
SPORTS = 6,
BLOCK = 8,
BLOCK_THIRD = 39,
// 自定义
LOBBY = 100, // 大厅
COLLECT = 101, // 收藏
RECENT = 102, // 最近
RECOMMEND = 103, // 推荐
HIGH_HIT = 104, // 高暴奖
TRIAL = 105, // 试玩
HOT = 0, // 热门
HOT_GAME = 10000, // 热门游戏
HOT_CHESS = 10001, // 热门棋牌
HOT_LOTTERY = 10002, // 热门彩票
HOT_ELECTRONIC = 10003, // 热门电子
HOT_FISHING = 10004, // 热门捕鱼
HOT_LIVE = 10005, // 热门真人
HOT_SPORTS = 10006, // 热门体育
HOT_BLOCK = 10008, // 热门原创
HOT_BLOCK_THIRD = 10039, // 热门区块链
}
export enum GameBigTypesEnum {
CHESS = 1,
ELECTRONIC = 2,
FISHING = 3,
LIVE = 4,
SPORTS = 5,
LOTTERY = 7,
BLOCK_THIRD = 10,
}
// 首页三级菜单
export enum GameChildTypesEnum {
ALL = '1', // 所有
HOT = '2', // 热门
RECENT = '3', // 最近
COLLECT = '4', // 收藏
}
export enum LotteryTypeKeysEnum {
tianCheng = 15,
GPI = 16,
SBCP = 30,
DB = 31,
SGWIN = 32,
}
export enum LotteryTypeIdEnum {
GPI = 504,
DB = 1385,
SBCP = 1326,
SGWIN = 1410,
tianCheng = 468,
}
export const LotteryNameByKey = {
GPI: 'GPI',
DB: 'DB',
SGWIN: 'SGWin',
tianCheng: '天成',
};
export enum GameMainKeysEnum {
CHESS = 'chess',
LOTTERY = 'lottery', // 热门彩票
LOTTERY_ALL = 'lotteryAll', // 全部彩票
ELECTRONIC = 'electronic',
FISHING = 'fishing',
BLOCK_THIRD = 'blockThird',
LIVE = 'live',
SPORTS = 'sports',
BLOCK = 'block',
LOBBY = 'lobby', // 大厅
COLLECT = 'collect', // 收藏
RECENT = 'recent', // 最近
RECOMMEND = 'recommend', // 推荐
HIGH_HIT = 'highHit', // 高暴奖
TRIAL = 'trial', // 试玩
HOT = 'hot', // 热门
HOT_GAME = 'hotGame', // 热门游戏
HOT_CHESS = 'hotChess', // 热门棋牌
HOT_LOTTERY = 'hotLottery', // 热门彩票
HOT_ELECTRONIC = 'hotElectronic', // 热门电子
HOT_FISHING = 'hotFishing', // 热门捕鱼
HOT_LIVE = 'hotLive', // 热门真人
HOT_SPORTS = 'hotSports', // 热门体育
HOT_BLOCK = 'hotBlock', // 热门原创
HOT_BLOCK_THIRD = 'hotBlockThird', // 热门区块链
}
export const gameMainTypesMap = (() => {
const result = {};
forEach(
filter(Object.keys(GameMainTypesEnum), val => isNaN(toNumber(val))),
(key) => {
if (GameMainKeysEnum[key]) {
result[GameMainTypesEnum[key]] = GameMainKeysEnum[key];
}
}
);
console.log('resultresultresult', result);
return result as Record<GameMainTypesEnum, GameMainKeysEnum>;
})();
// 默认首页游戏分类菜单
export const defaultHomeGameTabMenus = [
{ name: '推荐', key: GameMainTypesEnum.RECOMMEND, icon: 'recommend' },
// { name: '试玩', key: GameMainTypesEnum.TRIAL, icon: 'try' },
{ name: '收藏', key: GameMainTypesEnum.COLLECT, icon: 'home-star-solid' },
{ name: '最近', key: GameMainTypesEnum.RECENT, icon: 'clock-solid' },
];
export const gameTypeName = name => {
console.log(name);
let iconName = '';
switch (name) {
case '棋牌':
iconName = 'chess';
break;
case '电子':
iconName = 'electronic';
break;
case '捕鱼':
iconName = 'fishing';
break;
case '真人':
iconName = 'live';
break;
case '体育':
iconName = 'sports';
break;
case '彩票':
iconName = 'lottery';
break;
case '原创':
iconName = 'block';
break;
default:
break;
}
return iconName;
};
export const bigTypeNameConfig = {
1: GameMainKeysEnum.CHESS,
2: GameMainKeysEnum.ELECTRONIC,
3: GameMainKeysEnum.FISHING,
4: GameMainKeysEnum.LIVE,
5: GameMainKeysEnum.SPORTS,
7: GameMainKeysEnum.LOTTERY,
10: GameMainKeysEnum.BLOCK_THIRD,
};
export const serverImgPathMap = {
[GameBigTypesEnum.CHESS]: 'qipai',
[GameBigTypesEnum.ELECTRONIC]: 'dianzi',
[GameBigTypesEnum.FISHING]: 'buyu',
[GameBigTypesEnum.BLOCK_THIRD]: 'qukuailian',
};
export const bigTypeToMainTypeMap = {
1: GameMainTypesEnum.CHESS,
2: GameMainTypesEnum.ELECTRONIC,
3: GameMainTypesEnum.FISHING,
4: GameMainTypesEnum.LIVE,
5: GameMainTypesEnum.SPORTS,
7: GameMainTypesEnum.LOTTERY,
10: GameMainTypesEnum.BLOCK_THIRD,
};

6
constants/theme.ts

@ -1,6 +0,0 @@
// theme enum
export enum ThemeEnum {
LIGHT = 'light',
DARK = 'dark',
ORANGE = 'orange',
}

8
constants/user.ts

@ -1,8 +0,0 @@
// 用户注册方式
export enum RegisterWayEnum {
SMS_REGISTRATION = 1, // 短信注册
ACCOUNT_REGISTRATION = 2, // 账号注册
EMAIL_REGISTRATION = 3, // 邮箱注册
AGENT_ACCOUNT_OPENING_REGISTRATION = 4, // 代理开户注册
BULK_ACCOUNT_OPENING_REGISTRATION = 5, // 批量开户注册
}

3
hooks/index.ts

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

155
hooks/useGameMenus.ts

@ -1,155 +0,0 @@
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 = [
GameMainTypesEnum.CHESS,
GameMainTypesEnum.ELECTRONIC,
GameMainTypesEnum.FISHING,
GameMainTypesEnum.BLOCK_THIRD,
];
export const useGameMainMenus = (theme: ThemeEnum) => {
// 在 hook 顶层调用 useIsLoggedIn
const isLogin = useIsLoggedIn();
// 从 store 获取必要的数据 - 直接获取,不使用 useShallow
const menuSort = useGameStore((state) => state.menuSort);
const gameBigClass = useGameStore((state) => state.gameBigClass);
if (__DEV__) {
console.log('🎮 useGameMainMenus - menuSort:', menuSort, 'length:', menuSort?.length);
}
// 使用 useMemo 缓存计算结果,避免每次都创建新对象
return useMemo(() => {
const defaultMenus = cloneDeep(defaultHomeGameTabMenus);
if (theme === ThemeEnum.DARK) {
forEach(defaultMenus, (item) => {
if (item.key === GameMainTypesEnum.RECENT) {
item.icon = 'clock-solid_dark';
}
});
}
const gameMenu = map(menuSort, (item: Record<string, any>) => {
const typeName = gameMainTypesMap[item.type as GameMainTypesEnum];
const children = hasSubGameMainTypes.includes(item.type)
? map(gameBigClass?.[typeName], (it) => {
return {
name: it.play_cname,
key: `${it.play_id}`,
play_sort: it.play_sort,
darkImgSrc: `/images/game/${typeName}/dark_${it.play_id}.png`,
lightImgSrc: `/images/game/${typeName}/light_${it.play_id}.png`,
colorImgSrc: it.logo3_img_url || `/images/game/${typeName}/color_${it.play_id}.png`,
};
})
: [];
return {
name: item.play_name,
key: `${item.type}`,
icon: typeName,
logo: item.logo_url1,
children,
};
});
if (__DEV__) {
console.log(gameMenu, 'gameMenu');
}
return map(
[
...filter(defaultMenus, (item) => [GameMainTypesEnum.RECOMMEND].includes(item.key)),
...(isLogin
? filter(gameMenu, (item) => Number(item.key) !== GameMainTypesEnum.TRIAL)
: gameMenu),
...filter(defaultMenus, (item) =>
[GameMainTypesEnum.COLLECT, GameMainTypesEnum.RECENT].includes(item.key)
),
],
(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,
};
};

28
hooks/useTheme.ts

@ -7,44 +7,43 @@
import { useMemo } from 'react';
import { useColorScheme as useSystemColorScheme } from 'react-native';
import { useTheme as useThemeStore } from '@/stores';
import { Colors } from '@/theme';
import { ThemeEnum } from '@/constants/theme';
import Colors from '@/constants/Colors';
/**
* light | dark | orange
* light | dark
*
* settingsStore
* 'light' | 'dark' | 'orange' | 'auto'
* 'light' | 'dark' | 'auto'
*/
export function useColorScheme(): ThemeEnum {
export function useColorScheme(): 'light' | 'dark' {
const userTheme = useThemeStore();
const systemTheme = useSystemColorScheme();
// 如果用户选择了 'auto',则使用系统主题
if (userTheme === 'auto') {
return systemTheme === 'dark' ? ThemeEnum.DARK : ThemeEnum.LIGHT;
return systemTheme === 'dark' ? 'dark' : 'light';
}
// 否则使用用户选择的主题
return userTheme as ThemeEnum;
return userTheme;
}
/**
*
*
* @param props - { light?: string; dark?: string; orange?: string }
* @param props - { light?: string; dark?: string }
* @param colorName - Colors
* @returns
*
* @example
* ```tsx
* const textColor = useThemeColor({}, 'text');
* const customColor = useThemeColor({ light: '#000', dark: '#fff', orange: '#f90' }, 'text');
* const customColor = useThemeColor({ light: '#000', dark: '#fff' }, 'text');
* ```
*/
export function useThemeColor(
props: { light?: string; dark?: string; orange?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark & keyof typeof Colors.orange
props: { light?: string; dark?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
): string {
const theme = useColorScheme();
const colorFromProps = props[theme];
@ -84,7 +83,7 @@ export function useThemeColors() {
*
* @example
* ```tsx
* const { theme, colors, isDark, isLight, isOrange } = useThemeInfo();
* const { theme, colors, isDark } = useThemeInfo();
* ```
*/
export function useThemeInfo() {
@ -94,9 +93,8 @@ export function useThemeInfo() {
return useMemo(() => ({
theme,
colors,
isDark: theme === ThemeEnum.DARK,
isLight: theme === ThemeEnum.LIGHT,
isOrange: theme === ThemeEnum.ORANGE,
isDark: theme === 'dark',
isLight: theme === 'light',
}), [theme, colors]);
}

1
package.json

@ -37,7 +37,6 @@
"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",

154
pages/HomeScreen/components/BannerSwiper/index.tsx

@ -1,154 +0,0 @@
/**
*
*
*
*/
import React, { useState, useEffect, useRef, useCallback } from 'react';
import {
View,
Image,
TouchableOpacity,
ScrollView,
NativeScrollEvent,
NativeSyntheticEvent,
ActivityIndicator,
Dimensions,
Alert,
} from 'react-native';
import { useColorScheme } from '@/hooks';
// import type { Banner } from '@/types/home';
import { styles } from './styles';
import useMsgStore from '@/stores/msgStore';
interface BannerSwiperProps {}
const { width } = Dimensions.get('window');
/**
*
*/
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);
const autoPlayTimerRef = useRef<any>(null);
const { homeBanner } = useMsgStore();
// 加载轮播图数据
useEffect(() => {
// 如果有传入的 banners 数据,直接使用
if (homeBanner.length > 0) {
setLoading(false);
return;
}
// 如果没有数据,保持 loading 状态显示骨架屏
}, [homeBanner]);
// 处理 Banner 点击
const onBannerPress = useCallback((banner: Record<string, any>) => {
Alert.alert('轮播图', `点击了: ${banner.title || banner.id}`);
// 这里可以添加导航逻辑
}, []);
// 处理滚动事件
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
const contentOffsetX = event.nativeEvent.contentOffset.x;
const index = Math.round(contentOffsetX / (width - 24));
setCurrentIndex(Math.min(index, homeBanner.length - 1));
};
// 启动自动播放
const startAutoPlay = useCallback(() => {
if (homeBanner.length <= 1) return;
autoPlayTimerRef.current = setInterval(() => {
setCurrentIndex((prev) => {
const nextIndex = (prev + 1) % homeBanner.length;
scrollViewRef.current?.scrollTo({
x: nextIndex * (width - 24),
animated: true,
});
return nextIndex;
});
}, 5000);
}, [homeBanner.length]);
// 停止自动播放
const stopAutoPlay = useCallback(() => {
if (autoPlayTimerRef.current) {
clearInterval(autoPlayTimerRef.current);
}
}, []);
// 自动播放
useEffect(() => {
if (!loading && homeBanner.length > 0) {
startAutoPlay();
}
return () => stopAutoPlay();
}, [loading, homeBanner.length, startAutoPlay, stopAutoPlay]);
// 骨架屏 - 加载中显示占位符
if (loading || homeBanner.length === 0) {
return (
<View style={s.container}>
<View
style={[
s.image,
{
backgroundColor: colorScheme === 'dark' ? '#333' : '#e0e0e0',
},
]}
/>
</View>
);
}
return (
<View style={s.container}>
<ScrollView
ref={scrollViewRef}
style={s.scrollView}
horizontal
pagingEnabled
scrollEventThrottle={16}
onScroll={handleScroll}
showsHorizontalScrollIndicator={false}
onMomentumScrollBegin={stopAutoPlay}
onMomentumScrollEnd={startAutoPlay}
>
{homeBanner.map((banner) => (
<TouchableOpacity
key={banner.id}
onPress={() => onBannerPress(banner)}
activeOpacity={0.9}
>
<Image
source={{ uri: banner.subject }}
style={s.image}
resizeMode="cover"
/>
</TouchableOpacity>
))}
</ScrollView>
{/* 指示器 */}
<View style={s.indicatorContainer}>
{homeBanner.map((_, index) => (
<View
key={index}
style={[
s.indicator,
index === currentIndex && s.indicatorActive,
]}
/>
))}
</View>
</View>
);
}

61
pages/HomeScreen/components/BannerSwiper/styles.ts

@ -1,61 +0,0 @@
import { createThemeStyles } from '@/theme';
import { Dimensions } from 'react-native';
const { width } = Dimensions.get('window');
const BANNER_HEIGHT = width * 0.32534; // 保持 32.534% 的宽高比
/**
*
*/
export const styles = createThemeStyles((colors) => ({
container: {
width: '100%',
height: BANNER_HEIGHT,
backgroundColor: colors.backgroundSecondary,
borderRadius: 12,
overflow: 'hidden',
marginHorizontal: 12,
marginBottom: 12,
elevation: 3,
shadowColor: colors.cardShadow,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.15,
shadowRadius: 6,
},
scrollView: {
width: '100%',
height: '100%',
},
image: {
width: width - 24,
height: BANNER_HEIGHT,
},
indicatorContainer: {
position: 'absolute',
bottom: 12,
left: 0,
right: 0,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
indicator: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: 'rgba(255, 255, 255, 0.5)',
marginHorizontal: 4,
},
indicatorActive: {
width: 12,
height: 8,
backgroundColor: 'rgba(255, 255, 255, 0.95)',
},
loadingContainer: {
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.backgroundSecondary,
},
}));

126
pages/HomeScreen/components/BottomTabs.tsx

@ -1,126 +0,0 @@
/**
* Tabs
*/
import React, { useState, useCallback } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
} from 'react-native';
import { useColorScheme } from '@/hooks';
import { createThemeStyles } from '@/theme';
import { Colors } from '@/theme';
const { width } = Dimensions.get('window');
/**
*
*/
const styles = createThemeStyles((colors) => ({
container: {
backgroundColor: colors.card,
borderTopWidth: 1,
borderTopColor: colors.border,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
paddingBottom: 8,
paddingTop: 8,
},
tabItem: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 8,
},
tabIcon: {
fontSize: 24,
marginBottom: 4,
},
tabLabel: {
fontSize: 12,
color: colors.text + '80',
fontWeight: '500',
},
tabLabelActive: {
color: colors.primary,
fontWeight: '600',
},
}));
interface TabItem {
id: string;
label: string;
icon: string;
action: string;
}
interface BottomTabsProps {
theme?: 'light' | 'dark';
activeTab?: string;
onTabPress?: (tabId: string, action: string) => void;
items?: TabItem[];
}
/**
* Tab
*/
const DEFAULT_TABS: TabItem[] = [
{ id: 'recharge', label: '充值', icon: '💰', action: 'recharge' },
{ id: 'withdraw', label: '提现', icon: '💳', action: 'withdraw' },
{ id: 'activity', label: '活动', icon: '🎉', action: 'activity' },
{ id: 'service', label: '客服', icon: '🎧', action: 'service' },
{ id: 'help', label: '帮助', icon: '❓', action: 'help' },
];
/**
* Tabs
*/
export default function BottomTabs({
theme = 'light',
activeTab = 'recharge',
onTabPress,
items = DEFAULT_TABS,
}: BottomTabsProps) {
const colorScheme = useColorScheme();
const actualTheme = theme === 'light' || theme === 'dark' ? theme : colorScheme;
const s = styles[actualTheme];
const colors = Colors[actualTheme];
const [selectedTab, setSelectedTab] = useState(activeTab);
const handleTabPress = useCallback(
(tabId: string, action: string) => {
setSelectedTab(tabId);
onTabPress?.(tabId, action);
},
[onTabPress]
);
return (
<View style={s.container}>
{items.map((item) => (
<TouchableOpacity
key={item.id}
style={s.tabItem}
onPress={() => handleTabPress(item.id, item.action)}
activeOpacity={0.7}
>
<Text style={s.tabIcon}>{item.icon}</Text>
<Text
style={[
s.tabLabel,
selectedTab === item.id && s.tabLabelActive,
]}
>
{item.label}
</Text>
</TouchableOpacity>
))}
</View>
);
}

143
pages/HomeScreen/components/FastFootNav.tsx

@ -1,143 +0,0 @@
/**
*
*
* 使
*/
import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
Animated,
} from 'react-native';
import { createThemeStyles } from '@/theme';
import { useColorScheme } from '@/hooks';
import { mockNavItems } from '@/services/mockHomeService';
import type { NavItem } from '@/types/home';
/**
*
*/
const styles = createThemeStyles((colors) => ({
container: {
backgroundColor: colors.backgroundSecondary,
paddingVertical: 12,
paddingHorizontal: 12,
borderTopWidth: 1,
borderTopColor: colors.border,
},
scrollView: {
paddingHorizontal: 0,
},
navItem: {
paddingHorizontal: 14,
paddingVertical: 10,
marginRight: 10,
borderRadius: 8,
backgroundColor: colors.background,
justifyContent: 'center',
alignItems: 'center',
minWidth: 75,
elevation: 2,
shadowColor: colors.cardShadow,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
navItemActive: {
backgroundColor: colors.primary,
},
navIcon: {
fontSize: 22,
marginBottom: 4,
},
navText: {
fontSize: 12,
color: colors.text,
textAlign: 'center',
fontWeight: '500',
},
navTextActive: {
color: '#FFFFFF',
},
}));
interface FastFootNavProps {
items?: NavItem[];
onTabPress?: (tabId: string, action: string) => void;
}
/**
*
*/
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);
// 加载导航项数据
useEffect(() => {
if (propItems && propItems.length > 0) {
setItems(propItems);
return;
}
const loadItems = async () => {
try {
// const data = await getNavItems();
// setItems(data.length > 0 ? data : mockNavItems);
} catch (error) {
console.error('加载导航项失败:', error);
setItems(mockNavItems);
}
};
loadItems();
}, [propItems]);
const handleNavPress = (item: NavItem) => {
setSelectedId(item.id);
onTabPress?.(item.id, item.action);
};
if (items.length === 0) {
return null;
}
return (
<View style={s.container}>
<ScrollView
style={s.scrollView}
horizontal
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
>
{items.map((item) => (
<TouchableOpacity
key={item.id}
style={[
s.navItem,
selectedId === item.id && s.navItemActive,
]}
onPress={() => handleNavPress(item)}
activeOpacity={0.7}
>
<Text style={s.navIcon}>{item.icon || '🎮'}</Text>
<Text
style={[
s.navText,
selectedId === item.id && s.navTextActive,
]}
>
{item.name}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
);
}

219
pages/HomeScreen/components/GameMainMenus/index.tsx

@ -1,219 +0,0 @@
/**
*
*
* 使
*/
import React, { useRef, useEffect, useState, useCallback, useMemo } from 'react';
import { View, Text, ScrollView, TouchableOpacity, Animated, Image, Platform } from 'react-native';
// import type { GameCategory } from '@/types/home';
import { styles } from './styles';
import { useGameMainMenus, useMenuDataLoaded, useSelectedCategory } from '@/hooks/useGameMenus';
// import useGameStore from '@/stores/gameStore';
// 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 {
topHeight?: number;
showSubMenus?: boolean;
}
/**
*
*/
export default function GameMainMenu({
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();
// 使用 useMemo 缓存找到的索引,避免每次都重新计算
const selectedIndex = useMemo(() => {
return gameMenus.findIndex((cat) => cat.key === selectedCategory);
}, [selectedCategory, gameMenus]);
// 当分类改变时,滚动到该分类
useEffect(() => {
if (selectedIndex >= 0) {
scrollViewRef.current?.scrollTo({
x: selectedIndex * 100,
animated: true,
});
}
}, [selectedIndex]);
// 使用 useCallback 稳定 onPress 回调
const handleCategoryPress = useCallback(
(categoryKey: string) => {
setSelectedCategory(categoryKey);
},
[setSelectedCategory]
);
// 骨架屏 - 显示加载中的占位符
const renderSkeleton = () => (
<View style={s.container}>
<ScrollView
style={s.scrollView}
horizontal
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
>
{Array.from({ length: 6 }).map((_, index) => (
<View
key={`skeleton-${index}`}
style={[s.menuItem, { backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0' }]}
/>
))}
</ScrollView>
</View>
);
console.log('isDataLoaded', isDataLoaded);
// 如果动态数据还未加载,显示骨架屏
if (!isDataLoaded) {
return renderSkeleton();
}
return (
<View style={s.container}>
<ScrollView
ref={scrollViewRef}
style={s.scrollView}
horizontal
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
>
{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={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}
>
{menuContent}
</TouchableOpacity>
);
})}
</ScrollView>
</View>
);
}

70
pages/HomeScreen/components/GameMainMenus/styles.ts

@ -1,70 +0,0 @@
import { createThemeStyles, createResponsiveThemeStyles } from '@/theme';
import { Dimensions } from 'react-native';
// const { width } = Dimensions.get('window');
// const BANNER_HEIGHT = width * 0.32534; // 保持 32.534% 的宽高比
/**
*
*/
export const styles = createThemeStyles((colors) => ({
container: {
backgroundColor: colors.background,
paddingTop: 5,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
scrollView: {
paddingHorizontal: 12,
},
menuItem: {
paddingHorizontal: 12,
paddingVertical: 5,
marginRight: 8,
borderRadius: 0,
backgroundColor: 'transparent',
justifyContent: 'center',
alignItems: 'center',
elevation: 1,
shadowColor: colors.cardShadow,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
borderBottomColor: 'transparent',
borderBottomWidth: 2,
},
menuItemActive: {
// backgroundColor: `${colors.tint}15`, // 主题色 + 20% 透明度
elevation: 2,
shadowOpacity: 0.15,
borderBottomColor: colors.tint,
borderRadius: 0,
},
menuText: {
fontSize: 14,
color: colors.text,
fontWeight: '600',
},
menuTextActive: {
color: colors.tint,
},
menuIcon: {
width: 30,
height: 30,
marginBottom: 4,
},
}));
export const themeStyles = createResponsiveThemeStyles({
menuItemActive: {
backgroundColor: '',
},
}, {
menuItemActive: {
backgroundColor: '',
},
}, {
menuItemActive: {
backgroundColor: '',
},
});

177
pages/HomeScreen/components/Header.tsx

@ -1,177 +0,0 @@
/**
* Header
*
*/
import React, { useState, useCallback } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
TextInput,
Image,
Dimensions,
} from 'react-native';
import { useColorScheme } from '@/hooks';
import { createThemeStyles } from '@/theme';
import { Colors } 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 colorScheme = useColorScheme();
const s = styles[colorScheme];
const colors = Colors[colorScheme];
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>
);
}

164
pages/HomeScreen/components/HighPrizeGame.tsx

@ -1,164 +0,0 @@
/**
*
*
* 使
*/
import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
Animated,
Image,
} from 'react-native';
import { createThemeStyles } from '@/theme';
import { useColorScheme } from '@/hooks';
import { mockHighPrizeGames } from '@/services/mockHomeService';
import type { HighPrizeGame as HighPrizeGameType } from '@/types/home';
/**
*
*/
const styles = createThemeStyles((colors) => ({
container: {
backgroundColor: colors.background,
paddingVertical: 12,
paddingHorizontal: 12,
marginBottom: 12,
},
title: {
fontSize: 15,
fontWeight: 'bold',
color: colors.text,
marginBottom: 10,
},
scrollView: {
paddingHorizontal: 0,
},
gameItem: {
width: 110,
height: 110,
marginRight: 10,
borderRadius: 12,
backgroundColor: colors.card,
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
elevation: 3,
shadowColor: colors.cardShadow,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.15,
shadowRadius: 4,
},
gameIcon: {
fontSize: 44,
marginBottom: 4,
},
gameName: {
fontSize: 12,
color: colors.text,
textAlign: 'center',
fontWeight: '500',
},
prizeTag: {
position: 'absolute',
top: 6,
right: 6,
backgroundColor: colors.error,
paddingHorizontal: 6,
paddingVertical: 3,
borderRadius: 4,
elevation: 2,
shadowColor: colors.cardShadow,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
},
prizeText: {
fontSize: 10,
color: '#FFFFFF',
fontWeight: 'bold',
},
}));
interface HighPrizeGameProps {
games?: HighPrizeGameType[];
onGamePress?: (game: HighPrizeGameType) => void;
}
/**
*
*/
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);
// 加载高奖金游戏数据
useEffect(() => {
if (propGames && propGames.length > 0) {
setGames(propGames);
return;
}
const loadGames = async () => {
try {
// const data = await getHighPrizeGames();
// setGames(data.length > 0 ? data : mockHighPrizeGames);
} catch (error) {
console.error('加载高奖金游戏失败:', error);
setGames(mockHighPrizeGames);
}
};
loadGames();
}, [propGames]);
if (games.length === 0) {
return null;
}
return (
<View style={s.container}>
<Text style={s.title}>🏆 </Text>
<ScrollView
style={s.scrollView}
horizontal
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
>
{games.map((game) => (
<TouchableOpacity
key={game.id}
style={s.gameItem}
onPress={() => {
setSelectedId(game.id);
onGamePress?.(game);
}}
activeOpacity={0.7}
>
{game.icon ? (
<Image
source={{ uri: game.icon }}
style={{ width: '100%', height: '100%' }}
resizeMode="cover"
/>
) : (
<Text style={s.gameIcon}>🎰</Text>
)}
<Text style={s.gameName} numberOfLines={2}>
{game.play_up_name}
</Text>
<View style={s.prizeTag}>
<Text style={s.prizeText}>¥{Math.floor(game.payout_amount / 1000)}k</Text>
</View>
</TouchableOpacity>
))}
</ScrollView>
</View>
);
}

208
pages/HomeScreen/components/Lobby.tsx

@ -1,208 +0,0 @@
/**
*
*
* 使
*/
import React, { useState, useEffect, useMemo } from 'react';
import {
View,
Text,
StyleSheet,
FlatList,
TouchableOpacity,
Image,
ActivityIndicator,
Dimensions,
} from 'react-native';
import { createThemeStyles, useColorScheme, useThemeInfo } from '@/theme';
import { useSelectedCategory } from '@/hooks/useGameMenus';
import { getMockGamesByCategory } from '@/services/mockHomeService';
import type { Game } from '@/types/home';
const { width } = Dimensions.get('window');
/**
*
*/
const styles = createThemeStyles((colors) => ({
container: {
flex: 1,
backgroundColor: colors.background,
paddingHorizontal: 12,
paddingVertical: 12,
},
gameGrid: {
paddingBottom: 20,
},
gameCard: {
flex: 1,
margin: 6,
borderRadius: 12,
overflow: 'hidden',
backgroundColor: colors.card,
elevation: 3,
shadowColor: colors.cardShadow,
shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.15,
shadowRadius: 6,
},
gameCardPressed: {
opacity: 0.8,
},
gameImage: {
width: '100%',
height: 140,
backgroundColor: colors.backgroundSecondary,
justifyContent: 'center',
alignItems: 'center',
},
gameIcon: {
fontSize: 48,
},
gameInfo: {
padding: 10,
},
gameName: {
fontSize: 13,
fontWeight: '600',
color: colors.text,
marginBottom: 6,
},
gameButton: {
backgroundColor: colors.primary,
paddingVertical: 8,
borderRadius: 6,
alignItems: 'center',
marginTop: 6,
},
gameButtonText: {
color: '#FFFFFF',
fontSize: 12,
fontWeight: '600',
},
emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 40,
},
emptyText: {
fontSize: 14,
color: colors.textSecondary,
marginTop: 12,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
}));
interface LobbyProps {
games?: Game[];
onGamePress?: (game: Game) => void;
topHeight?: number;
}
/**
*
*/
export default function Lobby({
games: propGames,
onGamePress,
topHeight = 0,
}: LobbyProps) {
const colorScheme = useColorScheme();
const s = styles[colorScheme];
const { colors } = useThemeInfo();
const { selectedCategory } = useSelectedCategory();
const [games, setGames] = useState<Game[]>(propGames || []);
const [loading, setLoading] = useState(true);
// 加载游戏数据
useEffect(() => {
if (propGames && propGames.length > 0) {
setGames(propGames);
setLoading(false);
return;
}
const loadGames = async () => {
try {
setLoading(true);
// const response = await getGames(selectedCategory);
// setGames(response.games.length > 0 ? response.games : getMockGamesByCategory(selectedCategory));
} catch (error) {
console.error('加载游戏失败:', error);
setGames(getMockGamesByCategory(selectedCategory));
} finally {
setLoading(false);
}
};
loadGames();
}, [propGames, selectedCategory]);
const renderGameCard = ({ item }: { item: Game }) => (
<TouchableOpacity
style={s.gameCard}
activeOpacity={0.7}
onPress={() => onGamePress?.(item)}
>
<View style={s.gameImage}>
{item.icon ? (
<Image
source={{ uri: item.icon }}
style={{ width: '100%', height: '100%' }}
resizeMode="cover"
/>
) : (
<Text style={s.gameIcon}>🎮</Text>
)}
</View>
<View style={s.gameInfo}>
<Text style={s.gameName} numberOfLines={2}>
{item.play_up_name}
{item.play_cname ? ` - ${item.play_cname}` : ''}
</Text>
<TouchableOpacity
style={s.gameButton}
onPress={() => onGamePress?.(item)}
>
<Text style={s.gameButtonText}></Text>
</TouchableOpacity>
</View>
</TouchableOpacity>
);
if (loading) {
return (
<View style={s.loadingContainer}>
<ActivityIndicator size="large" color={colors.primary} />
</View>
);
}
if (games.length === 0) {
return (
<View style={s.emptyContainer}>
<Text style={{ fontSize: 40 }}>🎮</Text>
<Text style={s.emptyText}></Text>
</View>
);
}
return (
<View style={s.container}>
<FlatList
data={games}
renderItem={renderGameCard}
keyExtractor={(item) => item.id}
numColumns={2}
scrollEnabled={false}
contentContainerStyle={s.gameGrid}
/>
</View>
);
}

171
pages/HomeScreen/components/NoticeBar.tsx

@ -1,171 +0,0 @@
/**
*
*
* 使
*/
import React, { useState, useEffect, useRef } from 'react';
import {
View,
Text,
StyleSheet,
Animated,
TouchableOpacity,
Dimensions,
} from 'react-native';
import { createThemeStyles } from '@/theme';
import { useColorScheme } from '@/hooks';
import { mockNotices } from '@/services/mockHomeService';
import type { Notice } from '@/types/home';
const { width } = Dimensions.get('window');
/**
*
*/
const styles = createThemeStyles((colors) => ({
container: {
backgroundColor: colors.backgroundSecondary,
paddingVertical: 10,
paddingHorizontal: 12,
marginHorizontal: 12,
marginBottom: 12,
borderRadius: 8,
flexDirection: 'row',
alignItems: 'center',
elevation: 2,
shadowColor: colors.cardShadow,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 3,
},
label: {
fontSize: 12,
fontWeight: 'bold',
color: '#FFFFFF',
marginRight: 8,
backgroundColor: colors.primary,
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 4,
},
content: {
flex: 1,
fontSize: 12,
color: colors.text,
overflow: 'hidden',
},
closeButton: {
marginLeft: 8,
padding: 4,
},
closeText: {
fontSize: 16,
color: colors.textSecondary,
},
}));
interface NoticeBarProps {
notices?: Notice[];
onNoticePress?: (notice: Notice) => void;
}
/**
*
*/
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);
const animatedValue = useRef(new Animated.Value(1)).current;
// 加载公告数据
useEffect(() => {
if (propNotices && propNotices.length > 0) {
setNotices(propNotices);
return;
}
const loadNotices = async () => {
try {
// const data = await getNotices();
// setNotices(data.length > 0 ? data : mockNotices);
} catch (error) {
console.error('加载公告失败:', error);
setNotices(mockNotices);
}
};
loadNotices();
}, [propNotices]);
// 自动切换公告
useEffect(() => {
if (notices.length === 0) return;
const timer = setInterval(() => {
setCurrentNotice((prev) => (prev + 1) % notices.length);
}, 5000);
return () => clearInterval(timer);
}, [notices.length]);
// 处理关闭公告
const handleClose = () => {
Animated.timing(animatedValue, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start(() => {
setVisible(false);
});
};
// 处理公告点击
const handleNoticePress = () => {
if (notices.length > 0) {
onNoticePress?.(notices[currentNotice]);
}
};
if (!visible || notices.length === 0) {
return null;
}
const currentNoticeData = notices[currentNotice];
return (
<Animated.View
style={[
s.container,
{
opacity: animatedValue,
transform: [
{
scaleY: animatedValue,
},
],
},
]}
>
<Text style={s.label}>📢</Text>
<TouchableOpacity
style={{ flex: 1 }}
onPress={handleNoticePress}
activeOpacity={0.7}
>
<Text style={s.content} numberOfLines={1}>
{currentNoticeData.title || currentNoticeData.content}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={s.closeButton}
onPress={handleClose}
>
<Text style={s.closeText}></Text>
</TouchableOpacity>
</Animated.View>
);
}

26
pages/HomeScreen/components/index.ts

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

157
pages/HomeScreen/index.tsx

@ -1,157 +0,0 @@
/**
*
* HeaderBottomTabs
*
*/
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';
/**
*
*/
const styles = createThemeStyles((colors) => ({
container: {
flex: 1,
backgroundColor: colors.background,
},
contentContainer: {
flex: 1,
},
scrollContent: {
paddingBottom: 20,
},
}));
/**
*
*/
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 (
<>
<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>
);
}

1
pages/index.ts

@ -37,5 +37,4 @@
// 导出业务页面组件
export { default as TestPage } from './TestPage';
export { default as HomeScreen } from './HomeScreen';

14
pnpm-lock.yaml

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

2
scripts/proxy-server.js

@ -47,7 +47,7 @@ app.use(cors({
const API_TARGET = process.env.API_TARGET || 'https://51zhh5.notbug.org';
// 代理路径列表
const PROXY_PATHS = ['/api/v2'];
const PROXY_PATHS = ['/api/v1', '/api/v2', '/api/v3'];
// 为每个路径配置代理
PROXY_PATHS.forEach((path) => {

37
services/gameService.ts

@ -1,37 +0,0 @@
/**
*
* API
*/
import { request } from '@/utils/network/api';
/**
* API
*/
interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
}
/**
* tenant
*/
class GameService {
/**
*
*/
getHomePageData(data?: Record<string, any>): Promise<ApiResponse> {
return request.post('/v2', data, {
headers: {
cmdId: 381119,
paramType: 1,
apiName: 'getHomePageData',
},
});
}
}
// 导出单例
export const gameService = new GameService();
export default gameService;

1
services/index.ts

@ -5,5 +5,4 @@
export { default as authService } from './authService';
export { default as userService } from './userService';
export { default as tenantService } from './tenantService';
export { default as gameService } from './gameService';

233
services/mockHomeService.ts

@ -1,233 +0,0 @@
/**
* Mock
* API
*/
import type {
Banner,
Notice,
GameCategory,
Game,
HighPrizeGame,
NavItem,
} from '@/types/home';
/**
* Mock
*/
export const mockBanners: Banner[] = [
{
id: '1',
subject: 'https://via.placeholder.com/1080x350/FF6B6B/FFFFFF?text=Banner+1',
link_type: 1,
content: 'https://example.com',
title: '新年大优惠',
description: '充值送彩金',
},
{
id: '2',
subject: 'https://via.placeholder.com/1080x350/4ECDC4/FFFFFF?text=Banner+2',
link_type: 1,
content: 'https://example.com',
title: '周末狂欢',
description: '返水最高50%',
},
{
id: '3',
subject: 'https://via.placeholder.com/1080x350/45B7D1/FFFFFF?text=Banner+3',
link_type: 1,
content: 'https://example.com',
title: '限时活动',
description: '邀请好友送奖金',
},
];
/**
* Mock
*/
export const mockNotices: Notice[] = [
{
id: '1',
title: '系统维护通知',
content: '系统将于今晚22:00-23:00进行维护,期间无法正常使用',
content_type: 1,
create_time: '2025-11-08 10:00:00',
formatDate: '2025-11-08',
},
{
id: '2',
title: '新游戏上线',
content: '全新游戏《幸运转盘》已上线,欢迎体验',
content_type: 1,
create_time: '2025-11-07 15:30:00',
formatDate: '2025-11-07',
},
{
id: '3',
title: '活动规则更新',
content: '详见活动页面',
content_type: 3,
create_time: '2025-11-06 09:00:00',
formatDate: '2025-11-06',
},
];
/**
* Mock
*/
export const mockGameCategories: GameCategory[] = [
{ id: 0, key: 'recommend', name: '推荐', icon: 'star', big_type: 0 },
{ id: 1, key: 'chess', name: '棋牌', icon: 'chess', big_type: 1 },
{ id: 2, key: 'electronic', name: '电子', icon: 'game', big_type: 2 },
{ id: 3, key: 'fishing', name: '捕鱼', icon: 'fish', big_type: 3 },
{ id: 4, key: 'sports', name: '体育', icon: 'sports', big_type: 4 },
{ id: 5, key: 'lottery', name: '彩票', icon: 'lottery', big_type: 5 },
{ id: 6, key: 'live', name: '直播', icon: 'live', big_type: 6 },
];
/**
* Mock
*/
export const mockGames: Game[] = [
{
id: '1',
play_id: 1001,
play_up_name: '真人百家乐',
play_cname: '标准版',
icon: 'https://via.placeholder.com/100/FF6B6B/FFFFFF?text=Game+1',
big_type: 1,
index: 1,
},
{
id: '2',
play_id: 1002,
play_up_name: '电子老虎机',
play_cname: '幸运转盘',
icon: 'https://via.placeholder.com/100/4ECDC4/FFFFFF?text=Game+2',
big_type: 2,
index: 2,
},
{
id: '3',
play_id: 1003,
play_up_name: '捕鱼达人',
play_cname: '经典版',
icon: 'https://via.placeholder.com/100/45B7D1/FFFFFF?text=Game+3',
big_type: 3,
index: 3,
},
{
id: '4',
play_id: 1004,
play_up_name: '体育竞技',
play_cname: '足球',
icon: 'https://via.placeholder.com/100/96CEB4/FFFFFF?text=Game+4',
big_type: 4,
index: 4,
},
{
id: '5',
play_id: 1005,
play_up_name: '彩票游戏',
play_cname: '双色球',
icon: 'https://via.placeholder.com/100/FFEAA7/FFFFFF?text=Game+5',
big_type: 5,
index: 5,
},
{
id: '6',
play_id: 1006,
play_up_name: '真人直播',
play_cname: '美女荷官',
icon: 'https://via.placeholder.com/100/DFE6E9/FFFFFF?text=Game+6',
big_type: 6,
index: 6,
},
];
/**
* Mock
*/
export const mockHighPrizeGames: HighPrizeGame[] = [
{
id: '1',
play_id: 1001,
play_up_name: '真人百家乐',
play_cname: '标准版',
icon: 'https://via.placeholder.com/100/FF6B6B/FFFFFF?text=Prize+1',
big_type: 1,
index: 1,
payout_amount: 50000,
bet_amount: 1000,
odds: 50000,
cust_name: '幸运用户***',
avatar: 'https://via.placeholder.com/50/FF6B6B/FFFFFF?text=User',
update_time: '2025-11-08 14:30:00',
},
{
id: '2',
play_id: 1002,
play_up_name: '电子老虎机',
play_cname: '幸运转盘',
icon: 'https://via.placeholder.com/100/4ECDC4/FFFFFF?text=Prize+2',
big_type: 2,
index: 2,
payout_amount: 100000,
bet_amount: 500,
odds: 200000,
cust_name: '幸运玩家***',
avatar: 'https://via.placeholder.com/50/4ECDC4/FFFFFF?text=User',
update_time: '2025-11-08 13:15:00',
},
{
id: '3',
play_id: 1003,
play_up_name: '捕鱼达人',
play_cname: '经典版',
icon: 'https://via.placeholder.com/100/45B7D1/FFFFFF?text=Prize+3',
big_type: 3,
index: 3,
payout_amount: 75000,
bet_amount: 800,
odds: 93750,
cust_name: '大奖得主***',
avatar: 'https://via.placeholder.com/50/45B7D1/FFFFFF?text=User',
update_time: '2025-11-08 12:00:00',
},
];
/**
* Mock
*/
export const mockNavItems: NavItem[] = [
{ id: '1', name: '充值', icon: 'recharge', action: 'recharge' },
{ id: '2', name: '提现', icon: 'withdraw', action: 'withdraw' },
{ id: '3', name: '活动', icon: 'activity', action: 'activity' },
{ id: '4', name: '客服', icon: 'service', action: 'service' },
{ id: '5', name: '帮助', icon: 'help', action: 'help' },
];
/**
* Mock
*/
export const getMockHomePageData = () => {
return {
banners: mockBanners,
notices: mockNotices,
categories: mockGameCategories,
games: mockGames,
highPrizeGames: mockHighPrizeGames,
navItems: mockNavItems,
};
};
/**
* Mock
*/
export const getMockGamesByCategory = (categoryId: string | number) => {
if (categoryId === 0) {
return mockGames; // 推荐分类返回所有游戏
}
return mockGames.filter((game) => game.big_type === categoryId);
};

4
services/tenantService.ts

@ -4,12 +4,13 @@
*/
import { request } from '@/utils/network/api';
// import type { User, UpdateProfileFormData } from '@/schemas/user';
/**
* API
*/
interface ApiResponse<T = any> {
success: string;
code: number;
message: string;
data: T;
}
@ -31,6 +32,7 @@ class TenantService {
cmdId: 371130,
headerType: 1,
apiName: 'getPlatformData',
tid: '',
},
});
}

267
stores/gameStore.ts

@ -1,267 +0,0 @@
/**
*
* 使 Zustand + AsyncStorage
*/
import { create } from 'zustand';
// import { useShallow } from 'zustand/react/shallow';
import storageManager, { STORAGE_KEYS } from '@/utils/storageManager';
import gameService from '@/services/gameService';
import appConfig from '@/utils/config';
import useTenantStore from '@/stores/tenantStore';
import useMsgStore from '@/stores/msgStore';
import { filter, map, concat, cloneDeep, groupBy } from 'lodash-es';
import { GameMainKeysEnum, defaultHomeGameTabMenus } from '@/constants/game';
// 状态
interface State {
appLoginPopType: number;
receiveAwardPopType: number;
appLoginPop: boolean;
menuSort: Record<string, any>[];
rebateGameSort: Record<string, any>[];
originalGames: Record<string, any>[];
blockchainGames: Record<string, any>[];
homeHotGames: Record<string, any>[];
gamesTry: Record<string, any>[];
gamesTryPlayIds: number[];
smallClassGames: Record<string, any>;
gameBigClass: Record<string, any>;
selectedCategory: string; // 当前选中的游戏分类
}
// 操作
interface Actions {
setHomePageData: (data: Record<string, any>) => void;
setOriginalGames: (data: Record<string, any>[]) => void;
setBlockchainGames: (data: Record<string, any>[]) => void;
setGamesTry: (data: Record<string, any>[]) => void;
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>;
}
/**
* Store
*/
const useGameStore = create<State & Actions>()((set, get) => ({
// 初始状态
appLoginPopType: 0,
receiveAwardPopType: 0,
appLoginPop: false,
menuSort: [], // 游戏主菜单
rebateGameSort: [],
originalGames: [], // 原创游戏
blockchainGames: [], // 区块链游戏
homeHotGames: [], // 热门游戏
gamesTry: [], // 试玩游戏
gamesTryPlayIds: [], // 试玩游戏id列表
smallClassGames: {},
gameBigClass: {},
selectedCategory: '103', // 默认选中推荐分类
// 保存首页数据
setHomePageData: (data: any) => {
if (data) {
const { setNotices, setBanners } = useMsgStore.getState();
// 设置注册弹窗
const appLoginPopType = data.appLoginPopType || 0;
const receiveAwardPopType = data.receiveAwardPopType || 0;
const appLoginPop = data.appLoginPop || false;
// 菜单排序
const menuSort = filter(data.version_type, item => item.sort_v !== 0 && item.state == 1).sort((a, b) => {
return b.sort_v - a.sort_v;
});
console.log(menuSort, 'menuSort 1');
// version_shuffle
const rebateGameSort = data.version_shuffle || [];
// 所有游戏销售状态
// gameStore.setGameAllStates(res.data.gameState);
// 公告
setNotices(data.news?.data || []);
// 轮播图
setBanners(data.banners?.data || []);
set({ appLoginPopType, receiveAwardPopType, appLoginPop, menuSort, rebateGameSort });
// 原创游戏
get().setOriginalGames(data.originalGames);
// 区块链游戏
get().setBlockchainGames(data.hsGames);
// 试玩游戏
get().setGamesTry(data.gamesTry);
// 首页热门游戏
get().setHomeHotGames(data.homeHotGames?.[1] || []);
get().setSmallClassGame(data.homeHotGames);
// 三方游戏
get().setGameBigClass(data.thirdGames);
// 手动持久化
// storageManager.setItem(STORAGE_KEYS.TENANT_STORE, JSON.stringify({ tenantInfo: data }));
}
if (__DEV__) {
console.log('💾 Tenant info saved:', data);
}
},
setOriginalGames: (list: Record<string, any>[]) => {
const originalGames = map(list, (item: any) => ({
...item,
isOriginal: true,
})).sort((a: any, b: any) => a.hotVal - b.hotVal);
set({ originalGames });
},
setBlockchainGames: (list: Record<string, any>[]) => {
set({ blockchainGames: list || [] });
},
setGamesTry: (list: Record<string, any>[]) => {
set({ gamesTry: list || [] });
storageManager.session.setItem(STORAGE_KEYS.GAME_TRY, list);
const gamesTryPlayIds = concat(...map(list, item => map(item.subList, subItem => Number(subItem.play_id))));
set({ gamesTryPlayIds });
},
setHomeHotGames: (list: Record<string, any>[]) => {
set({ homeHotGames: list || [] });
},
setSmallClassGame: (data: Record<string, any>) => {
set({ smallClassGames: {
[GameMainKeysEnum.HOT_ELECTRONIC]: data?.[2] || [],
[GameMainKeysEnum.HOT_FISHING]: data?.[3] || [],
[GameMainKeysEnum.HOT_CHESS]: data?.[5] || [],
[GameMainKeysEnum.HOT_BLOCK_THIRD]: data?.[8] || [],
} });
},
setGameBigClass: (data: Record<string, any>) => {
const groupByType = cloneDeep(groupBy(data, item => item.big_type));
set({ gameBigClass: {
[GameMainKeysEnum.CHESS]: groupByType?.[1] || [],
[GameMainKeysEnum.ELECTRONIC]: groupByType?.[2] || [],
[GameMainKeysEnum.FISHING]: groupByType?.[3] || [],
[GameMainKeysEnum.LIVE]: groupByType?.[4] || [],
[GameMainKeysEnum.SPORTS]: groupByType?.[5] || [],
[GameMainKeysEnum.LOTTERY]: groupByType?.[7] || [],
[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 恢复状态的函数
export const restoreGameState = async () => {
try {
const stored = await storageManager.session.getItem(STORAGE_KEYS.TENANT_STORE);
if (stored) {
const state = JSON.parse(stored);
useGameStore.setState(state);
if (__DEV__) {
console.log('✅ Tenant state restored from storage');
}
}
} catch (error) {
console.error('Failed to restore tenant state:', error);
}
};
// 获取首页数据
export const requestHomePageData = async () => {
try {
const { tenantInfo } = useTenantStore.getState();
const params = {
tid: tenantInfo?.tid,
aseq: {
aseq: appConfig.app.aseqId,
},
version_type: {
version_type: 3, // 1 是棋牌, 2 是网赚 3, 是综合彩票
},
news: {
page_start: 1,
num_per_page: 999,
chan_con: 8,
state: 1,
message_id: '10,13', //10公告 13 跑马灯
sort_flag: 1,
vip_level: 0, //userInfo.value.cust_level || 0,
proxy: tenantInfo?.proxy,
apply: 8, // 1 棋牌;2 网赚;3 综合彩票;3 老版彩票; (apply后端设置默认为1,为可选参数)
language: 0, //Number(window.localStorage.getItem('languageNum') || '0'),
},
game_news: {
page_start: 1,
num_per_page: 9999,
state: 1,
message_id: 17,
sort_flag: 1,
tid: tenantInfo?.tid,
proxy: tenantInfo?.proxy,
chan_con: 0,
apply: 8,
ma_id: 10,
// vip_level: userInfo.value.cust_level || 0,
vip_level: 0,
type: 'MWEB',
language: 0,
},
banners: {
page_start: 1,
num_per_page: 999,
chan_con: 8,
state: 1,
message_id: 12,
sort_flag: 1,
tid: tenantInfo?.tid,
proxy: tenantInfo?.proxy,
apply: 8,
location: '1',
type: 'MWEB',
language: Number(window.localStorage.getItem('languageNum') || '0'),
},
homeHotGames: {
// cust_id: userInfo.value.cust_id || '0',
cust_id: '0',
},
proxyConfig: {
proxy: tenantInfo?.proxy,
},
hotGames: {
size: 12,
},
};
const { data } = await gameService.getHomePageData(params);
// ✅ 直接调用 setHomePageData action
useGameStore.getState().setHomePageData(data);
if (__DEV__) {
console.log('✅ Home-data info loaded:', data);
}
return Promise.resolve(data);
} catch (error) {
console.error('Failed to request home-data info:', error);
return Promise.reject(error);
}
};
export default useGameStore;

12
stores/index.ts

@ -8,6 +8,7 @@ export {
useUser,
useIsLoggedIn,
useToken,
useUserActions,
restoreUserState,
} from './userStore';
export type { User } from './userStore';
@ -20,6 +21,7 @@ export {
useNotificationsEnabled,
useSoundEnabled,
useHapticsEnabled,
useSettingsActions,
restoreSettingsState,
} from './settingsStore';
export type { Theme, Language } from './settingsStore';
@ -27,12 +29,8 @@ export type { Theme, Language } from './settingsStore';
// Tenant Store
export {
default as useTenantStore,
useTenantInfo,
useTenantStates,
useTenantActions,
restoreTenantState,
} from './tenantStore';
// Game Store
export {
default as useGameStore,
restoreGameState,
} from './gameStore';

72
stores/msgStore.ts

@ -1,72 +0,0 @@
/**
*
* 使 Zustand + AsyncStorage
*/
import { create } from 'zustand';
import storageManager, { STORAGE_KEYS } from '@/utils/storageManager';
import { filter } from 'lodash-es';
// import { useShallow } from 'zustand/react/shallow';
// import { tenantService } from '@/services';
/**
*
*/
interface State {
notices: Record<string, any>[];
homeBanner: Record<string, any>[];
mineBanner: Record<string, any>[];
}
/**
*
*/
interface Actions {
setNotices: (list: Record<string, any>[]) => void;
setBanners: (list: Record<string, any>[]) => void;
}
/**
* Store
*/
const useMsgStore = create<State & Actions>()((set, get) => ({
// state
notices: [],
homeBanner: [],
mineBanner: [],
// actions
setNotices: (list: Record<string, any>[]) => {
set({ notices: list });
if (__DEV__) {
console.log('💾 notices saved:', list);
}
},
setBanners: (list: Record<string, any>[]) => {
const homeBanner = filter(list, (item) => item.content1 && item.content1.includes('1'));
const mineBanner = filter(list, (item) => item.content1 && item.content1.includes('2'));
set({ homeBanner, mineBanner });
if (__DEV__) {
console.log('💾 banners saved:', list);
}
},
}));
// 从 AsyncStorage 恢复状态的函数
export const restoreMsgState = async () => {
try {
const stored = storageManager.session.getItem(STORAGE_KEYS.MSG_STORE);
if (stored) {
useMsgStore.setState(stored);
if (__DEV__) {
console.log('✅ Msg state restored from storage');
}
}
} catch (error) {
console.error('Failed to restore msg state:', error);
}
};
export default useMsgStore;

34
stores/settingsStore.ts

@ -5,12 +5,12 @@
import { create } from 'zustand';
import { useShallow } from 'zustand/react/shallow';
import storageManager, { STORAGE_KEYS } from '@/utils/storageManager';
import AsyncStorage from '@react-native-async-storage/async-storage';
/**
*
*/
export type Theme = 'light' | 'dark' | 'orange' | 'auto';
export type Theme = 'light' | 'dark' | 'auto';
/**
*
@ -59,7 +59,7 @@ export const useSettingsStore = create<SettingsState>()((set, get) => ({
setTheme: (theme) => {
set({ theme });
// 手动持久化
storageManager.local.setItem(STORAGE_KEYS.SETTINGS_STORE, get());
AsyncStorage.setItem('settings-storage', JSON.stringify(get()));
if (__DEV__) {
console.log('🎨 Theme changed:', theme);
}
@ -69,7 +69,7 @@ export const useSettingsStore = create<SettingsState>()((set, get) => ({
setLanguage: (language) => {
set({ language });
// 手动持久化
storageManager.local.setItem(STORAGE_KEYS.SETTINGS_STORE, get());
AsyncStorage.setItem('settings-storage', JSON.stringify(get()));
if (__DEV__) {
console.log('🌐 Language changed:', language);
}
@ -79,7 +79,7 @@ export const useSettingsStore = create<SettingsState>()((set, get) => ({
setNotificationsEnabled: (enabled) => {
set({ notificationsEnabled: enabled });
// 手动持久化
storageManager.local.setItem(STORAGE_KEYS.SETTINGS_STORE, get());
AsyncStorage.setItem('settings-storage', JSON.stringify(get()));
if (__DEV__) {
console.log('🔔 Notifications:', enabled ? 'enabled' : 'disabled');
}
@ -89,7 +89,7 @@ export const useSettingsStore = create<SettingsState>()((set, get) => ({
setSoundEnabled: (enabled) => {
set({ soundEnabled: enabled });
// 手动持久化
storageManager.local.setItem(STORAGE_KEYS.SETTINGS_STORE, get());
AsyncStorage.setItem('settings-storage', JSON.stringify(get()));
if (__DEV__) {
console.log('🔊 Sound:', enabled ? 'enabled' : 'disabled');
}
@ -99,7 +99,7 @@ export const useSettingsStore = create<SettingsState>()((set, get) => ({
setHapticsEnabled: (enabled) => {
set({ hapticsEnabled: enabled });
// 手动持久化
storageManager.local.setItem(STORAGE_KEYS.SETTINGS_STORE, get());
AsyncStorage.setItem('settings-storage', JSON.stringify(get()));
if (__DEV__) {
console.log('📳 Haptics:', enabled ? 'enabled' : 'disabled');
}
@ -109,7 +109,7 @@ export const useSettingsStore = create<SettingsState>()((set, get) => ({
resetSettings: () => {
set(DEFAULT_SETTINGS);
// 手动持久化
storageManager.local.setItem(STORAGE_KEYS.SETTINGS_STORE, get());
AsyncStorage.setItem('settings-storage', JSON.stringify(DEFAULT_SETTINGS));
if (__DEV__) {
console.log('🔄 Settings reset to default');
}
@ -119,9 +119,10 @@ export const useSettingsStore = create<SettingsState>()((set, get) => ({
// 从 AsyncStorage 恢复状态的函数
export const restoreSettingsState = async () => {
try {
const stored = await storageManager.local.getItem(STORAGE_KEYS.SETTINGS_STORE);
const stored = await AsyncStorage.getItem('settings-storage');
if (stored) {
useSettingsStore.setState(stored);
const state = JSON.parse(stored);
useSettingsStore.setState(state);
if (__DEV__) {
console.log('✅ Settings state restored from storage');
}
@ -151,3 +152,16 @@ 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,
}))
);

108
stores/tenantStore.ts

@ -4,36 +4,41 @@
*/
import { create } from 'zustand';
// import { useShallow } from 'zustand/react/shallow';
import storageManager, { STORAGE_KEYS } from '@/utils/storageManager';
import { tenantService } from '@/services';
import { useShallow } from 'zustand/react/shallow';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { STORAGE_KEYS } from '@/utils/storage';
import { tenantService } from '@/services/tenantService';
import { useEffect } from 'react';
/**
*
*/
export interface Tenant {
tid: number;
proxy: number;
create_time: string;
domain_addr: string
}
// export interface Tenant {
// id: string;
// username: string;
// email: string;
// avatar?: string;
// nickname?: string;
// phone?: string;
// createdAt?: string;
// }
// 状态
interface State {
tenantInfo: Tenant | null;
}
/**
*
*/
interface TenantState {
// 状态
tenantInfo: Record<string, any> | null;
// 操作
interface Actions {
// 操作
setTenantInfo: (data: Record<string, any>) => void;
requestTenantInfo: (data?: Record<string, any>) => Promise<any>;
}
/**
* Store
*/
const useTenantStore = create<State & Actions>()((set, get) => ({
const useTenantStore = create<TenantState>()((set, get) => ({
// 初始状态
tenantInfo: null,
@ -42,19 +47,39 @@ const useTenantStore = create<State & Actions>()((set, get) => ({
setTenantInfo: (data: any) => {
set({ tenantInfo: data });
// 手动持久化
storageManager.session.setItem(STORAGE_KEYS.TENANT_STORE, get());
storageManager.session.setItem(STORAGE_KEYS.TENANT_TID, data?.tid ?? '');
// AsyncStorage.setItem(STORAGE_KEYS.TENANT_STORE, JSON.stringify({ tenantInfo: data }));
if (__DEV__) {
console.log('💾 Tenant info saved:', data);
}
},
// 获取租户信息(调用 API 并使用 setTenantInfo 保存)
requestTenantInfo: async () => {
try {
const params = {
domain_addr: 'https://51zhh5.notbug.org',
};
const { data } = await tenantService.getPlatformData(params);
// 调用 setTenantInfo 来保存数据,避免重复代码
get().setTenantInfo(data);
if (__DEV__) {
console.log('✅ Tenant info loaded:', data);
}
return Promise.resolve(data);
} catch (error) {
console.error('Failed to request tenant info:', error);
return Promise.reject(error);
}
},
}));
// 从 AsyncStorage 恢复状态的函数
export const restoreTenantState = async () => {
try {
const stored = storageManager.session.getItem(STORAGE_KEYS.TENANT_STORE);
const stored = await AsyncStorage.getItem(STORAGE_KEYS.TENANT_STORE);
if (stored) {
const state = JSON.parse(stored);
useTenantStore.setState(state);
@ -74,29 +99,24 @@ export const restoreTenantState = async () => {
// 获取用户信息
export const useTenantInfo = () => useTenantStore((state) => state.tenantInfo);
// 租户数据是否加载完成
export const useTenantLoad = () => useTenantStore((state) => !!state.tenantInfo?.tid || !!state.tenantInfo?.create_time);
// 获取租户信息
export const requestTenantInfo = async (): Promise<Tenant> => {
try {
// 使用 getState() 而不是 hook
const { setTenantInfo } = useTenantStore.getState();
const params = {
domain_addr: 'https://51zhh5.notbug.org',
};
const { data } = await tenantService.getPlatformData(params);
setTenantInfo(data);
if (__DEV__) {
console.log('✅ Tenant info loaded:', data);
}
return Promise.resolve(data);
} catch (error) {
console.error('Failed to request tenant info:', error);
return Promise.reject(error);
}
};
// 获取租户状态
export const useTenantStates = () =>
useTenantStore(
useShallow((state) => ({
tenantInfo: state.tenantInfo,
tenantLoad: !!state.tenantInfo?.tid || !!state.tenantInfo?.create_time,
}))
);
// 获取租户操作方法
// 使用 useShallow 避免每次渲染都返回新对象
export const useTenantActions = () =>
useTenantStore(
useShallow((state) => ({
setTenantInfo: state.setTenantInfo,
requestTenantInfo: state.requestTenantInfo,
}))
);
export default useTenantStore;

52
stores/userStore.ts

@ -5,7 +5,7 @@
import { create } from 'zustand';
import { useShallow } from 'zustand/react/shallow';
import storageManager, { STORAGE_KEYS } from '@/utils/storageManager';
import AsyncStorage from '@react-native-async-storage/async-storage';
/**
*
@ -20,15 +20,16 @@ export interface User {
createdAt?: string;
}
// 状态
interface State {
/**
*
*/
interface UserState {
// 状态
user: User | null;
isLoggedIn: boolean;
token: string | null;
}
// 操作
interface Actions {
// 操作
setUser: (user: User) => void;
setToken: (token: string) => void;
login: (user: User, token: string) => void;
@ -39,7 +40,7 @@ interface Actions {
/**
* Store
*/
export const useUserStore = create<State & Actions>()((set, get) => ({
export const useUserStore = create<UserState>()((set, get) => ({
// 初始状态
user: null,
isLoggedIn: false,
@ -50,7 +51,7 @@ export const useUserStore = create<State & Actions>()((set, get) => ({
const newState = { user, isLoggedIn: true };
set(newState);
// 手动持久化
storageManager.session.setItem(STORAGE_KEYS.USER_STORE, newState);
AsyncStorage.setItem('user-storage', JSON.stringify(newState));
},
// 设置 token
@ -58,7 +59,8 @@ export const useUserStore = create<State & Actions>()((set, get) => ({
set({ token });
// 手动持久化 - 延迟执行以确保状态已更新
setTimeout(() => {
storageManager.session.setItem(STORAGE_KEYS.USER_STORE, get());
const state = get();
AsyncStorage.setItem('user-storage', JSON.stringify(state));
}, 0);
},
@ -72,9 +74,9 @@ export const useUserStore = create<State & Actions>()((set, get) => ({
set(newState);
// 同时保存 token 到 AsyncStorage(用于 API 请求)
storageManager.session.setItem('auth_token', token);
AsyncStorage.setItem('auth_token', token);
// 手动持久化整个状态
storageManager.session.setItem(STORAGE_KEYS.USER_STORE, newState);
AsyncStorage.setItem('user-storage', JSON.stringify(newState));
if (__DEV__) {
console.log('✅ User logged in:', user.username);
@ -91,9 +93,9 @@ export const useUserStore = create<State & Actions>()((set, get) => ({
set(newState);
// 清除 AsyncStorage 中的 token
storageManager.session.removeItem('auth_token');
AsyncStorage.removeItem('auth_token');
// 清除持久化状态
storageManager.session.removeItem('user-storage');
AsyncStorage.removeItem('user-storage');
if (__DEV__) {
console.log('👋 User logged out');
@ -107,7 +109,7 @@ export const useUserStore = create<State & Actions>()((set, get) => ({
const newUser = { ...currentUser, ...updates };
set({ user: newUser });
// 手动持久化
storageManager.session.setItem(STORAGE_KEYS.USER_STORE, { ...get(), user: newUser });
AsyncStorage.setItem('user-storage', JSON.stringify({ ...get(), user: newUser }));
if (__DEV__) {
console.log('📝 User updated:', updates);
@ -119,7 +121,7 @@ export const useUserStore = create<State & Actions>()((set, get) => ({
// 从 AsyncStorage 恢复状态的函数
export const restoreUserState = async () => {
try {
const stored = await storageManager.session.getItem(STORAGE_KEYS.USER_STORE);
const stored = await AsyncStorage.getItem('user-storage');
if (stored) {
const state = JSON.parse(stored);
useUserStore.setState(state);
@ -147,13 +149,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,
}))
);

5
theme/index.ts

@ -5,7 +5,7 @@
*/
// 导出颜色配置
export { default as Colors } from './Colors';
export { default as Colors } from '@/constants/Colors';
// 导出主题 Hooks
export {
@ -23,9 +23,6 @@ export {
View as ThemeView,
} from '@/components/Themed';
// 导出主题类型
export { ThemeEnum } from '@/constants/theme';
export type {
ThemedTextProps,
ThemedViewProps,

18
theme/styles.ts

@ -7,16 +7,14 @@
*/
import { StyleSheet, TextStyle, ViewStyle } from 'react-native';
import { Colors } from '@/theme';
import { ThemeEnum } from '@/constants/theme';
import Colors from '@/constants/Colors';
/**
*
*/
export type ThemeStyles = {
[ThemeEnum.LIGHT]: any;
[ThemeEnum.DARK]: any;
[ThemeEnum.ORANGE]: any;
light: any;
dark: any;
};
/**
@ -48,12 +46,11 @@ export type ThemeStyles = {
* ```
*/
export function createThemeStyles<T extends StyleSheet.NamedStyles<T>>(
createStyles: (colors: typeof Colors.light & typeof Colors.dark & typeof Colors.orange) => T
createStyles: (colors: typeof Colors.light) => T
): ThemeStyles {
return {
light: StyleSheet.create(createStyles(Colors.light)),
dark: StyleSheet.create(createStyles(Colors.dark)),
orange: StyleSheet.create(createStyles(Colors.orange)),
};
}
@ -64,7 +61,6 @@ export function createThemeStyles<T extends StyleSheet.NamedStyles<T>>(
*
* @param lightStyles -
* @param darkStyles -
* @param orangeStyles -
* @returns
*
* @example
@ -83,13 +79,11 @@ export function createThemeStyles<T extends StyleSheet.NamedStyles<T>>(
*/
export function createResponsiveThemeStyles<T extends StyleSheet.NamedStyles<T>>(
lightStyles: T,
darkStyles: T,
orangeStyles: T
darkStyles: T
): ThemeStyles {
return {
light: StyleSheet.create(lightStyles),
dark: StyleSheet.create(darkStyles),
orange: StyleSheet.create(orangeStyles),
};
}
@ -261,7 +255,7 @@ export const commonStyles = createThemeStyles((colors) => ({
* <View style={style.container} />
* ```
*/
export function getThemeStyle<T>(styles: ThemeStyles, theme: ThemeEnum): T {
export function getThemeStyle<T>(styles: ThemeStyles, theme: 'light' | 'dark'): T {
return styles[theme];
}

61
theme/utils.ts

@ -4,19 +4,18 @@
*
*/
import { Colors } from '@/theme';
import { ThemeEnum } from '@/constants/theme';
import Colors from '@/constants/Colors';
/**
*
*
* @param theme - ThemeEnum
* @param theme - 'light' | 'dark'
* @param colorName -
* @returns
*/
export function getThemeColor(
theme: ThemeEnum,
colorName: keyof typeof Colors.light & keyof typeof Colors.dark & keyof typeof Colors.orange
theme: 'light' | 'dark',
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
): string {
return Colors[theme][colorName];
}
@ -24,10 +23,10 @@ export function getThemeColor(
/**
*
*
* @param theme - ThemeEnum
* @param theme - 'light' | 'dark'
* @returns
*/
export function getThemeColors(theme: ThemeEnum) {
export function getThemeColors(theme: 'light' | 'dark') {
return Colors[theme];
}
@ -36,7 +35,6 @@ export function getThemeColors(theme: ThemeEnum) {
*
* @param lightStyle -
* @param darkStyle -
* @param orangeStyle -
* @param theme -
* @returns
*
@ -52,19 +50,9 @@ export function getThemeColors(theme: ThemeEnum) {
export function createThemedStyle<T>(
lightStyle: T,
darkStyle: T,
orangeStyle: T,
theme: ThemeEnum
theme: 'light' | 'dark'
): T {
switch (theme) {
case ThemeEnum.LIGHT:
return lightStyle;
case ThemeEnum.DARK:
return darkStyle;
case ThemeEnum.ORANGE:
return orangeStyle;
default:
return lightStyle;
}
return theme === 'dark' ? darkStyle : lightStyle;
}
/**
@ -72,7 +60,6 @@ export function createThemedStyle<T>(
*
* @param lightValue -
* @param darkValue -
* @param orangeValue -
* @param theme -
* @returns
*
@ -84,19 +71,9 @@ export function createThemedStyle<T>(
export function selectByTheme<T>(
lightValue: T,
darkValue: T,
orangeValue: T,
theme: ThemeEnum
theme: 'light' | 'dark'
): T {
switch (theme) {
case ThemeEnum.LIGHT:
return lightValue;
case ThemeEnum.DARK:
return darkValue;
case ThemeEnum.ORANGE:
return orangeValue;
default:
return lightValue;
}
return theme === 'dark' ? darkValue : lightValue;
}
/**
@ -129,8 +106,8 @@ export function withOpacity(color: string, opacity: number): string {
* @param theme -
* @returns
*/
export function isDarkTheme(theme: ThemeEnum): boolean {
return theme === ThemeEnum.DARK;
export function isDarkTheme(theme: 'light' | 'dark'): boolean {
return theme === 'dark';
}
/**
@ -139,17 +116,7 @@ export function isDarkTheme(theme: ThemeEnum): boolean {
* @param theme -
* @returns
*/
export function isLightTheme(theme: ThemeEnum): boolean {
return theme === ThemeEnum.LIGHT;
}
/**
*
*
* @param theme -
* @returns
*/
export function isOrangeTheme(theme: ThemeEnum): boolean {
return theme === ThemeEnum.ORANGE;
export function isLightTheme(theme: 'light' | 'dark'): boolean {
return theme === 'light';
}

121
types/home.ts

@ -1,121 +0,0 @@
/**
*
*/
/**
*
*/
export interface Banner {
id: string;
subject: string; // 图片URL
link_type: number; // 1: 外链, 2: 内部路由
content: string; // 链接内容
title?: string;
description?: string;
}
/**
*
*/
export interface Notice {
id: string;
title: string;
content: string;
content_type: number; // 1: 文本, 2: 图片, 3: 链接
create_time: string;
formatDate?: string;
is_save_pic?: boolean;
}
/**
*
*/
export interface GameCategory {
id: number;
key: string;
name: string;
icon: string;
logo?: string;
big_type?: number;
}
/**
*
*/
export interface Game {
id: string;
play_id: number;
upper_play_id?: number;
play_up_name: string; // 游戏平台名称
play_cname?: string; // 游戏子类名称
logo3_img_url?: string; // 游戏图标
icon?: string;
big_type: number; // 游戏大类型
mainType?: number;
index?: number;
description?: string;
}
/**
*
*/
export interface HighPrizeGame extends Game {
payout_amount: number; // 派彩金额
bet_amount: number; // 下注金额
odds: number; // 赔率
cust_name: string; // 用户名
avatar?: string; // 用户头像
update_time: string; // 更新时间
}
/**
*
*/
export interface NavItem {
id: string;
name: string;
icon: string;
action: string; // 导航动作
}
/**
*
*/
export interface HomePageData {
banners: Banner[];
notices: Notice[];
categories: GameCategory[];
games: Game[];
highPrizeGames: HighPrizeGame[];
navItems: NavItem[];
}
/**
* API
*/
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
type?: 'success' | 'error';
}
/**
*
*/
export interface PaginationParams {
page: number;
page_size: number;
[key: string]: any;
}
/**
*
*/
export interface GameListResponse {
games: Game[];
total: number;
page: number;
page_size: number;
}

11
utils/index.ts

@ -13,7 +13,10 @@ export {
export type { ApiResponse, ApiError, RequestConfig } from './network/api';
// Storage
export { default as storageManager, STORAGE_KEYS } from './storageManager';
export { default as Storage, STORAGE_KEYS } from './storage';
export { default as SessionStorage, SESSION_KEYS } from './sessionStorage';
export { default as StorageManager } from './storageManager';
export type { StorageType, StorageOptions } from './storageManager';
// Config
export { default as config, printConfig } from './config';
@ -23,7 +26,13 @@ export {
formatDate,
formatRelativeTime,
formatChatTime,
parseDate,
isToday,
isYesterday,
isSameDay,
addDays,
subtractDays,
startOfDay,
endOfDay,
} from './date';

6
utils/network/api.ts

@ -24,7 +24,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
import { router } from 'expo-router';
import { config } from '../config';
import { transformRequest, parseResponse } from './helper';
import { cloneDeep, pick, includes } from 'lodash-es';
import { cloneDeep, pick } from 'lodash-es';
import md5 from 'md5';
/**
@ -304,10 +304,6 @@ api.interceptors.response.use(
});
}
if (includes([500, 502, 503], error.response?.status) && includes(['371130'], `${error.config?.headers?.cmdId}`)) {
router.replace('/maintenance' as any);
}
// 处理不同的错误状态码
if (error.response) {
const { status, data } = error.response;

69
utils/network/helper.ts

@ -1,7 +1,6 @@
import { HmacMD5 } from 'crypto-js';
import Base64 from 'crypto-js/enc-base64';
import Latin1 from 'crypto-js/enc-latin1';
import { Platform } from 'react-native';
import md5 from 'md5';
import { AxiosResponse } from 'axios';
import * as des from './des';
@ -9,11 +8,11 @@ import NetworkError from './error';
import { toNumber, toString, startsWith, isString, isNumber } from 'lodash-es';
import { NetworkTypeEnum } from '@/constants/network';
import appConfig from '../config';
// import storageManager, { STORAGE_KEYS } from '../storageManager';
// import NetworkError from './error'
type PlatformType = 'IOS' | 'ANDROID' | 'H5_IOS';
// import { storeToRefs, useTenantStore, useUserStore, useAppStore, start } from '../index';
// import { isMobile, getBetPlatform } from '@star/utils';
// import { langToNum } from '@star/languages';
// 请求到的数据返回
export type NetworkResponse<T> = {
@ -21,16 +20,38 @@ export type NetworkResponse<T> = {
data: T;
};
const getBetPlatform = (): PlatformType => {
export const getBetPlatform = (isReturnIndex = false) => {
// 5=PC; 7=HOMESCREEN_IOS; 6=HOMESCREEN_ANDROID; 4=H5_IOS 3=IOS 2=H5_ANDROID; 1=ANDROID 8=马甲包
switch (Platform.OS) {
case 'ios':
return 'IOS';
case 'android':
return 'ANDROID';
default:
return 'H5_IOS';
}
// const platform = new URLSearchParams(window.location.search).get('platform');
// if (platform) {
// return platform?.includes('IOS') ? 'IOS' : platform?.includes('ANDROID') ? 'ANDROID' : '';
// }
// if (isAppMJB()) {
// return 'APPS_ANDROID';
// }
// if (isPWA()) {
// if (isIOS()) {
// return isReturnIndex ? 4 : 'HS_IOS';
// } else {
// return isReturnIndex ? 2 : 'HS_ANDROID';
// }
// }
// if (isAndroid()) {
// if (BASE_CONFIG.appVersion > 0) {
// return isReturnIndex ? 1 : 'ANDROID';
// } else {
// return isReturnIndex ? 2 : 'H5_ANDROID';
// }
// }
// if (isIOS()) {
// if (BASE_CONFIG.appVersion > 0) {
// return isReturnIndex ? 3 : 'IOS';
// } else {
// return isReturnIndex ? 4 : 'H5_IOS';
// }
// }
// return isReturnIndex ? 5 : 'PC';
};
const uuid = (len: number, radix: number) => {
@ -64,7 +85,7 @@ const uuid = (len: number, radix: number) => {
};
// 格式化要发送的数据
const formatSendData = (data: any, type: number = 0) => {
export const formatSendData = (data: any, type: number = 0) => {
// url code
if (type === 0) {
const arr: any[] = [];
@ -86,44 +107,44 @@ const formatSendData = (data: any, type: number = 0) => {
return JSON.stringify(data);
};
const getP = (p: any) => {
export const getP = (p: any) => {
return HmacMD5(p, '7NEkojNzfkk=').toString();
};
const enD = (rk: string, str: string) => {
export const enD = (rk: string, str: string) => {
const enc = des.des(rk, str, 1, 0, null, 1);
return Base64.stringify(Latin1.parse(enc));
};
const dnD = (rk: string, str: string) => {
export const dnD = (rk: string, str: string) => {
const s = Latin1.stringify(Base64.parse(str));
const d = des.des(rk, s, 0, 0, null, 1);
return d;
};
const enP = (rk: string, vk: string, t: number) => {
export const enP = (rk: string, vk: string, t: number) => {
const enc = des.des(vk, rk + t, 1, 0, null, 1);
return Base64.stringify(Latin1.parse(enc));
};
const dnP = (vk: string, str: string) => {
export const dnP = (vk: string, str: string) => {
const s = Latin1.stringify(Base64.parse(str));
const p = des.des(vk, s, 0, 0, null, 1);
return p;
};
const enC = (rk: string, vk: string, m: string) => {
export const enC = (rk: string, vk: string, m: string) => {
const enc = HmacMD5(m + rk, vk);
return Base64.stringify(enc);
};
const getRequestKey = (cmdId: number, data: any) => {
export const getRequestKey = (cmdId: number, data: any) => {
return `${cmdId}&${data ? md5(JSON.stringify(data)) : ''}`;
};
// 加工请求数据
export const transformRequest = (config: any) => {
const { headerType = 2, paramType = 0, cmdId, ...reset } = config.headers;
const { headerType = 2, paramType = 0, cmdId, tid, ...reset } = config.headers;
const headers: Record<string, any> = {};
// const { tenantInfo } = storeToRefs(useTenantStore());
// const { userInfo } = storeToRefs(useUserStore());
@ -132,8 +153,10 @@ export const transformRequest = (config: any) => {
const rk = md5(toString(Math.random() + t)).substring(0, 8);
const vk = appConfig.app.vk as string;
const pwds = enP(rk, vk, t);
// const tid = cmdId !== '371130' ? storageManager.session.getItem(STORAGE_KEYS.TENANT_TID) : '';
const tenantInfo = {
tid: 3,
};
let userInfo = {
cust_id: '',
cust_name: '',
@ -198,7 +221,7 @@ export const transformRequest = (config: any) => {
headers.cmdId = cmdId;
headers.aseqId = appConfig.app.aseqId;
headers.nc = appConfig.app.nc;
headers.tid = 3;
headers.tid = tid ?? tenantInfo.tid ?? '';
// 试玩游戏cust_id=0 header需要保持一致
headers.custId = (config.data?.cust_id === 0 ? '0' : '') || userInfo?.cust_id || '';
headers.reqId = uuid(32, 16);

220
utils/sessionStorage.ts

@ -0,0 +1,220 @@
/**
* Session Storage
*
* React Native sessionStorage
*
*
*
* -
* -
* -
* - API localStorage
*/
/**
* Session Storage
*/
export enum SESSION_KEYS {
TEMP_DATA = 'temp_data',
FORM_DRAFT = 'form_draft',
SEARCH_HISTORY = 'search_history',
CURRENT_TAB = 'current_tab',
SCROLL_POSITION = 'scroll_position',
FILTER_STATE = 'filter_state',
}
/**
* Session Storage
*
* 使 Map
*/
class SessionStorage {
private static storage: Map<string, string> = new Map();
/**
*
*/
static setString(key: string, value: string): void {
try {
this.storage.set(key, value);
if (__DEV__) {
console.log(`💾 SessionStorage set: ${key}`);
}
} catch (error) {
console.error(`SessionStorage setString error for key "${key}":`, error);
throw error;
}
}
/**
*
*/
static getString(key: string): string | null {
try {
const value = this.storage.get(key) ?? null;
if (__DEV__) {
console.log(`📖 SessionStorage get: ${key}`, value ? '✓' : '✗');
}
return value;
} catch (error) {
console.error(`SessionStorage getString error for key "${key}":`, error);
return null;
}
}
/**
* JSON
*/
static setObject<T>(key: string, value: T): void {
try {
const jsonValue = JSON.stringify(value);
this.storage.set(key, jsonValue);
if (__DEV__) {
console.log(`💾 SessionStorage set object: ${key}`);
}
} catch (error) {
console.error(`SessionStorage setObject error for key "${key}":`, error);
throw error;
}
}
/**
* JSON
*/
static getObject<T>(key: string): T | null {
try {
const jsonValue = this.storage.get(key);
if (jsonValue === undefined) {
return null;
}
const value = JSON.parse(jsonValue) as T;
if (__DEV__) {
console.log(`📖 SessionStorage get object: ${key}`);
}
return value;
} catch (error) {
console.error(`SessionStorage getObject error for key "${key}":`, error);
return null;
}
}
/**
*
*/
static remove(key: string): void {
try {
this.storage.delete(key);
if (__DEV__) {
console.log(`🗑 SessionStorage remove: ${key}`);
}
} catch (error) {
console.error(`SessionStorage remove error for key "${key}":`, error);
throw error;
}
}
/**
*
*/
static clear(): void {
try {
this.storage.clear();
if (__DEV__) {
console.log('🗑 SessionStorage cleared all');
}
} catch (error) {
console.error('SessionStorage clear error:', error);
throw error;
}
}
/**
*
*/
static getAllKeys(): string[] {
try {
const keys = Array.from(this.storage.keys());
if (__DEV__) {
console.log('🔑 SessionStorage all keys:', keys);
}
return keys;
} catch (error) {
console.error('SessionStorage getAllKeys error:', error);
return [];
}
}
/**
*
*/
static get length(): number {
return this.storage.size;
}
/**
*
*/
static has(key: string): boolean {
return this.storage.has(key);
}
/**
*
*/
static multiGet(keys: string[]): [string, string | null][] {
try {
return keys.map((key) => [key, this.storage.get(key) ?? null]);
} catch (error) {
console.error('SessionStorage multiGet error:', error);
return [];
}
}
/**
*
*/
static multiSet(keyValuePairs: [string, string][]): void {
try {
keyValuePairs.forEach(([key, value]) => {
this.storage.set(key, value);
});
if (__DEV__) {
console.log(`💾 SessionStorage multiSet: ${keyValuePairs.length} items`);
}
} catch (error) {
console.error('SessionStorage multiSet error:', error);
throw error;
}
}
/**
*
*/
static multiRemove(keys: string[]): void {
try {
keys.forEach((key) => {
this.storage.delete(key);
});
if (__DEV__) {
console.log(`🗑 SessionStorage multiRemove: ${keys.length} items`);
}
} catch (error) {
console.error('SessionStorage multiRemove error:', error);
throw error;
}
}
/**
*
*/
static getAll(): Record<string, string> {
const result: Record<string, string> = {};
this.storage.forEach((value, key) => {
result[key] = value;
});
return result;
}
}
export default SessionStorage;

184
utils/storage.ts

@ -0,0 +1,184 @@
/**
* AsyncStorage
*
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
/**
*
*/
export enum STORAGE_KEYS {
AUTH_TOKEN = 'auth_token',
USER_INFO = 'user_info',
SETTINGS = 'settings',
THEME = 'theme',
LANGUAGE = 'language',
USER_PREFERENCES = 'user_preferences',
TENANT_STORE = 'tenant_storage',
USER_STORE = 'user_storage',
SETTINGS_STORE = 'settings_storage',
}
/**
* Storage
*/
class Storage {
/**
*
*/
static async setString(key: string, value: string): Promise<void> {
try {
await AsyncStorage.setItem(key, value);
if (__DEV__) {
console.log(`💾 Storage set: ${key}`);
}
} catch (error) {
console.error(`Storage setString error for key "${key}":`, error);
throw error;
}
}
/**
*
*/
static async getString(key: string): Promise<string | null> {
try {
const value = await AsyncStorage.getItem(key);
if (__DEV__) {
console.log(`📖 Storage get: ${key}`, value ? '✓' : '✗');
}
return value;
} catch (error) {
console.error(`Storage getString error for key "${key}":`, error);
return null;
}
}
/**
* JSON
*/
static async setObject<T>(key: string, value: T): Promise<void> {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(key, jsonValue);
if (__DEV__) {
console.log(`💾 Storage set object: ${key}`);
}
} catch (error) {
console.error(`Storage setObject error for key "${key}":`, error);
throw error;
}
}
/**
* JSON
*/
static async getObject<T>(key: string): Promise<T | null> {
try {
const jsonValue = await AsyncStorage.getItem(key);
if (jsonValue === null) {
return null;
}
const value = JSON.parse(jsonValue) as T;
if (__DEV__) {
console.log(`📖 Storage get object: ${key}`);
}
return value;
} catch (error) {
console.error(`Storage getObject error for key "${key}":`, error);
return null;
}
}
/**
*
*/
static async remove(key: string): Promise<void> {
try {
await AsyncStorage.removeItem(key);
if (__DEV__) {
console.log(`🗑 Storage remove: ${key}`);
}
} catch (error) {
console.error(`Storage remove error for key "${key}":`, error);
throw error;
}
}
/**
*
*/
static async clear(): Promise<void> {
try {
await AsyncStorage.clear();
if (__DEV__) {
console.log('🗑 Storage cleared all');
}
} catch (error) {
console.error('Storage clear error:', error);
throw error;
}
}
/**
*
*/
static async getAllKeys(): Promise<string[]> {
try {
const keys = await AsyncStorage.getAllKeys();
if (__DEV__) {
console.log('🔑 Storage all keys:', keys);
}
return keys;
} catch (error) {
console.error('Storage getAllKeys error:', error);
return [];
}
}
/**
*
*/
static async multiGet(keys: string[]): Promise<[string, string | null][]> {
try {
const values = await AsyncStorage.multiGet(keys);
return values;
} catch (error) {
console.error('Storage multiGet error:', error);
return [];
}
}
/**
*
*/
static async multiSet(keyValuePairs: [string, string][]): Promise<void> {
try {
await AsyncStorage.multiSet(keyValuePairs);
if (__DEV__) {
console.log(`💾 Storage multiSet: ${keyValuePairs.length} items`);
}
} catch (error) {
console.error('Storage multiSet error:', error);
throw error;
}
}
/**
*
*/
static async multiRemove(keys: string[]): Promise<void> {
try {
await AsyncStorage.multiRemove(keys);
if (__DEV__) {
console.log(`🗑 Storage multiRemove: ${keys.length} items`);
}
} catch (error) {
console.error('Storage multiRemove error:', error);
throw error;
}
}
}
export default Storage;

377
utils/storageManager.ts

@ -4,304 +4,239 @@
* 使 localStorage (AsyncStorage) sessionStorage
*
* 使
* - local: 持久化数据
* - session: 临时数据
* - localStorage: 持久化数据
* - sessionStorage: 临时数据
*
*
* ```typescript
* // 使用 localStorage
* await storageManager.local.setItem('user', userData);
* const user = await storageManager.local.getItem('user');
* // 使用 localStorage(默认)
* await StorageManager.set('user', userData);
*
* // 使用 sessionStorage
* storageManager.session.setItem('temp', tempData);
* const temp = storageManager.session.getItem('temp');
* await StorageManager.set('temp', tempData, { type: 'session' });
*
* // 获取数据(自动从正确的存储中读取)
* const user = await StorageManager.get('user');
* const temp = await StorageManager.get('temp', { type: 'session' });
* ```
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
import Storage from './storage';
import SessionStorage from './sessionStorage';
/**
*
*
*/
export enum STORAGE_KEYS {
AUTH_TOKEN = 'auth_token',
THEME = 'theme',
LANGUAGE = 'language',
USER_PREFERENCES = 'user_preferences',
TENANT_STORE = 'tenant_storage',
USER_STORE = 'user_storage',
USER_INFO = 'user_info',
// 游戏相关
GAME_STORE = 'game_storage',
GAME_TRY = 'game_try',
SETTINGS_STORE = 'settings_storage',
MSG_STORE = 'msg_storage',
TEMP_DATA = 'temp_data',
FORM_DRAFT = 'form_draft',
SEARCH_HISTORY = 'search_history',
CURRENT_TAB = 'current_tab',
SCROLL_POSITION = 'scroll_position',
FILTER_STATE = 'filter_state',
TENANT_TID = 'tenant_tid',
APP_CONFIG = 'app_config',
APP_THEME = 'app_theme', // 主题
APP_PLATFORM = 'app_platform', // 平台
APP_TEMPLATE = 'app_template', // 模板
APP_LANGUAGE = 'app_language', // 语言
APP_ACTIVE_FOOTER_TAB_MENU = 'app_active_footer_tab_menu', // 底部菜单
APP_MAIN_MENU = 'app_main_menu', // 首页一级菜单原始数据
APP_ACTIVE_MAIN_MENU_TAB = 'app_active_main_menu_tab', // 游戏列表页主菜单
APP_ACTIVE_SUB_MENU_TAB = 'app_active_sub_menu_tab', // 游戏列表页二级菜单
APP_ACTIVE_CHILD_MENU_TAB = 'app_active_child_menu_tab', // 游戏列表页三级菜单
APP_USER_INFO = 'app_user_info', // 登录用户信息
POPUP_MODAL_LIST = 'app_popup_modal_list', // 首页弹窗列表
POPUP_MODAL_STATUS_MAP = 'popup_modal_status_map', // 首页弹窗状态
RECOVER_PASSWORD = 'recover_password', // 找回密码
CDN_PAGE_DATA_LOADED = 'cdn_page_data_loaded', // CDN首页数据是否已加载
}
export type StorageType = 'local' | 'session';
/**
*
*
*/
enum DataType {
STRING = 'string',
NUMBER = 'number',
BOOLEAN = 'boolean',
OBJECT = 'object',
ARRAY = 'array',
NULL = 'null',
}
/**
*
export interface StorageOptions {
/**
*
* - 'local': AsyncStorage
* - 'session':
*/
interface TypedValue {
type: DataType;
value: string;
type?: StorageType;
}
/**
*
*
*
*/
abstract class StorageBase {
class StorageManager {
/**
*
*
*/
protected static getDataType(value: any): DataType {
if (value === null) return DataType.NULL;
if (Array.isArray(value)) return DataType.ARRAY;
const typeStr = typeof value;
const dataType = DataType[typeStr.toUpperCase() as keyof typeof DataType];
return dataType || DataType.OBJECT;
static async setString(
key: string,
value: string,
options: StorageOptions = {}
): Promise<void> {
const { type = 'local' } = options;
if (type === 'session') {
SessionStorage.setString(key, value);
} else {
await Storage.setString(key, value);
}
}
/**
*
*
*/
protected static serializeValue(value: any): string {
const type = this.getDataType(value);
const typedValue: TypedValue = {
type,
value: typeof value === 'string' ? value : JSON.stringify(value),
};
return JSON.stringify(typedValue);
static async getString(
key: string,
options: StorageOptions = {}
): Promise<string | null> {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.getString(key);
} else {
return await Storage.getString(key);
}
}
/**
*
*
*/
protected static deserializeValue(data: string): any {
try {
const typedValue: TypedValue = JSON.parse(data);
const { type, value } = typedValue;
static async setObject<T>(
key: string,
value: T,
options: StorageOptions = {}
): Promise<void> {
const { type = 'local' } = options;
switch (type) {
case DataType.STRING:
return value;
case DataType.NUMBER:
return Number(value);
case DataType.BOOLEAN:
return value === 'true';
case DataType.NULL:
return null;
case DataType.OBJECT:
case DataType.ARRAY:
return JSON.parse(value);
default:
return value;
}
} catch (error) {
console.error('StorageBase deserialize error:', error);
return null;
if (type === 'session') {
SessionStorage.setObject(key, value);
} else {
await Storage.setObject(key, value);
}
}
}
/**
* AsyncStorage
* StorageBase使
*/
class LocalStorage extends StorageBase {
/**
*
*
*/
static async setItem(key: string, value: any): Promise<void> {
try {
const serialized = this.serializeValue(value);
await AsyncStorage.setItem(key, serialized);
if (__DEV__) {
console.log(`💾 LocalStorage set: ${key}`);
}
} catch (error) {
console.error(`LocalStorage setItem error for key "${key}":`, error);
throw error;
static async getObject<T>(
key: string,
options: StorageOptions = {}
): Promise<T | null> {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.getObject<T>(key);
} else {
return await Storage.getObject<T>(key);
}
}
/**
*
*
*/
static async getItem(key: string): Promise<any> {
try {
const value = await AsyncStorage.getItem(key);
if (value === null) {
if (__DEV__) {
console.log(`📖 LocalStorage get: ${key}`);
}
return null;
}
const result = this.deserializeValue(value);
if (__DEV__) {
console.log(`📖 LocalStorage get: ${key}`);
}
return result;
} catch (error) {
console.error(`LocalStorage getItem error for key "${key}":`, error);
return null;
static async remove(key: string, options: StorageOptions = {}): Promise<void> {
const { type = 'local' } = options;
if (type === 'session') {
SessionStorage.remove(key);
} else {
await Storage.remove(key);
}
}
/**
*
*
*/
static async removeItem(key: string): Promise<void> {
try {
await AsyncStorage.removeItem(key);
if (__DEV__) {
console.log(`🗑 LocalStorage remove: ${key}`);
}
} catch (error) {
console.error(`LocalStorage removeItem error for key "${key}":`, error);
throw error;
static async clear(options: StorageOptions = {}): Promise<void> {
const { type = 'local' } = options;
if (type === 'session') {
SessionStorage.clear();
} else {
await Storage.clear();
}
}
/**
*
*
*/
static async clear(): Promise<void> {
try {
await AsyncStorage.clear();
if (__DEV__) {
console.log('🗑 LocalStorage cleared all');
}
} catch (error) {
console.error('LocalStorage clear error:', error);
throw error;
static async getAllKeys(options: StorageOptions = {}): Promise<string[]> {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.getAllKeys();
} else {
return await Storage.getAllKeys();
}
}
}
/**
*
* StorageBase使
/**
*
*/
class SessionStorage extends StorageBase {
private static storage: Map<string, string> = new Map();
static async has(key: string, options: StorageOptions = {}): Promise<boolean> {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.has(key);
} else {
const value = await Storage.getString(key);
return value !== null;
}
}
/**
*
*
*/
static setItem(key: string, value: any): void {
try {
const serialized = this.serializeValue(value);
this.storage.set(key, serialized);
if (__DEV__) {
console.log(`💾 SessionStorage set: ${key}`);
}
} catch (error) {
console.error(`SessionStorage setItem error for key "${key}":`, error);
throw error;
static async multiGet(
keys: string[],
options: StorageOptions = {}
): Promise<[string, string | null][]> {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.multiGet(keys);
} else {
return await Storage.multiGet(keys);
}
}
/**
*
*
*/
static getItem(key: string): any {
try {
const value = this.storage.get(key);
if (value === undefined) {
if (__DEV__) {
console.log(`📖 SessionStorage get: ${key}`);
}
return null;
static async multiSet(
keyValuePairs: [string, string][],
options: StorageOptions = {}
): Promise<void> {
const { type = 'local' } = options;
if (type === 'session') {
SessionStorage.multiSet(keyValuePairs);
} else {
await Storage.multiSet(keyValuePairs);
}
const result = this.deserializeValue(value);
if (__DEV__) {
console.log(`📖 SessionStorage get: ${key}`);
}
return result;
} catch (error) {
console.error(`SessionStorage getItem error for key "${key}":`, error);
return null;
/**
*
*/
static async multiRemove(
keys: string[],
options: StorageOptions = {}
): Promise<void> {
const { type = 'local' } = options;
if (type === 'session') {
SessionStorage.multiRemove(keys);
} else {
await Storage.multiRemove(keys);
}
}
/**
*
* session storage
*/
static removeItem(key: string): void {
try {
this.storage.delete(key);
if (__DEV__) {
console.log(`🗑 SessionStorage remove: ${key}`);
}
} catch (error) {
console.error(`SessionStorage removeItem error for key "${key}":`, error);
throw error;
static getSize(options: StorageOptions = {}): number {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.length;
} else {
// AsyncStorage 不支持直接获取大小
return -1;
}
}
/**
*
* local + session
*/
static clear(): void {
try {
this.storage.clear();
static async clearAll(): Promise<void> {
await Storage.clear();
SessionStorage.clear();
if (__DEV__) {
console.log('🗑 SessionStorage cleared all');
}
} catch (error) {
console.error('SessionStorage clear error:', error);
throw error;
console.log('🗑 All storage cleared (local + session)');
}
}
}
/**
*
*/
const storageManager = {
local: LocalStorage,
session: SessionStorage,
};
export default storageManager;
export default StorageManager;

Loading…
Cancel
Save