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