基本结构搭建
实现步骤
- 在
Login/index.js
中创建登录页面基本结构 - 在 Login 目录中创建 index.scss 文件,指定组件样式
- 将
logo.png
和login.png
拷贝到 assets 目录中
代码实现
pages/Login/index.js
import './index.scss'
import { Card, Form, Input, Button } from 'antd'
import logo from '@/assets/logo.png'
const Login = () => {
return (
<div className="login">
<Card className="login-container">
<img className="login-logo" src={logo} alt="" />
{/* 登录表单 */}
<Form>
<Form.Item>
<Input size="large" placeholder="请输入手机号" />
</Form.Item>
<Form.Item>
<Input size="large" placeholder="请输入验证码" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" size="large" block>
登录
</Button>
</Form.Item>
</Form>
</Card>
</div>
)
}
export default Login
pages/Login/index.scss
.login {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background: center/cover url('~@/assets/login.png');
.login-logo {
width: 200px;
height: 60px;
display: block;
margin: 0 auto 20px;
}
.login-container {
width: 440px;
height: 360px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 50px rgb(0 0 0 / 10%);
}
.login-checkbox-label {
color: #1890ff;
}
}
表单校验实现
实现步骤
- 为 Form 组件添加
validateTrigger
属性,指定校验触发时机的集合 - 为 Form.Item 组件添加 name 属性
- 为 Form.Item 组件添加
rules
属性,用来添加表单校验规则对象
代码实现
page/Login/index.js
import './index.scss'
import { Card, Form, Input, Button, message } from 'antd'
import logo from '@/assets/logo.png'
import { useDispatch } from 'react-redux'
import { fetchLogin } from '@/store/modules/user'
import { useNavigate } from 'react-router-dom'
const Login = () => {
const dispatch = useDispatch()
const navigate = useNavigate()
const onFinish = async (values) => {
console.log(values)
// 触发异步action fetchLogin
await dispatch(fetchLogin(values))
// 1. 跳转到首页
navigate('/')
// 2. 提示一下用户
message.success('登录成功')
}
return (
<div className="login">
<Card className="login-container">
<img className="login-logo" src={logo} alt="" />
{/* 登录表单 */}
<Form onFinish={onFinish} validateTrigger="onBlur">
<Form.Item
name="mobile" //指定校验字段名
// 多条校验逻辑 先校验第一条 第一条通过之后再校验第二条
rules={[
{
required: true,
message: '请输入手机号',
},
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号格式'
}
]}>
<Input size="large" placeholder="请输入手机号" />
</Form.Item>
<Form.Item
name="code"
rules={[
{
required: true,
message: '请输入验证码',
},
]}>
<Input size="large" placeholder="请输入验证码" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" size="large" block>
登录
</Button>
</Form.Item>
</Form>
</Card>
</div>
)
}
export default Login
获取登录表单数据
实现步骤
- 为 Form 组件添加
onFinish
属性,该事件会在点击登录按钮时触发 - 创建 onFinish 函数,通过函数参数 values 拿到表单值
- Form 组件添加
initialValues
属性,来初始化表单值
代码实现
pages/Login/index.js
// 点击登录按钮时触发 参数values即是表单输入数据
const onFinish = formValue => {
console.log(formValue)
}
<Form
onFinish={ onFinish }
>...</Form>
封装request工具模块
业务背景: 前端需要和后端拉取接口数据,axios是使用最广的工具插件,针对于项目中的使用,我们需要做一些简单的封装
实现步骤
- 安装 axios 到项目
- 创建 utils/request.js 文件
- 创建 axios 实例,配置
baseURL,请求拦截器,响应拦截器
- 在 utils/index.js 中,统一导出request
npm i axios
// axios的封装处理
import axios from "axios"
import { getToken, removeToken } from "./token"
import router from "@/router"
// 1. 根域名配置
// 2. 超时时间
// 3. 请求拦截器 / 响应拦截器
const request = axios.create({
baseURL: 'http://geek.itheima.net/v1_0',
timeout: 5000
})
// 添加请求拦截器
// 在请求发送之前 做拦截 插入一些自定义的配置 [参数的处理]
request.interceptors.request.use((config) => {
// 操作这个config 注入token数据
// 1. 获取到token
// 2. 按照后端的格式要求做token拼接
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
}, (error) => {
return Promise.reject(error)
})
// 添加响应拦截器
// 在响应返回到客户端之前 做拦截 重点处理返回的数据
request.interceptors.response.use((response) => {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response.data
}, (error) => {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
// 监控401 token失效
console.dir(error)
if (error.response.status === 401) {
removeToken()
router.navigate('/login')
window.location.reload()
}
return Promise.reject(error)
})
export { request }
Axios
使用Redux管理token
安装Redux相关工具包
npm i react-redux @reduxjs/toolkit
配置Redux
import { createSlice } from '@reduxjs/toolkit'
import { http } from '@/utils'
const userStore = createSlice({
name: 'user',
// 数据状态
initialState: {
token:''
},
// 同步修改方法
reducers: {
setUserInfo (state, action) {
state.userInfo = action.payload
}
}
})
// 解构出actionCreater
const { setUserInfo } = userStore.actions
// 获取reducer函数
const userReducer = userStore.reducer
// 异步方法封装
const fetchLogin = (loginForm) => {
return async (dispatch) => {
const res = await http.post('/authorizations', loginForm)
dispatch(setUserInfo(res.data.token))
}
}
export { fetchLogin }
export default userReducer
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './modules/user'
export default configureStore({
reducer: {
// 注册子模块
user: userReducer
}
})
实现登录逻辑
业务逻辑:
- 跳转到首页
- 提示用户登录成功
import { message } from 'antd'
import useStore from '@/store'
import { fetchLogin } from '@/store/modules/user'
import { useDispatch } from 'react-redux'
const Login = () => {
const dispatch = useDispatch()
const navigate = useNavigate()
const onFinish = async formValue => {
await dispatch(fetchLogin(formValue))
navigate('/')
message.success('登录成功')
}
return (
<div className="login">
<!-- 省略... -->
</div>
)
}
export default Login
token持久化
业务背景: Token数据具有一定的时效时间,通常在几个小时,有效时间内无需重新获取,而基于Redux的存储方式又是基于内存的,刷新就会丢失,为了保持持久化,我们需要单独做处理
封装存取方法
// 封装存取方法
const TOKENKEY = 'token_key'
function setToken (token) {
return localStorage.setItem(TOKENKEY, token)
}
function getToken () {
return localStorage.getItem(TOKENKEY)
}
function clearToken () {
return localStorage.removeItem(TOKENKEY)
}
export {
setToken,
getToken,
clearToken
}
实现持久化逻辑
import { getToken, setToken } from '@/utils'
const userStore = createSlice({
name: 'user',
// 数据
initialState: {
token: getToken() || ''
},
// 同步修改方法
reducers: {
setUserInfo (state, action) {
state.token = action.payload
// 存入本地
setToken(state.token)
}
}
})
刷新浏览器,通过Redux调试工具查看token数据
请求拦截器注入token
业务背景: Token作为用户的数据标识,在接口层面起到了接口权限控制的作用,也就是说后端有很多接口都需要通过查看当前请求头信息中是否含有token数据,来决定是否正常返回数据
拼接方式:config.headers.Authorization =
Bearer ${token}}
utils/request.js
// 添加请求拦截器
request.interceptors.request.use(config => {
// if not login add token
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
路由鉴权实现
业务背景:封装
AuthRoute
路由鉴权高阶组件,实现未登录拦截,并跳转到登录页面
实现思路:判断本地是否有token,如果有,就返回子组件,否则就重定向到登录Login
实现步骤
- 在 components 目录中,创建
AuthRoute/index.jsx
文件 - 登录时,直接渲染相应页面组件
- 未登录时,重定向到登录页面
- 将需要鉴权的页面路由配置,替换为 AuthRoute 组件渲染
代码实现
components/AuthRoute/index.jsx
import { getToken } from '@/utils'
import { Navigate } from 'react-router-dom'
const AuthRoute = ({ children }) => {
const isToken = getToken()
if (isToken) {
return <>{children}</>
} else {
return <Navigate to="/login" replace />
}
}
export default AuthRoute
src/router/index.jsx
import { createBrowserRouter } from 'react-router-dom'
import Login from '@/pages/Login'
import Layout from '@/pages/Layout'
import AuthRoute from '@/components/Auth'
const router = createBrowserRouter([
{
path: '/',
element: <AuthRoute><Layout /></AuthRoute>,
},
{
path: '/login',
element: <Login />,
},
])
export default router