前端实际开发中经常遇到的问题汇总

2,523 阅读23分钟

前端开发

第一天记录

1.接口超时问题

后台响应时间太久,对前端用户非常不友好。

全局设置超时时间(单位ms)

axios.defaults.timeout = 2500;    //2.5s

单独设置超时时间(单位ms)

axios.get('/longRequest', {
  timeout: 5000
});

最近在写前后端项目过程中,针对容易出现的timeout问题,查阅相关资料,自己整理分享一些处理方式如下:

针对前端请求,后端因为断点或者电脑反应延迟、网络延迟等现象,明明后端接口能正常返回数据还报连接超时问题,可在前端vue的utils里面的request.js里面设置如下:

image.png


前端调用后端接口 超时处理 Promise.race() 应用

对于前端来说,需要调用后端的接口来获取数据从而渲染页面,但是有时候由于网络原因等等一切原因,后端接口在5秒被未返回,此时我们就可以给出调用接口超时的error。
在这里使用Promise.race() 来解决。

Promise.race([
    axios.get(url),  //调用后端接口
    new Promise(function(resolve,reject){  //5秒后执行,如果后端接口没有返回,则直接返回error
        setTimeout(()=>{reject(new Error('request timeout')),5000})
    })
    .then((response)=>{
        //成功返回后处理的逻辑
    })
])

Promse.race就是赛跑的意思,Promise.race([p1, p2])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

2.rpx,px等像素单位

一、px (pixel)

物理像素:

设备屏幕实际拥有的像素点。比如iPhone 6的屏幕在宽度方向有750个像素点,高度方向有1334个像素点,所以iPhone 6 总共有750*1334个物理像素。

逻辑像素:

也叫“设备独立像素”(Device Independent Pixel, DIP),可以理解为反映在CSS/JS代码里的像素点数。

具体参考文章:CSS中的px与物理像素、逻辑像素、1px边框问题

二、rem(font size of the root element)

rem是CSS3新增的相对长度单位,是指相对于根元素html的font-size计算值的大小,可理解为屏幕宽度的百分比。

营养疗效:

做web app的屏幕适配,因为不同手机型号的屏幕大小都不同,所以这时候我们就不能用px来做单位,rem是适配不同手机屏幕的一种方案

食用方法:

利用rem相对于html根字体的大小,来计算相应元素的宽高:

  • 第一种方法:一般将html的font-size设置为:20px或30px或50px或100px

  • 第二种方法:利用浏览器默认字体大小(16px),所以可以设置html的字体大小为16px*62.5%=10px,这样就是html{font-size:62.5%;},而不是html{font-size:10px;}因为浏览器(PC)最小就是12px。这样一来1rem = 10px

具体参考文章:rem与px的转换

三、em(font size of the element)

em是相对于父级字体大小的,使用场景较少。

致命弱点:

1、层层嵌套比较麻烦;

2、当我想改变其中一个div的字体大小时,整个布局可能乱套;

具体参考文章:px、em、rem区别介绍

四、rpx (responsive pixel)

可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。

如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

搬运官网:

image.png

建议:

开发微信小程序时设计师可以用 iPhone6 作为视觉稿的标准。

其他参考文章:px、物理像素、rem、rpx的关系

3.实际成品上线项目:游侠客

人员组成和配比,然后说明自己负责的部分(轮播图),

具体的问题:后台响应 数据比较多时,会如何处理(vuex????)

子元素会超出父元素

怪异模型、标准模型 box-sizing content-box 上下用margin 左右用padding

项目是vue制作的

最后,你有什么想问的?

公司主要做的产品是哪方面的

小程序 app uniapp 原生app pc 后台

谢谢

3.1后台响应数据比较多时,前端会如何处理

答案是:异步任务+二维数组

先说场景 在用uniApp 开发小程序时 遇到后端有一个接口一次性返回了超100条数据

此时在前端写的代码 this.swiperList = res.data ; 直接导致微信小程序报警 如图

image.png

这样子导致页面全部受到影响,数据加载变得非常卡顿。来看如何处理。

