前言
上一章节我们实现了底部导航栏,并且创建了三个主页面,这三个页面是需要展示底部导航栏,而我们本章节要制作的「登录注册页面」便是不需要底部导航栏的单独页面。
本教程已有线上地址在线地址,同学们可以在实战部分,对照着线上页面进行学习。
知识点
组件:Cell
、Input
、Button
、CheckBox
。
注册页面
我们的系统是面向多用户的,换句话说也就是一个纯正的 C 端项目,任何人都可以通过网站,注册一个新的账号。接下来开始注册页面的编写。
首先新建 Login
文件夹,在文件夹内添加两个文件 index.jsx
和 style.module.less
,我们先把注册页面的静态页面切出来,首先给 index.jsx
添加如下代码:
1 2 3 4 5 6 7 8 9
| import React from "react";
import s from "./style.module.less";
const Login = () => { return <div className={s.auth}>注册</div>; };
export default Login;
|
为它添加一个路由配置,打开 router/index.js
添加如下:
1 2 3 4 5 6
| import Login from '@/container/Login' ... { path: "/login", component: Login }
|
重启项目,如下所示代表登录注册页面创建成功了:
接下来为 Login/index.jsx
添加静态页面代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import React from "react"; import { Cell, Input, Button, Checkbox } from "zarm"; import CustomIcon from "@/components/CustomIcon";
import s from "./style.module.less";
const Login = () => { return ( <div className={s.auth}> <div className={s.head} /> <div className={s.tab}> <span>注册</span> </div> <div className={s.form}> <Cell icon={<CustomIcon type="zhanghao" />}> <Input clearable type="text" placeholder="请输入账号" /> </Cell> <Cell icon={<CustomIcon type="mima" />}> <Input clearable type="password" placeholder="请输入密码" /> </Cell> <Cell icon={<CustomIcon type="mima" />}> <Input clearable type="text" placeholder="请输入验证码" /> </Cell> </div> <div className={s.operation}> <div className={s.agree}> <Checkbox /> <label className="text-light"> 阅读并同意<a>《掘掘手札条款》</a> </label> </div> <Button block theme="primary"> 注册 </Button> </div> </div> ); };
export default Login;
|
文末已为同学们提供下本章节 demo 代码,样式部分不再详细说明。
上述代码中,关键部分是账号输入、密码输入、验证码输入,这三个输入框是需要获取数据作为接口的参数提交上去的。
很多时候,服务端没有开发好接口的时候,我们前端要做的任务就是先还原 UI
稿,把该切的页面都切出来,并且预留好需要给接口提交的数据交互,比如上述三个输入框。
样式编写部分,要注意的一点是 :global
这个关键词。由于我们采用的是 CSS Module
的形式进行开发,也就是你在页面中声明的类名都会根据当前页面,打一个唯一的 hash
值,比如我们页面中声明的 className={s.form}
,最终在浏览器中显示的是这样的:
_form_kpur3_30
是已经被编译过的样式,这样做的目的是避免和别的页面的样式重名,这是目前样式管理的一个诟病,当多人参与项目开发的时候,很难做到不污染全局样式名称,除非很小心的命名样式名称。
所以经过编译之后,想要修改 .form
下的 .za-cell
,如下写法,将无法修改成功:
1 2 3 4 5
| .form { .za-cell { color: red; } }
|
原因是,上述写法,.za-cell
会被编译加上 hash
,组件库 Zarm
内的 dom
类名还是叫 za-cell
,如上图所示。所以为了不加 hash
,就需要这样操作:
1 2 3 4 5 6 7
| .form { :global { .za-cell { color: red; } } }
|
这样 .za-cell
就不会被加上 hash
,如下图所示:
完成上述页面布局之后,你会看到这样一个效果:
少了一个验证码,我们使用插件 react-captcha-code
,我们通过 npm
下载它:
1
| npm i react-captcha-code -S
|
在代码中引入:
1 2 3 4 5 6 7 8 9 10
| ... import Captcha from "react-captcha-code" ... <Input clearable type="text" placeholder="请输入验证码" onChange={(value) => setVerify(value)} /> <Captcha charNum={4} />
|
浏览器展示如下所示:
此时我们已经切完注册页面需要的内容。
我们给页面加上相应的逻辑,首先是账号、密码、验证码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ... const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [verify, setVerify] = useState(''); ... <Input clearable type="text" placeholder="请输入账号" onChange={(value) => setUsername(value)} /> ... <Input clearable type="password" placeholder="请输入密码" onChange={(value) => setPassword(value)} /> ... <Input clearable type="text" placeholder="请输入验证码" onChange={(value) => setVerify(value)} />
|
当输入框内容修改的时候,onChange
会被触发,接受的回调函数参数,便是变化的输入值,此时我们将其保存在声明的变量中。
我们输入的验证码是需要和验证码图片里的验证码匹配的,所以我们还需要拿到图片里的验证码,我们作如下操作:
1 2 3 4 5 6 7 8 9 10
| import React, { useCallback } from 'react' ... const [captcha, setCaptcha] = useState('');
const handleChange = useCallback((captcha) => { console.log('captcha', captcha) setCaptcha(captcha) }, []); ... <Captcha charNum={4} onChange={handleChange} />
|
当验证码变化的时候,便能获取到相应的值。修改完上述代码,我们不妨测试一下:
到此,注册需要的参数都有了,我们开始编写注册方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import { Cell, Input, Button, Checkbox, Toast } from 'zarm' import { post } from '@/utils' ... const onSubmit = async () => { if (!username) { Toast.show('请输入账号') return } if (!password) { Toast.show('请输入密码') return } if (!verify) { Toast.show('请输入验证码') return }; if (verify != captcha) { Toast.show('验证码错误') return }; try { const { data } = await post('/api/user/register', { username, password }); Toast.show('注册成功'); } catch (error) { Toast.show('系统错误'); } }; ... <Button onClick={onSubmit} block theme="primary">注册</Button>
|
上述代码中,因为我们使用的是 async await
做异步处理,所以需要通过 try catch
来捕获异步处理过程中出现的错误,如果使用 Promise
的回调函数,则无需使用 try catch
,改动如下:
1 2 3 4 5 6
| post("/api/user/register", { username, password, }).then((res) => { });
|
尝试使用之前注册过的用户名,注册一个账号:
服务端给出正确的报错,我们再用一个未注册过的用户名:
此时我们大致将注册功能实现了。这里我不再展开讲样式部分,因为这样会使得文章中出现过多的重复代码,不以阅读,大家尽量根据标签的类名去查找 css
样式部分。
登录页面
登录页面的逻辑我们直接做到同一个页面中,通过一个 type
参数作为判断条件,判断当前状态是登录页面或是注册页面。
话不多说我们添加代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| ... import cx from 'classnames' ...
const Login = () => { ... const [type, setType] = useState('login');
return <div className={s.auth}> ... <div className={s.tab}> <span className={cx({ [s.avtive]: type == 'login' })} onClick={() => setType('login')}>登录</span> <span className={cx({ [s.avtive]: type == 'register' })} onClick={() => setType('register')}>注册</span> </div> </div> <div className={s.form}> ... { type == 'register' ? <Cell icon={<CustomIcon type="mima" />}> <Input clearable type="text" placeholder="请输入验证码" onChange={(value) => setVerify(value)} /> <Captcha ref={captchaRef} charNum={4} onChange={handleChange} /> </Cell> : null } </div> <div className={s.operation}> { type == 'register' ? <div className={s.agree}> <Checkbox /> <label className="text-light">阅读并同意<a>《掘掘手札条款》</a></label> </div> : null } <Button onClick={onSubmit} block theme="primary">{type == 'login' ? '登录' : '注册'}</Button> </div> }
|
注意,如果引入了新的工具包,请自行安装,如上述代码就需要安装 classnames。可以通过 npm i classnames -S 指令
代码分析:
上述代码中,通过 type
属性区分注册和登录。
首先是 tab
切换,通过 classname
来判断是否是当前高亮,用于样式控制。
其次,当 type == 'register'
的时候,才把验证码展示出来,因为登录这边咱们就不设置验证码,只在注册的时候显示。
最后是事件的判断,如果 type == 'login'
,则按钮文案显示为 登录
,否则为 注册
。
此时点击触发的 onSubmit
事件也很关键,同样需要通过 type
判断是登录还是注册,修改代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| const onSubmit = async () => { if (!username) { Toast.show("请输入账号"); return; } if (!password) { Toast.show("请输入密码"); return; } try { if (type == "login") { const { data } = await post("/api/user/login", { username, password, }); localStorage.setItem("token", data.token); } else { if (!verify) { Toast.show("请输入验证码"); return; } if (verify != captcha) { Toast.show("验证码错误"); return; } const { data } = await post("/api/user/register", { username, password, }); Toast.show("注册成功"); setType("login"); } } catch (error) { Toast.show("系统错误"); } };
|
由于登录注册的账号和密码是同一参数,我们这边就直接复用了逻辑,并通过 type
判断调用哪一个接口。
重启项目,验证登录接口是否成功,如果成功则会返回 token
信息,如下图所示:
此时,我们本地的 localStorage
里,已经存下了 token
,如下图所示:
保存 token
的形式有很多,你可以引入状态管理插件来对这些数据进行存储,但是这里我们对其进行简单处理,减少项目多余的负担,保证课程的完成度。有时候,成功的将课程完成,也是一种成就感。
总结
到此,我们的登录注册页面算是完成了,我们拿到的 token
是有时效性的,我在后台设置的是 24 小时的时效,如果过期了,请求其他接口时,就会报错,从而通过逻辑判断重新回到登录页面。下一章节,我会详细分析这块内容。