常见的业务场景实现方案

1.解决请求服务器接口跨域的问题

  • 本地项目请求服务器接口时,因为客户端的同源策略,导致了跨域问题

  • 配置允许本地跨域:
    在这里插入图片描述

  • /api指代我们要请求的接口域名,例如:this.$axios.get('/api/app.php?m=App&c=Index&a=index')

  • 不想每次接口都带上/api,可以更改axios的默认配置axios.defaults.baseURL = '/api';

  • 这样,请求接口就可以直接this.$axios.get('app.php?m=App&c=Index&a=index')

2.axios封装

请求拦截

  • 有些请求是需要用户登录之后才能访问,或者post请求时,我们需要序列化要提交的数据,这个时候我们就可以在请求发送前进行一个拦截
  • axios.interceptors.request.use里设置
// 先导入vuex, 因为我们要使用到里面的状态对象
// vuex的路径根据自己的路径去写
import store from '@/store/index';

// 请求拦截器
axios.interceptors.request.use(    
    config => {        
        // 每次发送请求之前判断vuex中是否存在token        
        // 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况
        // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断 
        const token = store.state.token;        
        token && (config.headers.Authorization = token);        
        return config;    
    },    
    error => {        
        return Promise.error(error);    
})
  • token一般在登录后,就会通过localStorage或者cookie存在本地,在用户每次进入页面时(即在main.js),会首先去本地存储读取token,存在就说明登录过,则更新vuex中token状态
  • 每次请求接口时都会在请求的header中携带token,后台再判断登录是否过期(在一个不需要用户登录的页面,前端请求携带了token,但后台可以不接收

响应拦截

  • axios.interceptors.response.use中设置
  • 服务器返回来的数据,在拿到之前可以先做一些处理
  • 后台返回的状态码是200则正常返回数据,否则根据错误的状态码类型进行统一错误处理
  • 未登录401:
    • 未登录则跳转到登录页,并携带当前页面的路径 redirect: router.currentRoute.fullPath
    • 登录完成后返回当前页面
  • token过期403:
    • 登陆过期对用户进行提示
    • 清除本地token localStorage.removeItem('token')
    • 清空vuex中token对象
    • 跳转到登录页
  • 请求不存在404
// 响应拦截器
axios.interceptors.response.use(    
    response => {   
        // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据     
        // 否则的话抛出错误
        if (response.status === 200) {            
            return Promise.resolve(response);        
        } else {            
            return Promise.reject(response);        
        }    
    },    
    // 服务器状态码不是2开头的的情况
    // 这里可以跟你们的后台开发人员协商好统一的错误状态码    
    // 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
    // 下面列举几个常见的操作,其他需求可自行扩展
    error => {            
        if (error.response.status) {            
            switch (error.response.status) {                
                // 401: 未登录
                // 未登录则跳转登录页面,并携带当前页面的路径
                // 在登录成功后返回当前页面,这一步需要在登录页操作。                
                case 401:                    
                    router.replace({                        
                        path: '/login',                        
                        query: { 
                            redirect: router.currentRoute.fullPath 
                        }
                    });
                    break;

                // 403 token过期
                // 登录过期对用户进行提示
                // 清除本地token和清空vuex中token对象
                // 跳转登录页面                
                case 403:
                     Toast({
                        message: '登录过期,请重新登录',
                        duration: 1000,
                        forbidClick: true
                    });
                    // 清除token
                    localStorage.removeItem('token');
                    store.commit('loginSuccess', null);
                    // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面 
                    setTimeout(() => {                        
                        router.replace({                            
                            path: '/login',                            
                            query: { 
                                redirect: router.currentRoute.fullPath 
                            }                        
                        });                    
                    }, 1000);                    
                    break; 

                // 404请求不存在
                case 404:
                    Toast({
                        message: '网络请求不存在',
                        duration: 1500,
                        forbidClick: true
                    });
                    break;
                // 其他错误,直接抛出错误提示
                default:
                    Toast({
                        message: error.response.data.message,
                        duration: 1500,
                        forbidClick: true
                    });
            }
            return Promise.reject(error.response);
        }
    }    
});
  • Toast是vant库的toast轻提示组件,根据ui库对应使用一个提示组件即可

