Browse Source
- src/components/StateView 提供 LoadingState/ErrorState(可重试)/EmptyState,颜色走主题 token - privacy 页复用 StateView 去除重复 loading/error 样板 - favorites/history 占位页接入 EmptyState 统一空态样式master
4 changed files with 124 additions and 92 deletions
@ -1,36 +1,14 @@ |
|||||
import { Ionicons } from '@expo/vector-icons'; |
import { EmptyState } from '../src/components/StateView'; |
||||
import { StyleSheet, Text, View } from 'react-native'; |
|
||||
import { colors } from '../src/theme'; |
|
||||
|
|
||||
/** |
/** |
||||
* 我的收藏页(占位) |
* 我的收藏页(占位) |
||||
*/ |
*/ |
||||
export default function FavoritesScreen() { |
export default function FavoritesScreen() { |
||||
return ( |
return ( |
||||
<View style={styles.container}> |
<EmptyState |
||||
<Ionicons name="heart-outline" size={48} color={colors.textSecondary} /> |
icon="heart-outline" |
||||
<Text style={styles.title}>我的收藏</Text> |
title="我的收藏" |
||||
<Text style={styles.subtitle}>收藏页面开发中,敬请期待</Text> |
subtitle="收藏页面开发中,敬请期待" |
||||
</View> |
/> |
||||
); |
); |
||||
} |
} |
||||
|
|
||||
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, |
|
||||
}, |
|
||||
}); |
|
||||
@ -1,36 +1,14 @@ |
|||||
import { Ionicons } from '@expo/vector-icons'; |
import { EmptyState } from '../src/components/StateView'; |
||||
import { StyleSheet, Text, View } from 'react-native'; |
|
||||
import { colors } from '../src/theme'; |
|
||||
|
|
||||
/** |
/** |
||||
* 浏览历史页(占位) |
* 浏览历史页(占位) |
||||
*/ |
*/ |
||||
export default function HistoryScreen() { |
export default function HistoryScreen() { |
||||
return ( |
return ( |
||||
<View style={styles.container}> |
<EmptyState |
||||
<Ionicons name="time-outline" size={48} color={colors.textSecondary} /> |
icon="time-outline" |
||||
<Text style={styles.title}>浏览历史</Text> |
title="浏览历史" |
||||
<Text style={styles.subtitle}>浏览历史页面开发中,敬请期待</Text> |
subtitle="浏览历史页面开发中,敬请期待" |
||||
</View> |
/> |
||||
); |
); |
||||
} |
} |
||||
|
|
||||
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, |
|
||||
}, |
|
||||
}); |
|
||||
@ -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 <View style={styles.box}>{children}</View>; |
||||
|
} |
||||
|
|
||||
|
/** 加载中 */ |
||||
|
export function LoadingState({ text = '加载中…' }: { text?: string }) { |
||||
|
return ( |
||||
|
<StateBox> |
||||
|
<ActivityIndicator size="large" color={colors.primary} /> |
||||
|
<Text style={styles.text}>{text}</Text> |
||||
|
</StateBox> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** 加载失败(可重试) */ |
||||
|
export function ErrorState({ |
||||
|
message = '加载失败,请重试', |
||||
|
onRetry, |
||||
|
}: { |
||||
|
message?: string; |
||||
|
onRetry?: () => void; |
||||
|
}) { |
||||
|
return ( |
||||
|
<StateBox> |
||||
|
<Ionicons name="alert-circle-outline" size={40} color={colors.danger} /> |
||||
|
<Text style={styles.text}>{message}</Text> |
||||
|
{onRetry ? ( |
||||
|
<Pressable style={styles.retryBtn} onPress={onRetry}> |
||||
|
<Text style={styles.retryText}>重试</Text> |
||||
|
</Pressable> |
||||
|
) : null} |
||||
|
</StateBox> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** 空数据 / 占位 */ |
||||
|
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 ( |
||||
|
<StateBox> |
||||
|
<Ionicons name={icon} size={48} color={colors.textSecondary} /> |
||||
|
<Text style={styles.title}>{title}</Text> |
||||
|
{subtitle ? <Text style={styles.text}>{subtitle}</Text> : null} |
||||
|
{actionLabel && onAction ? ( |
||||
|
<Pressable style={styles.retryBtn} onPress={onAction}> |
||||
|
<Text style={styles.retryText}>{actionLabel}</Text> |
||||
|
</Pressable> |
||||
|
) : null} |
||||
|
</StateBox> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
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, |
||||
|
}, |
||||
|
}); |
||||
Loading…
Reference in new issue