|
|
/** |
|
|
* 完整示例页面 |
|
|
* 展示所有工具的使用方法 |
|
|
*/ |
|
|
|
|
|
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', |
|
|
}, |
|
|
}); |
|
|
|
|
|
|