3.定时器问题

场景:在a页面写了一个定时,使得每秒钟打印1,然后跳转到b页面,跳转后发现定时器还在执行,这很消耗性能

方法一:

  • 1.先在data函数中定义定时器名称timer

  • 2.这样使用:

    this.timer = (() => {
        // 某些操作
    }, 1000)
    
  • 3.在beforeDestroy()生命周期内清除定时器:

    beforeDestroy() {
        clearInterval(this.timer);        
        this.timer = null;
    }
    
  • 缺点在于:

    • 它需要这个组件实例保存timer,可以的话最好只有生命周期钩子可以访问到它
    • 建立的代码独立于清理的代码,这样很难程序化清理所有我们建立起来的东西

方法二:通过$once这个事件侦听器在定义完定时器之后的位置来清除定时器

const timer = setInterval(() =>{                    
    // 某些定时器操作                
}, 500);            
// 通过$once来监听定时器,在beforeDestroy钩子可以被清除
this.$once('hook:beforeDestroy', () => {            
    clearInterval(timer);                                    
})
  • 类似于其他需要在当前页面使用,离开需要销毁的组件(例如一些第三方库的picker组件等等),都可以使用此方式来解决离开后在背后运行的问题

4.fastClick解决点击延迟和点击穿透问题

点击延迟

300ms延迟主要来源是浏览器为了实现双击缩放而引入的点击延迟。在双击缩放的场景下,浏览器需要等待一段时间来判断这次点击是否为双击操作,如果不是则执行单击操作,这段时间就是点击延迟。

点击延迟的引入是为了解决在移动设备上双击缩放和单击操作的冲突问题。由于双击缩放的操作频率较低,因此在单击操作中引入了点击延迟,以区分单击和双击事件。但是,这也导致了用户在点击按钮等操作时会感觉到明显的延迟。

使用fastClick来解决点击延迟,使用一些技巧来模拟点击事件,从而避免了点击延迟。

  • 1.安装fastClick

  • 2.在main.js中引入并初始化

    import FastClick from 'fastclick'; // 引入插件
    FastClick.attach(document.body); // 使用 fastclick
    

点击穿透

在移动设备上,当一个元素的点击事件被触发后,浏览器会在 300 ms后才会触发对该元素的点击事件,而在这 300 ms内,如果用户快速点击了其他元素,那么这些元素的点击事件也会被触发,这种现象被称为点击穿透问题。

  • 可以使用fastClick来消除300 ms点击延迟,并在元素的点击事件被触发时阻止事件冒泡
  • 前两步一样
  • 3.在元素的点击事件处理函数中调用 preventDefault 方法来阻止事件冒泡

5.路由懒加载

  • 在进入首屏时,不会加载过多的资源,从而减少首屏加载速度
export default new Router({
  routes: [    
        { 
            path: '/', 
            name: 'Index', 
            component: resolve => require(['@/view/index/index'], resolve) 
        }
   ]
})

6.开启gzip压缩代码

  • SPA首屏一次性加载所有资源,所以加载速度很慢

  • 前后端开启gzip,减少文件体积,能压缩到30%左右

  • vue-cli初始化的项目中,是默认有这个配置的,先安装插件

  • 在config/index.js文件中开启即可

    build: {
        // 其他代码
        …………
        productionGzip: true, // false不开启gizp,true开启
        // 其他代码
    }
    

7.前进刷新后退不刷新

需求一:

  • 搜索页面 ==> 到搜索结果页时,搜索结果页面要重新获取数据
  • 搜索结果页面 ==> 点击进入详情页 ==> 从详情页返回列表页时,要保存上次已经加载的数据和自动还原上次的浏览位置
  • 也就是说,从其他页面进到列表页,需要刷新获取数据,从详情页回到列表页不要刷新

在App.vue设置,假设列表页list.vue,详情页detail.vue,都是子组件:

