feat(auth): enhance login error handling with detailed error code mapping
All checks were successful
Frontend CI / ota-android (push) Successful in 1m29s
Frontend CI / build-and-push-web (push) Successful in 2m31s
Frontend CI / build-android-apk (push) Successful in 36m5s

- Add backend error_code support (INVALID_CREDENTIALS, USER_BANNED, etc.)
- Improve network error detection with code=0 fallback
- Add granular HTTP status code handling (400, 401, 403, 429, 5xx)
- Update LoginScreen to display authStore error or generic message
- Include notification service improvements:
  - Keep JPush connection alive in background via setKeepLongConnInBackground
  - Add manual sound/vibration for local notifications via preferences
- Fix SearchScreen layout by accounting for floating tab bar and safe area
This commit is contained in:
lafay
2026-04-28 17:25:27 +08:00
parent cf9feeae68
commit d2120a257d
5 changed files with 98 additions and 25 deletions

View File

@@ -107,12 +107,11 @@ export const LoginScreen: React.FC = () => {
try {
const success = await login({ username, password });
if (!success) {
if (!useAuthStore.getState().error) {
showPrompt({ title: '登录失败', message: '请检查用户名和密码', type: 'error' });
}
const errorMsg = useAuthStore.getState().error || '登录失败,请稍后重试';
showPrompt({ title: '登录失败', message: errorMsg, type: 'error' });
}
} catch (error: any) {
showPrompt({ title: '登录失败', message: error.message || '请检查用户名和密码', type: 'error' });
showPrompt({ title: '登录失败', message: error.message || '登录失败,请稍后重试', type: 'error' });
} finally {
setLoading(false);
}

View File

@@ -13,7 +13,7 @@ import {
ScrollView,
RefreshControl,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme';
@@ -30,6 +30,8 @@ import { useCursorPagination } from '../../hooks/useCursorPagination';
const TABS = ['帖子', '用户'];
const DEFAULT_PAGE_SIZE = 20;
// 与 TabsLayout 中的常量保持一致tabBarHeight 56 + 上下浮动间距 12*2
const FLOATING_TAB_BAR_HEIGHT = 56 + 12 * 2;
type SearchType = 'posts' | 'users';
@@ -41,6 +43,7 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
const colors = useAppColors();
const styles = useMemo(() => createSearchScreenStyles(colors), [colors]);
const router = useRouter();
const insets = useSafeAreaInsets();
const { searchHistory: history, addSearchHistory, clearSearchHistory } = useUserStore();
// 使用响应式 hook
@@ -73,6 +76,9 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
'4xl': 1200,
});
// 底部预留空间:悬浮 TabBar 高度 + 底部安全区 + 间距
const listBottomInset = FLOATING_TAB_BAR_HEIGHT + insets.bottom + spacing.md;
const [searchText, setSearchText] = useState('');
const [activeIndex, setActiveIndex] = useState(0);
const [hasSearched, setHasSearched] = useState(false);
@@ -238,7 +244,7 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
return (
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: responsivePadding }}
contentContainerStyle={{ paddingBottom: listBottomInset }}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
@@ -287,7 +293,7 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
/>
)}
keyExtractor={item => item.id}
contentContainerStyle={{ paddingBottom: responsivePadding }}
contentContainerStyle={{ paddingBottom: listBottomInset }}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
@@ -323,7 +329,7 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
return (
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: responsivePadding }}
contentContainerStyle={{ paddingBottom: listBottomInset }}
>
<ResponsiveGrid
columns={{ xs: 1, sm: 2, md: 2, lg: 3, xl: 3, '2xl': 4, '3xl': 4, '4xl': 5 }}
@@ -412,7 +418,7 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
</TouchableOpacity>
)}
keyExtractor={item => item.id}
contentContainerStyle={{ paddingVertical: responsiveGap }}
contentContainerStyle={{ paddingVertical: responsiveGap, paddingBottom: listBottomInset }}
showsVerticalScrollIndicator={false}
ListFooterComponent={userLoading ? <Loading size="sm" /> : null}
/>