395 lines
11 KiB
JavaScript
395 lines
11 KiB
JavaScript
"use client";
|
||
|
||
import React, { useState, useRef, useEffect } from "react";
|
||
import {
|
||
Input,
|
||
Button,
|
||
Swiper,
|
||
Tabs,
|
||
Divider,
|
||
Checkbox,
|
||
Toast,
|
||
} from "antd-mobile";
|
||
import { useRouter } from "next/navigation";
|
||
import styles from "./index.module.css";
|
||
import { JSEncrypt } from "jsencrypt";
|
||
import { handleLogin } from "@/store/actions";
|
||
import { saveUserInfo,removeUserInfo } from "@/utils/storeInfo";
|
||
import { connect } from "react-redux";
|
||
import { cryptoPassword } from "@/utils/crypto";
|
||
import require from "@/utils/require";
|
||
import {signOut,signIn} from "@/utils/auth";
|
||
/*
|
||
params格式:
|
||
{
|
||
mid: item.mid,
|
||
}
|
||
*/
|
||
const tabItems = [
|
||
{ key: "code", title: "验证码登录" },
|
||
{ key: "password", title: "帐号密码登录" },
|
||
];
|
||
function Login({ handleLogin }) {
|
||
const [activeIndex, setActiveIndex] = useState(0);
|
||
const [veriCode, setVeriCode] = useState("");
|
||
const [isCounting, setIsCounting] = useState(false);
|
||
const [seconds, setSeconds] = useState(60);
|
||
const [loginInfo, setLoginInfo] = useState({
|
||
mobilePhone: "",
|
||
regionCode: "86",
|
||
password: "",
|
||
checked: false,
|
||
});
|
||
const router = useRouter();
|
||
const swiperRef = useRef(null);
|
||
useEffect(() => {
|
||
handleLogin({ isSignin: false, userToken: null });
|
||
signOut()
|
||
removeUserInfo();
|
||
},[])
|
||
useEffect(() => {
|
||
let interval;
|
||
if (isCounting && seconds > 0) {
|
||
interval = setInterval(() => {
|
||
setSeconds(seconds - 1);
|
||
}, 1000);
|
||
} else {
|
||
setIsCounting(false);
|
||
setSeconds(60);
|
||
clearInterval(interval);
|
||
}
|
||
return () => {
|
||
clearInterval(interval);
|
||
};
|
||
}, [isCounting, seconds]);
|
||
const handleSubmit = async (type) => {
|
||
const { mobilePhone, password, regionCode, checked } = loginInfo;
|
||
//验证数据格式
|
||
if (!checked) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: "请先阅读并同意《用户协议》和《隐私政策》后登录",
|
||
position: "top",
|
||
});
|
||
return;
|
||
}
|
||
if (!mobilePhone.match(/^1[3456789]\d{9}$/)) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: "手机号码格式错误",
|
||
position: "top",
|
||
});
|
||
return;
|
||
}
|
||
if (type === "password") {
|
||
if (password.length < 8) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: "密码不得小于8位",
|
||
position: "top",
|
||
});
|
||
return;
|
||
}
|
||
if (password.length > 15) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: "密码不得大于15位",
|
||
position: "top",
|
||
});
|
||
return;
|
||
}
|
||
} else {
|
||
if (veriCode.length !== 6) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: "请输入正确的验证码",
|
||
position: "top",
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
//对手机号进行RSA加密
|
||
const encrypt = new JSEncrypt();
|
||
encrypt.setPublicKey(process.env.NEXT_PUBLIC_RSA_KEY);
|
||
const mobile_phone = encrypt.encrypt(mobilePhone);
|
||
//MD5加密password
|
||
const encryptedPassword = cryptoPassword(password);
|
||
//发送登录请求
|
||
let body = {
|
||
mobile_phone,
|
||
region_code: regionCode,
|
||
};
|
||
body =
|
||
type === "password"
|
||
? {
|
||
...body,
|
||
password: encryptedPassword,
|
||
}
|
||
: {
|
||
...body,
|
||
code: veriCode,
|
||
};
|
||
try {
|
||
const data = await require("POST", `/api/login/${
|
||
type === "password" ? "login_by_pswd" : "login_by_veri_code"
|
||
}`, {
|
||
body,
|
||
});
|
||
if (data.ret === -1) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: data.msg,
|
||
position: "top",
|
||
});
|
||
return;
|
||
}
|
||
//登录
|
||
saveUserInfo(data, mobilePhone, regionCode);
|
||
signIn(data);
|
||
handleLogin({ isSignin: true, userToken: data.data.token });
|
||
router.push("/");
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
};
|
||
//点击获取验证码
|
||
const handleVerification = async () => {
|
||
const { mobilePhone, regionCode, checked } = loginInfo;
|
||
if (!checked) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: "请先阅读并同意《用户协议》和《隐私政策》后登录",
|
||
position: "top",
|
||
});
|
||
return;
|
||
}
|
||
//手机号校验
|
||
if (!mobilePhone.match(/^1[3456789]\d{9}$/)) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: "手机号码格式错误",
|
||
position: "top",
|
||
});
|
||
return;
|
||
}
|
||
//开始倒计时
|
||
setIsCounting(true);
|
||
//对手机号进行RSA加密
|
||
const encrypt = new JSEncrypt();
|
||
encrypt.setPublicKey(process.env.NEXT_PUBLIC_RSA_KEY);
|
||
const mobile_phone = encrypt.encrypt(mobilePhone);
|
||
//发送短信验证码
|
||
try {
|
||
await fetch(`/api/veri_code/send`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({
|
||
mobile_phone,
|
||
region_code: regionCode,
|
||
}),
|
||
});
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
};
|
||
return (
|
||
<div className={`${styles.loginBox}`}>
|
||
<div className="mt-32 flex justify-between items-center px-2 text-gray-400 sticky top-0 z-10 bg-deepBg">
|
||
<Tabs
|
||
activeKey={tabItems[activeIndex].key}
|
||
onChange={(key) => {
|
||
const index = tabItems.findIndex((item) => item.key === key);
|
||
setActiveIndex(index);
|
||
swiperRef.current?.swipeTo(index);
|
||
}}
|
||
className={`w-full ${styles.customTabs}`}
|
||
>
|
||
{tabItems.map((item) => (
|
||
<Tabs.Tab
|
||
forceRender={false}
|
||
title={item.title}
|
||
key={item.key}
|
||
className="text-left"
|
||
/>
|
||
))}
|
||
</Tabs>
|
||
</div>
|
||
<Swiper
|
||
className="overflow-visible mt-6 "
|
||
direction="horizontal"
|
||
loop
|
||
indicator={() => null}
|
||
ref={swiperRef}
|
||
defaultIndex={activeIndex}
|
||
onIndexChange={(index) => {
|
||
setActiveIndex(index);
|
||
}}
|
||
>
|
||
<Swiper.Item className="px-10">
|
||
<div className="border-2 border-[#2c2b2f] rounded-2xl p-4">
|
||
<div className="flex flex-row flex-nowrap items-center mb-4">
|
||
<p className="text-base text-white mr-4">
|
||
+{loginInfo.regionCode}
|
||
</p>
|
||
<Input
|
||
clearable
|
||
placeholder="请输入手机号"
|
||
// disabled={true}
|
||
type="number"
|
||
maxLength={11}
|
||
onChange={(value) =>
|
||
setLoginInfo({ ...loginInfo, mobilePhone: value })
|
||
}
|
||
value={loginInfo.mobilePhone}
|
||
style={{ "--color": "#FFFFFF", "--font-size": "16px" }}
|
||
/>
|
||
</div>
|
||
<Divider />
|
||
<div className="flex flex-row flex-nowrap items-center">
|
||
<p className="text-base text-white mr-4 whitespace-nowrap">
|
||
验证码
|
||
</p>
|
||
<Input
|
||
placeholder="请输入验证码"
|
||
onChange={(value) => setVeriCode(value)}
|
||
value={veriCode}
|
||
type="number"
|
||
style={{
|
||
"--placeholder-color": "#FFFFFF80",
|
||
"--font-size": "16px",
|
||
}}
|
||
/>
|
||
<Button
|
||
shape="rounded"
|
||
size="mini"
|
||
disabled={isCounting}
|
||
onClick={handleVerification}
|
||
style={{ "--background-color": "#FF669E", color: "#FFFFFF" }}
|
||
className="whitespace-nowrap"
|
||
>
|
||
{isCounting ? `(${seconds})重新发送` : "获取验证码"}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<LoginBtn
|
||
loginInfo={loginInfo}
|
||
setLoginInfo={setLoginInfo}
|
||
handleSubmit={handleSubmit}
|
||
type={activeIndex ? "password" : "mobile"}
|
||
/>
|
||
</Swiper.Item>
|
||
<Swiper.Item className="px-10">
|
||
<div className="border-2 border-[#2c2b2f] rounded-2xl p-4">
|
||
<div className="flex flex-row flex-nowrap items-center mb-4">
|
||
<p className="text-base text-white mr-4">
|
||
+{loginInfo.regionCode}
|
||
</p>
|
||
<Input
|
||
clearable
|
||
placeholder="请输入手机号"
|
||
// disabled={true}
|
||
type="number"
|
||
maxLength={11}
|
||
onChange={(value) =>
|
||
setLoginInfo({ ...loginInfo, mobilePhone: value })
|
||
}
|
||
value={loginInfo.mobilePhone}
|
||
style={{ "--color": "#FFFFFF", "--font-size": "16px" }}
|
||
/>
|
||
</div>
|
||
<Divider />
|
||
<div className="flex flex-row flex-nowrap items-center">
|
||
<p className="text-base text-white mr-4 whitespace-nowrap">
|
||
密码
|
||
</p>
|
||
<Input
|
||
clearable
|
||
placeholder="请输入密码"
|
||
onChange={(value) =>
|
||
setLoginInfo({ ...loginInfo, password: value })
|
||
}
|
||
value={loginInfo.password}
|
||
type="password"
|
||
style={{
|
||
"--placeholder-color": "#FFFFFF80",
|
||
"--font-size": "16px",
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<LoginBtn
|
||
loginInfo={loginInfo}
|
||
setLoginInfo={setLoginInfo}
|
||
handleSubmit={handleSubmit}
|
||
type={activeIndex ? "password" : "mobile"}
|
||
/>
|
||
</Swiper.Item>
|
||
</Swiper>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const LoginBtn = ({ loginInfo, setLoginInfo, type, handleSubmit }) => {
|
||
const router = useRouter();
|
||
useEffect(() => {
|
||
console.log("loginInfo", loginInfo);
|
||
}, []);
|
||
return (
|
||
<div className="mt-16">
|
||
<div className="flex items-center">
|
||
<Checkbox
|
||
value={loginInfo?.checked}
|
||
onChange={(value) => setLoginInfo({ ...loginInfo, checked: value })}
|
||
style={{
|
||
"--icon-size": "14px",
|
||
"--font-size": "14px",
|
||
"--gap": "6px",
|
||
}}
|
||
></Checkbox>
|
||
<span className="text-[#FFFFFF80] font-medium text-xs ml-2">
|
||
我确认已满18周岁并同意
|
||
<span
|
||
onClick={() =>
|
||
router.push(
|
||
`${process.env.NEXT_PUBLIC_WEB_URL}/doc/useragreement`
|
||
)
|
||
}
|
||
className="text-[#FF669E] text-xs"
|
||
>
|
||
《用户协议》
|
||
</span>
|
||
、
|
||
<span
|
||
onClick={() =>
|
||
router.push(
|
||
`${process.env.NEXT_PUBLIC_WEB_URL}/doc/useragreement`
|
||
)
|
||
}
|
||
className="text-[#FF669E] text-xs"
|
||
>
|
||
《隐私政策》
|
||
</span>
|
||
</span>
|
||
</div>
|
||
<Button
|
||
shape="rounded"
|
||
size="middle"
|
||
block
|
||
onClick={() => handleSubmit(type)}
|
||
style={{ "--background-color": "#FF669E", color: "#FFFFFF" }}
|
||
className="mt-2"
|
||
>
|
||
登录
|
||
</Button>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const mapDispatchToProps = {
|
||
handleLogin,
|
||
};
|
||
export default connect(null, mapDispatchToProps)(Login);
|