You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

687 lines
18 KiB

/**
*
* 使
*/
import React, { useState, useEffect } from 'react';
import {
StyleSheet,
View,
Text,
TextInput,
TouchableOpacity,
ScrollView,
Switch,
Alert,
ActivityIndicator,
} from 'react-native';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Image } from 'expo-image';
// 导入所有工具
import {
// 工具函数
Storage,
STORAGE_KEYS,
formatDate,
formatRelativeTime,
formatChatTime,
// 状态管理
useUserStore,
useUser,
useIsLoggedIn,
useSettingsStore,
useTheme,
useLanguage,
useHapticsEnabled,
// 验证规则
loginSchema,
type LoginFormData,
// API 服务
authService,
// 自定义 Hooks
useDebounce,
useThrottle,
useHaptics,
} from '@/src';
export default function DemoScreen() {
const haptics = useHaptics();
// 状态管理示例
const user = useUser();
const isLoggedIn = useIsLoggedIn();
const login = useUserStore((state) => state.login);
const logout = useUserStore((state) => state.logout);
// 设置状态
const theme = useTheme();
const language = useLanguage();
const hapticsEnabled = useHapticsEnabled();
const setTheme = useSettingsStore((state) => state.setTheme);
const setLanguage = useSettingsStore((state) => state.setLanguage);
const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);
// 本地状态
const [searchText, setSearchText] = useState('');
const [searchResults, setSearchResults] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const [counter, setCounter] = useState(0);
const [storageValue, setStorageValue] = useState('');
// 表单配置
const {
control,
handleSubmit,
formState: { errors },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: '',
password: '',
},
});
// 防抖搜索示例
const debouncedSearch = useDebounce(async (text: string) => {
if (!text.trim()) {
setSearchResults([]);
return;
}
console.log('执行搜索:', text);
// 模拟 API 调用
await new Promise((resolve) => setTimeout(resolve, 500));
setSearchResults([
`结果 1: ${text}`,
`结果 2: ${text}`,
`结果 3: ${text}`,
]);
}, 500);
// 监听搜索文本变化
useEffect(() => {
debouncedSearch(searchText);
}, [searchText]);
// 节流点击示例
const throttledClick = useThrottle(() => {
haptics.light();
setCounter((prev) => prev + 1);
console.log('节流点击:', counter + 1);
}, 1000);
// 登录处理
const onLogin = async (data: LoginFormData) => {
try {
setLoading(true);
haptics.light();
// 模拟登录 API 调用
console.log('登录数据:', data);
// 实际项目中使用:
// const { user, token } = await authService.login(data);
// login(user, token);
// 模拟登录成功
const mockUser = {
id: '1',
username: data.email.split('@')[0],
email: data.email,
avatar: 'https://i.pravatar.cc/150?img=1',
nickname: '演示用户',
createdAt: new Date().toISOString(),
};
login(mockUser, 'mock-token-123456');
haptics.success();
Alert.alert('成功', '登录成功!');
} catch (error: any) {
haptics.error();
Alert.alert('失败', error.message || '登录失败');
} finally {
setLoading(false);
}
};
// 登出处理
const handleLogout = () => {
haptics.warning();
Alert.alert(
'确认',
'确定要退出登录吗?',
[
{ text: '取消', style: 'cancel' },
{
text: '确定',
onPress: () => {
logout();
haptics.success();
},
},
]
);
};
// 存储示例
const handleSaveToStorage = async () => {
try {
haptics.light();
const testData = {
message: 'Hello from AsyncStorage!',
timestamp: new Date().toISOString(),
counter,
};
await Storage.setObject(STORAGE_KEYS.USER_PREFERENCES, testData);
haptics.success();
Alert.alert('成功', '数据已保存到本地存储');
} catch (error) {
haptics.error();
Alert.alert('失败', '保存失败');
}
};
const handleLoadFromStorage = async () => {
try {
haptics.light();
const data = await Storage.getObject<any>(STORAGE_KEYS.USER_PREFERENCES);
if (data) {
setStorageValue(JSON.stringify(data, null, 2));
haptics.success();
} else {
setStorageValue('暂无数据');
haptics.warning();
}
} catch (error) {
haptics.error();
Alert.alert('失败', '读取失败');
}
};
// 主题切换
const handleThemeChange = () => {
haptics.selection();
const themes: Array<'light' | 'dark' | 'auto'> = ['light', 'dark', 'auto'];
const currentIndex = themes.indexOf(theme);
const nextTheme = themes[(currentIndex + 1) % themes.length];
setTheme(nextTheme);
};
// 语言切换
const handleLanguageChange = () => {
haptics.selection();
setLanguage(language === 'zh-CN' ? 'en-US' : 'zh-CN');
};
return (
<ScrollView style={styles.container}>
<View style={styles.content}>
{/* 标题 */}
<Text style={styles.title}>🎯 </Text>
<Text style={styles.subtitle}>使</Text>
{/* 用户状态显示 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>👤 (Zustand)</Text>
{isLoggedIn ? (
<View style={styles.userInfo}>
{user?.avatar && (
<Image
source={{ uri: user.avatar }}
style={styles.avatar}
contentFit="cover"
/>
)}
<View style={styles.userDetails}>
<Text style={styles.userName}>{user?.nickname}</Text>
<Text style={styles.userEmail}>{user?.email}</Text>
<Text style={styles.userDate}>
: {formatRelativeTime(user?.createdAt || '')}
</Text>
</View>
<TouchableOpacity
style={[styles.button, styles.logoutButton]}
onPress={handleLogout}
>
<Text style={styles.buttonText}>退</Text>
</TouchableOpacity>
</View>
) : (
<Text style={styles.infoText}></Text>
)}
</View>
{/* 登录表单 */}
{!isLoggedIn && (
<View style={styles.section}>
<Text style={styles.sectionTitle}>🔐 (React Hook Form + Zod)</Text>
<Controller
control={control}
name="email"
render={({ field: { onChange, value } }) => (
<View style={styles.inputGroup}>
<Text style={styles.label}></Text>
<TextInput
value={value}
onChangeText={onChange}
placeholder="请输入邮箱"
keyboardType="email-address"
autoCapitalize="none"
style={[
styles.input,
errors.email && styles.inputError,
]}
/>
{errors.email && (
<Text style={styles.errorText}>{errors.email.message}</Text>
)}
</View>
)}
/>
<Controller
control={control}
name="password"
render={({ field: { onChange, value } }) => (
<View style={styles.inputGroup}>
<Text style={styles.label}></Text>
<TextInput
value={value}
onChangeText={onChange}
placeholder="请输入密码"
secureTextEntry
style={[
styles.input,
errors.password && styles.inputError,
]}
/>
{errors.password && (
<Text style={styles.errorText}>{errors.password.message}</Text>
)}
</View>
)}
/>
<TouchableOpacity
style={[styles.button, styles.loginButton]}
onPress={handleSubmit(onLogin)}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}></Text>
)}
</TouchableOpacity>
</View>
)}
{/* 搜索示例 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>🔍 (useDebounce)</Text>
<TextInput
value={searchText}
onChangeText={setSearchText}
placeholder="输入搜索内容..."
style={styles.input}
/>
{searchResults.length > 0 && (
<View style={styles.searchResults}>
{searchResults.map((result, index) => (
<Text key={index} style={styles.searchResult}>
{result}
</Text>
))}
</View>
)}
</View>
{/* 节流点击示例 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}> (useThrottle)</Text>
<Text style={styles.infoText}>: {counter}</Text>
<TouchableOpacity
style={[styles.button, styles.primaryButton]}
onPress={throttledClick}
>
<Text style={styles.buttonText}>1</Text>
</TouchableOpacity>
</View>
{/* 本地存储示例 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>💾 (AsyncStorage)</Text>
<View style={styles.buttonRow}>
<TouchableOpacity
style={[styles.button, styles.primaryButton, styles.halfButton]}
onPress={handleSaveToStorage}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.secondaryButton, styles.halfButton]}
onPress={handleLoadFromStorage}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
{storageValue && (
<View style={styles.codeBlock}>
<Text style={styles.codeText}>{storageValue}</Text>
</View>
)}
</View>
{/* 日期格式化示例 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>📅 (Day.js)</Text>
<Text style={styles.infoText}>
: {formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')}
</Text>
<Text style={styles.infoText}>
: {formatRelativeTime(new Date())}
</Text>
<Text style={styles.infoText}>
: {formatChatTime(Date.now())}
</Text>
</View>
{/* 设置示例 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}> </Text>
<View style={styles.settingRow}>
<Text style={styles.settingLabel}>: {theme}</Text>
<TouchableOpacity
style={[styles.button, styles.smallButton]}
onPress={handleThemeChange}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
<View style={styles.settingRow}>
<Text style={styles.settingLabel}>: {language}</Text>
<TouchableOpacity
style={[styles.button, styles.smallButton]}
onPress={handleLanguageChange}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
<View style={styles.settingRow}>
<Text style={styles.settingLabel}></Text>
<Switch
value={hapticsEnabled}
onValueChange={(value) => {
haptics.selection();
setHapticsEnabled(value);
}}
/>
</View>
</View>
{/* 触觉反馈示例 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>📳 (Expo Haptics)</Text>
<View style={styles.buttonGrid}>
<TouchableOpacity
style={[styles.button, styles.hapticsButton]}
onPress={() => haptics.light()}
>
<Text style={styles.buttonText}>Light</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.hapticsButton]}
onPress={() => haptics.medium()}
>
<Text style={styles.buttonText}>Medium</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.hapticsButton]}
onPress={() => haptics.heavy()}
>
<Text style={styles.buttonText}>Heavy</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.successButton]}
onPress={() => haptics.success()}
>
<Text style={styles.buttonText}>Success</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.warningButton]}
onPress={() => haptics.warning()}
>
<Text style={styles.buttonText}>Warning</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.errorButton]}
onPress={() => haptics.error()}
>
<Text style={styles.buttonText}>Error</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.footer}>
<Text style={styles.footerText}>
使 📖
</Text>
</View>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
content: {
padding: 16,
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 8,
color: '#333',
},
subtitle: {
fontSize: 16,
color: '#666',
marginBottom: 24,
},
section: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
marginBottom: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
marginBottom: 12,
color: '#333',
},
userInfo: {
flexDirection: 'row',
alignItems: 'center',
},
avatar: {
width: 60,
height: 60,
borderRadius: 30,
marginRight: 12,
},
userDetails: {
flex: 1,
},
userName: {
fontSize: 16,
fontWeight: '600',
color: '#333',
},
userEmail: {
fontSize: 14,
color: '#666',
marginTop: 2,
},
userDate: {
fontSize: 12,
color: '#999',
marginTop: 4,
},
inputGroup: {
marginBottom: 16,
},
label: {
fontSize: 14,
fontWeight: '500',
marginBottom: 6,
color: '#333',
},
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
fontSize: 16,
backgroundColor: '#fff',
},
inputError: {
borderColor: '#ff3b30',
},
errorText: {
color: '#ff3b30',
fontSize: 12,
marginTop: 4,
},
button: {
paddingVertical: 12,
paddingHorizontal: 20,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
loginButton: {
backgroundColor: '#007AFF',
marginTop: 8,
},
logoutButton: {
backgroundColor: '#ff3b30',
paddingVertical: 8,
paddingHorizontal: 16,
},
primaryButton: {
backgroundColor: '#007AFF',
},
secondaryButton: {
backgroundColor: '#5856D6',
},
smallButton: {
backgroundColor: '#007AFF',
paddingVertical: 8,
paddingHorizontal: 16,
},
buttonRow: {
flexDirection: 'row',
gap: 12,
},
halfButton: {
flex: 1,
},
searchResults: {
marginTop: 12,
},
searchResult: {
padding: 12,
backgroundColor: '#f9f9f9',
borderRadius: 8,
marginBottom: 8,
color: '#333',
},
infoText: {
fontSize: 14,
color: '#666',
marginBottom: 8,
},
codeBlock: {
backgroundColor: '#f9f9f9',
borderRadius: 8,
padding: 12,
marginTop: 12,
},
codeText: {
fontFamily: 'monospace',
fontSize: 12,
color: '#333',
},
settingRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
settingLabel: {
fontSize: 16,
color: '#333',
},
buttonGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
},
hapticsButton: {
backgroundColor: '#5856D6',
flex: 1,
minWidth: '30%',
},
successButton: {
backgroundColor: '#34C759',
flex: 1,
minWidth: '30%',
},
warningButton: {
backgroundColor: '#FF9500',
flex: 1,
minWidth: '30%',
},
errorButton: {
backgroundColor: '#FF3B30',
flex: 1,
minWidth: '30%',
},
footer: {
marginTop: 24,
marginBottom: 40,
alignItems: 'center',
},
footerText: {
fontSize: 14,
color: '#999',
},
});