一、需求:写一个手机号码登录验证组件
在此记录代码逻辑
二、首先是移动端的页面样式兼容
采用scss样式:
import * as mtd from '../style.st';
整个页面对不同手机尺寸进行兼容(高度)
<mtd.rootDiv style={{ minHeight: minhHeight }}>
...
</mtd.rootDiv>
export const rootDiv = styled.div`
width: 100%;
position: relative;
min-height: calc(100vh - 90px);
box-sizing: border-box;
`;
三、判断页面是手机号码输入阶段还是接收验证码阶段(有两个页面,先输入手机号码点击获取验证码,之后就是输入验证码页面)
校验步骤:第一步录入 手机号; 第二步,输入验证码
const [pageStatus, setPageStatus] = useState(UserCheckStatus.InputPhone);//第一步是手机号
//1、手机号输入
{pageStatus === UserCheckStatus.InputPhone && renderInput()}
//2、验证码输入
{pageStatus === UserCheckStatus.InputCode && (
<VerificationCode
title={'输入验证码'}
code={code}
isSending={loading}
contact={contact}
onChange={handleChangeCode}
onComplete={handleComplete}
onSend={handleClick}
></VerificationCode>
)}
四、手机号码登录渲染
const renderInput = () => {
return (
<>
<mtd.title>{'验证码登录/注册'}</mtd.title>
<mtd.main>
<TelphoneInpput
currentref={phoneRef}
hasError={phoneError}
onBlur={handleBlur}
onChange={handleChange}//判断手机号是否正确
></TelphoneInpput>
<mtd.loginButton>
<Button
text={'获取验证码'}
disabled={disabled}
loading={loading}
onClick={handleClick}
></Button>
</mtd.loginButton>
</mtd.main>
<mtd.footer ref={footerRef}>
{/* 底部 */}
<AgreeClause></AgreeClause>
<CopyRight></CopyRight> //一些底部用户、协议名称
</mtd.footer>
</>
);
};
各函数:
const handleChange = (value: string) => {
let val = value.replace(/\s*/g, '');
setPhone(val);
if (areaCode === DefaultAreaCode && getReg(val, 'phone')) {//getReg手机号正则
// 大陆手机号&&设置【获取验证码】按钮可用
setDisabled(false);
} else if (areaCode !== DefaultAreaCode && val) {
// 国际手机号 && 存在手机号时
// 设置按钮可用
setDisabled(false);
}
};
const handleBlur = (value: string) => {
// 校验手机号是否合规
if (areaCode === DefaultAreaCode) {
// 大陆号码校验手机号
if (phone) {
if (!getReg(phone, 'phone')) {
Toast.Show({'手机号格式错误' });
setPhoneError(true);
setDisabled(true);
return false;
} else {
setDisabled(false);
setPhoneError(false);
}
}
}
};
// 点击发送验证码
const handleClick = async () => {
setLoading(true);
// 校验通过
let isValidated = true;
// 调用极验,(一种人机校验插件)
const result = await getCaptcha(isValidated);
if (result) {
// 设置切换成验证码输入区域
setPageStatus(UserCheckStatus.InputCode);
Toast.Show({'验证码已发送' });
}
setLoading(false);
};
五、验证码校验
import VerificationCode from "../verification-code";
<VerificationCode
title={"输入验证码"}
code={code}
isSending={loading}
contact={contact}
onChange={handleChangeCode}
onComplete={handleComplete}
onSend={handleClick}
></VerificationCode>
//
const VerificationCode: React.FC<IMVerificationCodeProps> = ({
title = '输入验证码',
contact,
code,
isSending = false,
onComplete,
onChange,
onStart,
onSend,
}) => {
// 多语言 Lang context
const lang = useContext(LocaleContext);
const sendText: string = lang.login.send_again || '重新发送';
const {
text,
loading = false,
remainSecond,
take,
} = useReCaptcha(sendText, CountingNumber); // 60秒
const handleChange = (code: string) => {
onChange?.(code);
};
const handleComplete = (code: string) => {
// alert("完成,发起校验请求" + code);
// 执行回调方法
onComplete?.(code);
};
// 重新发送方法
const handleSend = () => {
if (!loading) {
onSend?.();
}
};
useEffect(() => {
if (isSending) {
take();
}
}, [isSending]);
function processText() {
if (loading) {
return text + '(' + remainSecond + ')';
} else {
return text;
}
}
return (
<mtd.rootDiv>
<root.title>{输入验证码}</root.title>
<mtd.subTitleDiv>
<mtd.subTitle>
{'已发送验证码至'}
</mtd.subTitle>
<span>{contact?.replace('****', ' **** ')}</span>//将手机号中间四位处理:136 **** 7777
</mtd.subTitleDiv>
<mtd.cmpCode>
<VertifyCodeInput
value={code}
onChange={handleChange}
onComplete={handleComplete}
></VertifyCodeInput>
</mtd.cmpCode>
<mtd.sendBtn counting={loading} onClick={handleSend}>
{processText()}
</mtd.sendBtn>
</mtd.rootDiv>
);
};
VertifyCodeInput:
const VertifyCodeInput: React.FC<IVertifyCode> = ({
length = 6,
value = "",
onChange,
onComplete,
}) => {
const theme = useTheme();
const inputRef = useRef<HTMLInputElement>(null);
// 存储已输入的显示数字数组
const [numList, setNumList] = useState<string[]>([]);
const [isFocus, setIsFocus] = useState<boolean>(false);
const inputList = [...Array(length)].map((_, i) => i); // 位数数组,用于生成全部模拟输入框
const handleInputFocus = () => {
inputRef.current?.focus();
setIsFocus(true);
};
useEffect(() => {
setNumList(value.split(""));
}, [value]);
// 为了修复ios自动填充两次的bug
useEffect(() => {
if (value.length !== length) {
// 6位时自动完成
return;
}
let timer = setTimeout(() => {
// 执行回调
setTimeout(() => {
// 等页面上数字显示出来后再执行
onComplete && onComplete(value);
}, 120);
}, 300);
return () => {
// 清掉
clearTimeout(timer);
};
}, [value]);
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
let v = e.target.value;
if (v.length > 6) {
v = v.slice(0, 6);
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value) {
// 只保留数字
let val = e.target.value.replace(/[^\d]/g, "");
setTimeout(() => {
setNumList(val.split(""));
}, 100);
// ios验证码自动填充两次的关键
if (val.length < 7) {
onChange?.(val);
} else {
val = val.slice(0, 6);
return;
}
} else {
onChange?.("");
}
};
const handleBlur = () => {
setIsFocus(false);
};
// 默认选中该输入框
useEffect(() => {
handleInputFocus();
});
return (
<mtd.rootDiv onClick={handleInputFocus}>
<mtd.container>
<mtd.inputValue
{...{
ref: inputRef,
maxLength: length,
value: value,
type: "number",
inputMode: "numeric",
onInput: handleInput,
onChange: handleChange,
onBlur: handleBlur,
}}
></mtd.inputValue>
<mtd.boxContainer>
{inputList.map((item, index) => {
return (
<mtd.inputItem
key={index}
isCurrent={item === numList.length && isFocus}
style={{
borderBottom: `1px solid rgba(${
item === numList.length && isFocus ? theme.S6 : theme.M5
})`,
}}
>
{numList[item]}
</mtd.inputItem>
);
})}
</mtd.boxContainer>
</mtd.container>
</mtd.rootDiv>
);
};
验证码验证函数:handleComplete
// 手机号验证码登录
const handleComplete = (code: string) => {
let result = false; // 1s内响应了
let closeLoading: Function;
let checkTime = setTimeout(() => {
if (!result) {
// 接口1s内未响应
let { MountNode, timer, close } = Toast.Loading({
content: lang.login.checking || '正在验证',
duration: 30 * 1000,
})
closeLoading = () => {
close(MountNode, timer);
};
timer;
}
}, 1000);
let url = window.sessionStorage.getItem(returnKey) || '';
window.sessionStorage.removeItem(returnKey);
// 手机号验证码登录
smsLogin(phone, areaCode, code, url).then(({ Code, Data, Message, ReturnUrl }) => {
result = true;
// 清除正在验证提示
clearTimeout(checkTime);
if (closeLoading) {
closeLoading();
}
if (Code === 200) {
// 登录状态:0:失败;1、登录正常;2:未绑定微信:3:未绑定手机号 移动端不做绑定微信强制
if (Data !== LoginResult.failed) {
// 页面跳转到后端中转页,后端304到【我的投递】
location.href = BERedirectUrl;
} else {
// Data是0 登录失败
setCode('');
Toast.Show({ content: Message });
}
} else {
// 验证码不正确,清空验证码
setCode('');
Toast.Show({ content: lang.login.code_error });
}
});
};