实现思路:
登录:当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个token,拿到token之后(我会将这个token存贮到cookie中,保证刷新页面后能记住用户登录状态),后续所有请求都需要携带token,前端会根据token再去拉取一个 user_info 的接口来获取用户的详细信息(如用户权限,用户名等等信息)。
权限验证:通过token获取用户对应的 role,动态根据用户的 role 算出其对应有权限的路由,通过 router.addRoutes 动态挂载这些路由。
引入项目后通过git checkout -b login,创建登录子分支,通过git branch查看项目所有分支
实现步骤:
一.创建登录组件,并且通过路由的形式将登录组件渲染到App跟组件中,并通过路由重定向让用户在访问根路径时,自动跳转到登录组件。
在项目中src文件下新建views文件夹-login文件夹,新建index.vue文件,编写登录页面。输入vbase生成页面模板(需要提前安装 VSCode快捷插件 Vue VSCode Snippets)
<template>
<div>登录组件</div>
</template>
<script>
export default {
name: 'Login'
}
</script>
<style lang="less" scoped>
</style>
新建router文件夹,新建index.js文件,导入登录组件,新建路由规则和页面重定向,之后在App根组件中放一个路由占位符。
路由index.js代码:
import Vue from 'vue'
import Router from 'vue-router'
// 导入登录组件
import Login from '../views/login/Login.vue'
Vue.use(Router)
const router = new Router({
routes: [
// 当路径是/时,重定向路径/login
{ path: '/', redirect: '/login' },
// 当路径是/login时,展示组件Login
{ path: '/login', component: Login }
]
})
export default router
App.vue代码:
<template>
<div id="app">
<!-- 路由占位符 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
<style>
</style>
二.登录组件布局
登录容器中编写表单样式,可以去Element UI官网搜索表单样式,或者elf快速生成表单样式(需要先安装 Element UI快捷插件 Element UI Snippets)。然后运行npm i element-ui -S命令
页面样式lang="less",要安装less和less-laoder的开发依赖
在assets-css文件夹中,新建global.css,写下全局样式,在入口文件中导入全局样式。
/* 全局样式 */
html,
body,
#app{
height: 100%;
padding: 0;
margin: 0;
}
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './plugins/element.js'
// 导入全局样式
import './assets/css/global.css'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
在plugins文件夹中,新建element.js,按需引入Element UI样式全局注册 element 组件库。
import Vue from 'vue'
import { Button } from 'element-ui'
import { Form,FormItem } from 'element-ui'
import { Input } from 'element-ui'
//全局注册 element 组件库
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)
Vue.use(Button)
在全局入口main.js中导入element-ui
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// 导入element-ui
import './plugins/element.js'
// 导入全局样式
import './assets/css/global.css'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
Login.vue样式代码如下:
<template>
<!-- 登录容器 -->
<div class="login_container">
<!-- 登录盒子 -->
<div class="login_box">
<!-- 头像盒子 -->
<div class="avatar_box">
<img src="../../assets/logo.png" alt="" />
</div>
<!-- 表单 -->
<el-form label-width="0px" class="login_form">
<!-- 用户名 -->
<el-form-item>
<el-input></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item>
<el-input></el-input>
</el-form-item>
<!-- 按钮 -->
<el-form-item class="btns">
<el-button type="primary">登录</el-button>
<el-button type="info">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default {
name: 'Login'
}
</script>
<style lang="less" scoped>
.login_container {
// 登录组件背景色
background-color: #2b4b6b;
height: 100%;
// 登录盒子设置
.login_box {
width: 450px;
height: 300px;
background-color: #fff;
border-radius: 3px;
// 盒子水平垂直居中设置
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
// 头像盒子
.avatar_box {
width: 130px;
height: 130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
img {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #eee;
}
}
.login_form {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
.btns {
display: flex;
justify-content: flex-end;
}
}
}
}
</style>
三.表单组件的数据绑定
给登录表单绑定数据给el-form 通过:model进行属性绑定,绑定的值是一个数据对象,把数据对象在data中做定义,在为每一个具体表单项通过v-model双向绑定到每个数据对象的属性中。
Login.vue 代码
<template>
<!-- 登录容器 -->
<div class="login_container">
<!-- 登录盒子 -->
<div class="login_box">
<!-- 头像盒子 -->
<div class="avatar_box">
<img src="../../assets/logo.png" alt="" />
</div>
<!-- 表单 -->
<el-form label-width="0px" class="login_form" :model="loginForm">
<!-- 用户名 -->
<el-form-item>
<el-input v-model="loginForm.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item>
<el-input v-model="loginForm.password" type="password" placeholder="请输入密码"></el-input>
</el-form-item>
<!-- 按钮 -->
<el-form-item class="btns">
<el-button type="primary">登录</el-button>
<el-button type="info">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default {
name: 'Login',
data() {
return {
// 通过v-model双向绑定登录表单数据
loginForm: {
username: '',
password: ''
}
}
}
}
</script>
<style lang="less" scoped>
.login_container {
// 登录组件背景色
background-color: #2b4b6b;
height: 100%;
// 登录盒子设置
.login_box {
width: 450px;
height: 300px;
background-color: #fff;
border-radius: 3px;
// 盒子水平垂直居中设置
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
// 头像盒子
.avatar_box {
width: 130px;
height: 130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
img {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #eee;
}
}
.login_form {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
.btns {
display: flex;
justify-content: flex-end;
}
}
}
}
</style>
四.登录表单验证
实现思路:输入用户名密码的时候,鼠标离开文本框时。对填写的数据进行验证
步骤:(Form 组件提供了表单验证的功能,详情参见组件 | Element)
1.在el-form上面绑定一个 rules 属性,值是约定的rules验证对象。
2.在data中定义rules对象,这里包含验证规则,一般是数组形式。
3.将 Form-Item 的 prop 属性设置为需校验的验证规则字段名。
代码:
<template>
<!-- 登录容器 -->
<div class="login_container">
<!-- 登录盒子 -->
<div class="login_box">
<!-- 头像盒子 -->
<div class="avatar_box">
<img src="../../assets/logo.png" alt="" />
</div>
<!-- 表单 -->
<el-form label-width="0px" class="login_form" :model="loginForm" :rules="loginFormRules">
<!-- 用户名 -->
<el-form-item prop="username">
<el-input v-model="loginForm.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" placeholder="请输入密码"></el-input>
</el-form-item>
<!-- 按钮 -->
<el-form-item class="btns">
<el-button type="primary">登录</el-button>
<el-button type="info">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default {
name: 'Login',
data() {
return {
// 通过v-model双向绑定登录表单数据
loginForm: {
username: '',
password: ''
},
//表单验证规则
loginFormRules:{
//验证用户名
username:[
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
],
//验证密码
password:[
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 15, message: '长度在 6 到 15 个字符', trigger: 'blur' }
]
}
}
}
}
</script>
<style lang="less" scoped>
.login_container {
// 登录组件背景色
background-color: #2b4b6b;
height: 100%;
// 登录盒子设置
.login_box {
width: 450px;
height: 300px;
background-color: #fff;
border-radius: 3px;
// 盒子水平垂直居中设置
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
// 头像盒子
.avatar_box {
width: 130px;
height: 130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
img {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #eee;
}
}
.login_form {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
.btns {
display: flex;
justify-content: flex-end;
}
}
}
}
</style>
五.表单内容重置
实现思路:通过表单的实例对象。其中resetFields方法重置表单为初始值并移除校验结果。
步骤:
1.获取表单对象,给el-from 添加一个ref引用,它的值就是表单的实例对象
2.给重置按钮添加一个点击方法
3.在方法中通过this.$refs.引用对象.resetFields()重置
代码:
<template>
<!-- 登录容器 -->
<div class="login_container">
<!-- 登录盒子 -->
<div class="login_box">
<!-- 头像盒子 -->
<div class="avatar_box">
<img src="../../assets/logo.png" alt="" />
</div>
<!-- 表单 -->
<el-form label-width="0px" class="login_form" :model="loginForm" :rules="loginFormRules" ref="loginFormRef">
<!-- 用户名 -->
<el-form-item prop="username">
<el-input v-model="loginForm.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
></el-input>
</el-form-item>
<!-- 按钮 -->
<el-form-item class="btns">
<el-button type="primary">登录</el-button>
<el-button type="info" @click="resetLoginForm">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default {
name: 'Login',
data() {
return {
// 通过v-model双向绑定登录表单数据
loginForm: {
username: '',
password: ''
},
//表单验证规则
loginFormRules: {
//验证用户名
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
],
//验证密码
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 15, message: '长度在 6 到 15 个字符', trigger: 'blur' }
]
}
}
},
methods:{
// 点击重置清空表单方法
resetLoginForm(){
this.$refs.loginFormRef.resetFields()
}
}
}
</script>
<style lang="less" scoped>
.login_container {
// 登录组件背景色
background-color: #2b4b6b;
height: 100%;
// 登录盒子设置
.login_box {
width: 450px;
height: 300px;
background-color: #fff;
border-radius: 3px;
// 盒子水平垂直居中设置
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
// 头像盒子
.avatar_box {
width: 130px;
height: 130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
img {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #eee;
}
}
.login_form {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
.btns {
display: flex;
justify-content: flex-end;
}
}
}
}
</style>
六.登录预验证
实现思路:先获取表单的引用对象,之后调用表单的validate函数进行表单预校验,接收一个回调函数。里面的第一个形参是一个布尔值,通过布尔值可以判断是否通过校验
步骤:
1.获取表单对象,给el-from 添加一个ref引用,它的值就是表单的实例对象
2.给登录按钮添加一个点击方法
3.在方法中通过this.$refs.引用对象.validate()校验
代码:
<!-- 按钮 -->
<el-form-item class="btns">
<el-button type="primary" @click="login">登录</el-button>
<el-button type="info" @click="resetLoginForm">重置</el-button>
</el-form-item>
login(){
// 登录预验证 返回布尔值
this.$refs.loginFormRef.validate(valid=>{
console.log(valid);
})
}
七.发起登录请求,并实现页面跳转。
实现思路:发起登录请求,拿到返回数据,根据数据信息进行判断登录,登录成功后将返回的token保存到客户端的sessionStorage中,并跳转页面。
步骤:
1.发送请求需要先安装axios,新建util文件夹新建一个request.js文件。用来封装请求和相应拦截。
npm install axios
request.js
// 引入axios
import axios from 'axios'
// 创建一个axios实例
const http = axios.create({
// 基础路径,发请求的时候会有api
baseURL: '/api',
// 请求超时时间
timeout: 2000
});
// request请求拦截器
http.interceptors.request.use(
config => {
// config,配置对象,对象里面有一个属性很重要,header请求头
return config;
},
error => {
return Promise.error(error);
}
);
// response响应拦截
http.interceptors.response.use(
response => {
// do something
return Promise.resolve(response);
},
error => {
// do something
return Promise.reject(error);
}
)
// 暴漏axios
export default http
2.新建api文件夹,在api文件夹下建立login.js文件,定义一个接口,通过axios实例的get方法得到对象数据
login.js接口代码
// 引入封装的axios
import http from '../util/request'
// 定义一个请求登录数据的接口 loginForm是data中包含用户名密码的数据,为了获取输入的数据
export const getLogin = loginForm => {
// 调用axios实例的get方法(url,参数),返回一个promise对象,
// get方法{data: loginForm}形式,如果是post请求这里的参数只需要写loginForm
return http.get('/login', {data: loginForm})
}
3.新建mock文件夹,新建mock.js文件,通过Mock.mock()方法用来拦截请求,新建mockServerData-login.js文件用来存放模拟的后台数据。最后还要在程序入口main.js中引入mock.
mock.js
// 引入mock
import Mock from 'mockjs'
// 引入模拟的login页面数据
import {responeSuccess, responeFail, userDataList } from './mockServerData/login'
// 定义mock请求拦截 路径 请求方式 函数
Mock.mock('/api/login', 'get', function(options) {
// 请求参数 用户输入的用户名密码
const respone = JSON.parse(options.body)
const username = respone.username
const password = respone.password
// 模拟的后台用户信息数据
const userInfos = userDataList.userinfo
for (const user of userInfos) {
console.log('userName:' + user.username);
console.log('password:' + user.password);
// 判断输入的信息和后台的信息一致
if (username === user.username && password === user.password) {
// 这里返回的成功代码信息也是在后台模拟的
return responeSuccess
}
}
console.log('fail')
// 这里返回的失败代码信息也是在后台模拟的
return responeFail
})
mockServerData-login.js 模拟的后台数据
// 登录成功状态码
export const responeSuccess = {
data: {
meta: {
status: 200,
message: 'success',
token: '000111222333444555666'
}
}
};
// 登录用户信息
export const userDataList = {
userinfo: [{
username: 'admin',
password: '1234567890',
token: '000111222333444555666'
}, {
username: 'editor',
password: '0000000000',
token: '145145145123123123111'
}]
};
// 登录失败状态码
export const responeFail = {
data: {
meta: {
status: 500,
message: 'fail'
}
}
};
入口文件main.js
// 引入mock文件
import './mock/mock'
4.引入登录api,通过接口拿到数据状态进行判断
<script>
// 引入登录api
import { getLogin } from '../../api/login.js'
export default {
name: 'Login',
data() {
return {
// 通过v-model双向绑定登录表单数据
loginForm: {
username: '',
password: ''
},
// 表单验证规则
loginFormRules: {
// 验证用户名
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
],
// 验证密码
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 15, message: '长度在 6 到 15 个字符', trigger: 'blur' }
]
}
}
},
methods: {
// 点击重置清空表单方法
resetLoginForm() {
this.$refs.loginFormRef.resetFields()
},
login() {
// 登录预验证 返回布尔值
this.$refs.loginFormRef.validate(async valid => { // async异步和await配套
if (!valid) return false
// 发起请求,拿到服务器返回的数据,其中data是真实数据,解构出来,axios实例返回一个promise对象用await转换
const { data: res } = await getLogin(this.loginForm)
// 用拿到的后台状态数据进行判断登录
if (res.data.meta.status !== 200) return this.$message.error('登录失败')
this.$message.success('登录成功')
// 将返回的token保存到客户端的sessionStorage中。
window.sessionStorage.setItem('token', res.data.meta.token)
// 跳转到后台主页
this.$router.push('/home')
})
}
}
}
</script>
5.登录成功失败的提示.$message,需要先在element.js中添加如下代码,再将文件引入到main.js中才可以。
// 导入弹窗提示组件
import { Form, FormItem, Input, Button, Message} from 'element-ui'
// 将Message挂在原型对象上便于其他组件调用
Vue.prototype.$message = Message
八.登录导航守卫的来控制访问权限
思路:拿到路由对象,挂在beforeEach的导航守卫,传一个回调函数,接受三个形参。根据to来确认访问的地址和token的值来确定是否发生页面跳转。
router文件夹-index.js文件中添加挂载路由导航守卫-登录的代码
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入登录组件
import Login from '../views/login/Login.vue'
// 导入Home组件
import Home from '../components/Home'
Vue.use(VueRouter)
const routes = [
// 当路径是/时,重定向路径/login
{ path: '/', redirect: '/login' },
// 当路径是/login时,展示组件Login
{ path: '/login', component: Login },
{ path: '/home', component: Home }
]
const router = new VueRouter({
routes
})
// 挂载路由导航守卫-登录
router.beforeEach((to, from, next) => {
// to将要访问的路径
// from从哪个路径跳转来
// next函数,表示下一步放行
// 如果路径是登录页,下一步
if (to.path === '/login') return next()
// 获取token
const tokenStr = window.sessionStorage.getItem('token')
// 如果没有获取到token,返回登录页
if (!tokenStr) return next('/login')
// 如果存在则直接放行
next()
})
export default router
九.退出功能实现
思路:销毁本地token,跳转到登录页
步骤:在home页,给推出按钮添加一个点击事件,在事件中添加销毁token方法
<template>
<div>
<el-button type="info" @click="exit">退出</el-button>
</div>
</template>
<script>
export default {
name: 'Home',
methods: {
exit() {
// 清空token
window.sessionStorage.clear()
// 跳转登录页
this.$router.push('/login')
}
}
}
</script>