Files
frontend/src/screens/auth/QRCodeConfirmScreen.tsx

288 lines
7.9 KiB
TypeScript
Raw Normal View History

import React, { useEffect, useMemo, useState } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Image,
ActivityIndicator,
Alert,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useRouter, useLocalSearchParams } from 'expo-router';
2026-04-13 01:30:37 +08:00
import { qrcodeApi } from '@/services/auth';
import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme';
import { AppBackButton } from '../../components/common';
function createQrCodeConfirmStyles(colors: AppColors) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.default,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
backgroundColor: colors.background.paper,
borderBottomWidth: 1,
borderBottomColor: colors.divider,
},
closeButton: {
padding: spacing.sm,
},
headerTitle: {
fontSize: fontSizes.lg,
fontWeight: '600',
color: colors.text.primary,
},
placeholder: {
width: 40,
},
content: {
flex: 1,
padding: spacing.lg,
justifyContent: 'center',
},
infoCard: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.lg,
padding: spacing.xl,
alignItems: 'center',
marginBottom: spacing.xl,
shadowColor: colors.chat.shadow,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
infoText: {
fontSize: fontSizes.md,
color: colors.text.secondary,
marginBottom: spacing.lg,
},
userInfo: {
alignItems: 'center',
},
avatar: {
width: 80,
height: 80,
borderRadius: 40,
marginBottom: spacing.md,
},
avatarPlaceholder: {
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: colors.background.disabled,
justifyContent: 'center',
alignItems: 'center',
marginBottom: spacing.md,
},
nickname: {
fontSize: fontSizes.lg,
fontWeight: '600',
color: colors.text.primary,
},
buttonContainer: {
gap: spacing.md,
},
button: {
paddingVertical: spacing.md,
paddingHorizontal: spacing.xl,
borderRadius: borderRadius.md,
alignItems: 'center',
},
confirmButton: {
backgroundColor: colors.primary.main,
},
confirmButtonText: {
color: colors.text.inverse,
fontSize: fontSizes.md,
fontWeight: '600',
},
cancelButton: {
backgroundColor: colors.background.paper,
borderWidth: 1,
borderColor: colors.divider,
},
cancelButtonText: {
color: colors.text.secondary,
fontSize: fontSizes.md,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
marginTop: spacing.md,
fontSize: fontSizes.md,
color: colors.text.secondary,
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: spacing.xl,
},
errorText: {
marginTop: spacing.md,
fontSize: fontSizes.md,
color: colors.text.secondary,
textAlign: 'center',
},
backButton: {
marginTop: spacing.xl,
paddingVertical: spacing.md,
paddingHorizontal: spacing.xl,
backgroundColor: colors.primary.main,
borderRadius: borderRadius.md,
},
backButtonText: {
color: colors.text.inverse,
fontSize: fontSizes.md,
fontWeight: '600',
},
});
}
export const QRCodeConfirmScreen: React.FC = () => {
const router = useRouter();
const colors = useAppColors();
const styles = useMemo(() => createQrCodeConfirmStyles(colors), [colors]);
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(() => {
if (!sessionId) return;
void handleScan();
}, [sessionId]);
const handleScan = async () => {
try {
setLoading(true);
const response = await qrcodeApi.scan(sessionId);
setUserInfo(response.user);
} catch (err: any) {
const errorMsg = err.response?.data?.message || '扫码失败';
setError(errorMsg);
Alert.alert('扫码失败', errorMsg, [{ text: '确定', onPress: () => router.back() }]);
} finally {
setLoading(false);
}
};
const handleConfirm = async () => {
try {
setLoading(true);
await qrcodeApi.confirm(sessionId);
Alert.alert('登录成功', '网页端已登录', [{ text: '确定', onPress: () => router.back() }]);
} catch (err: any) {
const errorMsg = err.response?.data?.message || '确认登录失败';
Alert.alert('登录失败', errorMsg);
} finally {
setLoading(false);
}
};
const handleCancel = async () => {
try {
await qrcodeApi.cancel(sessionId);
} catch {
// 忽略取消错误
}
router.back();
};
if (loading && !userInfo) {
return (
<SafeAreaView style={styles.container}>
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={colors.primary.main} />
<Text style={styles.loadingText}>...</Text>
</View>
</SafeAreaView>
);
}
if (error) {
return (
<SafeAreaView style={styles.container}>
<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={() => router.back()}>
<Text style={styles.backButtonText}></Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<AppBackButton
onPress={handleCancel}
icon="close"
style={styles.closeButton}
iconColor={colors.text.secondary}
/>
<Text style={styles.headerTitle}></Text>
<View style={styles.placeholder} />
</View>
<View style={styles.content}>
<View style={styles.infoCard}>
<Text style={styles.infoText}></Text>
{userInfo && (
<View style={styles.userInfo}>
{userInfo.avatar ? (
<Image source={{ uri: userInfo.avatar }} style={styles.avatar} />
) : (
<View style={styles.avatarPlaceholder}>
<MaterialCommunityIcons name="account" size={40} color={colors.text.disabled} />
</View>
)}
<Text style={styles.nickname}>{userInfo.nickname}</Text>
</View>
)}
</View>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[styles.button, styles.confirmButton]}
onPress={handleConfirm}
disabled={loading}
>
{loading ? (
<ActivityIndicator color={colors.text.inverse} />
) : (
<Text style={styles.confirmButtonText}></Text>
)}
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.cancelButton]}
onPress={handleCancel}
disabled={loading}
>
<Text style={styles.cancelButtonText}></Text>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
);
};
export default QRCodeConfirmScreen;