ReactNative【实战系列教程】我的小红书 2 -- 快捷登录、手机号密码登录

发布于:2025-06-29 ⋅ 阅读:(14) ⋅ 点赞:(0)

最终效果

在这里插入图片描述

在这里插入图片描述

技术要点

用户协议 – 打开本地浏览器

点击后,直接打开本地浏览器浏览网页

 // 最终需修改为 《用户协议》 的网址
 Linking.openURL("https://www.baidu.com");

手机号输入框的 344 展示

在这里插入图片描述

onChangeText={(text: string) => {
  setPhone(formatPhone(text));
}}
export function formatPhone(phone: string): string {
  let trim: string = phone.replace(/\s+/g, "");
  const result = [trim.slice(0, 3), trim.slice(3, 7), trim.slice(7, 11)]
    .filter((item) => !!item)
    .join(" ");
  return result;
}

但在访问接口登录时,传参需清除空格

UserStore.requestLogin(replaceBlank(phone), pwd, (success: boolean) => {
export function replaceBlank(phone: string): string {
  return phone ? phone.replace(/\s+/g, "") : "";
}

代码实现

app/_layout.tsx

        <Stack>
          {/* 登录页 */}
          <Stack.Screen name="login" options={{ headerShown: false }} />
          <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
          <Stack.Screen name="+not-found" />
        </Stack>

app/login.tsx

import UserStore from "@/stores/UserStore";
import { formatPhone, replaceBlank } from "@/utils/StringUtil";
import { Toast } from "@ant-design/react-native";
import AntDesign from "@expo/vector-icons/AntDesign";
import Entypo from "@expo/vector-icons/Entypo";
import FontAwesome from "@expo/vector-icons/FontAwesome";
import { useRouter } from "expo-router";
import { useState } from "react";
import {
  Image,
  KeyboardAvoidingView,
  Linking,
  Platform,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  View,
} from "react-native";
import Animated from "react-native-reanimated";
export default function LoginScreen() {
  const router = useRouter();
  const [ifRead, setIfRead] = useState(false);
  const [ifQuickLogin, setIfQuickLogin] = useState(false);
  const [phone, setPhone] = useState<string>("");
  const [pwd, setPwd] = useState<string>("");
  const [showPassword, setShowPassword] = useState(false);
  const canLogin = phone?.length === 13 && pwd?.length >= 6;
  const onLoginPress = async () => {
    if (!canLogin) {
      return;
    }
    if (!ifRead) {
      Toast.show({
        content: "请先同意并阅读用户协议",
      });
      return;
    }

    UserStore.requestLogin(replaceBlank(phone), pwd, (success: boolean) => {
      if (success) {
        Toast.success("登录成功");
        router.replace("/(tabs)");
      } else {
        Toast.fail("登录失败,账号/密码错误!");
      }
    });
  };
  const render_passwordLogin = () => {
    const passwordLogin_styles = StyleSheet.create({
      inputBox: {
        borderColor: "#ccc",
        borderBottomColor: "#ccc",
        borderBottomWidth: 1,
        flexDirection: "row",
        alignItems: "center",
        marginBottom: 14,
        height: 55,
      },
      phonePrefix: {
        fontSize: 20,
        color: "#bbb",
        marginRight: 8,
      },
      phoneInput: {
        fontSize: 24,
        paddingLeft: 14,
        textAlignVertical: "bottom",
        height: 75,
        flex: 1,
      },
      passwordInput: {
        fontSize: 20,
        textAlignVertical: "bottom",
        flex: 1,
        height: 75,
      },
      loginButton: {
        backgroundColor: "#ff2442",
      },
      loginButtonDisable: {
        backgroundColor: "#DDDDDD",
      },
      moreBox: {
        flexDirection: "row",
        justifyContent: "space-between",
        marginBottom: 26,
      },
      tip: {
        fontSize: 14,
        color: "#bbb",
        marginBottom: 20,
        textAlign: "center",
      },
      toCodeLoginBtn: {
        flexDirection: "row",
        alignItems: "center",
      },
      toCodeLoginBtnTxt: {
        marginLeft: 6,
      },
      moreBoxTxt: {
        color: "#303080",
      },
    });
    return (
      <View>
        <Text style={passwordLogin_styles.tip}>
          未注册的手机号登录成功后将自动注册
        </Text>
        <KeyboardAvoidingView
          behavior={Platform.OS === "ios" ? "padding" : "height"}
          keyboardVerticalOffset={Platform.OS === "ios" ? 64 : 30}
        >
          <View style={passwordLogin_styles.inputBox}>
            <Text style={passwordLogin_styles.phonePrefix}>+86</Text>
            <AntDesign name="caretdown" size={14} color="#bbb" />
            <TextInput
              style={passwordLogin_styles.phoneInput}
              placeholder="请输入手机号"
              placeholderTextColor="#bbb"
              autoFocus={false}
              keyboardType="number-pad"
              maxLength={13}
              value={phone}
              onChangeText={(text: string) => {
                setPhone(formatPhone(text));
              }}
            />
          </View>
          <View style={passwordLogin_styles.inputBox}>
            <TextInput
              style={passwordLogin_styles.passwordInput}
              placeholder="请输入密码"
              placeholderTextColor="#bbb"
              autoFocus={false}
              maxLength={20}
              secureTextEntry={!showPassword}
              value={pwd}
              onChangeText={(text: string) => {
                setPwd(text);
              }}
            />
            <TouchableOpacity
              activeOpacity={0.7}
              onPress={() => {
                setShowPassword(!showPassword);
              }}
            >
              <Entypo
                name={showPassword ? "eye" : "eye-with-line"}
                size={28}
                color="#bbb"
              />
            </TouchableOpacity>
          </View>
        </KeyboardAvoidingView>
        <View style={passwordLogin_styles.moreBox}>
          <TouchableOpacity
            style={passwordLogin_styles.toCodeLoginBtn}
            onPress={() => {
              Toast.show({
                content: "待需要时完善",
              });
            }}
          >
            <FontAwesome name="exchange" size={12} color="#303080" />
            <Text
              style={[
                passwordLogin_styles.toCodeLoginBtnTxt,
                passwordLogin_styles.moreBoxTxt,
              ]}
            >
              验证码登录
            </Text>
          </TouchableOpacity>
          <TouchableOpacity
            onPress={() => {
              Toast.show({
                content: "待需要时完善",
              });
            }}
          >
            <Text style={passwordLogin_styles.moreBoxTxt}>忘记密码?</Text>
          </TouchableOpacity>
        </View>
        <TouchableOpacity
          style={[
            styles.btn,
            canLogin
              ? passwordLogin_styles.loginButton
              : passwordLogin_styles.loginButtonDisable,
          ]}
          activeOpacity={canLogin ? 0.7 : 1}
          onPress={onLoginPress}
        >
          <Text style={[styles.btnTxt]}>登录</Text>
        </TouchableOpacity>
      </View>
    );
  };
  return (
    <View style={styles.page}>
      <Image
        source={require("@/assets/images/icon.png")}
        style={{ width: 200, height: 200, marginTop: 100 }}
        resizeMode="cover"
      />
      <Animated.View style={styles.LoginBox}>
        {ifQuickLogin ? (
          <>
            <TouchableOpacity
              style={[styles.btn, styles.oneKeyLoginButton]}
              activeOpacity={0.7}
              onPress={() => {
                Toast.show({
                  content: "待需要时完善",
                });
              }}
            >
              <Text style={[styles.btnTxt, styles.oneKeyLoginTxt]}>
                一键登录
              </Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[styles.btn, styles.wxLoginButton]}
              activeOpacity={0.7}
              onPress={() => {
                Toast.show({
                  content: "待需要时完善",
                });
              }}
            >
              <AntDesign name="wechat" size={24} color="white" />
              <Text style={[styles.btnTxt]}>微信登录</Text>
            </TouchableOpacity>
          </>
        ) : (
          render_passwordLogin()
        )}
        <TouchableOpacity
          activeOpacity={0.7}
          style={styles.moreLoginWayBox}
          onPress={() => {
            setIfQuickLogin(!ifQuickLogin);
          }}
        >
          <Text style={[styles.moreLoginWayTxt]}>
            {ifQuickLogin ? "其他登录方式" : "快捷登录"}
          </Text>
          <Entypo name="chevron-small-right" size={20} />
        </TouchableOpacity>
        <View style={styles.moreBox}>
          <TouchableOpacity
            onPress={() => {
              setIfRead(!ifRead);
            }}
          >
            {ifRead ? (
              <AntDesign
                style={styles.moreIcon}
                name="checkcircle"
                size={14}
                color="#05c160"
              />
            ) : (
              <Entypo
                style={styles.moreIcon}
                name="circle"
                size={14}
                color="gray"
              />
            )}
          </TouchableOpacity>
          <Text style={styles.infoTxt}>我已阅读并同意</Text>
          <TouchableOpacity
            onPress={() => {
              // 最终需修改为 《用户协议》 的网址
              Linking.openURL("https://www.baidu.com");
            }}
          >
            <Text style={styles.linkTxt}>《用户协议》</Text>
          </TouchableOpacity>
          <Text style={styles.infoTxt}></Text>
          <TouchableOpacity
            onPress={() => {
              // 最终需修改为 《隐私政策》 的网址
              Linking.openURL("https://www.baidu.com");
            }}
          >
            <Text style={styles.linkTxt}>《隐私政策》</Text>
          </TouchableOpacity>
        </View>
      </Animated.View>
    </View>
  );
}
const styles = StyleSheet.create({
  page: {
    flex: 1,
    alignItems: "center",
    justifyContent: "space-between",
    backgroundColor: "#e8e8e7",
    padding: 40,
  },
  LoginBox: {
    width: "100%",
    padding: 20,
    marginBottom: 100,
  },
  btn: {
    width: "100%",
    height: 56,
    borderRadius: 28,
    justifyContent: "center",
    alignItems: "center",
    flexDirection: "row",
    marginBottom: 20,
  },
  btnTxt: {
    fontSize: 18,
    color: "white",
    marginBottom: 6,
    marginLeft: 14,
  },
  oneKeyLoginButton: {
    backgroundColor: "#ff2442",
  },
  oneKeyLoginTxt: {
    marginLeft: 4,
  },
  wxLoginButton: {
    backgroundColor: "#05c160",
  },
  moreBox: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center",
  },
  moreIcon: {
    marginRight: 4,
    marginTop: 2,
  },
  moreLoginWayBox: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center",
    marginBottom: 60,
  },
  moreLoginWayTxt: {
    fontSize: 16,
    marginBottom: 4,
  },
  infoTxt: {
    fontSize: 14,
    color: "gray",
  },
  linkTxt: {
    fontSize: 14,
    color: "blue",
  },
});

utils/StringUtil.ts

export function formatPhone(phone: string): string {
  let trim: string = phone.replace(/\s+/g, "");
  const result = [trim.slice(0, 3), trim.slice(3, 7), trim.slice(7, 11)]
    .filter((item) => !!item)
    .join(" ");
  return result;
}
export function replaceBlank(phone: string): string {
  return phone ? phone.replace(/\s+/g, "") : "";
}

网站公告

今日签到

点亮在社区的每一天
去签到