首先找到卡顿的原因 : 小程序是使用setData进行数据传递。官方要求是单词数据传递不能超过1024kb

而正常我实际开发中大部分都是直接 this.xxx = res.data ; 这样就直接导致了setData 可能会出现传输数据过大,而影响页面性能的问题。

怎么解决?

既然一次传输过多数据不可以,我能想到的方式是,可不可以后端做分页 答案是 部分场景优先后端进行分页,前端对各种状态下进行分页请求,例如,下拉,或者,触底加载等,这里不做赘述,我们来看另外一种问题场景。

如果这些数据假设有1万条。需求就是不能做分页,一次调用解决问题,但前端需要不能让页面卡死。

答案就是 异步任务 + 二维数组 分批导入。

	groupData(data,split) {
			let result = [];
			let groupItem;
			for (var i = 0; i < data.length; i++) {
				//每20条数据分割为一个数组
				if (i % split == 0) {
					groupItem != null && result.push(groupItem);
					groupItem = [];
					console.log(groupItem,'start')
				}
				groupItem.push(data[i]);
				console.log(groupItem,'itemmm')
			}
			result.push(groupItem);
			console.log(result,'resultlll')
			return result;
		},
 
findBuyinfoBYidApi() {
			findBuyinfoBYid({
				id: this.RushBuyId
			}).then(res => {
				if (res.data.result.records && res.data.result.records.length > 0) {
					首先先不将数据绑定 而是定义临时变量保存
 					let data = res.data.result.records; //包含大量数据的数组
                    调用提前封装好的方法 将n条数据分割为20份 也就是每20条数据为一个数组
					let groupData = this.groupData(data,20); 
                    最终将得出一个二位数组 里面数组又嵌套了实际的数据 
					for (let i = 0; i < groupData.length; i++) {
						setTimeout(() => {
                            异步任务加入  每i*1000秒  并解构数据将数据实际传递
							this.VerticalSwiper.push(...groupData[i]);
						}, 1000 * i);
					}
				}
			})
		},
 
 

上图大家看的会比较直观 image.png


假如后端返回1w条数据,如何渲染呢?本文提供两种方法

  1. 分次加载数据
  2. 滚动加载数据

1.分次加载数据

 

var total = 10000; // 总条数
	var once = 1000; // 每次加载条数
	var loadCount = total / once; // 加载次数
	var recordCount = 0;
	var ulObj = document.querySelector("ul.async-load");
	function addLi(){
		// 性能优化,建立一个虚拟的dom节点
		var fragment = document.createDocumentFragment();
		for(var index = 0 ; index < once ; index ++){
			var li = document.createElement("li");
			li.innerText = `<li>这是第${recordCount}次加载</li> li${index}`;
			fragment.appendChild(li);
		}
		ulObj.appendChild(fragment);
		recordCount++;
		loop();
	}
	function loop(){
		if(recordCount < loadCount){
			window.requestAnimationFrame(addLi)
		}
	}
	loop();

2.滚动加载数据

// 滚动加载数据
	var scrollUl = document.querySelector("ul.scroll-load");
	var scrollRecordCount = 0;
	function scrollLoad(){
		var boxHeight = $(".scroll-load").height(),
		    total = 10000,
			once = 1000,
			loadCount = total / once;
			loadata(once , scrollRecordCount);
		$(".scroll-load").scroll(function(){
			var realHeight = $(".scroll-load")[0].scrollHeight;
			var scrollHeight = $(this).scrollTop();
			if(boxHeight+scrollHeight + 50 >= realHeight && scrollRecordCount < loadCount){
				// 加载数据
				loadata(once , scrollRecordCount);
			}
		})
	}
	// 加载数据
	function loadata(once , recordCount){
		console.log(recordCount);
		var fragment = document.createDocumentFragment();
		for(var index = 0 ; index < once ; index++){
			var li = document.createElement("li");
			li.innerText = `<li>这是第${recordCount}次加载 li${index}</li>`
			fragment.appendChild(li);
		}
		scrollUl.appendChild(fragment);
		scrollRecordCount++;
	}
	scrollLoad();

最后在页面加上监听页面耗时时间