<keep-alive include="list">
	<router-view/>
</keep-alive>
  • 在keep-alive添加列表页的名字,缓存列表页
  • 在列表页的created函数添加ajax请求,这样只有第一次进入到列表页的时候才会请求数据
  • 当从列表页跳到详情页,再从详情页回来的时候,列表页就不会刷新

需求二:在需求一的基础上,可以在详情页中删除对应的列表项,返回到列表页需要刷新重新获取数据

方案一:

  • 在路由配置文件上对detail.vue增加一个meta属性

    {
        path: '/detail',
        name: 'detail',
        component: () => import('../view/detail.vue'),
        meta: {isRefresh: true}
    },
    
  • 这个meta属性可以在详情页中通过 this.$route.meta.isRefresh 来读取和设置

  • App.vue 文件里设置 watch 一下 $route 属性

        watch: {
           $route(to, from) {
               const fname = from.name
               const tname = to.name
               if (from.meta.isRefresh || (fname != 'detail' && tname == 'list')) {
                   from.meta.isRefresh = false
       				// 在这里重新请求数据
               }
           }
       },
    
    • 不需要在列表页的 created 函数里用 ajax 来请求数据了,统一放在 App.vue 里来处理
  • 触发请求数据有两个条件:

    • 从其他页面(除了详情页)进来列表时,需要请求数据
    • 从详情页返回到列表页时,如果详情页 meta 属性中的 isRefreshtrue,也需要重新请求数据
  • 当我们在详情页中删除了对应的列表项时,就可以将详情页 meta 属性中的 isRefresh 设为 true,这时再返回到列表页,页面会重新刷新

方案二:更简洁的方案,使用 router-view 的 key 属性

<keep-alive>
    <router-view :key="$route.fullPath"/>
</keep-alive>
  • keep-alive 让所有页面都缓存

  • 当不想缓存某个路由页面,要重新加载它时,可以在跳转时传一个随机字符串,这样就能重新加载了

  • 例如:从列表页进入了详情页,然后在详情页中删除了列表页中的某个选项,此时从详情页退回列表页时就要刷新,我们可以这样跳转:

    this.$router.push({
        path: '/list',
        query: { 'randomID': 'id' + Math.random() },
    })
    

8.vue中style中的scoped属性是如何实现样式隔离的

  • 使用scoped后,父组件的样式就不会渗透到子组件

    • 当 style 标签加上 scoped 属性时,scoped 会在 DOM 结构及 CSS 样式上加上唯一性的标记 data-v-xxx 属性,从而达到样式私有化,不污染全局的作用
  • 如果希望一个选择器能作用得更深,例如要影响子组件,就可以使用>>>操作符

    <style scoped>
    .a >>> .b { /* ... */ }
    </style>
    
    // 上段代码会被编译成:
    .a[data-v-f3f3eg9] .b { /* ... */ }
    
  • 像sass之类的预处理器无法正确解析>>>,可以使用/deep/::v-deep操作符,两者都是>>>的别名,同样可以正常工作

9.vue获取数据的两种方式

在vue中获取数据有两种方式:

  • 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据,在数据获取期间显示“加载中”之类的指示
  • 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航

导航完成之后获取

在组件的created钩子中获取数据期间,可以根据不同视图展示不同的loading状态:

  • 数据获取之前,页面组件已经加载好,我们不能加载页面内要展示数据那块组件,这时要有个loading的加载中的组件
  • 页面数据获取失败,可以理解成请求超时,我们要展示的是断网的组件
  • 如果是列表页,还要考虑空数据的情况,即为空提示的组件
<template>
    <div class="list">
        <!--加载中或者骨架屏-->
        <div v-if="loading">
       
        </div>

        <!--请求失败,即断网的提示组件-->
        <div v-if="error">
      
        </div>

        <!--页面内容-->
        <div v-if="requestFinished" class="content">
            <!--页面内容-->
            <div v-if="!isEmpty">
                <!--例如有个列表,当然肯定还会有其他内容-->
                <ul></ul>
            </div>

            <!--为空提示组件-->
            <div v-else>空空如也</div>
        </div>
    </div>
