Files
rn-app/app/(tabs)/demo.tsx
2025-11-06 22:48:08 +08:00

834 lines
23 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 完整示例页面
* 展示所有工具的使用方法
*/
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 { useRouter } from 'expo-router';
// ✅ 扁平化导入:从根目录的各个模块导入
// 工具函数
import {
Storage,
STORAGE_KEYS,
SessionStorage,
SESSION_KEYS,
formatDate,
formatRelativeTime,
formatChatTime
} from '@/utils';
// 状态管理
import {
useUserStore,
useUser,
useIsLoggedIn,
useSettingsStore,
useTheme,
useLanguage,
useHapticsEnabled,
useSettingsActions,
useTenantStates,
useTenantInfo,
} from '@/stores';
// 验证规则
import { loginSchema } from '@/schemas';
import type { LoginFormData } from '@/schemas';
// API 服务
import { authService } from '@/services';
// 自定义 Hooks
import { useDebounce, useThrottle, useHaptics } from '@/hooks';
// 主题组件
import { ThemeDemo } from '@/components/ThemeDemo';
export default function DemoScreen() {
console.log('=== DemoScreen 组件已渲染 ===');
const haptics = useHaptics();
const router = useRouter();
// 状态管理示例
const user = useUser();
const isLoggedIn = useIsLoggedIn();
const login = useUserStore((state) => state.login);
const logout = useUserStore((state) => state.logout);
const { tenantLoad } = useTenantStates();
const tenantInfo = useTenantInfo();
// 设置状态
const theme = useTheme();
const language = useLanguage();
const hapticsEnabled = useHapticsEnabled();
const { setTheme, setLanguage, setHapticsEnabled } = useSettingsActions();
// 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 [sessionValue, setSessionValue] = useState('');
// 表单配置
const {
control,
handleSubmit,
formState: { errors },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: '',
password: '',
},
});
// 防抖搜索示例
const debouncedSearch = useDebounce(async (text: string) => {
console.log('防抖搜索:', text);
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('失败', '读取失败');
}
};
// SessionStorage 处理函数
const handleSaveToSession = () => {
try {
haptics.light();
const testData = {
formDraft: {
title: '草稿标题',
content: '这是一个表单草稿示例',
},
timestamp: new Date().toISOString(),
counter: Math.floor(Math.random() * 100),
};
SessionStorage.setObject(SESSION_KEYS.FORM_DRAFT, testData);
haptics.success();
Alert.alert('成功', '数据已保存到会话存储(应用重启后会丢失)');
} catch (error) {
haptics.error();
Alert.alert('失败', '保存失败');
}
};
const handleLoadFromSession = () => {
try {
haptics.light();
const data = SessionStorage.getObject<any>(SESSION_KEYS.FORM_DRAFT);
if (data) {
setSessionValue(JSON.stringify(data, null, 2));
haptics.success();
} else {
setSessionValue('暂无数据(会话存储为空)');
haptics.warning();
}
} catch (error) {
haptics.error();
Alert.alert('失败', '读取失败');
}
};
const handleClearSession = () => {
try {
haptics.light();
SessionStorage.clear();
setSessionValue('');
haptics.success();
Alert.alert('成功', '会话存储已清空');
} 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}>📱 </Text>
<TouchableOpacity
style={[styles.button, styles.primaryButton]}
onPress={() => {
haptics.light();
router.push('/test');
}}
>
<Text style={styles.buttonText}> </Text>
</TouchableOpacity>
<Text style={styles.infoText}>
tabs
</Text>
<TouchableOpacity
style={[styles.button, { backgroundColor: '#9333ea', marginTop: 12 }]}
onPress={() => {
haptics.light();
router.push('/theme-test');
}}
>
<Text style={styles.buttonText}>🎨 </Text>
</TouchableOpacity>
<Text style={styles.infoText}>
</Text>
<TouchableOpacity
style={[styles.button, { backgroundColor: '#06b6d4', marginTop: 12 }]}
onPress={() => {
haptics.light();
router.push('/theme-example');
}}
>
<Text style={styles.buttonText}>📚 </Text>
</TouchableOpacity>
<Text style={styles.infoText}>
使 CSS
</Text>
</View>
{/* 租户信息显示 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>🏢 </Text>
<Text style={styles.infoText}>
: {tenantLoad ? '✅ 已加载' : '❌ 未加载'}
</Text>
{tenantInfo ? (
<>
<Text style={styles.infoText}>TID: {tenantInfo.tid || '无'}</Text>
<Text style={styles.infoText}>
: {tenantInfo.create_time || '无'}
</Text>
<Text style={styles.infoText}>
: {tenantInfo.domain_addr || '无'}
</Text>
</>
) : null}
</View>
{/* 用户状态显示 */}
<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" />
) : null}
<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>
) : null}
</View>
)}
/>
<TouchableOpacity
style={[styles.button, styles.loginButton]}
onPress={handleSubmit(onLogin)}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}></Text>
)}
</TouchableOpacity>
</View>
) : null}
{/* 搜索示例 */}
<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>
) : null}
</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>
) : null}
</View>
{/* 会话存储示例 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>🔄 (SessionStorage)</Text>
<Text style={styles.infoText}>
</Text>
<View style={styles.buttonRow}>
<TouchableOpacity
style={[styles.button, styles.primaryButton, styles.thirdButton]}
onPress={handleSaveToSession}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.secondaryButton, styles.thirdButton]}
onPress={handleLoadFromSession}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.errorButton, styles.thirdButton]}
onPress={handleClearSession}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
{sessionValue ? (
<View style={styles.codeBlock}>
<Text style={styles.codeText}>{sessionValue}</Text>
</View>
) : null}
</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>
<ThemeDemo />
</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',
justifyContent: 'space-between',
},
halfButton: {
flex: 1,
},
thirdButton: {
flex: 1,
marginHorizontal: 4,
},
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',
marginHorizontal: -4,
},
hapticsButton: {
backgroundColor: '#5856D6',
flex: 1,
minWidth: '30%',
margin: 4,
},
successButton: {
backgroundColor: '#34C759',
flex: 1,
margin: 4,
minWidth: '30%',
},
warningButton: {
backgroundColor: '#FF9500',
flex: 1,
minWidth: '30%',
margin: 4,
},
errorButton: {
backgroundColor: '#FF3B30',
flex: 1,
minWidth: '30%',
margin: 4,
},
footer: {
marginTop: 24,
marginBottom: 40,
alignItems: 'center',
},
footerText: {
fontSize: 14,
color: '#999',
},
});