Refactor the message management system to address synchronization issues and improve performance through request deduplication and enhanced lifecycle management. - Implement in-flight promise tracking in `MessageSyncService` to prevent redundant network requests for conversations and messages. - Enhance `useMessages` hook with `useFocusEffect` to trigger automatic synchronization when a chat screen regains focus. - Add `forceSync` capability to `MessageManager` and `MessageSyncService` to allow manual overrides of loading states during re-entry. - Consolidate WebSocket synchronization logic in `WSMessageHandler` using a throttled and deduplicated trigger mechanism. - Clean up unused styles and deprecated hooks (`baseHooks.ts`, `bubbleStyles.ts`, `inputStyles.ts`). - Update `metro.config.js` and `package.json` to include `@expo/ui` and optimize resolver settings.
483 lines
14 KiB
TypeScript
483 lines
14 KiB
TypeScript
/**
|
||
* 身份认证设置页面
|
||
* 用户查看认证状态、提交认证申请
|
||
*/
|
||
|
||
import React, { useEffect, useState } from 'react';
|
||
import {
|
||
View,
|
||
Text,
|
||
StyleSheet,
|
||
TouchableOpacity,
|
||
ScrollView,
|
||
Image,
|
||
Alert,
|
||
ActivityIndicator,
|
||
} from 'react-native';
|
||
import { StatusBar } from 'expo-status-bar';
|
||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||
import { useRouter } from 'expo-router';
|
||
import { SimpleHeader } from '../../components/common';
|
||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||
import { useAppColors, type AppColors } from '../../theme';
|
||
import {
|
||
useVerificationStore,
|
||
useVerificationStatus,
|
||
useVerificationLoading,
|
||
} from '../../stores/auth';
|
||
import { showPrompt } from '@/services/ui';
|
||
import * as hrefs from '../../navigation/hrefs';
|
||
|
||
const STATUS_CONFIG = {
|
||
not_submitted: {
|
||
title: '未认证',
|
||
description: '完成身份认证,解锁更多专属功能',
|
||
icon: 'account-question-outline',
|
||
color: '#999',
|
||
},
|
||
pending: {
|
||
title: '审核中',
|
||
description: '您的认证申请正在审核中,请耐心等待',
|
||
icon: 'clock-outline',
|
||
color: '#FFA500',
|
||
},
|
||
approved: {
|
||
title: '已认证',
|
||
description: '您已完成身份认证',
|
||
icon: 'check-decagram',
|
||
color: '#4CAF50',
|
||
},
|
||
rejected: {
|
||
title: '认证失败',
|
||
description: '您的认证申请被拒绝,可以重新提交',
|
||
icon: 'close-circle-outline',
|
||
color: '#F44336',
|
||
},
|
||
};
|
||
|
||
const IDENTITY_TEXT: Record<string, string> = {
|
||
general: '普通用户',
|
||
student: '在校学生',
|
||
teacher: '教职工',
|
||
staff: '官方工作人员',
|
||
alumni: '校友',
|
||
external: '外部人员',
|
||
};
|
||
|
||
export const VerificationSettingsScreen: React.FC = () => {
|
||
const colors = useAppColors();
|
||
const styles = createStyles(colors);
|
||
const router = useRouter();
|
||
const status = useVerificationStatus();
|
||
const loading = useVerificationLoading();
|
||
const { fetchStatus, reset } = useVerificationStore();
|
||
|
||
useEffect(() => {
|
||
fetchStatus();
|
||
return () => {
|
||
reset();
|
||
};
|
||
}, []);
|
||
|
||
const handleStartVerification = () => {
|
||
router.push(hrefs.hrefVerificationGuide());
|
||
};
|
||
|
||
const handleViewRecords = () => {
|
||
showPrompt({
|
||
title: '认证记录',
|
||
message: '认证记录功能即将上线',
|
||
type: 'info',
|
||
});
|
||
};
|
||
|
||
if (loading && !status) {
|
||
return (
|
||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||
<StatusBar style="auto" />
|
||
<SimpleHeader title="身份认证" onBack={() => router.back()} />
|
||
<View style={styles.loadingContainer}>
|
||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||
</View>
|
||
</SafeAreaView>
|
||
);
|
||
}
|
||
|
||
const statusKey = (status?.verification_status || 'not_submitted') as keyof typeof STATUS_CONFIG;
|
||
const statusConfig = STATUS_CONFIG[statusKey];
|
||
const identityText = IDENTITY_TEXT[status?.identity || 'general'];
|
||
|
||
return (
|
||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||
<StatusBar style="auto" />
|
||
<SimpleHeader title="身份认证" onBack={() => router.back()} />
|
||
<ScrollView
|
||
contentContainerStyle={styles.scrollContent}
|
||
showsVerticalScrollIndicator={false}
|
||
>
|
||
{/* 状态卡片 */}
|
||
<View style={styles.statusCard}>
|
||
<View style={[styles.statusIconContainer, { backgroundColor: statusConfig.color + '20' }]}>
|
||
<MaterialCommunityIcons
|
||
name={statusConfig.icon as any}
|
||
size={48}
|
||
color={statusConfig.color}
|
||
/>
|
||
</View>
|
||
<Text style={styles.statusTitle}>{statusConfig.title}</Text>
|
||
<Text style={styles.statusDescription}>{statusConfig.description}</Text>
|
||
|
||
{status?.verification_status === 'approved' && (
|
||
<View style={styles.identityBadge}>
|
||
<MaterialCommunityIcons name="check-circle" size={16} color={colors.primary.main} />
|
||
<Text style={styles.identityText}>{identityText}</Text>
|
||
</View>
|
||
)}
|
||
|
||
{status?.latest_record?.reject_reason && (
|
||
<View style={styles.rejectReasonContainer}>
|
||
<Text style={styles.rejectReasonLabel}>拒绝原因:</Text>
|
||
<Text style={styles.rejectReasonText}>
|
||
{status.latest_record.reject_reason}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
|
||
{/* 认证优势 */}
|
||
{status?.verification_status !== 'approved' && (
|
||
<View style={styles.benefitsSection}>
|
||
<Text style={styles.sectionTitle}>认证优势</Text>
|
||
<View style={styles.benefitsList}>
|
||
<View style={styles.benefitItem}>
|
||
<View style={styles.benefitIconContainer}>
|
||
<MaterialCommunityIcons
|
||
name="check-decagram"
|
||
size={24}
|
||
color={colors.primary.main}
|
||
/>
|
||
</View>
|
||
<View style={styles.benefitContent}>
|
||
<Text style={styles.benefitTitle}>专属认证标识</Text>
|
||
<Text style={styles.benefitDescription}>
|
||
获得身份认证徽章,提升账号可信度
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<View style={styles.benefitItem}>
|
||
<View style={styles.benefitIconContainer}>
|
||
<MaterialCommunityIcons
|
||
name="lock-open-outline"
|
||
size={24}
|
||
color={colors.primary.main}
|
||
/>
|
||
</View>
|
||
<View style={styles.benefitContent}>
|
||
<Text style={styles.benefitTitle}>解锁专属频道</Text>
|
||
<Text style={styles.benefitDescription}>
|
||
访问仅限认证用户的专属内容和频道
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<View style={styles.benefitItem}>
|
||
<View style={styles.benefitIconContainer}>
|
||
<MaterialCommunityIcons
|
||
name="account-group-outline"
|
||
size={24}
|
||
color={colors.primary.main}
|
||
/>
|
||
</View>
|
||
<View style={styles.benefitContent}>
|
||
<Text style={styles.benefitTitle}>加入社群圈子</Text>
|
||
<Text style={styles.benefitDescription}>
|
||
加入需要身份认证的校友群、班级群等
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{/* 操作按钮 */}
|
||
<View style={styles.actionSection}>
|
||
{(status?.verification_status === 'not_submitted' ||
|
||
status?.verification_status === 'rejected') && (
|
||
<TouchableOpacity
|
||
style={styles.primaryButton}
|
||
onPress={handleStartVerification}
|
||
activeOpacity={0.9}
|
||
>
|
||
<MaterialCommunityIcons name="shield-check-outline" size={20} color={colors.primary.contrast} />
|
||
<Text style={styles.primaryButtonText}>
|
||
{status?.verification_status === 'rejected' ? '重新认证' : '开始认证'}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
)}
|
||
|
||
{status?.verification_status === 'pending' && (
|
||
<TouchableOpacity
|
||
style={styles.secondaryButton}
|
||
onPress={handleViewRecords}
|
||
activeOpacity={0.8}
|
||
>
|
||
<MaterialCommunityIcons name="file-document-outline" size={20} color={colors.primary.main} />
|
||
<Text style={styles.secondaryButtonText}>查看认证记录</Text>
|
||
</TouchableOpacity>
|
||
)}
|
||
|
||
{status?.verification_status === 'approved' && (
|
||
<>
|
||
<View style={styles.verifiedInfo}>
|
||
<View style={styles.verifiedInfoRow}>
|
||
<Text style={styles.verifiedInfoLabel}>认证身份</Text>
|
||
<Text style={styles.verifiedInfoValue}>{identityText}</Text>
|
||
</View>
|
||
{status.latest_record?.real_name && (
|
||
<View style={styles.verifiedInfoRow}>
|
||
<Text style={styles.verifiedInfoLabel}>真实姓名</Text>
|
||
<Text style={styles.verifiedInfoValue}>
|
||
{status.latest_record.real_name}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
{status.latest_record?.department && (
|
||
<View style={styles.verifiedInfoRow}>
|
||
<Text style={styles.verifiedInfoLabel}>院系/部门</Text>
|
||
<Text style={styles.verifiedInfoValue}>
|
||
{status.latest_record.department}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
</>
|
||
)}
|
||
</View>
|
||
|
||
{/* 帮助信息 */}
|
||
<View style={styles.helpSection}>
|
||
<Text style={styles.helpTitle}>遇到问题?</Text>
|
||
<Text style={styles.helpText}>
|
||
如果在认证过程中遇到问题,请联系客服或在设置中提交反馈
|
||
</Text>
|
||
</View>
|
||
</ScrollView>
|
||
</SafeAreaView>
|
||
);
|
||
};
|
||
|
||
function createStyles(colors: AppColors) {
|
||
return StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: colors.background.paper,
|
||
},
|
||
loadingContainer: {
|
||
flex: 1,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
header: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
paddingHorizontal: 16,
|
||
paddingVertical: 12,
|
||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||
borderBottomColor: colors.divider || '#E0E0E0',
|
||
},
|
||
backButton: {
|
||
width: 40,
|
||
height: 40,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
headerTitle: {
|
||
fontSize: 18,
|
||
fontWeight: '600',
|
||
color: colors.text?.primary || '#333',
|
||
},
|
||
headerRight: {
|
||
width: 40,
|
||
},
|
||
scrollContent: {
|
||
flexGrow: 1,
|
||
padding: 24,
|
||
},
|
||
statusCard: {
|
||
alignItems: 'center',
|
||
paddingVertical: 32,
|
||
paddingHorizontal: 24,
|
||
backgroundColor: colors.background.default,
|
||
borderRadius: 16,
|
||
marginBottom: 24,
|
||
},
|
||
statusIconContainer: {
|
||
width: 96,
|
||
height: 96,
|
||
borderRadius: 48,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginBottom: 16,
|
||
},
|
||
statusTitle: {
|
||
fontSize: 24,
|
||
fontWeight: '700',
|
||
color: colors.text?.primary || '#333',
|
||
marginBottom: 8,
|
||
},
|
||
statusDescription: {
|
||
fontSize: 15,
|
||
color: colors.text?.secondary || '#666',
|
||
textAlign: 'center',
|
||
lineHeight: 22,
|
||
},
|
||
identityBadge: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
backgroundColor: colors.primary.main + '15',
|
||
borderRadius: 20,
|
||
paddingVertical: 8,
|
||
paddingHorizontal: 16,
|
||
marginTop: 16,
|
||
},
|
||
identityText: {
|
||
fontSize: 14,
|
||
fontWeight: '600',
|
||
color: colors.primary.main,
|
||
marginLeft: 6,
|
||
},
|
||
rejectReasonContainer: {
|
||
marginTop: 16,
|
||
padding: 12,
|
||
backgroundColor: colors.error.light + '20',
|
||
borderRadius: 8,
|
||
width: '100%',
|
||
},
|
||
rejectReasonLabel: {
|
||
fontSize: 13,
|
||
fontWeight: '600',
|
||
color: colors.error.main,
|
||
marginBottom: 4,
|
||
},
|
||
rejectReasonText: {
|
||
fontSize: 13,
|
||
color: colors.error.dark,
|
||
lineHeight: 18,
|
||
},
|
||
benefitsSection: {
|
||
marginBottom: 24,
|
||
},
|
||
sectionTitle: {
|
||
fontSize: 17,
|
||
fontWeight: '600',
|
||
color: colors.text?.primary || '#333',
|
||
marginBottom: 16,
|
||
},
|
||
benefitsList: {
|
||
gap: 12,
|
||
},
|
||
benefitItem: {
|
||
flexDirection: 'row',
|
||
alignItems: 'flex-start',
|
||
backgroundColor: colors.background.default,
|
||
borderRadius: 12,
|
||
padding: 16,
|
||
},
|
||
benefitIconContainer: {
|
||
width: 48,
|
||
height: 48,
|
||
borderRadius: 12,
|
||
backgroundColor: colors.primary.main + '15',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginRight: 12,
|
||
},
|
||
benefitContent: {
|
||
flex: 1,
|
||
},
|
||
benefitTitle: {
|
||
fontSize: 15,
|
||
fontWeight: '600',
|
||
color: colors.text?.primary || '#333',
|
||
marginBottom: 4,
|
||
},
|
||
benefitDescription: {
|
||
fontSize: 13,
|
||
color: colors.text?.secondary || '#666',
|
||
lineHeight: 18,
|
||
},
|
||
actionSection: {
|
||
marginBottom: 24,
|
||
},
|
||
primaryButton: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
height: 56,
|
||
borderRadius: 14,
|
||
backgroundColor: colors.primary.main,
|
||
},
|
||
primaryButtonText: {
|
||
fontSize: 17,
|
||
fontWeight: '600',
|
||
color: colors.text.inverse,
|
||
marginLeft: 8,
|
||
},
|
||
secondaryButton: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
height: 56,
|
||
borderRadius: 14,
|
||
backgroundColor: colors.primary.main + '15',
|
||
borderWidth: 1,
|
||
borderColor: colors.primary.main,
|
||
},
|
||
secondaryButtonText: {
|
||
fontSize: 17,
|
||
fontWeight: '600',
|
||
color: colors.primary.main,
|
||
marginLeft: 8,
|
||
},
|
||
verifiedInfo: {
|
||
backgroundColor: colors.background.default,
|
||
borderRadius: 12,
|
||
padding: 16,
|
||
},
|
||
verifiedInfoRow: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
paddingVertical: 8,
|
||
},
|
||
verifiedInfoLabel: {
|
||
fontSize: 15,
|
||
color: colors.text?.secondary || '#666',
|
||
},
|
||
verifiedInfoValue: {
|
||
fontSize: 15,
|
||
fontWeight: '500',
|
||
color: colors.text?.primary || '#333',
|
||
},
|
||
helpSection: {
|
||
alignItems: 'center',
|
||
paddingVertical: 16,
|
||
},
|
||
helpTitle: {
|
||
fontSize: 15,
|
||
fontWeight: '600',
|
||
color: colors.text?.primary || '#333',
|
||
marginBottom: 8,
|
||
},
|
||
helpText: {
|
||
fontSize: 13,
|
||
color: colors.text?.hint || '#999',
|
||
textAlign: 'center',
|
||
lineHeight: 18,
|
||
},
|
||
});
|
||
}
|
||
|
||
export default VerificationSettingsScreen; |