window.onload = () => {
	    const load = Date.now() - performance.timing.navigationStart;
	    $("h1").html(`页面消耗了 ${load} ms`);
	}

3.2子元素超出了父元素会怎么办?

这是一个比较常见的问题,需要灵活掌握

1.如果使用float 子元素浮动,如果子元素比父元素大,会撑开父元素

此时在父元素上加上 overflow:hidden


2.flex布局子元素超出父元素

遇到的问题: 父元素是body,宽度给100%。子元素想要距离屏幕两侧各10px,但我给子元素也设置了100%且是flex布局,这时宽度超出屏幕,页面开始横向滑动! 最终想要的效果是不横向滑动页面完整展示。 现在记录一下这个问题,警告自己,当时写的时候脑子估计废掉了。

image.png

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		
<style type="text/css">
	.box{
		width:300px;
		height: 300px;
		background-color: #D2691E;
		margin:0 auto;
	}
	
	.con1{
		margin:10px;
		width: 100%;
		display: flex;
		background-color: #ED4040;
	}
</style>
	</head>
	<body>

    <div class="box">
		<div class="con1">
			1
		</div>
    </div>
	</body>
</html>

类似上图中,子元素跑出去了。解决办法就是去掉子元素中宽度100%。就变成

image.png

但是有时候就需要让子元素有一个百分比宽度,孙元素好分配子元素的宽度。那么给子元素小于100%的宽度,比如98%?

image.png

太小了,因为还有10px的边距还是展不开,可是我没办法算精准给百分之多少正好两侧都空10px,所以这种方法不行。

这时我再给子元素加个子元素,宽度给他百分之百。

.con2{
		margin:10px;
		width: 100%;
		background-color: #0087F1;
	}

image.png 这时候孙元素在子元素里面呆着,而且两侧有10px边距!由于子元素设置了flex,所以这时候就听话了。

那么一开始的问题,我要子元素距离两侧各精准10px,且宽度为100%,就需要把父元素设置为flex,特性问题,高度填充了。

image.png

写个高度

image.png

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		
<style type="text/css">
	.box{
		width:300px;
		height: 300px;
		background-color: #D2691E;
		margin:0 auto;
		display: flex;
	}
	
	.con1{
		margin:10px;
		width: 100%;
		height:20px;
		/* display: flex; */
		background-color: #ED4040;
	}
</style>
	</head>
	<body>

    <div class="box">
		<div class="con1">
			1
		</div>
    </div>
	</body>
</html>

其实这个问题不是个问题,还是自己对于flex理解不够深。为啥子元素一定要设置100%,因为子元素里还需要写其他孙元素,由百分比控制的。

二更:问题用padding解决,无需flex,但宽度需要改变(减少2 * padding)。给父元素设置左右padding,子元素宽度百分之百合适。 但是这时发现新问题,子元素margin-top无效,这是因为嵌套div中margin-top出现转移,转移给了父元素。

image.png

解决办法可以父元素加上padding-top,但是高度这时候就撑大了,需要高度减去一个padding-top;但是也可以在父元素里加overfl:hidden。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		
<style type="text/css">
	.box1{
		width:300px;
		height: 300px;
		background-color: #D2691E;
		margin:0 auto;
		display: flex;
	}
	
	.con1{
		margin:10px;
		width: 100%;
		height:20px;
		background-color: #ED4040;
	}
	
	.box2{
		width:280px;
		height: 300px;
		background-color: #D2691E;
		margin:10px auto;
		padding:0 10px;
		overflow: hidden;
	}
	
	.con2{
		margin:10px auto;
		width: 100%;
		height:20px;
		background-color: #ED4040;
	}
</style>
	</head>
	<body>

    <div class="box1">
		<div class="con1">
			1
		</div>
    </div>
	
	<div class="box2">
		<div class="con2">
			2
		</div>
	</div>
	</body>
</html>

image.png

这个padding方法需要去计算宽度或者高度了,然后对应减去增加的padding。

3.3盒子模型

1. 什么是盒子模型?

在HTML页面中,每一个元素都可以看作一个盒子。

