轻松聘
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

107 lines
2.7 KiB

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