feat: update
This commit is contained in:
@@ -4,8 +4,7 @@ import { Link, Tabs } from 'expo-router';
|
||||
import { Pressable } from 'react-native';
|
||||
|
||||
import Colors from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/components/useColorScheme';
|
||||
import { useClientOnlyValue } from '@/components/useClientOnlyValue';
|
||||
import { useColorScheme, useClientOnlyValue } from '@/hooks';
|
||||
|
||||
// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
|
||||
function TabBarIcon(props: {
|
||||
|
||||
@@ -18,17 +18,23 @@ import {
|
||||
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,
|
||||
formatChatTime
|
||||
} from '@/utils';
|
||||
|
||||
// 状态管理
|
||||
// 状态管理
|
||||
import {
|
||||
useUserStore,
|
||||
useUser,
|
||||
useIsLoggedIn,
|
||||
@@ -36,24 +42,28 @@ import {
|
||||
useTheme,
|
||||
useLanguage,
|
||||
useHapticsEnabled,
|
||||
useSettingsActions,
|
||||
useTenantStates,
|
||||
useTenantInfo,
|
||||
} from '@/stores';
|
||||
|
||||
// 验证规则
|
||||
loginSchema,
|
||||
type LoginFormData,
|
||||
// 验证规则
|
||||
import { loginSchema } from '@/schemas';
|
||||
import type { LoginFormData } from '@/schemas';
|
||||
|
||||
// API 服务
|
||||
authService,
|
||||
appService,
|
||||
// API 服务
|
||||
import { authService } from '@/services';
|
||||
|
||||
// 自定义 Hooks
|
||||
useDebounce,
|
||||
useThrottle,
|
||||
useHaptics,
|
||||
} from '@/src';
|
||||
// 自定义 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();
|
||||
@@ -61,13 +71,17 @@ export default function DemoScreen() {
|
||||
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 = useSettingsStore((state) => state.setTheme);
|
||||
const setLanguage = useSettingsStore((state) => state.setLanguage);
|
||||
const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);
|
||||
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('');
|
||||
@@ -75,6 +89,7 @@ export default function DemoScreen() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [counter, setCounter] = useState(0);
|
||||
const [storageValue, setStorageValue] = useState('');
|
||||
const [sessionValue, setSessionValue] = useState('');
|
||||
|
||||
// 表单配置
|
||||
const {
|
||||
@@ -103,21 +118,6 @@ export default function DemoScreen() {
|
||||
setSearchResults([`结果 1: ${text}`, `结果 2: ${text}`, `结果 3: ${text}`]);
|
||||
}, 500);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('=== useEffect 开始执行 ===');
|
||||
console.log('appService:', appService);
|
||||
console.log('getPlatformData 方法:', appService.getPlatformData);
|
||||
|
||||
appService
|
||||
.getPlatformData()
|
||||
.then((res: any) => {
|
||||
console.log('getPlatformData 成功:', res);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error('getPlatformData 失败:', err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 监听搜索文本变化
|
||||
useEffect(() => {
|
||||
debouncedSearch(searchText);
|
||||
@@ -216,6 +216,59 @@ export default function DemoScreen() {
|
||||
}
|
||||
};
|
||||
|
||||
// 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();
|
||||
@@ -238,6 +291,68 @@ export default function DemoScreen() {
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 用户状态显示 */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>👤 用户状态 (Zustand)</Text>
|
||||
@@ -373,6 +488,39 @@ export default function DemoScreen() {
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 日期格式化示例 */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>📅 日期格式化 (Day.js)</Text>
|
||||
@@ -383,6 +531,12 @@ export default function DemoScreen() {
|
||||
<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>
|
||||
@@ -598,6 +752,10 @@ const styles = StyleSheet.create({
|
||||
halfButton: {
|
||||
flex: 1,
|
||||
},
|
||||
thirdButton: {
|
||||
flex: 1,
|
||||
marginHorizontal: 4,
|
||||
},
|
||||
searchResults: {
|
||||
marginTop: 12,
|
||||
},
|
||||
|
||||
@@ -9,7 +9,10 @@ import { Alert, Platform } from 'react-native';
|
||||
import 'react-native-reanimated';
|
||||
import { PaperProvider, MD3DarkTheme, MD3LightTheme } from 'react-native-paper';
|
||||
|
||||
import { useColorScheme } from '@/components/useColorScheme';
|
||||
// ✅ 从 hooks 目录导入
|
||||
import { useColorScheme } from '@/hooks';
|
||||
// ✅ 从 stores 目录导入
|
||||
import { restoreUserState, restoreSettingsState, useTenantActions } from '@/stores';
|
||||
|
||||
export {
|
||||
// Catch any errors thrown by the Layout component.
|
||||
@@ -29,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(() => {
|
||||
@@ -41,6 +45,31 @@ export default function RootLayout() {
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
// 恢复持久化状态并初始化应用数据
|
||||
useEffect(() => {
|
||||
async function initializeApp() {
|
||||
try {
|
||||
// 1. 恢复本地存储的状态
|
||||
await Promise.all([restoreUserState(), restoreSettingsState()]);
|
||||
|
||||
// 2. 调用初始化接口(获取平台数据等)
|
||||
if (__DEV__) {
|
||||
console.log('🚀 Initializing app data...');
|
||||
}
|
||||
|
||||
await requestTenantInfo();
|
||||
|
||||
if (__DEV__) {
|
||||
console.log('✅ Platform data loaded:');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize app:', error);
|
||||
// 初始化失败不应该阻止应用启动,只记录错误
|
||||
}
|
||||
}
|
||||
initializeApp();
|
||||
}, []);
|
||||
|
||||
// 检查热更新
|
||||
useEffect(() => {
|
||||
async function checkForUpdates() {
|
||||
|
||||
119
app/test-page.tsx
Normal file
119
app/test-page.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { StyleSheet, ScrollView } from 'react-native';
|
||||
import { Stack, useRouter } from 'expo-router';
|
||||
import { ThemedText, ThemedView } from '@/components';
|
||||
|
||||
/**
|
||||
* 测试页面
|
||||
*
|
||||
* 这是一个独立的业务页面示例,不包含底部 tabs
|
||||
*
|
||||
* 特点:
|
||||
* - 带有返回按钮的 header
|
||||
* - 不包含底部导航栏
|
||||
* - 可以作为业务页面的模板
|
||||
*/
|
||||
export default function TestPage() {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 配置页面 header */}
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: '测试页面',
|
||||
headerShown: true,
|
||||
headerBackTitle: '返回',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ThemedView style={styles.container}>
|
||||
<ScrollView style={styles.scrollView}>
|
||||
<ThemedView style={styles.content}>
|
||||
<ThemedText type="title" style={styles.title}>
|
||||
测试页面
|
||||
</ThemedText>
|
||||
|
||||
<ThemedText style={styles.description}>
|
||||
这是一个独立的业务页面示例,展示了如何创建不包含底部 tabs 的页面。
|
||||
</ThemedText>
|
||||
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">页面特点</ThemedText>
|
||||
<ThemedText style={styles.item}>✅ 带有返回按钮的 header</ThemedText>
|
||||
<ThemedText style={styles.item}>✅ 不包含底部导航栏</ThemedText>
|
||||
<ThemedText style={styles.item}>✅ 支持主题切换</ThemedText>
|
||||
<ThemedText style={styles.item}>✅ 可以作为业务页面模板</ThemedText>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">使用场景</ThemedText>
|
||||
<ThemedText style={styles.item}>• 详情页面</ThemedText>
|
||||
<ThemedText style={styles.item}>• 表单页面</ThemedText>
|
||||
<ThemedText style={styles.item}>• 设置页面</ThemedText>
|
||||
<ThemedText style={styles.item}>• 其他业务页面</ThemedText>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">路由说明</ThemedText>
|
||||
<ThemedText style={styles.item}>
|
||||
文件路径: app/test-page.tsx
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.item}>
|
||||
访问路径: /test-page
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.item}>
|
||||
跳转方式: router.push('/test-page')
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.infoBox}>
|
||||
<ThemedText type="defaultSemiBold">💡 提示</ThemedText>
|
||||
<ThemedText style={styles.infoText}>
|
||||
对于复杂的业务页面,建议在 screens/ 目录下创建独立的组件,
|
||||
然后在 app/ 目录下的路由文件中引用。
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
</ThemedView>
|
||||
</ScrollView>
|
||||
</ThemedView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
content: {
|
||||
padding: 20,
|
||||
},
|
||||
title: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
description: {
|
||||
marginBottom: 24,
|
||||
lineHeight: 24,
|
||||
},
|
||||
section: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
item: {
|
||||
marginTop: 8,
|
||||
marginLeft: 8,
|
||||
lineHeight: 24,
|
||||
},
|
||||
infoBox: {
|
||||
padding: 16,
|
||||
borderRadius: 8,
|
||||
backgroundColor: 'rgba(0, 122, 255, 0.1)',
|
||||
marginTop: 8,
|
||||
},
|
||||
infoText: {
|
||||
marginTop: 8,
|
||||
lineHeight: 22,
|
||||
},
|
||||
});
|
||||
|
||||
259
app/theme-example.tsx
Normal file
259
app/theme-example.tsx
Normal file
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* 主题系统使用示例
|
||||
*
|
||||
* 展示四种不同的主题样式使用方式
|
||||
*/
|
||||
|
||||
import { ScrollView, View, Text, TouchableOpacity } from 'react-native';
|
||||
import { Stack } from 'expo-router';
|
||||
import {
|
||||
useColorScheme,
|
||||
useThemeColors,
|
||||
useThemeInfo,
|
||||
commonStyles,
|
||||
createThemeStyles,
|
||||
ThemedText,
|
||||
ThemedView,
|
||||
} from '@/theme';
|
||||
|
||||
// 方式 3: 创建自定义主题样式(推荐用于复杂组件)
|
||||
const customStyles = createThemeStyles((colors) => ({
|
||||
header: {
|
||||
backgroundColor: colors.primary,
|
||||
padding: 20,
|
||||
borderRadius: 12,
|
||||
marginBottom: 16,
|
||||
},
|
||||
headerText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
section: {
|
||||
backgroundColor: colors.card,
|
||||
padding: 16,
|
||||
borderRadius: 8,
|
||||
marginBottom: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
sectionTitle: {
|
||||
color: colors.text,
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
marginBottom: 12,
|
||||
},
|
||||
codeBlock: {
|
||||
backgroundColor: colors.backgroundTertiary,
|
||||
padding: 12,
|
||||
borderRadius: 6,
|
||||
marginTop: 8,
|
||||
},
|
||||
codeText: {
|
||||
color: colors.textSecondary,
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
},
|
||||
}));
|
||||
|
||||
export default function ThemeExampleScreen() {
|
||||
const theme = useColorScheme();
|
||||
const colors = useThemeColors();
|
||||
const { isDark } = useThemeInfo();
|
||||
const s = commonStyles[theme]; // 通用样式类
|
||||
const custom = customStyles[theme]; // 自定义样式
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: '主题系统示例',
|
||||
headerStyle: {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
headerTintColor: colors.text,
|
||||
}}
|
||||
/>
|
||||
<ScrollView style={s.containerPadded}>
|
||||
{/* 自定义样式示例 */}
|
||||
<View style={custom.header}>
|
||||
<Text style={custom.headerText}>
|
||||
主题系统使用示例
|
||||
</Text>
|
||||
<Text style={[custom.headerText, { fontSize: 14, marginTop: 8 }]}>
|
||||
当前主题: {theme} {isDark ? '🌙' : '☀️'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* 方式 1: 使用主题组件 */}
|
||||
<View style={custom.section}>
|
||||
<Text style={custom.sectionTitle}>
|
||||
方式 1: 使用主题组件
|
||||
</Text>
|
||||
<ThemedView style={{ padding: 12, borderRadius: 6 }}>
|
||||
<ThemedText type="title">这是标题</ThemedText>
|
||||
<ThemedText type="subtitle">这是副标题</ThemedText>
|
||||
<ThemedText>这是普通文本</ThemedText>
|
||||
</ThemedView>
|
||||
<View style={custom.codeBlock}>
|
||||
<Text style={custom.codeText}>
|
||||
{`import { ThemedText, ThemedView } from '@/theme';
|
||||
|
||||
<ThemedView>
|
||||
<ThemedText type="title">标题</ThemedText>
|
||||
</ThemedView>`}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 方式 2: 使用通用样式类 */}
|
||||
<View style={custom.section}>
|
||||
<Text style={custom.sectionTitle}>
|
||||
方式 2: 使用通用样式类(类似 CSS 类名)
|
||||
</Text>
|
||||
<View style={s.card}>
|
||||
<Text style={s.textTitle}>卡片标题</Text>
|
||||
<Text style={s.textSecondary}>卡片描述文本</Text>
|
||||
<View style={s.spacingMd} />
|
||||
<TouchableOpacity style={s.button}>
|
||||
<Text style={s.buttonText}>主要按钮</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={s.spacingSm} />
|
||||
<TouchableOpacity style={s.buttonOutline}>
|
||||
<Text style={s.buttonTextOutline}>次要按钮</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={custom.codeBlock}>
|
||||
<Text style={custom.codeText}>
|
||||
{`import { useColorScheme, commonStyles } from '@/theme';
|
||||
|
||||
const theme = useColorScheme();
|
||||
const s = commonStyles[theme];
|
||||
|
||||
<View style={s.card}>
|
||||
<Text style={s.textTitle}>标题</Text>
|
||||
<TouchableOpacity style={s.button}>
|
||||
<Text style={s.buttonText}>按钮</Text>
|
||||
</TouchableOpacity>
|
||||
</View>`}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 方式 3: 使用自定义主题样式 */}
|
||||
<View style={custom.section}>
|
||||
<Text style={custom.sectionTitle}>
|
||||
方式 3: 使用自定义主题样式(推荐)
|
||||
</Text>
|
||||
<View style={{ padding: 12 }}>
|
||||
<Text style={{ color: colors.text }}>
|
||||
本页面的 header 和 section 就是使用自定义主题样式创建的
|
||||
</Text>
|
||||
</View>
|
||||
<View style={custom.codeBlock}>
|
||||
<Text style={custom.codeText}>
|
||||
{`import { createThemeStyles } from '@/theme';
|
||||
|
||||
const styles = createThemeStyles((colors) => ({
|
||||
header: {
|
||||
backgroundColor: colors.primary,
|
||||
padding: 20,
|
||||
},
|
||||
headerText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 24,
|
||||
},
|
||||
}));
|
||||
|
||||
const theme = useColorScheme();
|
||||
<View style={styles[theme].header}>
|
||||
<Text style={styles[theme].headerText}>标题</Text>
|
||||
</View>`}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 方式 4: 使用 Hooks + 内联样式 */}
|
||||
<View style={custom.section}>
|
||||
<Text style={custom.sectionTitle}>
|
||||
方式 4: 使用 Hooks + 内联样式(动态场景)
|
||||
</Text>
|
||||
<View style={{
|
||||
backgroundColor: colors.backgroundSecondary,
|
||||
padding: 16,
|
||||
borderRadius: 8,
|
||||
}}>
|
||||
<Text style={{ color: colors.text, fontSize: 16 }}>
|
||||
这是使用 useThemeColors() 动态获取颜色的文本
|
||||
</Text>
|
||||
<View style={{ height: 12 }} />
|
||||
<Text style={{ color: colors.textSecondary, fontSize: 14 }}>
|
||||
适合需要动态计算样式的场景
|
||||
</Text>
|
||||
</View>
|
||||
<View style={custom.codeBlock}>
|
||||
<Text style={custom.codeText}>
|
||||
{`import { useThemeColors } from '@/theme';
|
||||
|
||||
const colors = useThemeColors();
|
||||
|
||||
<View style={{
|
||||
backgroundColor: colors.background,
|
||||
padding: 16,
|
||||
}}>
|
||||
<Text style={{ color: colors.text }}>
|
||||
动态文本
|
||||
</Text>
|
||||
</View>`}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 颜色展示 */}
|
||||
<View style={custom.section}>
|
||||
<Text style={custom.sectionTitle}>
|
||||
主题颜色展示
|
||||
</Text>
|
||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
|
||||
{Object.entries(colors).map(([key, value]) => (
|
||||
<View
|
||||
key={key}
|
||||
style={{
|
||||
backgroundColor: value as string,
|
||||
padding: 8,
|
||||
borderRadius: 6,
|
||||
minWidth: 100,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
}}
|
||||
>
|
||||
<Text style={{
|
||||
color: key.includes('background') || key.includes('card') || key.includes('input')
|
||||
? colors.text
|
||||
: '#FFFFFF',
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
}}>
|
||||
{key}
|
||||
</Text>
|
||||
<Text style={{
|
||||
color: key.includes('background') || key.includes('card') || key.includes('input')
|
||||
? colors.textSecondary
|
||||
: '#FFFFFF',
|
||||
fontSize: 8,
|
||||
}}>
|
||||
{value}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 底部间距 */}
|
||||
<View style={s.spacingXl} />
|
||||
</ScrollView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
317
app/theme-test.tsx
Normal file
317
app/theme-test.tsx
Normal file
@@ -0,0 +1,317 @@
|
||||
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, useSettingsActions } from '@/stores';
|
||||
import { useHaptics } from '@/hooks';
|
||||
import Colors from '@/constants/Colors';
|
||||
|
||||
export default function ThemeTestScreen() {
|
||||
const currentTheme = useTheme();
|
||||
const { setTheme } = useSettingsActions();
|
||||
const haptics = useHaptics();
|
||||
const systemColorScheme = useSystemColorScheme();
|
||||
|
||||
// 强制重新渲染的状态
|
||||
const [renderKey, setRenderKey] = useState(0);
|
||||
|
||||
// 直接计算实际应用的主题 - 不使用 useColorScheme hook
|
||||
const actualTheme: 'light' | 'dark' = useMemo(() => {
|
||||
return currentTheme === 'auto'
|
||||
? (systemColorScheme === 'dark' ? 'dark' : 'light')
|
||||
: currentTheme;
|
||||
}, [currentTheme, systemColorScheme]);
|
||||
|
||||
// 使用 useMemo 确保颜色对象在主题改变时重新计算
|
||||
const colors = useMemo(() => {
|
||||
console.log('🎨 Recalculating colors for theme:', actualTheme);
|
||||
return Colors[actualTheme] as Record<string, any>;
|
||||
}, [actualTheme]);
|
||||
|
||||
// 监听主题变化
|
||||
useEffect(() => {
|
||||
console.log('🎨 Theme changed:', { currentTheme, systemColorScheme, actualTheme, renderKey });
|
||||
setRenderKey(prev => prev + 1);
|
||||
}, [currentTheme, systemColorScheme, actualTheme]);
|
||||
|
||||
const handleThemeChange = (newTheme: 'light' | 'dark' | 'auto') => {
|
||||
haptics.selection();
|
||||
console.log('🎨 Changing theme to:', newTheme);
|
||||
setTheme(newTheme);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: '主题测试',
|
||||
headerStyle: {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
headerTintColor: colors.text,
|
||||
}}
|
||||
/>
|
||||
<ScrollView
|
||||
style={[styles.container, { backgroundColor: colors.background }]}
|
||||
>
|
||||
{/* 主题信息 */}
|
||||
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||
<Text style={[styles.title, { color: colors.text }]}>
|
||||
当前主题信息
|
||||
</Text>
|
||||
<Text style={[styles.infoText, { color: colors.textSecondary }]}>
|
||||
用户设置: {currentTheme}
|
||||
</Text>
|
||||
<Text style={[styles.infoText, { color: colors.textSecondary }]}>
|
||||
系统主题: {systemColorScheme || 'light'}
|
||||
</Text>
|
||||
<Text style={[styles.infoText, { color: colors.textSecondary }]}>
|
||||
实际应用: {actualTheme}
|
||||
</Text>
|
||||
<Text style={[styles.infoText, { color: colors.textSecondary }]}>
|
||||
渲染次数: {renderKey}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* 主题切换按钮 */}
|
||||
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||
<Text style={[styles.title, { color: colors.text }]}>
|
||||
切换主题
|
||||
</Text>
|
||||
<View style={styles.buttonRow}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.themeButton,
|
||||
{
|
||||
backgroundColor: currentTheme === 'light' ? colors.primary : colors.backgroundTertiary,
|
||||
borderColor: colors.border,
|
||||
}
|
||||
]}
|
||||
onPress={() => handleThemeChange('light')}
|
||||
>
|
||||
<Text style={[
|
||||
styles.buttonText,
|
||||
{ color: currentTheme === 'light' ? '#fff' : colors.text }
|
||||
]}>
|
||||
☀️ 浅色
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.themeButton,
|
||||
{
|
||||
backgroundColor: currentTheme === 'dark' ? colors.primary : colors.backgroundTertiary,
|
||||
borderColor: colors.border,
|
||||
}
|
||||
]}
|
||||
onPress={() => handleThemeChange('dark')}
|
||||
>
|
||||
<Text style={[
|
||||
styles.buttonText,
|
||||
{ color: currentTheme === 'dark' ? '#fff' : colors.text }
|
||||
]}>
|
||||
🌙 深色
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.themeButton,
|
||||
{
|
||||
backgroundColor: currentTheme === 'auto' ? colors.primary : colors.backgroundTertiary,
|
||||
borderColor: colors.border,
|
||||
}
|
||||
]}
|
||||
onPress={() => handleThemeChange('auto')}
|
||||
>
|
||||
<Text style={[
|
||||
styles.buttonText,
|
||||
{ color: currentTheme === 'auto' ? '#fff' : colors.text }
|
||||
]}>
|
||||
🔄 自动
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 文本颜色展示 */}
|
||||
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||
<Text style={[styles.title, { color: colors.text }]}>
|
||||
文本颜色
|
||||
</Text>
|
||||
<Text style={[styles.colorText, { color: colors.text }]}>
|
||||
Primary Text - {colors.text}
|
||||
</Text>
|
||||
<Text style={[styles.colorText, { color: colors.textSecondary }]}>
|
||||
Secondary Text - {colors.textSecondary}
|
||||
</Text>
|
||||
<Text style={[styles.colorText, { color: colors.textTertiary }]}>
|
||||
Tertiary Text - {colors.textTertiary}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* 背景颜色展示 */}
|
||||
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||
<Text style={[styles.title, { color: colors.text }]}>
|
||||
背景颜色
|
||||
</Text>
|
||||
<View style={[styles.colorBox, { backgroundColor: colors.background }]}>
|
||||
<Text style={[styles.colorText, { color: colors.text }]}>
|
||||
Primary Background - {colors.background}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.colorBox, { backgroundColor: colors.backgroundSecondary, borderWidth: 1, borderColor: colors.border }]}>
|
||||
<Text style={[styles.colorText, { color: colors.text }]}>
|
||||
Secondary Background - {colors.backgroundSecondary}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.colorBox, { backgroundColor: colors.backgroundTertiary }]}>
|
||||
<Text style={[styles.colorText, { color: colors.text }]}>
|
||||
Tertiary Background - {colors.backgroundTertiary}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 主题颜色展示 */}
|
||||
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||
<Text style={[styles.title, { color: colors.text }]}>
|
||||
主题颜色
|
||||
</Text>
|
||||
<View style={styles.colorGrid}>
|
||||
<View style={[styles.colorBox, { backgroundColor: colors.primary }]}>
|
||||
<Text style={[styles.colorText, { color: '#fff' }]}>Primary - {colors.primary}</Text>
|
||||
</View>
|
||||
<View style={[styles.colorBox, { backgroundColor: colors.secondary }]}>
|
||||
<Text style={[styles.colorText, { color: '#fff' }]}>Secondary - {colors.secondary}</Text>
|
||||
</View>
|
||||
<View style={[styles.colorBox, { backgroundColor: colors.success }]}>
|
||||
<Text style={[styles.colorText, { color: '#fff' }]}>Success - {colors.success}</Text>
|
||||
</View>
|
||||
<View style={[styles.colorBox, { backgroundColor: colors.warning }]}>
|
||||
<Text style={[styles.colorText, { color: '#fff' }]}>Warning - {colors.warning}</Text>
|
||||
</View>
|
||||
<View style={[styles.colorBox, { backgroundColor: colors.error }]}>
|
||||
<Text style={[styles.colorText, { color: '#fff' }]}>Error - {colors.error}</Text>
|
||||
</View>
|
||||
<View style={[styles.colorBox, { backgroundColor: colors.info }]}>
|
||||
<Text style={[styles.colorText, { color: '#fff' }]}>Info - {colors.info}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* UI 元素展示 */}
|
||||
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||
<Text style={[styles.title, { color: colors.text }]}>
|
||||
UI 元素
|
||||
</Text>
|
||||
<View style={[styles.card, { backgroundColor: colors.card, borderColor: colors.border }]}>
|
||||
<Text style={[styles.cardText, { color: colors.text }]}>
|
||||
这是一个卡片组件
|
||||
</Text>
|
||||
<Text style={[styles.cardSubtext, { color: colors.textSecondary }]}>
|
||||
卡片背景色: {colors.card}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={[styles.input, { backgroundColor: colors.inputBackground, borderColor: colors.inputBorder }]}>
|
||||
<Text style={[styles.inputText, { color: colors.textSecondary }]}>
|
||||
输入框样式预览
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={[styles.separator, { backgroundColor: colors.separator }]} />
|
||||
|
||||
<TouchableOpacity style={[styles.button, { backgroundColor: colors.buttonPrimary }]}>
|
||||
<Text style={[styles.buttonText, { color: '#fff' }]}>
|
||||
按钮示例
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
section: {
|
||||
margin: 16,
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 16,
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
buttonRow: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
themeButton: {
|
||||
flex: 1,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 8,
|
||||
borderWidth: 2,
|
||||
alignItems: 'center',
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
colorText: {
|
||||
fontSize: 14,
|
||||
marginBottom: 8,
|
||||
fontFamily: 'monospace',
|
||||
},
|
||||
colorBox: {
|
||||
padding: 16,
|
||||
borderRadius: 8,
|
||||
marginBottom: 8,
|
||||
alignItems: 'center',
|
||||
},
|
||||
colorGrid: {
|
||||
gap: 8,
|
||||
},
|
||||
card: {
|
||||
padding: 16,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
marginBottom: 12,
|
||||
},
|
||||
cardText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginBottom: 4,
|
||||
},
|
||||
cardSubtext: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
},
|
||||
input: {
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
marginBottom: 12,
|
||||
},
|
||||
inputText: {
|
||||
fontSize: 14,
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
marginVertical: 12,
|
||||
},
|
||||
button: {
|
||||
padding: 16,
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user