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,
+ },
+});