</template>

导航完成之前获取

  • 是在页面的beforeRouteEnter钩子中请求数据,只有在数据获取成功之后才会跳转导航页面

    beforeRouteEnter (to, from, next) {        
        api.article.articleDetail(to.query.id).then(res=> {            
            next(vm => {                
                vm.info = res.data;                
                vm.loadFinish = true            
            })        
        })    
    },
    
  • beforeRouteEnter钩子中this不能使用,所以要想进行赋值操作或者调用方法,只能通过在next()方法的回调函数中处理,这个回调函数的第一个参数就代表了this,它会在组件初始化成功后进行操作

  • 赋值操作也可以写在method方法中,但是调用这个赋值方法还是vm.yourFunction()的方式

  • 由于是先获取到数据之后再跳转加载组件的,我们就不需要在预期的页面内展示loading组件

  • 我们需要在当前页面进入之前,即在上一个页面的时候有一个加载的提示,比如页面顶部的进度条,这样用户体验比较好

  • 具体实现,使用NProgress这个滚动条效果插件

    • 先安装

    • 在main.js中引入

    • 在main.js中根据自己的需求进行一些配置:

      NProgress.configure({     
          easing: 'ease',  // 动画方式    
          speed: 500,  // 递增进度条的速度    
          showSpinner: false, // 是否显示加载ico    
          trickleSpeed: 200, // 自动递增间隔    
          minimum: 0.3 // 初始化时的最小百分比
      })
      
    • 在main.js中,每次切换页面就调用进度条,在即将进入新页面组件前就关闭进度条

      router.beforeEach((to, from , next) => {
          // 每次切换页面时,调用进度条
          NProgress.start();
          // 这个一定要加,没有next()页面不会跳转的
          next();
      });
      router.afterEach(() => {  
          // 在即将进入新的页面组件前,关闭掉进度条
          NProgress.done()
      })
      

10.多个请求下loading的展示与关闭

一般情况下,在vue中结合axios的拦截器控制loading展示和关闭,如果每次只有一次请求运行时没问题的,但同时多个请求并发就会出现问题。

例如:同时发起两个请求,在请求前,拦截器 this.isShowLoading = true 将 loading 打开。现在有一个请求结束了,this.isShowLoading = false 拦截器关闭 loading,但是另一个请求由于某些原因并没有结束。造成的后果就是页面请求还没完成,loading 却关闭了,用户会以为页面加载完成了,结果页面不能正常运行,导致用户体验不好。

解决方案:

  • 增加一个loadingCount变量,用来计算请求的次数

  • 再增加两个方法,对 loadingCount 进行增减操作

    methods: {
        addLoading() {
        this.isShowLoading = true
        this.loadingCount++
    	},
        isCloseLoading() {
        	this.loadingCount--
        if (this.loadingCount == 0) {
        	this.isShowLoading = false
        	}
        }
    }
    
  • 拦截器变成:

            // 添加请求拦截器
            this.$axios.interceptors.request.use(config => {
                this.addLoading()
                return config
            }, error => {
                this.isShowLoading = false
                this.loadingCount = 0
                this.$Message.error('网络异常,请稍后再试')
                return Promise.reject(error)
            })
    
            // 添加响应拦截器
            this.$axios.interceptors.response.use(response => {
                this.isCloseLoading()
                return response
            }, error => {
                this.isShowLoading = false
                this.loadingCount = 0
                this.$Message.error('网络异常,请稍后再试')
                return Promise.reject(error)
            })
    
  • 拦截器的功能:每发起一个请求,打开loading,同时 loadingCount 加 1;每当一个请求结束, loadingCount 减 1,并判断 loadingCount 是否为 0,如果为 0 则关闭 loading

11.表格打印

打印需要的组件是print-js

一般的表格打印直接仿照打印组件即可:

printJS({
    printable: id, // DOM id
    type: 'html',
    scanStyles: false,
})

