登录流程
首先说一下现在项目搭建的程度;vue-cli 创建的项目引入了 iview 组件库,安装好了 less,安装了 vue-router 路由;配置好了跳转到后台和登录页面的路由;使用 iview做好了登录页面和后台页面的框架,如图:后台是需要登录之后才能访问的,所以要对访问进行拦截;1. 标识一下需要拦截的页面一个网站有很多页面,一些是需要登录过后才能访问的,一些是不用登录也能访问...
首先说一下现在项目搭建的程度;
vue-cli 创建的项目
引入了 iview 组件库,安装好了 less,安装了 vue-router 路由;
配置好了跳转到后台和登录页面的路由;
使用 iview做好了登录页面和后台页面的框架,如图:
后台是需要登录之后才能访问的,所以要对访问进行拦截;
1. 标识一下需要拦截的页面
一个网站有很多页面,一些是需要登录过后才能访问的,一些是不用登录也能访问的;
所以我们要在路由中对需要登录才能访问的页面进行一下标识,以区分这两者;
标识 meta
// meta 字段中 auth_login 为 true 的需要进行登录拦截检测;
{
path: '/admin',
component: Admin,
meta: {
auth_login: true
},
children: [
{
path: 'user',
component: adminUser,
meta: {
auth_login: true
},
},
{
path: 'role',
component: adminRole,
meta: {
auth_login: true
},
}
]
},
2. 路由拦截器
路由守卫也就是路由拦截器,通过路由拦截器在每一次路由跳转的时候都做一下判断,是否需要验证登录;
后续再判断是否登录成功,这里先用 true
代替;
const router = new vueRouter({...});
// 前置守卫--在进入新的路由前被拦截 路由拦截器
router.beforeEach((to, from, next)=>{
/**
* 1. to 到哪里去(路由对象)
* 2. from 从哪里来(路由对象)
* 3. next 是否允许通行(函数)
*
* 查看 to中有没有需要拦截的标识,判断是否拦截
* 不需要拦截的话直接 next() 通行;
* 需要拦截的话,判断是否登录,如果登录了则通行,没登录则拦截;
*/
// 是否需要拦截
if(to.meta.auth_login){
// 需要拦截 -- 判断是否登录
if(true){
next();
}else{
next({path: '/login'})
}
}else{
// 不需要拦截 -- 通行
next();
}
})
3. 请求
有些时候 前端页面和服务接口不在同一个服务器,如果通过 ajax 请求的话是会碰到跨域问题的;
而在 webpack
服务器中有一个 代理转发请求 的功能可以解决这个问题;就是我们请求自己的服务器,由服务器去请求数据,因为跨域是浏览器限制的,在 nodejs
是没有限制的;
如:
前端地址:
http://localhost:8080/login
服务器地址:
http://console.ranyunlong.com:8080/renren-fast/swagger/
大体流程就是:
我们从浏览器请求开发服务器 (webpack-dev-server
),开发服务器请求真实的服务器,真实的服务器把结果返回给开发服务器,开发服务器再返回给浏览器;
在开发服务器中配置规则:
vue-cli 3.0 中配置 webpack
的方式:
1)新建文件 vue.config.js
2)进行配置
https://www.webpackjs.com/configuration/dev-server/#devserver-proxy
【vue.config.js】
module.exports = {
devServer: {
proxy: {
"/api": {
// 转发的目标地址
target: "http://console.ranyunlong.com:8080",
// 路径重写
pathRewrite: {
"^/api": "/renren-fast"
}
}
}
}
}
讲解:
真实路径:
http://console.ranyunlong.com:8080/renren-fast/sys/login
但是如果我们访问这个路径就跨域了,所以我们要访问我们的开发服务器:
http://localhost:8080/api/sys/login
经过如上规则配置就相当于访问了真实路径;
当我们访问的路径有 /api
时,就匹配了 devServer.proxy
的规则,进入了我们写的这条规则;
就会把我们的 http://localhost:8080
替换成 target
的目标地址http://console.ranyunlong.com:8080
变成:
http://console.ranyunlong.com:8080/api/sys/login
再经过路径重写,将 /api
替换成 /renren-fast
,
所以访问到的路径就是:
http://console.ranyunlong.com:8080/renren-fast/sys/login
3)测试配置是否生效
重启服务器
访问路径:http://localhost:8080/api/captcha.jpg
返回:
{"msg":"uuid不能为空","code":500}
和访问真实的服务器返回的数据是相同的,配置成功;
4. 验证码
因为已经做了代理了,所以可以直接访问到 服务器的数据,验证码是通过 get
方式请求的,这里直接写就好了
<img src="/api/captcha.jpg?uuid=0101010" alt="">
但是这个的这个 uuid
只能用一次,这个问题可以通过模块 uuid
来解决;
# 安装
npm i uuid
// 导入
// uuid()会生成一个字符串
import uuid from 'uuid'
// 计算属性拼接成验证码路径;
// 没有在计算属性直接使用 uuid() 生成路径是因为后面要点击验证码换一张验证码,计算属性的值没法设置,写到 data方便后续更新;
export default {
data() {
return{
uid: uuid()
}
},
computed: {
codeUrl(){
return '/api/captcha.jpg?uuid=' + this.uid;
}
}
}
<!--绑定 计算属性-->
<img :src="codeUrl" alt="">
点击验证码,生成新的验证码
<img @click="newCodeUrl()" :src="codeUrl" alt="">
methods:{
newCodeUrl(){
this.uid = uuid();
}
},
computed: {
codeUrl(){
return '/api/captcha.jpg?uuid=' + this.uid;
}
},
data() {
return{
uid: uuid()
}
},
5. 验证表单数据
步骤见博客:
6. 表单提交
表单提交,是点击登陆按钮,然后检测表单数据,检测通过之后,进行 ajax 提交;
登录按钮
<Button @click="loginFn()" long>login</Button>
登录按钮 点击事件
loginFn(){
// this.$refs['loginForm'] 表单对象
this.$refs['loginForm'].validate((val)=>{
/**
* val true: 效验成功
* false: 效验失败
*/
});
}
axios
在这里,我们提交没有使用 ajax
而是使用了 axios
模块;
文档:https://www.kancloud.cn/yunye/axios/234845
1)安装 axios
npm install axios
2)新建文件夹 src/api/
3)新建文件 src/api/http.js
在这里使用自定义配置新建一个 axios
实例
// 导入 axios模块
import axios from 'axios'
// 创建一个请求模板 新建一个axios 实例
const http = axios.create({
// 设置基础路径
baseURL: '/api',
})
// 拦截请求的 每次请求都会经过这个方法
http.interceptors.request.use((config) => {
console.log(config);
// 可以在这里处理所有的 请求错误
return config;
})
// 拦截响应的 每次收到响应都会经过这个方法
http.interceptors.response.use((response)=>{
console.log(response);
// 可以在这里处理所有的 响应错误
return response;
})
// 暴露出去
export default http;
4) 登录接口 src/api/login.js
导入新建的 axios
实例,然后执行 post
请求;
/**
* 登录接口
*/
import http from "./http";
function login(data){
return http.post('/sys/login', data);
}
export default login;
5)在页面中调用这个登录接口,传入数据
<Button @click="loginFn()" long>login</Button>
import login from '../api/login'
_________
data(){
return {
uid: uuid(),
userInfo: {
username: '',
password: '',
captcha: ''
},
}
},
methods:{
loginFn(){
// this.$refs['loginForm'] 表单对象
this.$refs['loginForm'].validate((val)=>{
/**
* val true: 效验成功
* false: 效验失败
*/
if(val) {
login({
...this.userInfo,
uuid: this.uid
})
}
});
}
},
6)通过响应消息判断请求是否成功
请求成功:
存储成功凭据:localStorage
跳转到后台页面: this.$router.push('/admin')
请求失败:
弹窗提示:iview 的全局提示
刷新验证码:this.uid = uuid();
loginFn(){
// this.$refs['loginForm'] 表单对象
this.$refs['loginForm'].validate((val)=>{
/**
* val true: 效验成功
* false: 效验失败
*/
if(val) {
login({
...this.userInfo,
uuid: this.uid
}).then((response)=>{
// response 响应过来的信息
// response.data.code === 0 请求成功
const {code, msg, token} = response.data;
if(code === 0) {
// 请求成功,存储成功信息
localStorage.setItem('token', token)
// 路由跳转
this.$router.push('/admin');
}else {
// iview 的全局提示
this.$Message.error('登陆失败:' + msg);
// 刷新验证码
this.uid = uuid();
}
})
}
});
}
在路由拦截器中,判断是否有登陆凭证,有的话通行;
const loginResult = localStorage.getItem('token');
// 前置守卫--在进入新的路由前被拦截 路由拦截器
router.beforeEach((to, from, next)=>{
/**
* 1. to 到哪里去(路由对象)
* 2. from 从哪里来(路由对象)
* 3. next 是否允许通行(函数)
*
* 查看 to中有没有需要拦截的标识,判断是否拦截
* 不需要拦截的话直接 next() 通行;
* 需要拦截的话,判断是否登录,如果登录了则通行,没登录则拦截;
*/
// 是否需要拦截
if(to.meta.auth_login){
// 需要拦截 -- 判断是否登录
const loginResult = localStorage.getItem('token');
if(loginResult){
next();
}else{
next({path: '/login'})
}
}else{
// 不需要拦截 -- 通行
next();
}
})
至此:登陆成功
获取菜单
- 每次提交请求的时候,都要 带上
token
否则请求不到数据
【src/api/http.js】
// 请求拦截器
http.interceptors.request.use((request)=> {
request.headers.token = localStorage.getItem('token');
return request;
})
- 将无效的
token
清除掉
如果用户伪造token
在后台是请求不到数据的,token
无效时,返回的code
为401
,
如果token
为用户伪造的,则将存储在localStorage
中的token
字段清除掉;
【src/api/http.js】
// 响应拦截器
http.interceptors.response.use((response)=>{
if(response.data.code === 401){
localStorage.removeItem('token');
}
return response;
})
- 新建请求接口
src/api/getMenuList.js
/**
* 菜单列表接口
*/
import http from './http'
function getMenuList(){
return http.get('/sys/menu/list');
}
export default getMenuList;
- 在页面中请求数据
导入请求接口
import getMenuList from '../api/getMenuList'
export default {
data(){
return {
menu: []
}
},
beforeCreate(){
// 请求数据
getMenuList().then((response)=>{
this.menu = response.data;
})
}
}
将请求到的数据打印看一下,没问题的话定义一个工具函数处理成方便我们遍历的形式
- 工具函数
src/utils/deepMenu.js
/**
* 递归菜单
* @param {Array} menu 菜单数据
* @param {Number} parentId 父级Id
*/
function deepMenu(menu, parentId){
if(Array.isArray(menu)){
return menu.filter((item, index, arr)=>{
if(item.parentId === parentId){
item.children = deepMenu(menu, item.menuId)
return true;
}
})
}
}
export default deepMenu;
- 使用工具函数,生成我们需要的数据格式
import getMenuList from '../api/getMenuList'
import deepMenu from '../urils/deepMenu'
export default {
data(){
return {
menu: []
}
},
beforeCreate(){
getMenuList().then((response)=>{
this.menu = response.data;
})
},
computed: {
menuList() {
return deepMenu(this.menu, 0);
}
},
}
- 遍历数据
<Sider :collapsed-width="78" >
<Menu theme="dark" width="auto" accordion>
<Submenu
v-for="item in menuList"
:name="item.menuId"
:key="item.menuId"
>
<template slot="title">
<Icon type="ios-paper" />
{{item.name}}
</template>
<MenuItem
v-for="child in item.children"
:name="child.menuId"
:key="child.menuId"
>
{{child.name}}
</MenuItem>
</Submenu>
</Menu>
</Sider>
- 点击跳转路径
其实<MenuItem>
是有to
属性的,和<router-link>
的to
属性作用相同,但是这里因为接口的数据的原因,就不使用to
了,写一个点击事件
注意,直接给<MenuItem>
写点击事件是无效的,需要嵌套一个标签,给这个标签写点击事件
<MenuItem
v-for="child in item.children"
:name="child.menuId"
:key="child.menuId"
>
<div @click="goto(child.url)">
{{child.name}}
</div>
</MenuItem>
methods: {
goto(url){
this.$router.push({
path: `/${url}`
})
}
}
OK,列表也遍历出来了
更多推荐
所有评论(0)