vue3项目实战-第六章-登录页(表单校验/模板适配/Pinia管理用户数据/持久化存储)
1、页面搭建与路由配置
在这里,我们要实现的是登录页面,首先,引入静态模板
紧接着,配置路由,因为它是一级路由,直接和Layout并列即可。
{path: '/login',component: Login}
完成之后,浏览器输入login,我们就来到了登录页下面。
2、表单校验
在这里,我们需要考虑一些问题:服务器是怎么知道用户有没有登录的?当用户点击登录的时候,服务器怎么知道用户输入的账号和密码是匹配的?如果用户输入一些乱七八糟的字符,浏览器会给出用户相应的提示吗?给出这些提示的依据是什么?代码应该怎样来编写呢?带着这些问题呢,一起来看看接下来的步骤吧
当用户输入了正确的账号和密码后,浏览器会生成一个令牌字符串Token,它可以用来记录用户当前的登录状态,如果有,说明用户就登陆了,反之,则没有登录,但是前端只能判断token的有无,而token的有效性要交给后端去判断。
那么,我们怎样避免用户输入一些乱七八糟的字符呢?就需要使用一些规则对我们的表单进行校验。通过表单校验,可以省去一些错误的请求提交,为后端节省接口压力。接下来,会详细介绍三种校验方法‘
首先,来看看实现原理
通常情况下,表单校验由三个组件共同完成:
基础步骤如下:
(1)准备表单对象
const form = ref({account: '',password: '',agree: true
})
(2)准备校验规则
const rules = {account: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],password: [{ required: true, message: '密码不能为空', trigger: 'blur' },{ min: 6, max: 14, message: '密码长度为6-14个字符', trigger: 'blur' },],}
(3)指定表单域的校验字段名
<el-form-item prop="account" label="账户"><el-input v-model="form.account" /></el-form-item><el-form-item prop="password" label="密码"><el-input v-model="form.password" /></el-form-item>
(4)把表单对象进行双向绑定
<el-form ref="formRef" :model="form" :rules="rules" label-position="right" label-width="60px" status-icon>
由于ElementPlus内置的表单校验配置只能完成一些基本的校验,如果想要定制一些特定的校验规则,还需要开发者自己来编写相关的逻辑,接下来,看看自定义校验吧
(1)首先,定义一个agree,将其值设为true
agree:true
(2)编写逻辑
agree: [{validator: (rule, value, callback) => {console.log(value)// 自定义校验逻辑// 勾选就通过 不勾选就不通过if (value) {callback()} else {callback(new Error('请勾选隐私条款和服务条款协议'))}}}]
(3)绑定并使用
<el-form-item prop="agree" label-width="22px"><el-checkbox size="large" v-model="form.agree">我已同意隐私条款和服务条款</el-checkbox></el-form-item>
最后,是表单的统一校验:当用户将表单中所有的内容完成之后,点击登录按钮的时候才会通过校验,发生页面跳转
(1)获取form实例做统一校验
const formRef = ref(null)
(2)绑定
<el-form ref="formRef" :model="form" :rules="rules" label-position="right" label-wid
运行演示:用户一上来直接点击登录
3、基础功能实现
简单介绍一下实现过程:当用户点击i登录按钮时,需要向服务器发起请求“用户要登录了”,服务器带着这个请求执行登录操作,将用户的信息保存到本地token,如果检测到账号和密码匹配成功的话,将登录结果反馈给用户。接下来,是代码实现
(1)封装登录接口
export const loginAPI = ({ account, password }) => {return request({url: '/login',method: 'POST',data: {account,password}})
}
(2)调用接口,拿到数据(登录之前先对用户输入的内容进行校验,如果校验通过,执行登录操作,反之,提示用户异常信息。)
const doLogin = () => {const { account, password } = form.value// 调用实例方法formRef.value.validate(async (valid) => {// valid: 所有表单都通过校验 才为trueconsole.log(valid)// 以valid做为判断条件 如果通过校验才执行登录逻辑if (valid) {// TODO LOGINawait userStore.getUserInfo({ account, password })// 1. 提示用户ElMessage({ type: 'success', message: '登录成功' })// 2. 跳转首页router.replace({ path: '/' })}})
}
登录成功,跳转到首页,
登录失败,提示错误信息。在本项目中,直接使用测试账号,只要正常复制粘贴,不会出现登录失败的情况。
由于不只有在登录的时候会出现这个问题,其它地方可能也会出现,所以可以把错误处理相关的逻辑封装在响应拦截器里边,
详细如下:
httpInstance.interceptors.response.use(res => res.data, e => {// 统一错误提示ElMessage({type: 'warning',message: e.response.data.message})return Promise.reject(e)
})
保存之后,如果用户输入了错误的用户名或密码,就会提示相应的内容
4、Pinia管理用户数据(持久化)
由于用户数据的特殊性,在很多组件中都有可能进行共享,共享的数据可以使用Pinia来管理,更加方便后续的使用。那么,我们如何使用pinia管理数据?可以把和数据相关的所有操作(state+action)都放到pinia中,组件只负责触发action函数。
核心代码:
(1)定义用户数据
import { defineStore } from 'pinia'
import { ref } from 'vue'
import {loginAPI} from '@/apis/user'
export const useUserStore = defineStore('user', () => {//定义获取用户数据的stateconst userInfo = ref({})//定义获取接口数据的action方法const getUserInfo = async ({ account, password }) => {const res = await loginAPI({ account, password })userInfo.value = res.result}//以对象方式return出去return {userInfo,getUserInfo}})
(2)在index.vue中引入调用:
1)引入
import {useUserStore} from '@/stores/user'
const userStore = useUserStore()
2)使用
await userStore.getUserInfo({account,password})
这里,由于我们保存的用户数据中有一个叫token的数据,它可以用来标记用户的登录状态,但它有自己的有效期,过一段时间会失效,而Pinia中保存的数据是基于内存的,一旦刷新就会丢失,为了保持登录状态就要做到页面刷新的时候不让用户数据丢失,在这里就需要配合持久化进行存储。
这里,用到了一个新的插件Pinia-plugin-persistedstate,运行机制:在设置state的时候会自动把数据同步给localStorage,在获取state数据的时候会优先从localStorage中取。在使用之前,需要先安装,
npm i pinia-plugin-persistedstate
安装完成之后,配置这样一个属性:
persist: true,
5、模板适配
未登录状态下,我们看到的效果是
在登录页面,点击右上角进入网站首页,显示如下效果。
而在登录状态下,我们看到的效果是这样的
那么,我们怎样实现这种效果呢?一起来看看吧
在这里,我们可以通过简单的条件渲染来实现
登录状态下
<template v-if="userStore.userInfo.token"><li><a href="javascript:;"><i class=" iconfont icon-user"></i>lyp1234567</a></li><li><el-popconfirm @confirm="confirm" title="确认退出吗?" confirm-button-text="确认" cancel-button-text="取消"><template #reference><a href="javascript:;">退出登录</a></template></el-popconfirm></li><li><a href="/member/order">我的订单</a></li><li><a href="/member">会员中心</a></li></template>
非登录状态下
<template v-else><li><a href="javascript:;" @click="$router.push('/login')">请先登录</a></li><li><a href="javascript:;">帮助中心</a></li><li><a href="javascript:;">关于我们</a></li></template>
6、请求拦截
当用户点击登录时,首先从Pinia中获取用户数据,如果有的话,根据后端要求将其拼接到Bearer后面
httpInstance.interceptors.request.use(config => {// 1. 从pinia获取token数据const userStore = useUserStore()// 2. 按照后端的要求拼接token数据const token = userStore.userInfo.tokenif (token) {config.headers.Authorization = `Bearer ${token}`}return config
}, e => Promise.reject(e))
7、退出登录
当用户确认退出登录时,需要清空用户数据并跳转到登录页。以下是详细代码:
import { useUserStore } from '@/stores/userStore'
import { useRouter } from 'vue-router'
const userStore = useUserStore()
const router = useRouter()
const confirm = () => {console.log('用户要退出登录了')// 退出登录业务逻辑实现// 1.清除用户信息 触发actionuserStore.clearUserInfo()// 2.跳转到登录页router.push('/login')
}
下期见~