From 7e849de54895ac156105dc261f5f1b83030dd264 Mon Sep 17 00:00:00 2001 From: doublex001 <16157008+doublex001@user.noreply.gitee.com> Date: Mon, 31 Aug 2026 16:59:40 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=96=B0=E5=A2=9E=E5=85=B1?= =?UTF-8?q?=E4=BA=AB=20StateView=20=E4=B8=89=E6=80=81=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=B9=B6=E6=8E=A5=E5=85=A5=E6=95=B0=E6=8D=AE/=E5=8D=A0?= =?UTF-8?q?=E4=BD=8D=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/components/StateView 提供 LoadingState/ErrorState(可重试)/EmptyState,颜色走主题 token - privacy 页复用 StateView 去除重复 loading/error 样板 - favorites/history 占位页接入 EmptyState 统一空态样式 --- app/favorites.tsx | 34 ++--------- app/history.tsx | 34 ++--------- app/settings/privacy.tsx | 41 ++------------ src/components/StateView.tsx | 107 +++++++++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 92 deletions(-) create mode 100644 src/components/StateView.tsx diff --git a/app/favorites.tsx b/app/favorites.tsx index 3409e9d..e123e71 100644 --- a/app/favorites.tsx +++ b/app/favorites.tsx @@ -1,36 +1,14 @@ -import { Ionicons } from '@expo/vector-icons'; -import { StyleSheet, Text, View } from 'react-native'; -import { colors } from '../src/theme'; +import { EmptyState } from '../src/components/StateView'; /** * 我的收藏页(占位) */ export default function FavoritesScreen() { return ( - - - 我的收藏 - 收藏页面开发中,敬请期待 - + ); } - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.pageBg, - alignItems: 'center', - justifyContent: 'center', - }, - title: { - marginTop: 12, - fontSize: 20, - fontWeight: '700', - color: colors.textPrimary, - }, - subtitle: { - marginTop: 8, - fontSize: 13, - color: colors.textSecondary, - }, -}); \ No newline at end of file diff --git a/app/history.tsx b/app/history.tsx index a5e4905..a43e9ab 100644 --- a/app/history.tsx +++ b/app/history.tsx @@ -1,36 +1,14 @@ -import { Ionicons } from '@expo/vector-icons'; -import { StyleSheet, Text, View } from 'react-native'; -import { colors } from '../src/theme'; +import { EmptyState } from '../src/components/StateView'; /** * 浏览历史页(占位) */ export default function HistoryScreen() { return ( - - - 浏览历史 - 浏览历史页面开发中,敬请期待 - + ); } - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.pageBg, - alignItems: 'center', - justifyContent: 'center', - }, - title: { - marginTop: 12, - fontSize: 20, - fontWeight: '700', - color: colors.textPrimary, - }, - subtitle: { - marginTop: 8, - fontSize: 13, - color: colors.textSecondary, - }, -}); \ No newline at end of file diff --git a/app/settings/privacy.tsx b/app/settings/privacy.tsx index 40d9710..d626cad 100644 --- a/app/settings/privacy.tsx +++ b/app/settings/privacy.tsx @@ -1,8 +1,9 @@ import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; -import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Switch, Text, View } from 'react-native'; +import { Pressable, ScrollView, StyleSheet, Switch, Text, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { usePrivacyCenter } from '../../src/hooks'; +import { ErrorState, LoadingState } from '../../src/components/StateView'; import { colors } from '../../src/theme'; import type { PrivacyLevelInfo, PrivacySettingItem, PrivacySection } from '../../src/types'; @@ -40,7 +41,7 @@ function PrivacyLevelCard({ level }: { level: PrivacyLevelInfo }) { {level.description} - console.log('进一步提升隐私')}> + {}}> 查看隐私报告 @@ -68,7 +69,6 @@ function SettingRow({ router.push(item.route); return; } - console.log('[PrivacyCenter] 点击', item.id); }; if (item.type === 'switch') { @@ -136,9 +136,7 @@ export default function PrivacyCenterScreen() { return (
router.back()} /> - - - + ); } @@ -147,13 +145,7 @@ export default function PrivacyCenterScreen() { return (
router.back()} /> - - - 加载失败,请重试 - - 重试 - - + ); } @@ -214,29 +206,6 @@ const styles = StyleSheet.create({ padding: 12, paddingBottom: 24, }, - stateBox: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - gap: 12, - }, - stateText: { - marginTop: 12, - fontSize: 14, - color: colors.textSecondary, - }, - retryBtn: { - marginTop: 16, - paddingHorizontal: 24, - paddingVertical: 8, - borderRadius: 18, - backgroundColor: colors.primaryBtn, - }, - retryText: { - fontSize: 13, - fontWeight: '600', - color: colors.white, - }, levelCard: { borderRadius: 12, padding: 16, diff --git a/src/components/StateView.tsx b/src/components/StateView.tsx new file mode 100644 index 0000000..cc02650 --- /dev/null +++ b/src/components/StateView.tsx @@ -0,0 +1,107 @@ +import { Ionicons } from '@expo/vector-icons'; +import type { ReactNode } from 'react'; +import { ActivityIndicator, Pressable, StyleSheet, Text, View } from 'react-native'; +import { colors } from '../theme'; + +/* ===================== 通用状态视图 ===================== + * 统一承载「加载中 / 加载失败 / 空数据」三态,供数据页复用, + * 避免各页面重复编写 ActivityIndicator + 重试按钮样板代码。 + * 颜色全部走主题 token,遵守 AGENTS.md 配色约束。 + */ + +function StateBox({ children }: { children: ReactNode }) { + return {children}; +} + +/** 加载中 */ +export function LoadingState({ text = '加载中…' }: { text?: string }) { + return ( + + + {text} + + ); +} + +/** 加载失败(可重试) */ +export function ErrorState({ + message = '加载失败,请重试', + onRetry, +}: { + message?: string; + onRetry?: () => void; +}) { + return ( + + + {message} + {onRetry ? ( + + 重试 + + ) : null} + + ); +} + +/** 空数据 / 占位 */ +export function EmptyState({ + icon = 'document-text-outline', + title, + subtitle, + actionLabel, + onAction, +}: { + icon?: keyof typeof Ionicons.glyphMap; + title: string; + subtitle?: string; + actionLabel?: string; + onAction?: () => void; +}) { + return ( + + + {title} + {subtitle ? {subtitle} : null} + {actionLabel && onAction ? ( + + {actionLabel} + + ) : null} + + ); +} + +const styles = StyleSheet.create({ + box: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + gap: 12, + paddingHorizontal: 32, + }, + text: { + marginTop: 4, + fontSize: 14, + color: colors.textSecondary, + textAlign: 'center', + }, + title: { + marginTop: 12, + fontSize: 20, + fontWeight: '700', + color: colors.textPrimary, + }, + retryBtn: { + marginTop: 16, + paddingHorizontal: 24, + paddingVertical: 8, + borderRadius: 18, + backgroundColor: colors.primaryBtn, + }, + retryText: { + fontSize: 13, + fontWeight: '600', + color: colors.white, + }, +});