前端:手机号码验证组件

842 阅读3分钟

一、需求:写一个手机号码登录验证组件

在此记录代码逻辑

二、首先是移动端的页面样式兼容

采用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 });
    }
    });
};