element-ui表格打印(其他组件库的表格同理)

  • 表面上看起来是一个表格,实则上是两个表格组成的
  • 表头为一个表格,表体又是一个表格,导致的问题是:打印的时候表体和表头错位,在表格出现滚动条的时候也会造成错位

解决方案:

  • 将两个表格合成一个表格,print-js组件打印的时候,实际上是把id对应的dom里的内容提取出来打印

  • 所以在传入id之前,先把表头所在的表格内容提取出来,插到第二个表格里,从而将两个表格合并

    function printHTML(id) {
        const html = document.querySelector('#' + id).innerHTML
        // 新建一个 DOM
        const div = document.createElement('div')
        const printDOMID = 'printDOMElement'
        div.id = printDOMID
        div.innerHTML = html
    
        // 提取第一个表格的内容 即表头
        const ths = div.querySelectorAll('.el-table__header-wrapper th')
        const ThsTextArry = []
        for (let i = 0, len = ths.length; i < len; i++) {
            if (ths[i].innerText !== '') ThsTextArry.push(ths[i].innerText)
        }
    
        // 删除多余的表头
        div.querySelector('.hidden-columns').remove()
        // 第一个表格的内容提取出来后已经没用了 删掉
        div.querySelector('.el-table__header-wrapper').remove()
    
        // 将第一个表格的内容插入到第二个表格
        let newHTML = '<tr>'
        for (let i = 0, len = ThsTextArry.length; i < len; i++) {
            newHTML += '<td style="text-align: center; font-weight: bold">' + ThsTextArry[i] + '</td>'
        }
    
        newHTML += '</tr>'
        div.querySelector('.el-table__body-wrapper table').insertAdjacentHTML('afterbegin', newHTML)
        // 将新的 DIV 添加到页面 打印后再删掉
        document.querySelector('body').appendChild(div)
        
        printJS({
            printable: printDOMID,
            type: 'html',
            scanStyles: false,
            style: 'table { border-collapse: collapse }' // 表格样式
        })
    
        div.remove()
    }
    

12.解决打包之后文件、图片、背景图资源不存在或者路径错误的问题

在这里插入图片描述

  • 项目的config文件夹下的index.js文件,这个配置选项就好使我们打包后的资源公共路径,默认的值为'/',即根路径,所以打包后的资源路径为根目录下的static

  • 如果打包后的资源没有放在服务器的根目录,而是在根目录下的mobile等文件夹的话,那么打包后的路径和代码中的路径就会有冲突了,导致资源找不到

  • 为了解决这个问题,可以在打包的时候把上面这个路径由的根目录'/'改为'./'相对路径
    在这里插入图片描述

  • 打包后的图片、js等路径就是'./static/img/xx.jpg'这样的相对路径,就不会出错了

  • 但是背景图的路径还是不对,因为此时的相对就变成了static/css/文件夹下的static/img/xx.jpg,但是实际上static/css/文件夹下没有static/img/xx.jpg,即static/css/static/img/xx.jpg是不存在的

  • 相对于当前的css文件的路径,要把css中的背景图的加个公共路径'../../',即让它往上返回两级到和index.html文件同级的位置,那么此时的相对路径static/img/xx.jpg就能找到对应的资源了

  • 因为背景图是通过loader解析的,所以自然在loader的配置中修改,打开build文件夹下的utils文件,找到exports.cssLoaders的函数,添加上该配置即可:
    在这里插入图片描述

  • 如果路由模式是history,那么打包放在服务器,必须要和后台服务器配合,不然会出现白屏等各种奇怪的问题

13.注册功能前端到后端数据库的一套流程

cookie + session 登录

  • 客户端每次请求都是独立的,服务器端就不能判断请求是否来自同一个用户,也就不能判断用户的登录状态

  • cookie就是用来存放客—>服的信息,以文本的形式存在客户端,每次请求就会携带第一次登录时写入的cookie

  • 服务器通过创建session来验证

  • session可能存放在很多地方,例如:内存、文件、数据库

  • 流程如下:
    在这里插入图片描述

  • 服务器响应了HTTP请求,通过Set-Cookie头信息,把SessinId写入cookie

  • 后续客户端访问携带cookie,服务器使用cookie来进行身份验证

  • 存在的问题:

    • 服务器需要存放大量的SessionId,导致服务器压力过大
    • SessionId存放在cookie,无法避免CSRF攻击