这个盒子由:内容(content)、内边距(padding)、边框(border)、外边距(margin)四部分组成。

image.png

2. 盒模型分为几种?

盒模型根据浏览器的不同,所使用的盒子模型也不一样。(IE使用的是怪异盒模型,但是在IE10以后,改用其他的了)

盒模型分为两种:

标准盒模型:一个块的总宽度 = width + padding(左右)+ border(左右)+ margin(左右)

怪异盒模型:一个块的总宽度 = width + margin(左右)(怪异盒模型中的width已经包含了padding和border的值)

3. 标准模型和怪异模型的转换(重点)

box-sizing:content-box; 将采用标准模式的盒子模型标准

box-sizing:border-box; 将采用怪异模式的盒子模型标准

box-sizing:inherit; 规定应从父元素继承 box-sizing 属性的值。

4. JS盒模型

除了标准盒模型和怪异盒模型外,在JS中还有一个JS盒模型,了解即可

image.png

5. 盒模型的使用

使用盒模型会产生双边距重合问题,这个时候就需要 BFC(下篇讲解)


第二天记录

1.权限分配问题

1.登录控制 哪些页面不需要登录就可以进入

实现思路: 在路由配置metal里配置是否需要登录 默认需要登录,在路由之前的钩子里进行判断

2.路由控制 不同角色访问不同的页面

实现思路:路由配置分成静态路由和动态路由,动态路由部分按权限返回的结果进行动态加载

3.页面上的操作按钮控制 不同的角色在同一个页面 可以点击的按钮不同

写一个命令进行操作控制

4.列表上增加删除修改 控制 在获取列表数据是返回显示哪些按钮

5.菜单显示控制 根据不同的角色显示不同的菜单

6.账号切换时 重新加载对应路由

image.png



FinClip 前端工程师如何在前端中实现不同角色与权限的控制及落地,从而控制不同的用户能够访问不同的页面呢?

前言

对于大部分管理后台而言,角色权限都是一个重要的环节。通过角色权限的配置,我们可以轻松的调整各个用户所拥有的各个模块或者说页面的权限,从而让用户只能访问到对应权限的页面。

通俗易懂的来说,就是哪些页面是向所有用户开放的,哪些是需要登录后才能访问的,哪些是要拥有xx角色权限才能访问的等等(这里的xx指的是管理员、普通成员等这些的角色)。

在后台管理系统中角色权限的方案设计是很重要的。

  • 其一,好的设计能为后面新增的模块或者说页面省下很多功夫。
  • 其二,好的设计能为之后的拓展功能(比如权限具体控制某个按钮等)提供更多的维护和设计思路。
  • 其三,好的设计可使代码可读性更强,更能一眼区分开权限代码与业务代码。

对于角色权限而言,真正进行把关的是应该是后端。 首先是因为前端的相关代码校验是可以被数据造假通过的,安全性并不高。其次,在一个系统中前端所调用的接口是不应该被无权调用通过并且返回数据的。因此接口这块后端必须严格根据权限去控制,谨防无权限直接调用得到数据返回。简而言之就是即使前端没有把控页面和权限,用户也不能够获取没有权限的页面或者模块的相关数据和操作的,后端应该是可以判断他越权访问并拒绝返回数据的。但是若无前端把控,那这样整个系统的体验将会很糟糕,比如访问无权限页面时各种报错问题等等。因此前端在角色权限中更多职责的应是完善用户的交互体验

角色权限控制的整个流程中,前端整个流程步骤应是首先展示无需登录的默认页面(比如404页面、登录页、注册页),然后在登录或浏览器刷新时调用后端接口拿到后端给的该账户的权限数据,然后将数据注入到系统中,整个系统拿到权限数据后就开始对页面的展现内容以及页面导航进行生成,最终生成一个只展示当前用户所拥有对应权限的系统。从而达到整个角色权限的控制。综上所述,前端在角色权限中更多职责的应是完善用户的交互体验。

本文将从下面三个方面,讲述前端角色权限的实现

  • 登录权限控制
  • 角色权限控制
  • 内容权限控制

1.登录权限控制

