常见的业务场景实现方案

5.业务场景建模 业务场景分析是一种需求分析方法。本文通过瑞星咖啡的示例介绍了业务场景建模的方法和步骤 阅读详情

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 属性中的 isRefresh 为 true,也需要重新请求数据
  • 当我们在详情页中删除了对应的列表项时,就可以将详情页 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'}

设计模式在具体业务场景运用 设计模式运用好坏一个项目的可维护、持续发展、扩展性、性能优化有很大的关系。本文档是经过近10年的工作经验和根据具体项目业务中提炼出来的。 大家都知道,要灵活运用设计模式,实践是最好的老师。而这些设计模式针对具体的业务,我们能否想到用什么设计模式,是关键。这个文档通过移动项目业务实例介绍一些常用的设计模式。供大家参考. 立即下载

相关推荐

业务场景搭建案例.flv

数据分析师如何搭建业务场景的小案例,来自天善智能,七周成为数据分析师(秦路)-第二周-业务。

业务场景梳理,BA必备

场景、流程梳理

2401_87233417的博客 3333

STM32定时器ARR寄存器详解:从‘水桶接水’到精准测速,你的PWM和编码器都靠它

本文深入解析STM32定时器ARR寄存器的核心原理与应用技巧,从PWM波形生成到编码器测速实战。通过水桶模型和硬件本质的比喻,揭示ARR如何作为定时器的'心跳节拍器',并详细探讨其在动态调频、死区时间配置和高精度PWM中的关键作用。特别针对编码器模式下的ARR配置和溢出补偿算法进行优化建议,帮助工程师提升系统性能和实时性。

weixin_33734785的博客 379

什么是业务场景

5.2.3 业务场景 组织应打破传统的基于技术专业化职能分工 形成的垂直业务体系,以用户日益动态和个性化 的需求为牵引构建基于能力赋能的新型业务架构,根据竞争合作优势和业务架构设计端到端的业务场景,以形成支撑柔性战略的灵活业。 组织应从目标、内容资源等方面统筹考虑,系开展业务场景设计包括但不限于: a) 分析明确 各利益相关者的业务场景需求以及可度量、实现目标 ; b) 准确界定 业务构成、过程以及面向各利益相关者的交付物 ;d c) 充分定义实现业务场景所需要的人、财、物、数据、技术等资..

4591

【一文搞懂几种常用推荐算法】

协同过滤:适合用户-物品交互数据丰富的场景。矩阵分解:能处理稀疏数据,适合显式评分预测。基于内容:依赖物品特征,解决冷启动问题。NMF:适用于非负数据(如点击率、购买记录)。根据实际需求选择算法,或结合多种方法构建混合推荐系统。

专注技术的博主 2638

esp32c6 开发实战:低功耗蓝牙(BLE)开发

蓝牙栈在此程序流程中已经启动并运行,但是应用程序的功能尚未定义。功能的定义是通过响应事件来实现的,当其他设备尝试读取或写入参数以及建立连接时就会触发事件回调函数处理。BT控制器在控制器端实现主机控制器接口(HCI),链路层(LL)和物理层(PHY)。BT控制器对用户应用程序不可见,并处理BLE堆栈的较低层。蓝牙堆栈(Bluedroid stack)定义了经典蓝牙(BT Classic)和低功耗蓝牙(BLE)的通用定义和API。的判断)等对于启动低功耗蓝牙来说不重要的代码,函数初始化和启用BT控制器。

月入鱼饵的博客 2742

常见的业务场景

常见的业务场景 场景1:数据涨跌异常如何处理? 场景2:如何评估渠道质量,确定投放优先级? 场景3:一个功能/内容上线后,如何评估其价值? 场景4:如何了解数字背后的用户? 场景5:对于羊毛党,如何查出谁在薅? 场景1:solution   对比分析,多维度拆解   常见假设:     活动影响:查对应活动页面对应动作的数据波动,关注活动是否有地域属性     版本发布:...

u013242906的博客 6079

Android 14.0 首次开机默认授予app运行时权限(去掉运行时授权弹窗)

在14.0的系统rom产品定制化开发中,在6.0以后对于权限的申请,都需要动态申请,所以会在系统首次启动后,在app的首次运行时, 会弹出授权窗口,会让用户手动授予app运行时权限,在由于系统产品开发需要要求默认授予app运行时权限,不需要用户默认授予运行时弹窗,所以需要在首次开机默认授予所有app运行时权限

安卓兼职framework和app工程师的博客 2531

业务分析系列主题:业务场景