token 登录

  • token是服务端生成的一串字符串,第一次登录后,服务端会生成一个token给客户端,客户端自动保存
  • 后续再访问就带上这个token来完成身份认证
  • 优点:
    • 服务器不用存token,不会造成压力
    • token可以放到前端任何地方
  • 缺点:token下发之后,只要在生效时间内就一直有效,如果服务器想收回token权限不容易

14.token过期 如何实现刷新

  • 服务器:定义一个刷新token的接口,该接口需要验证当前token的有效性,并根据需要生成一个新的token,返回客户端
  • 客户端:发现当前token过期后,向服务端发送刷新token的请求,并把当前token和刷新token的回调函数传递给服务器
  • 服务器:收到请求后,验证当前token的有效性
    • 有效就生成一个新的token,并返回客户端
    • 无效就向客户端返回一个错误码或提示信息
  • 客户端:收到新的token时,将token存在本地,并调用token的回调函数

15.无感刷新token

token过期,用户也不用手动重新登录即可继续使用,实现细节跟具体场景调整

  • 定义token的过期时间,如30min
  • 在发送请求时,检查token是否快要过期了,如在token过期时间前5min检查
  • 如果token即将过期,发送请求给后端,请求新的token
  • if 返回新的token,将其保存在本地,并更新所有请求中的token
  • if 返回错误或者新的token无效,清除本地token,并跳转到登录页

前端:在请求拦截器中实现token的检查和更新

  • 在请求头设置Authorization字段,将token发送给后端
  • token即将过期,在请求拦截器发送刷新token的请求
  • 并把新的token保存至本地,同时更新所有请求的Authorization字段

后端:

  • 实现一个token的刷新接口,接受旧的token并返回新的token
  • 处理新请求时,对旧的进行验证,以确保请求合法
  • 验证通过,就返回新的token给前端;否则返回错误信息

16.如何触发下拉刷新、上拉加载

下拉刷新

实现下拉刷新主要分为三步:

  • 监听touchstart事件,记录初始位置的值,e.touched[0].pageY
  • 监听touchmove事件,记录并计算当前滑动的位置与初始位置的差值,大于0表示向下拉动,并借助CSS3的translateY属性使元素跟随手势向下滑动对应的差值,同时设置一个允许滑动的最大值
  • 监听touchend事件,如此时元素滑动到最大值,则触发callback,同时translateY设为0,元素回到初始位置

上拉加载

  • 上拉加载更多数据是在页面滚动时触发,一般是页面滚动到底部时触发,也可以选择滚动到一定位置触发
  • 以滚动到页面底部为例,实现原理:
    • 获得当前滚动条的scrollTop、当前可视范围的高度值clientHeight、文档的总高度scrollHeight
    • 当scrollTop + clientHeight >= scrollHeight,则触发callback

17.扫描二维码登录的原理

移动端基于token的认证机制

  • 在登录的时候不仅传入账号密码,还传入了手机的设备信息
  • 服务端验证账号密码正确后,还会做两件事:
    • 1.将账号与设备关联起来,某种意义上,设备信息就代表账号
    • 生成一个token,在token与账号、设备信息关联
  • 移动端下次登录时要携带token、设备信息
  • 因为token可能会被劫持,但设备信息是唯一的

二维码登录原理