登录权限控制,简而言之就是实现哪些页面能被未登录的用户访问,哪些页面只有用户登录后才能被访问。

实现这个功能也很简单,下面例举出 2 种常见的实现方案。

第一种为将无需登录的页面路由放在一起,代码如下:

let invisible = [
  {
    path: '/login', //登录页面
    name: 'Login',
    component: Login,
  },
  {
    path: '/404',
    name: 'index-notFount',
    component: () => import('@/pages/core/NotFount/index'),
  },
];

export default invisible;

定义一个invisible数组,数组中包含着所有无需登录就可以查看的页面路由。

// 引入无需登录的页面
  import invisible from './invisible';

  let router = new Router({
    routes: [
      ...invisible,
    ],
  });

  const invisibleMap = [];
  invisible.forEach(item => {
    if (item.name) {
      invisibleMap.push(item.name);
    }
  });

  router.beforeEach(async (to, from, next) => {
    if (!invisibleMap.includes(to.name)) {
        // 业务逻辑判断登录等
    }
    else {
      next();
    }
  })

引入invisible,路由名称映射到invisibleMap数组上,在路由守卫中拦截判断。由此做到无需登录的页面可以直接查看(放在invisible数组中),需要登录的页面则会进行登录等业务判断。

除上述方法外,也可在路由对象中以添加meta的方式去实现登录页面权限控制,相关代码如下:

export const routes = [
      {
         path: '/login', //登录页面
         name: 'Login',
         component: Login,
      },
      {
         path:"/list", // 列表页
         name:"List",
         meta:{
            need_login:true //需要登录
         }
      }
    ]

代码如上所示,登录页面由于无需登录,因此可不用设置meta.need_login属性,然而列表页面需要登录,因此设置need_login属性。

同第一种方式一样,真正的拦截在路由守卫之中,代码如下:

router.beforeEach((to, from, next) => {
  if (to.meta.need_login) {
    // 业务逻辑判断登录等
      
  } else {
    next();
  }
});

此处拿到need_login字段,判断是否为需要登录的路由页面,如若是,则进行下一步登录逻辑判断等,如若不是,则可放行。

2.角色权限控制

在讨论角色权限控制之前,我们应该先清楚一个点:在引入了角色概念的系统中,任何该系统中的账号都应该至少拥有一个或几个角色身份,这样该账号就拥有当前这一个角色(或几个角色)的相关权限功能。简而言之,我们不会直接把权限赋予给用户,而是通过角色来赋予给用户。角色权限控制主要是解决给不同角色赋予不同权限从而赋予不同账户权限,接下来先了解一下角色的概念。

在某个系统当中,存在3个比较普遍性的角色:普通成员、管理员以及超级管理员。普通成员能够浏览系统的 a、b、c 三个模块,但是它不能查看和编辑 d、e 模块(假设只有d、e模块可编辑)。管理员拥有普通会员的所有权限,另外它还能查看 d、e 模块和编辑d模块。超级管理员拥有此系统的所有权限,因此相比于管理员而言,在这就多出一个编辑e模块。

当然,就上述而言,都是一些简单的角色划分。本文不在此做更多的深讨。

那么角色权限,在设计上能否以前端为主导呢?(即后端只为账号标识为某某角色,控制角色权限这块由前端主导)

我们通过一个简单的例子来回答上述问题。

我们暂且根据以上的较为普遍的角色来做简单设计。

export const permission = {
   member:["Home"], //普通成员
   admin:["Home" ,"Notify"],  // 管理员
   super_admin:["Home" ,"Notify","Manage"]  // 超级管理员
 }

在上述角色权限中,普通成员拥有首页权限,管理员拥有首页、通知权限,超级管理员则还额外拥有管理的权限。

如果以前端为主导,那么后端则应是在登录接口返回当前账户所属哪些角色。拿到该账号的角色后后就去上面的配置文件里取出该角色所能访问的页面权限,随后将这部分页面权限加载到系统中从而达到权限控制的目的(需要注意的是,数组里面的值应和对应页面的路由名称相匹配)。

