refactor(App, navigation): migrate to Expo Router and clean up navigation structure
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 4m27s
Frontend CI / ota-android (push) Successful in 11m6s
Frontend CI / build-android-apk (push) Successful in 1h16m45s

- Updated App entry point to utilize Expo Router, simplifying the navigation setup.
- Removed legacy navigation components and services to streamline the codebase.
- Adjusted package.json to reflect the new entry point and updated dependencies for compatibility.
- Enhanced overall application structure by consolidating navigation logic and improving maintainability.
This commit is contained in:
lafay
2026-03-24 14:21:31 +08:00
parent b91e78c921
commit 2ddb9cadd8
111 changed files with 1829 additions and 3214 deletions

View File

@@ -11,19 +11,15 @@ import {
ActivityIndicator,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { colors, spacing, borderRadius, shadows, fontSizes } from '../../theme';
import { RootStackParamList } from '../../navigation/types';
import { authService, resolveAuthApiError } from '../../services/authService';
import { showPrompt } from '../../services/promptService';
type ForgotPasswordNavigationProp = NativeStackNavigationProp<RootStackParamList, 'Auth'>;
export const ForgotPasswordScreen: React.FC = () => {
const navigation = useNavigation<ForgotPasswordNavigationProp>();
const router = useRouter();
const [email, setEmail] = useState('');
const [verificationCode, setVerificationCode] = useState('');
const [newPassword, setNewPassword] = useState('');
@@ -95,7 +91,7 @@ export const ForgotPasswordScreen: React.FC = () => {
});
if (ok) {
showPrompt({ title: '重置成功', message: '密码已重置,请重新登录', type: 'success' });
navigation.goBack();
router.back();
}
} catch (error: any) {
showPrompt({ title: '重置失败', message: resolveAuthApiError(error, '请稍后重试'), type: 'error' });
@@ -192,7 +188,7 @@ export const ForgotPasswordScreen: React.FC = () => {
{loading ? <ActivityIndicator size="small" color="#fff" /> : <Text style={styles.submitButtonText}></Text>}
</TouchableOpacity>
<TouchableOpacity style={styles.backButton} onPress={() => navigation.goBack()}>
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
<Text style={styles.backButtonText}></Text>
</TouchableOpacity>
</View>

View File

@@ -22,24 +22,22 @@ import {
StatusBar,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { colors, spacing, borderRadius, shadows, fontSizes } from '../../theme';
import { useAuthStore } from '../../stores';
import { RootStackParamList } from '../../navigation/types';
import * as hrefs from '../../navigation/hrefs';
import { useResponsive, useResponsiveValue } from '../../hooks';
import { showPrompt } from '../../services/promptService';
type LoginNavigationProp = NativeStackNavigationProp<RootStackParamList, 'Auth'>;
// 分栏布局断点
const SPLIT_BREAKPOINT = 768;
export const LoginScreen: React.FC = () => {
const navigation = useNavigation<LoginNavigationProp>();
const router = useRouter();
const login = useAuthStore((state) => state.login);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const storeError = useAuthStore((state) => state.error);
const setStoreError = useAuthStore((state) => state.setError);
@@ -131,6 +129,12 @@ export const LoginScreen: React.FC = () => {
}
}, [storeError]);
useEffect(() => {
if (isAuthenticated) {
router.replace(hrefs.hrefHome());
}
}, [isAuthenticated, router]);
const clearError = () => {
setErrorMsg(null);
setStoreError(null);
@@ -158,7 +162,7 @@ export const LoginScreen: React.FC = () => {
// 跳转到注册页
const handleGoToRegister = () => {
navigation.navigate('Register' as any);
router.push(hrefs.hrefAuthRegister());
};
// 渲染左侧面板Logo和品牌信息- 优化大屏布局
@@ -297,7 +301,10 @@ export const LoginScreen: React.FC = () => {
</View>
{/* 忘记密码 */}
<TouchableOpacity style={styles.forgotPassword} onPress={() => navigation.navigate('ForgotPassword' as any)}>
<TouchableOpacity
style={styles.forgotPassword}
onPress={() => router.push(hrefs.hrefAuthForgot())}
>
<Text style={styles.forgotPasswordText}></Text>
</TouchableOpacity>

View File

@@ -10,28 +10,24 @@ import {
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useRoute, useNavigation, RouteProp } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RootStackParamList } from '../../navigation/types';
import { useRouter, useLocalSearchParams } from 'expo-router';
import { qrcodeApi } from '../../services/authService';
import { colors, spacing, fontSizes, borderRadius } from '../../theme';
type QRCodeConfirmRouteProp = RouteProp<RootStackParamList, 'QRCodeConfirm'>;
type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
import { AppBackButton } from '../../components/common';
export const QRCodeConfirmScreen: React.FC = () => {
const route = useRoute<QRCodeConfirmRouteProp>();
const navigation = useNavigation<NavigationProp>();
const { sessionId } = route.params;
const router = useRouter();
const { sessionId: sessionIdParam } = useLocalSearchParams<{ sessionId?: string }>();
const sessionId = sessionIdParam || '';
const [loading, setLoading] = useState(false);
const [userInfo, setUserInfo] = useState<{ id: string; nickname: string; avatar: string } | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
// 扫码
handleScan();
}, []);
if (!sessionId) return;
void handleScan();
}, [sessionId]);
const handleScan = async () => {
try {
@@ -42,7 +38,7 @@ export const QRCodeConfirmScreen: React.FC = () => {
const errorMsg = err.response?.data?.message || '扫码失败';
setError(errorMsg);
Alert.alert('扫码失败', errorMsg, [
{ text: '确定', onPress: () => navigation.goBack() }
{ text: '确定', onPress: () => router.back() }
]);
} finally {
setLoading(false);
@@ -54,7 +50,7 @@ export const QRCodeConfirmScreen: React.FC = () => {
setLoading(true);
await qrcodeApi.confirm(sessionId);
Alert.alert('登录成功', '网页端已登录', [
{ text: '确定', onPress: () => navigation.goBack() }
{ text: '确定', onPress: () => router.back() }
]);
} catch (err: any) {
const errorMsg = err.response?.data?.message || '确认登录失败';
@@ -70,7 +66,7 @@ export const QRCodeConfirmScreen: React.FC = () => {
} catch (err) {
// 忽略取消错误
}
navigation.goBack();
router.back();
};
if (loading && !userInfo) {
@@ -90,7 +86,7 @@ export const QRCodeConfirmScreen: React.FC = () => {
<View style={styles.errorContainer}>
<MaterialCommunityIcons name="alert-circle" size={64} color={colors.error.main} />
<Text style={styles.errorText}>{error}</Text>
<TouchableOpacity style={styles.backButton} onPress={() => navigation.goBack()}>
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
<Text style={styles.backButtonText}></Text>
</TouchableOpacity>
</View>
@@ -101,9 +97,7 @@ export const QRCodeConfirmScreen: React.FC = () => {
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<TouchableOpacity onPress={handleCancel} style={styles.closeButton}>
<MaterialCommunityIcons name="close" size={24} color="#666" />
</TouchableOpacity>
<AppBackButton onPress={handleCancel} icon="close" style={styles.closeButton} iconColor="#666" />
<Text style={styles.headerTitle}></Text>
<View style={styles.placeholder} />
</View>

View File

@@ -22,25 +22,23 @@ import {
StatusBar,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { colors, spacing, borderRadius, shadows, fontSizes } from '../../theme';
import { authService, resolveAuthApiError } from '../../services/authService';
import { useAuthStore } from '../../stores';
import { RootStackParamList } from '../../navigation/types';
import * as hrefs from '../../navigation/hrefs';
import { useResponsive, useResponsiveValue } from '../../hooks';
import { showPrompt } from '../../services/promptService';
type RegisterNavigationProp = NativeStackNavigationProp<RootStackParamList, 'Auth'>;
// 分栏布局断点
const SPLIT_BREAKPOINT = 768;
export const RegisterScreen: React.FC = () => {
const navigation = useNavigation<RegisterNavigationProp>();
const router = useRouter();
const register = useAuthStore((state) => state.register);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
// 响应式布局
const { isLandscape, width } = useResponsive();
@@ -48,6 +46,12 @@ export const RegisterScreen: React.FC = () => {
// 是否使用分栏布局
const isSplitLayout = width >= SPLIT_BREAKPOINT;
useEffect(() => {
if (isAuthenticated) {
router.replace(hrefs.hrefHome());
}
}, [isAuthenticated, router]);
const [username, setUsername] = useState('');
const [nickname, setNickname] = useState('');
const [email, setEmail] = useState('');
@@ -238,7 +242,7 @@ export const RegisterScreen: React.FC = () => {
// 跳转到登录页
const handleGoToLogin = () => {
navigation.navigate('Login' as any);
router.push(hrefs.hrefAuthLogin());
};
// 渲染左侧面板Logo和品牌信息- 优化大屏布局