|
|
@@ -1,34 +1,34 @@
|
|
|
-import { useRouter } from 'expo-router';
|
|
|
-import React, { useEffect, useRef, useState } from 'react';
|
|
|
+import { useRouter } from "expo-router";
|
|
|
+import React, { useEffect, useRef, 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';
|
|
|
+ Alert,
|
|
|
+ ImageBackground,
|
|
|
+ KeyboardAvoidingView,
|
|
|
+ Platform,
|
|
|
+ ScrollView,
|
|
|
+ StatusBar,
|
|
|
+ StyleSheet,
|
|
|
+ Text,
|
|
|
+ TextInput,
|
|
|
+ TouchableOpacity,
|
|
|
+ View,
|
|
|
+} from "react-native";
|
|
|
+import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
|
|
|
|
-import { Images } from '@/constants/images';
|
|
|
-import { login, sendVerifyCode } from '@/services/user';
|
|
|
+import { Images } from "@/constants/images";
|
|
|
+import { login, sendVerifyCode } from "@/services/user";
|
|
|
|
|
|
export default function LoginScreen() {
|
|
|
const router = useRouter();
|
|
|
const insets = useSafeAreaInsets();
|
|
|
|
|
|
- const [phone, setPhone] = useState('');
|
|
|
- const [verifyCode, setVerifyCode] = useState('');
|
|
|
+ const [phone, setPhone] = useState("");
|
|
|
+ const [verifyCode, setVerifyCode] = useState("");
|
|
|
const [agreeFlag, setAgreeFlag] = useState(false);
|
|
|
const [countdown, setCountdown] = useState(0);
|
|
|
const [disabled, setDisabled] = useState(false);
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
- const [tips, setTips] = useState('获取验证码');
|
|
|
+ const [tips, setTips] = useState("获取验证码");
|
|
|
|
|
|
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
|
|
|
|
@@ -51,7 +51,7 @@ export default function LoginScreen() {
|
|
|
setDisabled(true);
|
|
|
let count = 60;
|
|
|
setTips(`${count}s后重新获取`);
|
|
|
-
|
|
|
+
|
|
|
timerRef.current = setInterval(() => {
|
|
|
count--;
|
|
|
if (count > 0) {
|
|
|
@@ -65,7 +65,7 @@ export default function LoginScreen() {
|
|
|
// 重置倒计时
|
|
|
const resetCountdown = () => {
|
|
|
setDisabled(false);
|
|
|
- setTips('获取验证码');
|
|
|
+ setTips("获取验证码");
|
|
|
if (timerRef.current) {
|
|
|
clearInterval(timerRef.current);
|
|
|
timerRef.current = null;
|
|
|
@@ -75,38 +75,38 @@ export default function LoginScreen() {
|
|
|
// 获取验证码
|
|
|
const handleGetVerifyCode = async () => {
|
|
|
if (disabled) return;
|
|
|
-
|
|
|
+
|
|
|
if (phone && isChinesePhoneNumber(phone)) {
|
|
|
try {
|
|
|
- const res = await sendVerifyCode(phone, 'LOGIN');
|
|
|
+ const res = await sendVerifyCode(phone, "LOGIN");
|
|
|
if (res) {
|
|
|
- Alert.alert('提示', '验证码已发送');
|
|
|
+ Alert.alert("提示", "验证码已发送");
|
|
|
startCountdown();
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- Alert.alert('错误', '获取验证码失败,请重试');
|
|
|
+ Alert.alert("错误", "获取验证码失败,请重试");
|
|
|
}
|
|
|
} else {
|
|
|
- Alert.alert('提示', '请输入正确的手机号');
|
|
|
+ Alert.alert("提示", "请输入正确的手机号");
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 登录
|
|
|
const handleLogin = async () => {
|
|
|
if (!agreeFlag) {
|
|
|
- Alert.alert('提示', '请您先阅读并同意用户协议和隐私政策');
|
|
|
+ Alert.alert("提示", "请您先阅读并同意用户协议和隐私政策");
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (phone && isChinesePhoneNumber(phone) && verifyCode) {
|
|
|
setLoading(true);
|
|
|
try {
|
|
|
const result = await login({
|
|
|
- loginWay: 'MOBILE',
|
|
|
+ loginWay: "MOBILE",
|
|
|
mobile: phone,
|
|
|
verifycode: verifyCode,
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
if (result.success) {
|
|
|
// TODO: 如果 needInfo 为 true,跳转到完善信息页面
|
|
|
// if (result.needInfo) {
|
|
|
@@ -115,14 +115,14 @@ export default function LoginScreen() {
|
|
|
// }
|
|
|
router.back();
|
|
|
} else {
|
|
|
- Alert.alert('错误', '登录失败,请检查验证码');
|
|
|
+ Alert.alert("错误", "登录失败,请检查验证码");
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- Alert.alert('错误', '登录失败');
|
|
|
+ Alert.alert("错误", "登录失败");
|
|
|
}
|
|
|
setLoading(false);
|
|
|
} else {
|
|
|
- Alert.alert('提示', '请输入手机号和验证码');
|
|
|
+ Alert.alert("提示", "请输入手机号和验证码");
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -140,7 +140,7 @@ export default function LoginScreen() {
|
|
|
>
|
|
|
<KeyboardAvoidingView
|
|
|
style={styles.keyboardView}
|
|
|
- behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
|
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
|
>
|
|
|
<ScrollView
|
|
|
contentContainerStyle={styles.scrollContent}
|
|
|
@@ -148,7 +148,9 @@ export default function LoginScreen() {
|
|
|
showsVerticalScrollIndicator={false}
|
|
|
>
|
|
|
{/* 底部表单区域 */}
|
|
|
- <View style={[styles.bottom, { paddingBottom: insets.bottom + 70 }]}>
|
|
|
+ <View
|
|
|
+ style={[styles.bottom, { paddingBottom: insets.bottom + 70 }]}
|
|
|
+ >
|
|
|
{/* 表单 */}
|
|
|
<View style={styles.form}>
|
|
|
{/* 手机号输入 */}
|
|
|
@@ -179,11 +181,19 @@ export default function LoginScreen() {
|
|
|
maxLength={6}
|
|
|
/>
|
|
|
<TouchableOpacity
|
|
|
- style={[styles.verifyBtn, disabled && styles.verifyBtnDisabled]}
|
|
|
+ style={[
|
|
|
+ styles.verifyBtn,
|
|
|
+ disabled && styles.verifyBtnDisabled,
|
|
|
+ ]}
|
|
|
onPress={handleGetVerifyCode}
|
|
|
disabled={disabled}
|
|
|
>
|
|
|
- <Text style={[styles.verifyBtnText, disabled && styles.verifyBtnTextDisabled]}>
|
|
|
+ <Text
|
|
|
+ style={[
|
|
|
+ styles.verifyBtnText,
|
|
|
+ disabled && styles.verifyBtnTextDisabled,
|
|
|
+ ]}
|
|
|
+ >
|
|
|
{tips}
|
|
|
</Text>
|
|
|
</TouchableOpacity>
|
|
|
@@ -192,20 +202,43 @@ export default function LoginScreen() {
|
|
|
</View>
|
|
|
|
|
|
{/* 协议勾选 */}
|
|
|
- <TouchableOpacity
|
|
|
- style={styles.agree}
|
|
|
- onPress={() => setAgreeFlag(!agreeFlag)}
|
|
|
- >
|
|
|
- <View style={[styles.radio, agreeFlag && styles.radioChecked]}>
|
|
|
- {agreeFlag && <View style={styles.radioInner} />}
|
|
|
- </View>
|
|
|
- <Text style={styles.agreeText}>
|
|
|
- 我已阅读并同意
|
|
|
- <Text style={styles.linkText}>《用户协议》</Text>
|
|
|
- 跟
|
|
|
- <Text style={styles.linkText}>《隐私政策》</Text>
|
|
|
+ <View style={styles.agree}>
|
|
|
+ <TouchableOpacity
|
|
|
+ style={styles.agreeCheckboxArea}
|
|
|
+ onPress={() => setAgreeFlag(!agreeFlag)}
|
|
|
+ activeOpacity={0.8}
|
|
|
+ >
|
|
|
+ <View
|
|
|
+ style={[styles.radio, agreeFlag && styles.radioChecked]}
|
|
|
+ >
|
|
|
+ {agreeFlag && <View style={styles.radioInner} />}
|
|
|
+ </View>
|
|
|
+ <Text style={styles.agreeText}>我已阅读并同意</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ <Text
|
|
|
+ style={styles.linkText}
|
|
|
+ onPress={() =>
|
|
|
+ router.push({
|
|
|
+ pathname: "/agreement",
|
|
|
+ params: { type: "user.html" },
|
|
|
+ })
|
|
|
+ }
|
|
|
+ >
|
|
|
+ 《用户协议》
|
|
|
</Text>
|
|
|
- </TouchableOpacity>
|
|
|
+ <Text style={styles.agreeText}>跟</Text>
|
|
|
+ <Text
|
|
|
+ style={styles.linkText}
|
|
|
+ onPress={() =>
|
|
|
+ router.push({
|
|
|
+ pathname: "/agreement",
|
|
|
+ params: { type: "privacy.html" },
|
|
|
+ })
|
|
|
+ }
|
|
|
+ >
|
|
|
+ 《隐私政策》
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
|
|
|
{/* 按钮区域 */}
|
|
|
<View style={styles.btnArea}>
|
|
|
@@ -221,7 +254,7 @@ export default function LoginScreen() {
|
|
|
resizeMode="stretch"
|
|
|
>
|
|
|
<Text style={styles.btnText}>
|
|
|
- {loading ? '登录中...' : '登录'}
|
|
|
+ {loading ? "登录中..." : "登录"}
|
|
|
</Text>
|
|
|
</ImageBackground>
|
|
|
</TouchableOpacity>
|
|
|
@@ -241,7 +274,7 @@ export default function LoginScreen() {
|
|
|
const styles = StyleSheet.create({
|
|
|
container: {
|
|
|
flex: 1,
|
|
|
- backgroundColor: '#1a1a2e',
|
|
|
+ backgroundColor: "#1a1a2e",
|
|
|
},
|
|
|
background: {
|
|
|
flex: 1,
|
|
|
@@ -251,60 +284,64 @@ const styles = StyleSheet.create({
|
|
|
},
|
|
|
scrollContent: {
|
|
|
flexGrow: 1,
|
|
|
- justifyContent: 'flex-end',
|
|
|
+ justifyContent: "flex-end",
|
|
|
},
|
|
|
bottom: {
|
|
|
- width: '100%',
|
|
|
+ width: "100%",
|
|
|
},
|
|
|
form: {
|
|
|
paddingHorizontal: 25,
|
|
|
},
|
|
|
formItem: {
|
|
|
- flexDirection: 'row',
|
|
|
- alignItems: 'center',
|
|
|
+ flexDirection: "row",
|
|
|
+ alignItems: "center",
|
|
|
paddingVertical: 12,
|
|
|
},
|
|
|
label: {
|
|
|
- color: '#fff',
|
|
|
+ color: "#fff",
|
|
|
fontSize: 14,
|
|
|
width: 50,
|
|
|
},
|
|
|
input: {
|
|
|
flex: 1,
|
|
|
- color: '#fff',
|
|
|
+ color: "#fff",
|
|
|
fontSize: 14,
|
|
|
paddingVertical: 8,
|
|
|
- outlineStyle: 'none',
|
|
|
+ outlineStyle: "none",
|
|
|
} as any,
|
|
|
codeInput: {
|
|
|
flex: 1,
|
|
|
},
|
|
|
divider: {
|
|
|
height: 1,
|
|
|
- backgroundColor: 'rgba(255,255,255,0.2)',
|
|
|
+ backgroundColor: "rgba(255,255,255,0.2)",
|
|
|
},
|
|
|
verifyBtn: {
|
|
|
- backgroundColor: '#000',
|
|
|
+ backgroundColor: "#000",
|
|
|
borderRadius: 4,
|
|
|
paddingHorizontal: 10,
|
|
|
paddingVertical: 5,
|
|
|
minWidth: 80,
|
|
|
- alignItems: 'center',
|
|
|
+ alignItems: "center",
|
|
|
},
|
|
|
verifyBtnDisabled: {
|
|
|
- backgroundColor: '#ccc',
|
|
|
+ backgroundColor: "#ccc",
|
|
|
},
|
|
|
verifyBtnText: {
|
|
|
- color: '#fff',
|
|
|
+ color: "#fff",
|
|
|
fontSize: 12,
|
|
|
},
|
|
|
verifyBtnTextDisabled: {
|
|
|
- color: '#666',
|
|
|
+ color: "#666",
|
|
|
+ },
|
|
|
+ agreeCheckboxArea: {
|
|
|
+ flexDirection: "row",
|
|
|
+ alignItems: "center",
|
|
|
},
|
|
|
agree: {
|
|
|
- flexDirection: 'row',
|
|
|
- alignItems: 'center',
|
|
|
- justifyContent: 'center',
|
|
|
+ flexDirection: "row",
|
|
|
+ alignItems: "center",
|
|
|
+ justifyContent: "center",
|
|
|
marginTop: 25,
|
|
|
paddingHorizontal: 25,
|
|
|
},
|
|
|
@@ -313,64 +350,64 @@ const styles = StyleSheet.create({
|
|
|
height: 16,
|
|
|
borderRadius: 8,
|
|
|
borderWidth: 1,
|
|
|
- borderColor: 'rgba(255,255,255,0.5)',
|
|
|
+ borderColor: "rgba(255,255,255,0.5)",
|
|
|
marginRight: 6,
|
|
|
- justifyContent: 'center',
|
|
|
- alignItems: 'center',
|
|
|
+ justifyContent: "center",
|
|
|
+ alignItems: "center",
|
|
|
},
|
|
|
radioChecked: {
|
|
|
- borderColor: '#8b3dff',
|
|
|
- backgroundColor: '#8b3dff',
|
|
|
+ borderColor: "#8b3dff",
|
|
|
+ backgroundColor: "#8b3dff",
|
|
|
},
|
|
|
radioInner: {
|
|
|
width: 6,
|
|
|
height: 6,
|
|
|
borderRadius: 3,
|
|
|
- backgroundColor: '#fff',
|
|
|
+ backgroundColor: "#fff",
|
|
|
},
|
|
|
agreeText: {
|
|
|
- color: '#fff',
|
|
|
+ color: "#fff",
|
|
|
fontSize: 12,
|
|
|
},
|
|
|
linkText: {
|
|
|
- color: '#8b3dff',
|
|
|
+ color: "#8b3dff",
|
|
|
},
|
|
|
btnArea: {
|
|
|
paddingTop: 15,
|
|
|
- alignItems: 'center',
|
|
|
+ alignItems: "center",
|
|
|
},
|
|
|
btn: {
|
|
|
width: 234,
|
|
|
height: 45,
|
|
|
- overflow: 'hidden',
|
|
|
+ overflow: "hidden",
|
|
|
},
|
|
|
loginBtnBg: {
|
|
|
- width: '100%',
|
|
|
- height: '100%',
|
|
|
- justifyContent: 'center',
|
|
|
- alignItems: 'center',
|
|
|
+ width: "100%",
|
|
|
+ height: "100%",
|
|
|
+ justifyContent: "center",
|
|
|
+ alignItems: "center",
|
|
|
},
|
|
|
btnDisabled: {
|
|
|
opacity: 0.6,
|
|
|
},
|
|
|
btnText: {
|
|
|
- color: '#fff',
|
|
|
+ color: "#fff",
|
|
|
fontSize: 14,
|
|
|
- fontWeight: '600',
|
|
|
+ fontWeight: "600",
|
|
|
},
|
|
|
btnBack: {
|
|
|
width: 234,
|
|
|
height: 35,
|
|
|
- backgroundColor: '#fff',
|
|
|
+ backgroundColor: "#fff",
|
|
|
borderRadius: 25,
|
|
|
- justifyContent: 'center',
|
|
|
- alignItems: 'center',
|
|
|
+ justifyContent: "center",
|
|
|
+ alignItems: "center",
|
|
|
marginTop: 10,
|
|
|
borderWidth: 1,
|
|
|
- borderColor: '#fff',
|
|
|
+ borderColor: "#fff",
|
|
|
},
|
|
|
btnBackText: {
|
|
|
- color: '#888',
|
|
|
+ color: "#888",
|
|
|
fontSize: 12,
|
|
|
},
|
|
|
});
|