在上述设计中,后端只负责给账户标识对应角色,并且写入库中,在登录时返回给前端此账户对应的角色。到了这一步,可能会有同学存在疑问,这样不是也挺好的吗,前端不也可以控制角色权限嘛。别急,那现在来思考一个问题。如果对一个已上线的系统项目,现需要紧急新增一个角色比如x,那么前端就要急需修改配置文件(配置文件如上图),此时还不够,还需把之前的y用户移动到x角色下,那么此时不光是前端要改配置文件,后端也需要在库中把y用户移动到x角色下。这样的改动显得非常容易出错且复杂。

综上所述,在角色权限这块,其实最好的办法就是交给后端去配置,有哪些角色,账户对应哪些角色这些逻辑应当是后端负责,后端通过登录直接返回该账户所拥有的权限,前端这块无需过度关注角色主要职责应是根据后端的权限返回,展示对应的权限页面和菜单。这样即使碰到上述的修改也能轻便灵巧的解决。

以下介绍角色权限的方案。

如后端返回的账户权限结构如下

{
   "home": {
     "id":"100",
     "name":"home",
     "desc":"首页",
     "value":true,
     "children": [],
   }
 }

在这个权限结构之中,id为页面或者说模块的唯一标识id,name此处最好与前端路由页面对象的name值相对应,desc为菜单上展示的名称,value代表这个模块或者页面是否展示,children数组为此页面的二级页面数组,对于路由的权限控制和菜单的渲染生成都有着重要的影响。

在此结构中,前端通过判断 value 来决定这个页面是否有权限展示,children 下为当前页面或者说模块下的二级页面,三级页面等,结构跟 home 应是一样的。如若一级页面value为false,那下面的二级、三级应当都无权展示。

此时前端需要做的是递归遍历后端返回的这个结构,当判断value为 false 的时候,把对应到的路由页面给过滤掉。

// 生成过滤路由和菜单的方法  
function filterRouter(arr, obj, type) {
  if (Array.isArray(obj)) {
    // 数组处理
    obj.forEach(item => {
      handleRouterItem(arr, item, type);
    });
  } else {
    // 对象处理
    for (let item in obj) {
      handleRouterItem(arr, obj[item], type);
    }
  }
}

// 处理每个元素节点
function handleRouterItem(arr, item, type) {
  // 确定这个页面或模块是不展示的
  if (item.value === false) {
    if (type === 'menu') {
      assistance(arr, routerMap[item.name]);
    } else {
      assistanceRouter(arr, routerMap[item.name]);
    }
  } else if (item.childrens && item.childrens.length > 0) {
    filterRouter(arr, item.childrens, type);
  }
}

function assistanceRouter(arr, name, obj) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i].name === name) {
      // 无权限页面设置meta字段或者直接删除
      // arr.splice(i, 1);
      Vue.prototype.$set(arr[i].meta, 'hasRoleAuth', false);
      return true;
    } else {
      if (arr[i].children && arr[i].children.length > 0) {
        if (assistanceRouter(arr[i].children, name, arr[i])) {
          return;
        }
      }
    }
  }
}

function assistance(arr, name, obj) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i].name === name) {
      arr.splice(i, 1);
      return true;
    } else {
      if (arr[i].children && arr[i].children.length > 0) {
        if (assistance(arr[i].children, name, arr[i])) {
          return;
        }
      }
    }
  }
}

export const rolePermission = () => {
  // router为所有页面的路由结构,roleRouter为后端返回的角色权限对象
  filterRouter(router, roleRouter);
  router.addRoutes(router);
}

在上述代码中,router为前端的路由对象数组,roleRouter为后端返回的该账号所拥有的角色权限的相应的数据结构。

在filterRouter函数中,遍历roleRouter数据结构中的每一项,把每一项的处理逻辑交给handleRouterItem。

在handleRouterItem函数中,判断每一项的value字段是否为false,如若为false,则说明这个模块或者说页面是没有权限展示的。那么则应该交给assistanceRouter和assistance过滤掉该模块或者页面。