本文介绍了了业务场景的概念、要素以及创建方法,与大家分享! 业务场景作为一种需求分析技术用途十分广泛。本文涛哥就和大家聊聊业务场景是什么,以及如何创建业务场景。 一、业务场景是什么? 所谓“场景”,本意是指:影视剧情中的人物在特定时间与空间内发生的行动。 而业务场景,意思是说:企业和商家需要在用户某个特定的环节中,适时提供给消费者可能需要的以及关联的产品或服务。 以“职场白骨精典型的一天”为例,那么业务场景包括: 一大早,“阿姨帮”的家政服务人员来敲门、做早餐; 小白们吃完早餐,用“滴滴”叫车

Larry的博客 1万+

GEE数据融合——Landsat (collection 2,level 2 )4、5、7、8、9长时间序列影像数据融合和视频导出分析

长时间序列影像数据融合是指将Landsat影像数据集合2级2(Level 2)中的4、5、7、8和9这五个卫星的数据进行融合。具体来说,这包括将同一地点的多个卫星影像数据进行处理和整合,以产生一个单一的、具有更高质量的影像产品。在长时间序列影像数据融合中,首先需要对不同卫星的影像数据进行校正,以消除不同卫星之间的光谱差异、辐射度问题和几何纠正误差等。然后,通过使用融合算法,将多个卫星的影像数据进行组合,以生成一张全新的、高质量的合成影像。

此星光明博客 1736

23、资产分散化与有效前沿:投资组合优化的核心策略

本文深入探讨了现代投资组合理论的核心概念,包括分散投资的重要性、均值-方差优化框架以及有效前沿的构建与应用。通过案例分析和数学模型,阐述了如何通过科学方法降低投资组合风险并实现最优风险收益权衡。文章还介绍了实际应用中的求解方法与决策流程,为投资者提供系统的投资组合优化指导。

cc789的博客 115

eve 中 GitHub 扩展包 @eve/git 的架构设计与实现:从职责边界到 Connect 代理的 gh 工具

`@eve/git` 是 eve 仓库中 `eve/extensions/git` 扩展的私有源码包,集中承载全部 GitHub 面向的能力:Pull Request 技能、GitHub 指令,以及在配置 `github` 后由 Connect 代理鉴权的 `gh` 工具与签名提交(signed-commit)工作流。本文将依据 [packages/eve-git/AGENTS.md](https

gitblog_01112的博客 822

Allegro如何进行四层板板层设计

板层的设计

weixin_42499230的博客 3193

死信队列-常见的业务场景

死信队列是用来接收那些无法被正常消费的消息。当消息因某些原因被拒绝、过期或投递失败,它将被发送到对应的死信队列,便于后续排查或补偿。

刘皇叔说Java的博客 1002

UNET改进77:全网首发-添加CFBlock |

本文介绍了在UNet模型中集成CFBlock注意力机制的实现方法。主要分为三个步骤:1) 创建CFBlock模块,包含卷积注意力机制和MLP;2) 在UNet连接处添加注意力模块;3) 在卷积层中嵌入注意力机制。具体实现包括:定义Conv、MLP和ConvolutionalAttention等基础模块,构建CFBlock核心结构;在UNet跳跃连接处插入注意力模块;将注意力机制整合到DoubleConv和Down模块中。该方法通过在不同位置引入注意力机制,可有效提升模型的特征提取能力。代码实现使用PyTor

AIcurator的博客 388

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

基于数据库实现的分布式锁

概述 在单机时代,虽然不需要分布式锁,但也面临过类似的问题,只不过在单机的情况下,如果有多个线程要同时访问某个共享资源的时候,我们可以采用线程间加锁的机制,即当某个线程获取到这个资源后,就立即对这个资源进行加锁,当使用完资源之后,再解锁,其它线程就可以接着使用了。例如,在JAVA中,甚至专门提供了一些处理锁机制的一些API(synchronize/Lock等)。 但是到了分布式系统的时代,这种线程...

朱小厮的博客 3万+

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Ant Design Pro中后台设计模式:常见业务场景解决方案

你是否还在为中后台系统开发中的复杂表单处理、数据表格交互、状态管理等问题而烦恼?是否在面对业务需求频繁变更时,感到代码难以维护和扩展?本文将基于Ant Design Pro框架,深入探讨常见业务场景的解决方案,帮助你提升中后台系统的开发效率和质量。 读完本文,你将能够: - 掌握Ant Design Pro中表格数据管理的最佳实践 - 学会使用分步表单处理复杂数据录入 - 理解中后台系统的状态管...

gitblog_00488的博客 774

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

服务端向客户端推送数据的实现方案

所适用的业务场景:最典型的就是聊天功能。而且 SSE 使用的是 http 协议(websocket 使用的是 ws 协议),也就是现有的服务端都支持 SSE,无需像 websocket 一样需要服务端提供额外的支持。对于当前计算机的发展来说,几乎很少出现同时不支持 websocket 和 sse 的情况,所以轮询是在极端情况下浏览器实在是不支持 websocket 和 sse 的下策。首先轮询需要不断的发起请求,每一个请求都需要经过 http 建立连接的流程 (比如三次握手,四次挥手),是没有必要的消耗。

