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: 所有表单都通过校验 才为true
console.log(valid)
// 以valid做为判断条件 如果通过校验才执行登录逻辑
if (valid) {
// TODO LOGIN
await 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', () => {
//定义获取用户数据的state
const 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.token
if (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.清除用户信息 触发action
userStore.clearUserInfo()
// 2.跳转到登录页
router.push('/login')
}
下期见~