在assistanceRouter和assistance函数中,它俩的主要作用则是在数组路由对象中找到name值和参数name一致的路由对象,在assistanceRouter函数中则是在meta对象中用hasRoleAuth字段做以标记,代表无权访问用以路由权限也可以和assistance函数一样做过滤处理。在assistance中则是把无权限的页面过滤用于菜单生成。

以上的这种方式是通过递归遍历后端的权限字段,将已有的路由结构给过滤一遍,从而生成对应权限的路由结构和菜单的一种方式。

这样就实现了用户只能按照他对应的权限列表里的权限规则访问及菜单看到相应的页面。

动态添加路由rolePermission这部分代码最好单独封装起来,因为用户登录和刷新页面时都需要调用。

3.退出及切换用户

在引入角色权限的系统之中,退出及切换用户也是相当重要的。因为不同账号的权限往往不同。因此要格外注意退出及切换账号的时候不能带着上一个账户的权限信息,不然会引发严重的漏洞。

那么针对角色权限的退出及注销我们可以采取哪些解决方案呢。

解决方案有两种。

第一种方案是用户在退出或切换账户后刷新浏览器,但是这种方案会给用户带来不那么友好的体验。

第二种方案则是当用户退出后,初始化相关路由实例,代码如下:。

import Router from 'vue-router';
import router from '@/router';
import store from '@/store/index.js';
import invisible from '@/router/invisible';

export const resetRouter = () => {
  let newRouter = new Router({
    routes: [...invisible],
  });
  router.matcher = newRouter.matcher;
  store.commit('CLEAR_ROLE_AUTH');
};

初始化当前账户的动态路由,并且将vuex中的当前角色的权限信息也一并给清除掉。

4.内容权限控制

在上一part的角色权限中,它做到了让不同账户访问不同的页面,但是往往有时候需要更细腻的去控制页面中的某个元素,如增删改一一对应了一个按钮,这个时候,就需要针对页面的内容,做出内容权限控制。

在本文中,就简单的以增删改作为内容权限控制内容。

沟通后,现在后端的返回结构应该是这样:

{
   "home": {
     "id":"100",
     "name":"home",
     "desc":"首页",
     "value":true,
     "children": [],
     "options": {
     	"create": true,
        "delete": true,
        "update": true,
     }
   }
 }

在当前这个结构中,home首页存在三个内容权限控制,分别为创建、删除、更新(如需新增,可在与后端沟通好字段后加在options内)。

在拿到这样的数据结构后,我们还需设计一个方案,让这个权限结构和页面内容相关联起来,在这,我们用到了指令。我们创建一个全局的自定义指令 permission,伪代码如下:

import router from '@/router';
import store from '@/store';

app.directive('permission', {
  mounted(el, binding, vnode) {
    const permission = binding.value; // 获取指令值
    const current_page = router.currentRoute.value.name; // 获取当前路由名称
    const options = getOptions(current_page) // getOptions方法为拿到路由名称对应的角色权限对象
    if (!options[permission]) {
      el.parentElement.removeChild(el); // 没有该内容权限
    }
  },
});

在上述代码中,首先拿到指令值,再获取到当前路由名称,通过getOptions方法拿到该路由名称对应的角色权限数据结构中的相关对象,进而判断options内是否有该内容权限,如若没有,则将该dom移除。

在html中,指令的用法如下:

<template>
    <div>
      <button v-permission="'create'">创建</button>  
	  <button v-permission="'update'">修改</button>
      <button v-permission="'delete'">删除</button>
    </div>
</template>

看到这,相信大家都已经明白了内容权限控制的流程和逻辑。简而言之,就是将角色权限的相关数据结构与dom相关通过指令绑定起来。

对于特殊的业务场景,如隐藏后导致样式混乱、UI 设计不协调等。此时则应具体根据项目内的需求去判断是否隐藏还是弹出提示无权限等,在本文中不做过多的叙述。

尾言

权限控制在前端更多的应用是为了优化用户体验,除此以外也为应用加固了一层防护,但是需要注意的是前端的相关校验是可以通过技术手段破解的。然而权限问题关乎到软件系统所有数据的安危。

因此为了确保系统平稳运行,前后端都应该做好自己的权限防护。

本文转载至 FinClip 博客。