扫码登录分为三个阶段:待扫描、已扫描待确认、已确认

  1. 待扫描阶段:
  • 即生成二维码阶段,这个阶段跟移动端没有关系,是PC端和服务端的交互
  • PC端携带设备信息发起生成二维码请求,服务端会生成唯一的二维码ID
    • 可以理解为UUID,并将二维码ID与PC设备信息关联起来
  • PC端接收到二维码ID后,将其以二维码的形式展示,等待移动端扫码
  • PC端还会启动一个定时器,轮询查询二维码状态
  • 如果移动端不扫码,那一段时间后二维码会失效
  1. 已扫描待确认阶段:
  • 例如:在PC端登录微信时,手机扫码后,PC端的二维码会变成已扫码,请在手机端确认,这是移动端和服务端的交互
  • 首先手机扫码后获取二维码ID,将手机端登录的token和二维码ID作为参数发送给服务端
  • 服务端接受请求后,会把token与二维码ID关联起来
  • 再生成一个一次性token并返回给移动端,这个一次性token用作确认时的凭证
  • PC端的定时器轮询发现二维码状态已经发生变化,就会将二维码更新为已扫描,请确认
  1. 已确认阶段:
  • 移动端携带获取的一次性token确认登录,服务端校对完成后,会更新二维码状态
  • 并给PC端生成一个正式的token,后面PC端再持有这个token访问服务端
  • PC端的定时器轮询二维码发现为登录状态,并会获得生成的token,完成登录,后续访问都基于token完成
  • 服务器端和手机端一样,维护着token与二维码、PC设备信息、账号等信息

18.页面权限控制

一个网站有不同的角色,比如管理员和普通用户,要求不同的角色能访问的页面是不一样的,如果一个页面,有角色越权访问,这时就得做出限制了

  • 第一种方法:通过动态添加路由和菜单来做控制,不能访问的页面不添加到路由表里
  • 第二种方法:所有的页面都在路由表里,只是在访问的时候要判断一下角色权限,有权限就允许访问,没有权限就拒绝,跳转到 404 页面

第一种方法

思路:

  • 在每一个路由的 meta 属性里,将能访问该路由的角色添加到 roles
  • 用户每次登陆后,将用户的角色返回
  • 在访问页面时,把路由的 meta 属性和用户的角色进行对比
  • 如果用户的角色在路由的 roles 里,那就是能访问,如果不在就拒绝访问

路由信息:

routes: [
    {
        path: '/login',
        name: 'login',
        meta: {
            roles: ['admin', 'user']
        },
        component: () => import('../components/Login.vue')
    },
    {
        path: 'home',
        name: 'home',
        meta: {
            roles: ['admin']
        },
        component: () => import('../views/Home.vue')
    },
]

页面控制:

// 假设角色有两种:admin 和 user
// 这里是从后台获取的用户角色
const role = 'user'
// 在进入一个页面前会触发 router.beforeEach 事件
router.beforeEach((to, from, next) => {
    if (to.meta.roles.includes(role)) {
        next()
    } else {
        next({path: '/404'})
    }
})

登录验证:一般只要登录过一次,接下来其他页面都是能访问的,以下代码展示如何用token控制登录验证

router.beforeEach((to, from, next) => {
    // 如果有token 说明该用户已登陆
    if (localStorage.getItem('token')) {
        // 在已登陆的情况下访问登陆页会重定向到首页
        if (to.path === '/login') {
            next({path: '/'})
        } else {
            next({path: to.path || '/'})
        }
    } else {
        // 没有登陆则访问任何页面都重定向到登陆页
        if (to.path === '/login') {
            next()
        } else {
            next(`/login?redirect=${to.path}`)
        }
    }
})

第二种方法:动态菜单

常见的需求:根据后台数据动态添加路由和菜单,因为不同的用户有不同的权限,能访问的页面不一样

利用vue-Rounter的addRoutes方法动态添加路由,参数必须是个符合routes选项要求的数组

const router = new Router({
    routes: [
        {path: '/', redirect: '/home'},
    ]   
})
router.addRoutes([
    {
        path: '/login',
        name: 'login',
        component: () => import('../components/Login.vue')
    }
])

在动态添加路由过程中,如果有404页面,要放在最后添加,否则在登录的时候添加完页面会重定向404页面。类似于这样的规则一定要最后添加:{path: '*', redirect: '/404'}

  • 8
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值