m0_65912066的博客 1265

4种延时任务实现方案

点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天 10:33更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2021超神之路,很肝~...

芋艿V 216

告别日期选择痛点:Layui时间选择器业务场景适配方案解析

你是否还在为项目中的日期时间选择功能头疼?客户要求的格式千奇百怪,业务限制层出不穷——从简单的生日选择到复杂的酒店预订日期范围,从精确到秒的交易时间到只显示工作日的排班系统。别担心,本文将带你深入剖析Layui时间选择器(Laydate)的实战适配方案,读完你将掌握: - 5种核心业务场景的快速实现方法 - 日期格式化与自定义显示的技巧 - 高级限制与交互优化的实战代码 - 性能优化与常见问题解决...

gitblog_00611的博客 352

接口限流方案:从原理到实现

在高并发场景下,流量的突然增加可能导致服务器负载过高,甚至导致系统崩溃。为了应对这些挑战,接口限流成为一种重要的保护措施。除了常见的算法限流方案,像Nginx这样的代理服务器也提供了高效的限流机制,尤其在应对恶意流量、DDoS攻击等场景下表现优异。固定窗口计数法滑动窗口计数法漏桶算法令牌桶算法基于Nginx的IP限流分布式限流方案选择适合的限流策略取决于业务场景:如果需要平稳处理流量并避免服务崩溃,推荐使用漏桶算法。如果需要支持突发流量并提供更好的用户体验,推荐使用令牌桶算法。

weixin_44876263的博客 1569

常见负载方案

详见的负载方案: 1. 集中式负载均衡方案   通过软负载(nginx,Apache),硬负载(F5),这种方案常见于中小型企业,所有服务通过Nginx将请求分配到业务服务器上,注意点:要保证nginx的高可用。    2. 客户端负载均衡方案   提供服务注册发现机制,服务启动将ip,port等信息注册到注册中心,客户端/消费者从注册中心获取服务列表,然后根据自定义的负载策略...

andiaoqiong6043的博客 329

Java 实现本地缓存的常用方案详解

本文详细介绍了Java实现本地缓存的四种主流方案:手动实现、Guava Cache、Caffeine和Ehcache。本地缓存通过内存存储高频数据,显著提升系统性能。手动实现简单但功能有限;Guava Cache功能全面,适合中大型项目;Caffeine性能卓越,采用先进算法;Ehcache则适合企业级扩展需求。文章通过代码示例展示了各方案的使用方法,并分析了它们的优缺点,帮助开发者根据项目需求选择合适的缓存方案。

专注于与编程相关的知识内容分享 690

5000人企业的AntFlow实战:从审批卡点到性能倍增的全案解析

你是否还在为工作流系统的审批延迟、流程卡顿和扩展性不足而困扰?某5000人规模的制造企业曾面临**审批流程平均耗时超8小时**、**高峰期流程实例失败率达15%** 的严峻问题。本文将深度剖析该企业基于AntFlow重构工作流系统的全过程,展示如何通过**虚拟节点引擎**、**多租户隔离**和**三级缓存架构**,将核心指标优化至**审批耗时...

gitblog_01168的博客 507

Spring设计中事务管理机制在复杂业务场景下的应用研究

在复杂业务场景中,合理选择事务传播行为至关重要,例如在一个电商订单处理系统中,订单创建方法调用库存扣减方法,若订单创建和库存扣减都需在同一事务中完成,可使用PROPAGATION_REQUIRED;若订单创建成功但库存扣减失败,事务回滚,订单也将被撤销,避免超卖现象。结合金融、电商等多领域复杂业务案例,探讨事务传播行为、隔离级别在实际应用中的选择与配置,分析应用过程中常见问题及解决方案,为开发人员在复杂业务场景中有效运用Spring事务管理提供全面指导,助力构建数据一致性和业务完整性得以保障的应用系统。

2501_91518745的博客 436

实战 | canal 实现Mysql到Elasticsearch实时增量同步

题记 关系型数据库Mysql/Oracle增量同步Elasticsearch是持续关注的问题,也是社区、QQ群等讨论最多的问题之一。 问题包含但不限于: 1、Mysql如何同步到Elasticsearch? 2、Logstash、kafka_connector、canal选型有什么不同,如何取舍? 3、能实现同步增删改查吗? … 本文给出答案。 1、Canal同步 1.1 canal官方已支持...

铭毅天下Elasticsearch 3万+
上一篇: 实现快速排序所踩的坑
下一篇: 常见的状态码
ZoeLandia
博客等级 码龄6年 734粉丝 183原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值