整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          純CSS 實現格子背景(國際象棋棋盤) - 掘金

          純CSS 實現格子背景(國際象棋棋盤) - 掘金

          文簡介

          點贊 + 收藏 + 關注=學會了

          這次會使用css畫出一個格子背景。并且一步步分析如何實現~

          思路

          直接給答案:通過2個相等的直角三角形拼接,形成一個正方形。

          三角形可以使用 background-image 的漸變來實現。

          html,
          body {
            margin: 0;
            width: 100%;
            height: 100%;
          }
          body {
            background-image: linear-gradient(45deg, #000 25%, transparent 0);
          }

          此時出來的效果如上圖所示。

          做一個45度的線性漸變,第一個顏色是#000(黑色),占整個背景貼片的25%,其余部分都是紅色。

          在上面的基礎上,用 background-size 來控制背景貼片的大小。

          body {
            background-image: linear-gradient(45deg, #000 25%, transparent 0);
            background-size: 200px 200px;
          }

          開始有點想法了嗎?

          此時如果我們再畫多一個反過來的黑色的直角三角形,拼在一起不就成了正方形了嗎?

          反過來的三角形怎么畫呢?我嘗試將黑色從 25% 改成 75%,會得到以下效果

          body {
            background-image: linear-gradient(45deg, #000 75%, transparent 0);
            background-size: 200px 200px;
          }

          可以看到紅色的三角形就是原本黑色三角形反過來的樣子。

          把上圖的“白色三角形”變成黑色,原本的黑色三角形(25%)繼續保留。

          于是我又加多層漸變~

          body {
            background-image:
              linear-gradient(45deg, #000 25%, transparent 0),
              linear-gradient(45deg, transparent 75%, #000 0);
            background-size: 200px 200px;
          }

          簡化一下代碼:

          body {
            background-image: linear-gradient(45deg, #000 25%, transparent 0, transparent 75%, #000 0);
            background-size: 200px 200px;   
          }

          最后再做多一層上面的效果,然后移動一下其中一層的位置,就可以合并成一個黑色正方形。

          body {
            background-image:
              linear-gradient(45deg, #000 25%, transparent 0, transparent 75%, #000 0),
              linear-gradient(45deg, #000 25%, transparent 0, transparent 75%, #000 0);
            background-position: 0 0, 100px 100px;
            background-size: 200px 200px;
          }

          大功告成。

          最后需要提醒的是,在本例中 background-position 第二個漸變的位移是 background-size 的一半,這樣就能實現這種格子背景了~

          完整代碼

          <style>
            html,
            body {
              margin: 0;
              width: 100%;
              height: 100%;
            }
          
            body {
              background-image:
                linear-gradient(45deg, #000 25%, transparent 0, transparent 75%, #000 0),
                linear-gradient(45deg, #000 25%, transparent 0, transparent 75%, #000 0);
              background-position: 0 0, 100px 100px;
              background-size: 200px 200px;
            }
          </style>

          這是做成背景的完整代碼。

          單實現2048小游戲

          想實現2048游戲書寫代碼時可以分為三個步驟

          一、HTML部分

          先書寫HTML把游戲結構搭建出來

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
              <link rel="stylesheet" href="index.css">
          </head>
          <body>
          <!--最外部的大框-->
          <div class="outermost"> //包裹游戲全局的大盒子
              <!--title-->
              <span class="top"><b>SCORE:<span id="score01"></span></b></span>//頂部實時顯示的游戲分數
              <!--游戲大框框-->
              <div class="big">//2048游戲為四行四列因此需要16個div盒子
                  <div class="cell" id="c00"></div>
                  <div class="cell" id="c01"></div>
                  <div class="cell" id="c02"></div>
                  <div class="cell" id="c03"></div>
           
                  <div class="cell" id="c10"></div>
                  <div class="cell" id="c11"></div>
                  <div class="cell" id="c12"></div>
                  <div class="cell" id="c13"></div>
           
                  <div class="cell" id="c20"></div>
                  <div class="cell" id="c21"></div>
                  <div class="cell" id="c22"></div>
                  <div class="cell" id="c23"></div>
           
                  <div class="cell" id="c30"></div>
                  <div class="cell" id="c31"></div>
                  <div class="cell" id="c32"></div>
                  <div class="cell" id="c33"></div>
              //游戲結束時會彈出的提示框
              </div>
              <!--提示框-->
              <div class="tips" id="gameover">
                 <p>GAME OVER!!! <br>
                     SCORE: <span id="score02">0</span><br>
                     <button class="startbtn">重新開始</button>
                 </p>
              </div>
              <!--重玩一遍-->
              <div class="foot">
                  <button class="replay"><a>重玩一遍</a></button>
              </div>
          </div>
          <script type="text/javascript" src="index.js"></script>
          </body>
          </html>

          二、css部分

          經過了第一步的搭建游戲框架,第二部就是給游戲添加樣式,使它能顯示出來

          *{
              padding: 0px;
              margin: 0px auto;
              font-family: Arial;
           
          }
          /*最外部的大框*/
          .outermost{
              width: 480px;
              height: 600px;
              font-size: 40px;
              margin-top: 120px;
          }
          /*title*/
          <!--頂部顯示分數的樣式-->
          .top{
              margin: auto;
          }
          .top span{
              color: red;
          }
          /*游戲大框框*/
          .big{
              width: 480px;
              height: 480px;
              background:pink;
              border-radius: 8px;
          }
          <!--給每一個盒子包裹的小框子添加樣式-->
          .cell{
              list-style: none;
              float: left;
              display: inline-block;
              width: 100px;
              height: 100px;
              line-height: 100px;
              text-align: center;
              background-color: #fbf8cd;
              margin-left: 16px;
              margin-top: 16px;
              border-radius: 6px;
          }
          <!--提前把出現的數2、4、8、16等的數所在的格子給添加好樣式增加游戲體驗感-->
          .n2{background-color:#f65e3b;color:#776e65}
          .n4{background-color:#33b5e5;color:#776e65}
          .n8{background-color:#f2b179;color:#776e65}
          .n16{background-color:#f59563;color:#776e65}
          .n32{background-color:#f67c5f;color:#776e65}
          .n64{background-color:#f65e3b;color:#776e65}
          .n128{background-color:#edcf72;color:#776e65}
          .n256{background-color:#edcc61;color:#776e65}
          .n512{background-color:#9c0;color:#776e65}
          .n1024{background-color:#33b5e5;color:#776e65;font-size:40px}
          .n2048{background-color:#09c;color:#776e65;font-size:40px}
          /*提示框樣式*/
          .tips{
              border: 1px solid #cccccc;
              background: #FFFFFF;
              width: 400px;
              height: 200px;
              border-radius: 10px;
              color: #ff4456;
              text-align: center;
              line-height: 60px;
              position: absolute;
              top: 50%;
              left: 50%;
              margin-left: -200px;
              margin-top: -100px;
              display: none;
          }
          .tips .startbtn{
              height: 50px;
              width: 200px;
              color: #FFFFFF;
              font-size: 20px;
              line-height: 50px;
              border-radius: 10px;
              background: cornflowerblue;
              border: none;
          }
          /*重玩一遍*/
          .foot{
              width: 200px;
              height: 50px;
          }
          .foot>.replay{
              width: 200px;
              height: 50px;
              background: aquamarine;
              margin-top: 60px;
              color: lightpink;
              border:0;
              font-size: 24px;
              font-weight: bold;
              border-radius: 6px;
          }

          書寫好了HTML+CSS部分游戲的模樣也就出來了,如下圖所示:

          三、JS部分

          下面就到了最后也是最關鍵的一步----添加行為,也就是JS部分的書寫,給其添加效果

          //創建一個對象,里面存儲所有的游戲數據及游戲方法
          var game={
          	data : [],   //定義一個數組,用來存所有的游戲的數據
          	score : 0,   //定義一個分數的屬性
          	gamerunning : 1,   //定義一個游戲運行的狀態,將其設置為1與其他狀態區分開
          	gameover : 0,     //定義一個游戲結束的狀態
          	status : 0,      //這個是目前游戲的狀態,時刻的跟上面兩個狀態做比較,確定游戲處于運行或者結束
          	start : function(){   //游戲開始時候的方法
          //		游戲開始的時候肯定是要把游戲的狀態設置成游戲運行的狀態
          //		this==game
          		this.status=this.gamerunning;
          //		游戲開始的時候分數清空
          		this.score=0;
          //		數組中的所有元素全部設置成0
          		this.data=[
          			[0,0,0,0],
          			[0,0,0,0],
          			[0,0,0,0],
          			[0,0,0,0]
          		];
          		this.randomNum();//調用下面自定義的隨機函數,可以在移動和開始的時候隨機出來一個數
          		this.randomNum();//調用兩次是因為這是游戲開始時的方法,開局隨機出現兩個數和位置,因此需要調用兩次
          		this.dataView();//調用下面所寫的更新視圖的方法
          	},
          //	隨機數的函數,開始的時候隨機生成,移動的時候隨機生成
          	randomNum: function(){
          		while(true){
          			//		隨機生成行和列 0 - 3隨機整數
          			var r=Math.floor( Math.random() * 4 );   //隨機生成一個行
          			var c=Math.floor( Math.random() * 4 );   //隨機生成一個列
          			
          			if(this.data[r][c]==0){
          				var num=Math.random() > 0.5 ? 2 : 4;//隨機出現2或4
          				this.data[r][c]=num;
          				break;
          			}
          		}
          	},
          //	更新試圖的方法
          	dataView: function(){
          //		大的循環,然后把所有的元素全部遍歷一遍
          		for(var r=0; r < 4; r++){
          			for(var c=0; c < 4; c++){
          //				找到對應的div
          				var div=document.getElementById("c" + r + c);  //字符串拼接
          				if(this.data[r][c] !=0){
          //					數組中對應的內容放到格子上面去
          					div.innerHTML=this.data[r][c];
          //					樣式也寫成對應的
          					div.className="cell n" + this.data[r][c];
          				}else{
          					div.innerHTML="";
          					div.className="cell"
          				}
          			}
          		}
          //		更新分數
          		document.getElementById("score01").innerHTML=this.score;
          		//游戲沒有結束的時候 彈出層時刻都是隱藏的
          		if(this.status==this.gamerunning){
          			document.getElementById("gameover").style.display="none";
          		}else{
          			document.getElementById("gameover").style.display="block";
          			document.getElementById("score02").innerHTML=this.score;
          		}
          	},
          //	判斷游戲是否結束的方法
          	isgameover: function(){
          		for(var r=0; r < 4; r++){
          			for(var c=0; c < 4; c++){
          				if(this.data[r][c]==0){  //里面有空格子的時候,游戲還是可以運行
          					return false;   //表示游戲還沒有結束
          				}
          				if(c < 3){//判斷左右是否有相同的
          					if(this.data[r][c]==this.data[r][c+1]){
          						return false;
          					}
          				}
          				if(r < 3){
          					if(this.data[r][c]==this.data[r+1][c]){
          						return false;
          					}
          				}
          			}
          		}
          		return true;
          	},

          //移動的方法,左 右 上 下四個部分

          //	左 右 上 下
          //	左移的方法
          	moveLeft: function(){
          		var before=String(this.data);   //之前做一次轉換
          //		具體的移動需要處理的邏輯,直接處理好每一行即可
          		for(var r=0;r < 4;r ++){
          			this.moveLeftInRow(r);
          		}
          		var after=String(this.data);  //移動之后再做一次轉換
          //		如果說移動之前不等于移動之后,肯定是發生了移動
          		if(before !=after){
          			this.randomNum();   //生成隨機數
          //			生成的隨機數可能會造成游戲的gameover
          			if(this.isgameover()){
          //				改變游戲的狀態
          				this.status=this.gameover
          			}
          //			更新視圖
          			this.dataView();
          		}
          	},
          	moveLeftInRow: function(r){   //只去做處理每一行的邏輯
          		for(var c=0; c < 3; c++){
          			var nextc=this.getNextinRow(r,c);
          			if(nextc !=-1){
          				if(this.data[r][c]==0){
          //					如果等于0,直接替換
          					this.data[r][c]=this.data[r][nextc];
          					this.data[r][nextc]=0;  //位置恢復成0
          					c --;   //要讓位置恢復到原地
          				}else if(this.data[r][c]==this.data[r][nextc]){
          					this.data[r][c] *=2;   //位置直接翻一倍
          					this.data[r][nextc]=0;
          					this.score +=this.data[r][c];  //更新分數
          				}
          			}else{   //沒有找到
          				break;   //直接退出循環
          			}
          		}
          	},
          	getNextinRow: function(r,c){
          		for(var i=c + 1; i < 4; i++){
          			if(this.data[r][i] !=0){
          				return i;    //表示已經找到位置,并且把位置返回出來
          			}
          		}
          		return -1;   //返回一個標識符
          	},
          //	右移的方法
          	moveRight: function(){
          		var before=String(this.data);
          		for(var r=0; r < 4; r++){
          			this.moveRightInRow(r);
          		}
          		var after=String(this.data);
          		if(before !=after){
          			this.randomNum();
          			if(this.isgameover()){
          				this.status=this.gameover;
          			}
          			this.dataView();
          		}
          	},
          	moveRightInRow: function(r){
          		for(var c=4; c > 0; c--){
          			var prevc=this.getPrevInRow(r,c);
          			if(prevc !=-1){
          				if(this.data[r][c]==0){
          					this.data[r][c]=this.data[r][prevc];
          					this.data[r][prevc]=0;
          					c ++
          				}else if(this.data[r][c]==this.data[r][prevc]){
          					this.data[r][c] *=2;
          					this.data[r][prevc]=0;
          					this.score +=this.data[r][c];
          				}
          			}else{
          				break;
          			}
          		}
          	},
          	getPrevInRow: function(r,c){
          		for(var i=c - 1; i >=0; i--){
          			if(this.data[r][i] !=0){
          				return i;
          			}
          		}
          		return -1;
          	},
          //	上移
          	moveUp: function(){
          		var before=String(this.data);
          		for(var c=0; c < 4; c++){
          			this.moveUpInCol(c);
          		}
          		var after=String(this.data);
          		if(before !=after){
          			this.randomNum();
          			if(this.isgameover()){
          				this.status=this.gameover;
          			}
          			this.dataView();
          		}
          	},
          	moveUpInCol: function(c){
          		for(var r=0;r < 4; r++){
          			var nextr=this.getNextInCol(r,c);
          			if(nextr !=-1){
          				if(this.data[r][c]==0){
          					this.data[r][c]=this.data[nextr][c];
          					this.data[nextr][c]=0;
          					r -- ;
          				}else if(this.data[r][c]==this.data[nextr][c]){
          					this.data[r][c] *=2;
          					this.data[nextr][c]=0;
          					this.score +=this.data[r][c];
          				}
          			}else{
          				break;
          			}
          		}
          	},
          	getNextInCol: function(r,c){
          		for(var i=r + 1; i < 4; i++){
          			if(this.data[i][c] !=0){
          				return i;
          			}
          		}
          		return -1;
          	},
          //	下移的方法
          	moveDown: function(){
          		var before=String(this.data);
          		for(var c=0;c < 4; c++){
          			this.moveDownInCol(c);
          		}
          		var after=String(this.data);
          		if(before !=after){
          			this.randomNum();
          			if(this.isgameover()){
          				this.status=this.gameover;
          			}
          			this.dataView();
          		}
          	},
          	moveDownInCol: function(c){
          		for(var r=3; r > 0; r--){
          			var prev=this.getPrevIncol(r,c);
          			if(prev !=-1){
          				if(this.data[r][c]==0){
          					this.data[r][c]=this.data[prev][c];
          					this.data[prev][c]=0;
          					r -- ;
          				}else if(this.data[r][c]==this.data[prev][c]){
          					this.data[r][c] *=2;
          					this.data[prev][c]=0;
          					this.score +=this.data[r][c];
          				}
          			}else{
          				break;
          			}
          		}
          	},
          	getPrevIncol: function(r,c){
          		for(var i=r - 1; i >=0; i--){
          			if(this.data[i][c] !=0){
          				return i;
          			}
          		}
          		return -1;
          	},
          }
          game.start();
          console.log(game.data)
          console.log(game.status);
          console.log(game.score);
          //鍵盤事件
          document.onkeydown=function(){
          	if(event.keyCode==37){
          		//console.log("左")
          		game.moveLeft();
          	}else if(event.keyCode==38){
          		//console.log("上")
          		game.moveUp()
          	}else if(event.keyCode==39){
          		//console.log("右")
          		game.moveRight()
          	}else if(event.keyCode==40){
          		//console.log("下")
          		game.moveDown()
          	}
          }
          //touch事件
          //手指按下
          var startX;//設定開始起始位置的x坐標
          var startY;//設定開始起始位置的y坐標
          var endX;//設定結束滑動位置的x坐標
          var endY;//設定結束滑動位置的y坐標
          document.addEventListener('touchstart',function(){
          //	console.log("手指按下了屏幕")
          	console.log(event);
          	startX=event.touches[0].pageX;
          	startY=event.touches[0].pageY;
          })
          //手指移動
          //document.addEventListener('touchmove',function(){
          //	console.log("手指的移動")
          //})
          //手指松開
          document.addEventListener("touchend",function(){
          //	console.log("手指松開")
          	console.log(event);
          	endX=event.changedTouches[0].pageX;//如何獲取結束時的位置x
          	endY=event.changedTouches[0].pageY;
          	var X=endX - startX;
          	var Y=endY - startY
          	var absX=Math.abs(X) > Math.abs(Y);
          	var absY=Math.abs(Y) > Math.abs(X);
          	if(X > 0 && absX){
          		console.log("右滑動")
          		game.moveRight()
          	}else if(X < 0 && absX){
          		console.log("左滑動")
          		game.moveLeft()
          	}if(Y > 0 && absY){
          		console.log("下滑動")
          		game.moveDown()
          	}if(Y < 0 && absY){
          		console.log("上滑動")
          		game.moveUp()
          	}
          })

          如下為游戲效果圖

          就這樣一個簡單的2048游戲就完成啦~


          最后

          非常感謝您能看到這里~

          關注我~帶給你更多驚喜~

          簡單介紹下自定義指令

          在介紹 Permission 指令之前,我們先來簡單了解一下自定義指令是什么。

          Vue的自定義指令通過Vue.directive方法來創建。

          • bind:指令第一次綁定到元素時調用。在這里可以執行一次性的初始化設置。
          • inserted:被綁定元素插入父元素時調用。注意,父元素可能還未存在,所以不能進行DOM操作。
          • update:被綁定元素所在的組件更新時調用,但是可能發生在其子組件更新之前。可以比較更新前后的值,執行相應的操作。
          • componentUpdated:被綁定元素所在的組件及其子組件全部更新后調用。可以執行操作,例如更新DOM。
          • unbind:指令與元素解綁時調用。可以執行清理操作。

          除了上述的鉤子函數外,還可以在指令對象中定義其他屬性和方法:

          • name:指令的名稱,用于在模板中綁定指令。
          • value:指令的綁定值,可以是一個表達式或變量。
          • arg:指令的參數,用于傳遞額外的信息。
          • modifiers:修飾符對象,可以用于傳遞額外的標記信息。
          • el:指令所綁定的元素。
          • vm:指令所屬的Vue實例。
          • expression:指令的表達式。
          • context:指令所在上下文的Vue實例。

          下面是一個簡單的自定義指令示例:

          Vue.directive('my-directive', {
            bind(el, binding, vnode) {
              // 初始化設置
            },
            inserted(el, binding, vnode) {
              // 元素插入父元素時調用
            },
            update(el, binding, vnode) {
              // 組件更新時調用
            },
            componentUpdated(el, binding, vnode) {
              // 組件及子組件更新后調用
            },
            unbind(el, binding, vnode) {
              // 解綁時調用
            }
          });
          


          在模板中使用自定義指令:

          <div v-my-directive="value"></div>
          


          內置指令與自定義指令

          常用的內置指令有 v-if、v-show、v-bind、v-for等等,大家都用過,也就不在過多贅述了。

          先問一個問題

          Q: Vue 自定義指令在什么時機執行?

          A: 是在運行時解析并執行的

          從內置到自定義指令中間到底發生了什么?

          可能是由于之前沒有深入了解,一直認為內置指令和自定義指令都在編譯時解析。

          從下圖中可以編譯時內置指令在到 Render 時已經執行完成,然而到了運行時之后才開始自定義指令的解析工作。

          簡單舉個例子

          常常碰到的面試題,“v-if 和 v-show有什么區別”,區別可不是簡簡單單的一個前者是直接刪除DOM,后者是改變display屬性。

          v-if 在生成 VNode 前就已經在模板編譯階段進行了判斷,而 v-show 也是在編譯時解析,只不過在 v-if 在編譯時就確定了渲染元素,而v-show在運行時根據條件進行顯示和隱藏。

          如下摘抄 《深入淺出Vue.js》中 15.1.1 中 v-if 指令的原理概述 的部分代碼

          v-if

          <!-- 模板 -->
          <li v-if="has">if</li>
          <li v-else>else</li>
          


          // 編譯后
          (has)
              ? _c('li',[_v("if")])
              : _c('li',[_v("else")])
          


          當 has 為 true,就會將第一個 li 元素創建 VNode。

          • _c 指的是 createElement,用于創建組件節點。
          • _v 是用來創建文本 VNode

          v-show

          我們再看看 v-show, 很明顯是一個指令,與我們在render 函數中寫指令是一樣的,但是這樣來看 v-if 肯定是一個語法糖,因為它并不是真正意義上的指令。

          也就是說自定義指令時機不可能在內置指令之前解析(當然,如果你要是通過 vue-template-compiler 直接修改編譯時或者其他騷操作,就當俺沒說)。

          感興趣的朋友可以去 vue-template-explorer.netlify.app/ 上試試

          在真正了解了內置指令的執行時機,之后我們接下來寫一個自定義的指令

          寫一個的 Permission 指令

          常見的權限顆粒度

          在實現 Permission 指令之前,我們需要先了解一下常見的權限顆粒度。下面是一些常見的方案。

          一般常見的方案有

          • 將按鈕封裝成組件,然后通過 props 傳遞參數,來控制是否顯示or可操作
          • 封裝一個組件,通過插槽形式傳遞按鈕,再通過prop傳值控制
          • 寫一個自定義指令,通過 value 控制元素是否顯示

          相比 v-if 的好處有以下幾點

          1. 可以輕松地擴展權限控制功能。
          2. 支持多個權限碼、支持異步獲取權限等。
          3. 語義化效果更加明顯。

          如何實現

          先寫一個簡單的顯示隱藏控制,可以通過響應式數據控制。

          // 全局自定義指令
          Vue.directive("permission", {
            // 在元素被插入到 DOM 中時觸發
            inserted(el, binding) {
              // 如果綁定值為 false,則從父節點中移除元素
              if (!binding.value) {
                el.parentNode.removeChild(el); // 移除元素
              }
            },
            // 在元素更新時觸發
            update(el, binding) {
              // 如果綁定值為 true
              if (binding.value) {
                // 如果元素沒有父節點(即之前被移除了)
                if (!el.parentNode) {
                  // 將元素插入到原來的位置
                  el.__v_originalParent.insertBefore(el, el.__v_anchor || null);
                }
              } else {
                // 如果元素有父節點
                if (el.parentNode) {
                  // 創建一個注釋節點作為替換元素的占位符
                  el.__v_anchor=document.createComment("");
                  el.__v_originalParent=el.parentNode;
                  // 用注釋節點替換原來的元素
                  el.parentNode.replaceChild(el.__v_anchor, el); 
                }
              }
            },
          });
          


          在 inserted 鉤子中,當元素被插入到 DOM 中時,根據綁定值的狀態,如果為 false,則從父節點中移除該元素,實現隱藏的效果。

          在 update 鉤子中,當元素更新時,根據綁定值的狀態,如果為 true,并且元素之前被移除了(沒有父節點),則將元素插入到原來的位置,實現顯示的效果。如果綁定值為 false,并且元素當前有父節點,則創建一個注釋節點作為替換元素的占位符,并將注釋節點替換掉原來的元素,實現隱藏的效果。

          使用方式

          使用方式很簡單,在指令中傳入需要的值即可。

          <template>
            <div id="app">
              isVisible: {{ isVisible }}
              <button v-permission="isVisible">自定義指令</button>
              <button @click="isVisible=!isVisible">Toggle</button>
            </div>
          </template>
          
          <script>
          export default {
            name: "App",
            data() {
              return {
                isVisible: true,
              };
            },
          };
          </script>
          


          效果

          可以看到效果基本上與 v-if 一致,當然這個是運行時的操作,在原理上來說還是有區別的。

          但是如果只是一個簡單的變量的話,我們的這個 v-permission 沒有太大意義。真的可以直接通過 v-if 控制即可,因為我們需要異步請求,并且實現統一管理。接下來會加強一下這個指令。

          強化一下功能

          在常見中后臺中表格頁出現4個以上的按鈕,如下圖,有新增、刪除、編輯、查詢。從業務中來,又從業務中走出去。

          一般需要在某個模塊、功能又或者某個路由下的add|delete|update|query進行權限控制,又或者其他的一些操作。

          所以我們一個按鈕權限應該是 頁面::新增,接下來就需要一個checkPermission 方法來提供支撐。

          假設現在在某個API中能夠拿到所有按鈕級別的權限,并且與服務端同學約定的格式一致。

          {
              "orders": [
                  "add",
                  "update",
                  "delete",
                  "query",
                  "detail",
                  "enable",
                  "disable"
              ]
          }
          


          如上JSON,orders 作為訂單頁面,而對應能夠操作 Actions 放在數組內,若不存在則認定為沒有此權限。

          消費方

          消費方傳入orders::update 則代表需要獲取到訂單的編輯權限

          <button v-permission="'orders::update'">Update</button>
          


          指令

          在指令區域,我們需要將指令的鉤子(這里的示例只在inserted中調整了)改為異步等待,剛進入 inserted鉤子時需要將 display 設置為 none,或者在 bind 鉤子里面去做這個事情,不然會出現一閃而過的效果。

          然后在這里面去調用checkPermission傳入 binding.value 也就是 orders::update 然后在這里等待 checkPermission 的返回值然后判斷是否需要刪除此元素。

          Vue.directive("permission", {
            async inserted(el, binding) {
              // 默認先改為 none,等數據回來之后再進行操作,你通過class來控制
              el.style.display="none";
              const hasPermission=await checkPermission(binding.value);
              el.style.display="";
              if (!hasPermission) {
                el.parentNode?.removeChild(el); // 移除元素
              }
            },
            ...
          });
          


          checkPermission 校驗權限

          接收 orders::update,通過splitPermissionString分割成orders和update。

          然后再去發送請求,但是如果一個頁面有多處同時調用v-permission,可能會存在重復請求的問題。

          所以通過 controller.hasRequested 和 controller.task 來控制請求的重復性。這個是有必要的,目前JavaScript中沒發現類似與Java中的wait/notify(鎖機制)。所以只是一個仿造Java的鎖機制

          const controller={
            // 是否發過請求
            hasRequested: false,
            // 權限集合
            permissionList: [],
            // 真正的請求任務
            task: null,
          };
          
          const checkPermission=async (value=null)=> {
            // 截取對應的模塊和操作
            const [module=null, operate=null]=splitPermissionString(value) ?? [];
          
            // 判斷模塊和操作是否存在
            if (!module || !operate) return false;
          
            // 判斷是否發送過請求
            if (!controller.hasRequested) {
              controller.hasRequested=true;
              controller.task=getPermissionsApi();
            }
          
            // 獲取權限數據,進行賦值
            controller.permissionList=await controller.task ?? [];
          
            // 判斷是否有權限
            return controller.permissionList[module]?.includes(operate) ?? false;
          };
          


          看看效果

          可以看到在發送請求前默認先隱藏,而后等待接口數據回來,重新判定哪些沒有權限,然后控制顯示或者刪除元素節點。

          補充其他輔助性方法

          // 模擬請求
          const getPermissionsApi=async ()=>
            fetch(`/api.json?t=${new Date().getTime()}`).then((res)=> res.json());
          
          // 分割字符串
          const splitPermissionString=(str)=> {
            try {
              if (typeof str==="string" && str.includes("::")) {
                const [firstPart, secondPart]=str.split("::");
                return [firstPart.trim(), secondPart.trim()];
              } else {
                throw new Error("Invalid permission string or delimiter not found");
              }
            } catch (error) {
              console.error(error.message);
              return [];
            }
          };
          



          作者:嚴老濕
          鏈接:https://juejin.cn/post/7259356504758779965


          主站蜘蛛池模板: 亚洲日韩精品一区二区三区无码 | 国产萌白酱在线一区二区| 国产精品视频免费一区二区| 中文字幕一区二区三区在线播放 | 一区二区三区四区免费视频| 国产一区二区三区福利| 亚洲一区二区三区在线网站| 国产av福利一区二区三巨| 无码日本电影一区二区网站| 中文字幕一精品亚洲无线一区| 韩国精品一区视频在线播放| 人妻av无码一区二区三区| 成人免费区一区二区三区| 日本强伦姧人妻一区二区| 欧美激情国产精品视频一区二区| 久久国产精品最新一区| 国模大尺度视频一区二区| 精品一区二区三区视频在线观看| 国产一区二区三区播放心情潘金莲 | 国产免费一区二区三区免费视频| 精品人妻无码一区二区三区蜜桃一 | 在线观看国产区亚洲一区成人| 欧洲精品一区二区三区在线观看| 国产精品无码一区二区在线| 激情亚洲一区国产精品| 亚洲一区在线观看视频| 精品亚洲一区二区| 国产在线一区二区在线视频| 国产精品污WWW一区二区三区| 在线视频亚洲一区| 国产精品免费综合一区视频| 精品国产高清自在线一区二区三区 | 亚洲熟妇无码一区二区三区| 精品视频一区二区三区四区| 精品国产乱码一区二区三区| 精品国产一区二区三区久久久狼| 久久人做人爽一区二区三区| 麻豆国产一区二区在线观看| 一区二区不卡在线| 成人丝袜激情一区二区| 蜜臀AV无码一区二区三区|