需要实现的效果
代码实现过程
上一篇我们搭建了开发环境的基本结构:Vite 2.x + React + Zarm + Less + React Router v6 + Axios + flexible.js 搭建前端 H5 开发环境,下面我们开始实现登记注册页面的开发。
1.准备字体库
首先我们在https://www.iconfont.cn/新建一个项目
然后再项目里面添加字体图标,可以自己搜索想要的图标添加入库
然后把入库的图标添加到项目里
最后在我们项目那里生成在线链接即可
2.创建图标公用组件
我们新建 components/CustomIcon/index.jsx
,添加如下代码:
// 自定义 Iconfont 图标,提供了一个 createFromIconfont 方法,方便开发者调用在 iconfont.cn 上自行管理的图标。 import { Icon } from 'zarm'; // 这里直接使用我们上面弄的项目字体链接 export default Icon.createFromIconfont('//at.alicdn.com/t/font_3376813_il53dc6ij5.js');
3.安装验证码插件
https://www.npmjs.com/package/react-captcha-code
基于 React 和 canvas 的一个验证码组件.
npm i react-captcha-code -S
import React, { useCallback, useRef } from 'react'; import Captcha from 'react-captcha-code'; export const Basic = () => { const handleChange = useCallback((captcha) => { console.log('captcha:', captcha); }, []); const captchaRef = useRef<HTMLCanvasElement>(); const handleClick = () => { // 刷新验证码 (captchaRef as any).current.refresh(); }; return ( <> <Captcha ref={captchaRef} charNum={6} onChange={handleChange} /> <div> <button onClick={handleClick}>更换验证码</button> </div> </> ); };
4.安装类名连接classnames
https://www.npmjs.com/package/classnames
一个简单的 JavaScript 实用程序,用于有条件地将类名连接在一起。
classNames('foo', 'bar'); // => 'foo bar' classNames('foo', { bar: true }); // => 'foo bar' classNames({ 'foo-bar': true }); // => 'foo-bar' classNames({ 'foo-bar': false }); // => '' classNames({ foo: true }, { bar: true }); // => 'foo bar' classNames({ foo: true, bar: true }); // => 'foo bar' // lots of arguments of various types classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux' // other falsy values are just ignored classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
5.组件编写
我们新建 src/container/Login
文件夹,用于存放登录注册模块相关代码
交互逻辑:写在 index.jsx
里面
import { useState, useCallback } from 'react' import { Cell, Input, Button, Toast } from 'zarm' import CustomIcon from '@/components/CustomIcon' import Captcha from "react-captcha-code" import classNames from "classNames" import { login, register } from "./api/index" import s from './style.module.less' const Login = () => { const [username, setUsername] = useState(''); // 账号 const [password, setPassword] = useState(''); // 密码 const [verify, setVerify] = useState(''); // 验证码 const [captcha, setCaptcha] = useState(''); // 验证码变化后存储值 const [type, setType] = useState('login'); // 登录注册类型 // 验证码变化,回调方法 const handleChange = useCallback((captcha) => { console.log('验证码变化,回调方法', captcha) setCaptcha(captcha) }, []); const onSubmit = async () => { if (!username) { Toast.show('请输入账号') return } if (!password) { Toast.show('请输入密码') return } try { // 判断是否是登录状态 if (type == 'login') { // 执行登录接口,获取 token const { status, desc, data } = await login({ username, password }); console.log('登录接口', status, data) if(status === 200) { // 将 token 写入 localStorage localStorage.setItem('token', data.token); Toast.show('登录成功'); } else { Toast.show(desc); } } else { if (!verify) { Toast.show('请输入验证码') return }; if (verify != captcha) { Toast.show('验证码错误') return }; const { status, desc, data } = await register({ username, password }); console.log('注册接口', status, data) if(status === 200) { Toast.show('注册成功'); // 注册成功,自动将 tab 切换到 login 状态 setType('login'); } else { Toast.show(desc); } } } catch (error) { Toast.show('系统错误'); } }; return <div className={s.auth}> <div className={s.head} /> <div className={s.tab}> <span className={classNames({ [s.avtive]: type == 'login' })} onClick={() => setType('login')}>登录</span> <span className={classNames({ [s.avtive]: type == 'register' })} onClick={() => setType('register')}>注册</span> </div> <div className={s.form}> <Cell icon={<CustomIcon type="user" />}> <Input clearable type="text" placeholder="请输入账号" onChange={(value) => setUsername(value)} /> </Cell> <Cell icon={<CustomIcon type="password" />}> <Input clearable type="password" placeholder="请输入密码" onChange={(value) => setPassword(value)} /> </Cell> { type == 'register' ? <Cell icon={<CustomIcon type="captcha" />}> <Input clearable type="text" placeholder="请输入验证码" onChange={(value) => setVerify(value)} /> <Captcha charNum={4} onChange={handleChange} /> </Cell> : null } </div> <div className={s.operation}> <Button block theme="primary" onClick={onSubmit}>{type == 'login' ? '登录' : '注册'}</Button> </div> </div> } export default Login
样式:写在 style.module.less
里,图片我这边放在了 src/assets/images
里面
.auth { min-height: 100vh; background-image: linear-gradient(217deg, #6fb9f8, #3daaf85e, #49d3fc1a, #3fd3ff00); .head { height: 200px; background: url('@/assets/images/cryptocurrency.png') no-repeat center; background-size: 120%; border-bottom-left-radius: 12px; border-bottom-right-radius: 12px; img { width: 34px; margin: 15px 0 0 15px; } } .tab { color: #597fe7; padding: 30px 24px 10px 24px; > span { margin-right: 10px; font-size: 14px; font-weight: bold; &.avtive { font-size: 20px; border-bottom: 2PX solid #597fe7; padding-bottom: 6px; } } } .form { padding: 0 6px; :global { .za-cell { background-color: transparent; &::after { border-top: none; } } } } .operation { padding: 10px 24px 0 24px; } }
接口配置:写在 api/index.js
里
import { fetchData } from "@/utils/axios.js"; // 注册 export function register(data) { return fetchData('/api/user/register', 'post', data); } // 登录 export function login(data) { return fetchData('/api/user/login', 'post', data); }
6.测试
先注册已经注册过的账号试试:kaimo313,123456
注册正常的账号:注册成功之后就会切换到登录的模块
我们看到本地数据库里已经注册成功了。
然后我们登录账号 kaimo313,123456
登录成功之后,token 会被存起来,然后跳转到我们的首页