|
|
@@ -0,0 +1,346 @@
|
|
|
+import { Images } from "@/constants/images";
|
|
|
+import { getUserInfo, realNameAuth, UserInfo } from "@/services/user";
|
|
|
+import { useFocusEffect, useRouter } from "expo-router";
|
|
|
+import React, { useCallback, useState } from "react";
|
|
|
+import {
|
|
|
+ Alert,
|
|
|
+ ImageBackground,
|
|
|
+ KeyboardAvoidingView,
|
|
|
+ Platform,
|
|
|
+ ScrollView,
|
|
|
+ StatusBar,
|
|
|
+ StyleSheet,
|
|
|
+ Text,
|
|
|
+ TextInput,
|
|
|
+ TouchableOpacity,
|
|
|
+ View,
|
|
|
+} from "react-native";
|
|
|
+import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
|
+
|
|
|
+const NAME_REG = /^[\u4e00-\u9fa5]{2,10}$/;
|
|
|
+const ID_REG = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
|
|
|
+
|
|
|
+const maskName = (name: string) => {
|
|
|
+ if (!name) return "";
|
|
|
+ if (name.length <= 1) return name;
|
|
|
+ return name[0] + "*".repeat(name.length - 1);
|
|
|
+};
|
|
|
+
|
|
|
+const maskIdNum = (id: string) => {
|
|
|
+ if (!id) return "";
|
|
|
+ if (id.length <= 8) return id;
|
|
|
+ return id.slice(0, 3) + "*".repeat(id.length - 7) + id.slice(-4);
|
|
|
+};
|
|
|
+
|
|
|
+export default function CertificationScreen() {
|
|
|
+ const router = useRouter();
|
|
|
+ const insets = useSafeAreaInsets();
|
|
|
+ const [user, setUser] = useState<UserInfo | null>(null);
|
|
|
+ const [idName, setIdName] = useState("");
|
|
|
+ const [idNum, setIdNum] = useState("");
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
+ const [submitting, setSubmitting] = useState(false);
|
|
|
+
|
|
|
+ const loadUser = useCallback(async () => {
|
|
|
+ setLoading(true);
|
|
|
+ try {
|
|
|
+ const info = await getUserInfo();
|
|
|
+ setUser(info);
|
|
|
+ } catch (e) {
|
|
|
+ console.error("获取用户信息失败:", e);
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ useFocusEffect(
|
|
|
+ useCallback(() => {
|
|
|
+ loadUser();
|
|
|
+ }, [loadUser]),
|
|
|
+ );
|
|
|
+
|
|
|
+ const isVerified = user?.realNameFlag === 1;
|
|
|
+
|
|
|
+ const handleBack = () => router.back();
|
|
|
+
|
|
|
+ const handleSubmit = async () => {
|
|
|
+ const trimName = idName.trim();
|
|
|
+ const trimId = idNum.trim();
|
|
|
+
|
|
|
+ if (!trimName) {
|
|
|
+ Alert.alert("提示", "请输入真实姓名");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!NAME_REG.test(trimName)) {
|
|
|
+ Alert.alert("提示", "请输入正确的姓名格式(2-10个中文字符)");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!trimId) {
|
|
|
+ Alert.alert("提示", "请输入身份证号");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!ID_REG.test(trimId)) {
|
|
|
+ Alert.alert("提示", "请输入正确的身份证号码");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ setSubmitting(true);
|
|
|
+ const ok = await realNameAuth(trimName, trimId);
|
|
|
+ if (ok) {
|
|
|
+ Alert.alert("提示", "提交成功", [
|
|
|
+ { text: "确定", onPress: () => router.back() },
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error("提交实名认证失败:", e);
|
|
|
+ } finally {
|
|
|
+ setSubmitting(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <ImageBackground
|
|
|
+ source={{ uri: Images.mine.kaixinMineBg }}
|
|
|
+ style={styles.container}
|
|
|
+ resizeMode="cover"
|
|
|
+ >
|
|
|
+ <StatusBar barStyle="light-content" />
|
|
|
+
|
|
|
+ <View style={[styles.header, { paddingTop: insets.top }]}>
|
|
|
+ <TouchableOpacity style={styles.backBtn} onPress={handleBack}>
|
|
|
+ <Text style={styles.backIcon}>‹</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ <Text style={styles.headerTitle}>实名认证</Text>
|
|
|
+ <View style={styles.placeholder} />
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <KeyboardAvoidingView
|
|
|
+ style={{ flex: 1 }}
|
|
|
+ behavior={Platform.OS === "ios" ? "padding" : undefined}
|
|
|
+ >
|
|
|
+ <ScrollView
|
|
|
+ style={styles.scrollView}
|
|
|
+ showsVerticalScrollIndicator={false}
|
|
|
+ keyboardShouldPersistTaps="handled"
|
|
|
+ >
|
|
|
+ <View style={styles.content}>
|
|
|
+ <View style={styles.card}>
|
|
|
+ {loading ? (
|
|
|
+ <Text style={styles.loadingText}>加载中...</Text>
|
|
|
+ ) : isVerified ? (
|
|
|
+ <>
|
|
|
+ <View style={styles.verifiedBanner}>
|
|
|
+ <Text style={styles.verifiedBannerText}>✓ 已完成实名认证</Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View style={styles.formItem}>
|
|
|
+ <Text style={styles.label}>真实姓名</Text>
|
|
|
+ <View style={styles.readonly}>
|
|
|
+ <Text style={styles.readonlyText}>
|
|
|
+ {maskName(user?.idName || "")}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View style={styles.formItem}>
|
|
|
+ <Text style={styles.label}>身份证号</Text>
|
|
|
+ <View style={styles.readonly}>
|
|
|
+ <Text style={styles.readonlyText}>
|
|
|
+ {maskIdNum(user?.idNum || "")}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View style={styles.tips}>
|
|
|
+ <Text style={styles.tipsIcon}>ⓘ</Text>
|
|
|
+ <Text style={styles.tipsText}>
|
|
|
+ 实名信息已提交,不可修改
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <View style={styles.formItem}>
|
|
|
+ <Text style={styles.label}>真实姓名</Text>
|
|
|
+ <TextInput
|
|
|
+ style={styles.input}
|
|
|
+ value={idName}
|
|
|
+ onChangeText={setIdName}
|
|
|
+ placeholder="请输入您的真实姓名"
|
|
|
+ placeholderTextColor="#bbb"
|
|
|
+ maxLength={20}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View style={styles.formItem}>
|
|
|
+ <Text style={styles.label}>身份证号</Text>
|
|
|
+ <TextInput
|
|
|
+ style={styles.input}
|
|
|
+ value={idNum}
|
|
|
+ onChangeText={setIdNum}
|
|
|
+ placeholder="请输入您的身份证号码"
|
|
|
+ placeholderTextColor="#bbb"
|
|
|
+ maxLength={18}
|
|
|
+ autoCapitalize="characters"
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View style={styles.tips}>
|
|
|
+ <Text style={styles.tipsIcon}>⚠️</Text>
|
|
|
+ <Text style={styles.tipsText}>
|
|
|
+ 请确保您填写的信息真实有效,提交后将无法修改
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[styles.submitBtn, submitting && styles.submitBtnDisabled]}
|
|
|
+ onPress={handleSubmit}
|
|
|
+ disabled={submitting}
|
|
|
+ activeOpacity={0.8}
|
|
|
+ >
|
|
|
+ <Text style={styles.submitBtnText}>
|
|
|
+ {submitting ? "提交中..." : "提交"}
|
|
|
+ </Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </ScrollView>
|
|
|
+ </KeyboardAvoidingView>
|
|
|
+ </ImageBackground>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+const styles = StyleSheet.create({
|
|
|
+ container: {
|
|
|
+ flex: 1,
|
|
|
+ },
|
|
|
+ header: {
|
|
|
+ flexDirection: "row",
|
|
|
+ alignItems: "center",
|
|
|
+ justifyContent: "space-between",
|
|
|
+ paddingHorizontal: 10,
|
|
|
+ height: 80,
|
|
|
+ },
|
|
|
+ backBtn: {
|
|
|
+ width: 40,
|
|
|
+ height: 40,
|
|
|
+ justifyContent: "center",
|
|
|
+ alignItems: "center",
|
|
|
+ },
|
|
|
+ backIcon: {
|
|
|
+ fontSize: 32,
|
|
|
+ color: "#fff",
|
|
|
+ fontWeight: "bold",
|
|
|
+ },
|
|
|
+ headerTitle: {
|
|
|
+ fontSize: 16,
|
|
|
+ fontWeight: "bold",
|
|
|
+ color: "#fff",
|
|
|
+ },
|
|
|
+ placeholder: {
|
|
|
+ width: 40,
|
|
|
+ },
|
|
|
+ scrollView: {
|
|
|
+ flex: 1,
|
|
|
+ },
|
|
|
+ content: {
|
|
|
+ paddingHorizontal: 15,
|
|
|
+ paddingTop: 10,
|
|
|
+ paddingBottom: 30,
|
|
|
+ },
|
|
|
+ card: {
|
|
|
+ backgroundColor: "#fff",
|
|
|
+ borderRadius: 15,
|
|
|
+ padding: 20,
|
|
|
+ },
|
|
|
+ loadingText: {
|
|
|
+ textAlign: "center",
|
|
|
+ color: "#999",
|
|
|
+ paddingVertical: 40,
|
|
|
+ },
|
|
|
+ verifiedBanner: {
|
|
|
+ backgroundColor: "#e6f9ef",
|
|
|
+ borderRadius: 8,
|
|
|
+ paddingVertical: 10,
|
|
|
+ paddingHorizontal: 14,
|
|
|
+ marginBottom: 20,
|
|
|
+ alignItems: "center",
|
|
|
+ },
|
|
|
+ verifiedBannerText: {
|
|
|
+ color: "#1aad5a",
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: "600",
|
|
|
+ },
|
|
|
+ formItem: {
|
|
|
+ marginBottom: 20,
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ fontSize: 14,
|
|
|
+ color: "#333",
|
|
|
+ fontWeight: "500",
|
|
|
+ marginBottom: 10,
|
|
|
+ },
|
|
|
+ input: {
|
|
|
+ width: "100%",
|
|
|
+ height: 40,
|
|
|
+ paddingHorizontal: 10,
|
|
|
+ borderWidth: 1,
|
|
|
+ borderColor: "#e5e5e5",
|
|
|
+ borderRadius: 5,
|
|
|
+ fontSize: 14,
|
|
|
+ color: "#333",
|
|
|
+ backgroundColor: "#f8f8f8",
|
|
|
+ },
|
|
|
+ readonly: {
|
|
|
+ width: "100%",
|
|
|
+ height: 40,
|
|
|
+ paddingHorizontal: 10,
|
|
|
+ borderWidth: 1,
|
|
|
+ borderColor: "#e5e5e5",
|
|
|
+ borderRadius: 5,
|
|
|
+ backgroundColor: "#f5f5f5",
|
|
|
+ justifyContent: "center",
|
|
|
+ },
|
|
|
+ readonlyText: {
|
|
|
+ fontSize: 14,
|
|
|
+ color: "#666",
|
|
|
+ letterSpacing: 1,
|
|
|
+ },
|
|
|
+ tips: {
|
|
|
+ flexDirection: "row",
|
|
|
+ alignItems: "flex-start",
|
|
|
+ padding: 10,
|
|
|
+ marginBottom: 20,
|
|
|
+ backgroundColor: "#fff9e6",
|
|
|
+ borderRadius: 5,
|
|
|
+ borderLeftWidth: 2,
|
|
|
+ borderLeftColor: "#ffdd02",
|
|
|
+ },
|
|
|
+ tipsIcon: {
|
|
|
+ fontSize: 14,
|
|
|
+ marginRight: 6,
|
|
|
+ },
|
|
|
+ tipsText: {
|
|
|
+ flex: 1,
|
|
|
+ fontSize: 12,
|
|
|
+ color: "#ff9900",
|
|
|
+ lineHeight: 18,
|
|
|
+ },
|
|
|
+ submitBtn: {
|
|
|
+ backgroundColor: "#FC7D2E",
|
|
|
+ borderRadius: 8,
|
|
|
+ paddingVertical: 13,
|
|
|
+ alignItems: "center",
|
|
|
+ },
|
|
|
+ submitBtnDisabled: {
|
|
|
+ opacity: 0.6,
|
|
|
+ },
|
|
|
+ submitBtnText: {
|
|
|
+ color: "#fff",
|
|
|
+ fontSize: 16,
|
|
|
+ fontWeight: "600",
|
|
|
+ },
|
|
|
+});
|