vue項目中實現手勢密碼


tips:本文是做記錄用的

思路:

  本來應該全部都用canvas來實現的,但時間緊迫 寫的時候只想着圓圈用li寫,線用canvas,寫到一半才想通,不過還好這一通下來還算比較順利

  第一步:頁面中的9個點用v-for循環出來li,ul設置成寬高相等的正方形。給li設置margin,保證一行只能裝得下三個li,然后ul用display:flex;justify-contentspace-between; align-content: space-between;給子元素排成九宮格;

  第二步:先獲取九個點圓心的位置,在手指按下移動的方法中判斷當前手指的坐標是否到li區域內,然后把對應位置的數字存進密碼數組,調用畫線的方法,把密碼數組中的數字對應的點連起來,最后連到手指的位置

  第三步:在手指松開的方法中進行各種判斷,判斷是創建密碼還是登錄,判斷密碼長度是否小於4,有錯的話顏色變紅,最后再判斷密碼是否正確

說明:

  1、手勢密碼部分以組件的形式引入需要用到的頁面中

  2、父組件中不傳值的話就默認是創建手勢密碼(創建時密碼長度不能小於4),需要輸入兩次,兩次密碼必須一致,如果是登錄,父組件就把密碼傳給子組件,子組件就會根據密碼判斷當前輸入是否正確,執行時請看控制台

不足之處:

  1、未處理創建或登錄成功之后的事情(寫一個emit就行了,把輸入的密碼傳給父組件)

  2、還有一個就是canvas的高度問題,這里是用的設備高度的86%,如果九個點下面緊挨着有其他按鈕的話是點不了的,因為被canvas覆蓋了

演示:

父組件未傳值,此時是創建手勢密碼

 

 

 

 登錄時,父組件穿的值為<gestureUnlock :fatherPassword="[1,2,3,4,5]"></gestureUnlock>

下面分別是密碼正確和密碼錯誤的情況

 

 

 

 

 

 

密碼組件:

  1 <template>
  2   <div class="gestureUnlock">
  3     <div class="gesture">
  4       <ul>
  5         <li ref="selectLi" v-for="(item, index) in list" :key="item.id"
  6             :class="{'selectedOuter': password.indexOf(index) !== -1 ? true : false,
  7             'selectedOuter2': password.indexOf(index) !== -1 && redStyle ? true : false}">
  8           <span :class="{'selectedInside': password.indexOf(index) !== -1 ? true : false,
  9                 'selectedInside2': password.indexOf(index) !== -1 && redStyle ? true : false}">
 10             <!--圓心-->
 11             <i ref="selectLiO"></i>
 12           </span>
 13         </li>
 14       </ul>
 15     </div>
 16     <div class="canvasDiv">
 17       <!-- <canvas id="canvasClearTop">此瀏覽器不支持canvas</canvas> -->
 18       <canvas id="canvas"  @touchstart="start" @touchmove="move" @touchend="end">此瀏覽器不支持canvas</canvas>
 19     </div>
 20     <div class='incorrectTip'><span v-show="tips">incorrect pattern</span></div>
 21   </div>
 22 </template>
 23 
 24 <script>
 25   export default {
 26     name: "GestureUnlock",
 27     data () {
 28       return {
 29         list: [
 30           {id:0, top: 0, left: 0, isSelected: false},
 31           {id:1, top: 0, left: 0, isSelected: false},
 32           {id:2, top: 0, left: 0, isSelected: false},
 33           {id:3, top: 0, left: 0, isSelected: false},
 34           {id:4, top: 0, left: 0, isSelected: false},
 35           {id:5, top: 0, left: 0, isSelected: false},
 36           {id:6, top: 0, left: 0, isSelected: false},
 37           {id:7, top: 0, left: 0, isSelected: false},
 38           {id:8, top: 0, left: 0, isSelected: false},
 39         ],
 40         left: [], // 圓心x坐標
 41         top: [], // 圓心y坐標
 42         password: [], // 用來存儲創建密碼,從上到下,從左到右依次是123,456,789
 43         cas: '', // 畫筆
 44         casClearTop:'', // 上部清除線條的畫布對象
 45         clientWidth: 0,
 46         clientHeight: 0,
 47         isCorrect: true, // 密碼是否且是否正確
 48         redStyle: false, // li樣式是否為紅色
 49         createPassword: Array, // 這個用來存一下父組件傳過來的fatherPassword,因為子組件不能直接修改父組件傳過來的值
 50         radius: Number, // 半徑
 51         tips: false // 錯誤提示是否顯示
 52       }
 53     },
 54     props: {
 55       // 存儲確認密碼,變成組件后由父組件傳過來,默認是空數組
 56       fatherPassword: {
 57         default: ()=>[], // 這個地方不能寫成default: []
 58         type: Array
 59       }
 60     },
 61     created () {
 62       // 存一下父組件傳過來的fatherPassword,因為子組件不能直接修改父組件傳過來的值
 63       this.createPassword = this.fatherPassword
 64     },
 65     mounted() {
 66       // 獲取到的是每個方塊中心i標簽的位置,
 67       for (let i = 0; i < this.$refs.selectLiO.length; i++) {
 68         this.left.push(this.$refs.selectLiO[i].getBoundingClientRect().left)
 69         this.top.push(this.$refs.selectLiO[i].getBoundingClientRect().top)
 70       }
 71       this.radius = this.$refs.selectLiO[0].getBoundingClientRect().left - this.$refs.selectLi[0].getBoundingClientRect().left
 72       console.log('半徑為:', this.radius)
 73       console.log(this.left)
 74       console.log(this.top)
 75       this.clientWidth = document.documentElement.clientWidth
 76       this.clientHeight = document.documentElement.clientHeight
 77       console.log('設備寬高:', this.clientWidth, this.clientHeight)
 78       this.cas = document.getElementById('canvas').getContext('2d');
 79       document.getElementById('canvas').width = this.clientWidth;
 80       // canvas高度為最后一個圓的圓心加半徑乘以1.5,就是大於最后一行多一點
 81       document.getElementById('canvas').height = this.top[this.top.length-1] + this.radius*1.5;
 82       // this.casClearTop = document.getElementById('canvasClearTop').getContext('2d');
 83       // document.getElementById('canvasClearTop').width = this.clientWidth;
 84       // document.getElementById('canvasClearTop').height = this.top[0] - this.radius*1.5;
 85     },
 86     methods: {
 87       // 手指點下
 88       start (e) {
 89         if(e.touches.length > 1 || e.scale && e.scale !== 1) { // 多點觸碰或者縮放
 90           console.log('這樣不行', e)
 91         } else {
 92           console.log('start', e.touches[0].pageX , e.touches[0].pageY)
 93         }
 94       },
 95       // 手指移動
 96       move (e) {
 97         // this.casClearTop.clearRect(0,0,200,200);
 98         let nowLeft = e.touches[0].pageX
 99         let nowTop = e.touches[0].pageY
100         for (var i = 0; i < this.left.length; i++) {
101           // 圓心坐標
102           let oLeft = this.left[i]
103           let oTop = this.top[i]
104           if((oLeft - this.radius) <= nowLeft && nowLeft <= (oLeft + this.radius) && (oTop - this.radius) <= nowTop && nowTop <= (oTop + this.radius)) {
105             if (this.password.length === 0 && this.password.indexOf(i) === -1) {
106               this.password.push(i) // 直接存進密碼
107             } else if(this.password.indexOf(i) === -1){
108               console.log('連中的值:', this.password[this.password.length - 1])
109               let value = this.password[this.password.length - 1] // 根據此值(下標)找出對應的this.left和this.top
110               // value是上一個點的值,i是當前連接點的值
111               // 1-9 9-1、3-7 7-3、2-8 8-2、4-6 6-4
112               if (i === 0 && value === 8 || i === 8 && value === 0 ||
113                 i === 2 && value === 6 || i === 6 && value === 2 ||
114                 i === 1 && value === 7 || i === 7 && value === 1 ||
115                 i === 3 && value === 5 || i === 5 && value === 3) {
116                 // this.password中存的是下標
117                 if (this.password.indexOf(4) === -1) {this.password.push(4)}
118               } else if(i === 2 && value === 0 || i === 0 && value === 2) { // 1-3  3-1
119                 if (this.password.indexOf(1) === -1) {this.password.push(1)}
120               } else if(i === 6 && value === 8 || i === 8 && value === 6){ // 7-9  9-7
121                 if (this.password.indexOf(7) === -1) {this.password.push(7)}
122               }else if(i === 0 && value === 6 || i === 6 && value === 0){ // 1-7  7-1
123                 if (this.password.indexOf(3) === -1) {this.password.push(3)}
124               }else if(i === 2 && value === 8 || i === 8 && value === 2){ // 3-9  9-3
125                 if (this.password.indexOf(5) === -1) {this.password.push(5)}
126               }
127               // 存密碼
128               this.password.push(i)
129             }
130           }
131         }
132         this.paint(nowLeft, nowTop, true)
133       },
134       // 畫線的方法
135       paint (nowX, nowY, color) {
136         // console.log('paint')
137         // this.casClearTop.clearRect(0,0,200,200); // 因為不是在這個canvas上畫的,所以清了也沒用
138         this.cas.clearRect(0,0,this.clientWidth,this.clientHeight); // 每次畫都清空整個畫布
139         this.cas.beginPath();
140         for (var i = 0; i < this.password .length; i++) {
141           this.cas.lineTo(this.left[this.password [i]], this.top[this.password [i]]); // 從這個開始
142         }
143         this.cas.lineTo(nowX, nowY);
144         if (!color) {
145           this.cas.strokeStyle = '#ff4b4b'
146         } else {
147           this.cas.strokeStyle = '#498bcb'
148         }
149         this.cas.lineJoin = "round"
150         this.cas.lineWidth = 2;
151         this.cas.stroke();
152         // 清除li內圓形區域的線條
153         this.password.forEach((item) => {
154           this.clearArcFun(this.left[item], this.top[item], this.radius)
155         })
156       },
157       // 清除li內的圓形區域
158       clearArcFun (centerX, centerY, radius) {
159         var stepClear = 1; //別忘記這一步
160         var _this = this
161         clearArc(centerX, centerY, radius);
162         function clearArc(x, y, radius){ // 圓心x,y,半徑radius
163           var calcWidth = radius - stepClear;
164           var calcHeight = Math.sqrt(radius * radius - calcWidth * calcWidth);
165           var posX = x - calcWidth;
166           var posY = y - calcHeight;
167           var widthX = 2 * calcWidth;
168           var heightY = 2 * calcHeight;
169           if(stepClear <= radius){
170             _this.cas.clearRect(posX, posY, widthX, heightY);
171             stepClear += 1;
172             clearArc(x, y, radius);
173           }
174         }
175       },
176       // 手指松開
177       end () {
178         console.log('end', this.password)
179         if (this.createPassword.length === 0) { // 創建密碼的第一次
180           if(this.password.length >= 4) {
181             this.tips = false
182             // 此時再調用一次paint,傳undefined, undefined,避免最后一條多余的線出現
183             this.paint(undefined, undefined, true)
184             // 不變紅
185             this.redStyle = false
186             this.createPassword = this.password
187             this.$emit('firstDown', {success: true})
188             // 500ms后清空樣式
189             console.log('第一次設置密碼createPassword:', this.createPassword)
190             console.log('第一次設置密碼password:', this.password)
191             setTimeout(() => {
192               this.password = []
193               this.cas.clearRect(0,0,this.clientWidth,this.clientHeight);
194             }, 500)
195           } else if(this.password.length < 4 && this.password.length !== 0) {
196             console.log('創建密碼時長度小於4')
197             this.tips = true
198             this.paint(undefined, undefined, false)
199             // 長度小於4樣式為紅色
200             this.redStyle = true
201             // 清空畫布,顏色變正常,不然下次輸入還是紅色
202             setTimeout(() => {
203               this.password = []
204               this.cas.clearRect(0,0,this.clientWidth,this.clientHeight);
205               this.redStyle = false // 顏色變藍,不然下次輸入還是紅色
206             }, 500)
207           }
208         } else { // 創建密碼的第二次 或者 登錄,不管是啥反正都是拿password和createPassword(第一次輸入的密碼或者父組件傳過來的密碼)比較
209           console.log('createPassword.length不為0,進入密碼比較環節')
210           console.log('createPassword:', this.createPassword)
211           console.log('password:', this.password)
212           if (this.password.toString() === this.createPassword.toString()) {
213             this.tips = false
214             // 設置/登錄成功
215             console.log('設置/登錄成功')
216             this.$emit('onDrawDone', {success: true, pwd: this.password})
217             setTimeout(() => {
218               this.password = []
219               this.cas.clearRect(0,0,this.clientWidth,this.clientHeight);
220               this.redStyle = false // 沒true好像就可以沒有false,加上吧保險一點
221             }, 500)
222           } else if(this.password.length !== 0){ // 兩次輸入不一致/密碼不正確    這里寫this.password.length !== 0是為了防止點一下canvas也會出現輸入錯誤的提示
223             this.tips = true
224             this.paint(undefined, undefined, false)
225             // 兩次輸入不一致/密碼不正確 樣式為紅色
226             this.redStyle = true // 有true下面必得有false
227             console.log('失敗')
228             // 清空畫布,顏色變藍
229             setTimeout(() => {
230               this.password = [] // 還有藍色是因為前幾個存在於那個數組,得把password清空
231               this.cas.clearRect(0,0,this.clientWidth,this.clientHeight);
232               this.redStyle = false
233               console.log(this.redStyle)
234             }, 500)
235           }
236         }
237       }
238     }
239   }
240 </script>
241 
242 <style lang="less" scoped>
243   .incorrectTip{
244     height: .5rem;
245     span{
246       /*line-height: .8rem;*/
247       color: #ff4b4b;
248     }
249   }
250   .gestureUnlock{
251     margin: 0 auto;
252   }
253   .gesture{
254     margin: 1.0rem auto 0;
255     ul{
256       margin: auto;
257       display: flex;
258       width: 8.88rem;
259       height: 8.88rem;
260       justify-content: space-between;
261       align-content: space-between;
262       flex-wrap: wrap;
263       li{
264         display: flex;
265         align-items:center;
266         justify-content:center;
267         margin: 0.45rem 0.45rem;
268         border-radius: 50%;
269         width: 1.2rem;
270         height: 1.2rem;
271         border: 0.08rem solid #e0e0e0;
272         /*寬度是1.2rem,邊框是0.08rem,所以半徑是0.68rem,1rem=37.5px,所以0.68x37.5 = 25.5px*/
273         span{
274           display: flex;
275           align-items:center;
276           justify-content:center;
277           width: 0.40rem;
278           height: 0.40rem;
279           border-radius: 50%;
280           i{
281             display: inline-block;
282             width: 1px;
283             height: 1px;
284           }
285         }
286       }
287       /*被選中的樣式*/
288       .selectedOuter{
289         border: 0.08rem solid #498bcb;
290         .selectedInside{
291           background: #498bcb;
292         }
293       }
294       .selectedOuter2{
295         border: 0.08rem solid #ff4b4b;
296         .selectedInside2{
297           background: #ff4b4b;
298         }
299       }
300     }
301   }
302   .canvasDiv{
303     position: fixed;
304     top:0;
305     left: 0;
306     // background: rgba(0,0,0,0.1);
307     z-index: 100;
308     #canvasClearTop{
309       position: absolute;
310       top: 0;
311       left: 0;
312       background: rgba(255,0,0,0.2)
313     }
314   }315 </style>

 

 

父組件調用(創建密碼):

  1 <template>
  2   <!--首次登陸設置手勢密碼-->
  3   <div class="createGesture">
  4     <div class="picture">
  5       <img :src='logoImg' alt="">
  6     </div>
  7     <div class="words">
  8       <p v-if="!isShowConfirm">{{$t('createGesture.createGesture')}}</p>
  9       <p v-if="!isShowConfirm">{{$t('createGesture.drawTips')}}.</p>
 10       <p v-if="isShowConfirm">{{$t('createGesture.confirmGesture')}}</p>
 11       <p v-if="isShowConfirm">{{$t('createGesture.drawTips2')}}.</p>
 12     </div>
 13     <!--下面這是模擬登錄時傳密碼過去-->
 14     <!-- <gestureUnlock @firstDown="onceDraw" @onDrawDone='fromNinePoint' :fatherPassword="[1,2,3,4,5]"></gestureUnlock> -->
 15     <!--此頁面是創建密碼,需要輸入兩次,組件不傳值,fatherPassword默認是一個空數組-->
 16     <gestureUnlock @firstDown="onceDraw" @onDrawDone='fromNinePoint'></gestureUnlock>
 17     <!-- @firstDown="onceDraw"是第一次輸入密碼的事件  @onDrawDone='fromNinePoint'第二次完成密碼的事件 -->
 18     <div class="bottom">
 19       <p>{{$t('createGesture.bottomTips')}}.</p>
 20       <div>
 21         <a class="btn_text" @click="skip"> {{$t('createGesture.skip')}} </a>
 22       </div>
 23     </div>
 24   </div>
 25 </template>
 26 
 27 <script>
 28   import gestureUnlock from '../../components/gestureUnlock'
 29   import Vue from 'vue';
 30   import { Grid, GridItem } from 'vant';
 31   Vue.use(Grid).use(GridItem);
 32   export default {
 33     name: "createGesture",
 34     components: {
 35       gestureUnlock
 36     },
 37     data() {
 38       return {
 39         logoImg: require('./Zurich_logo.png'),
 40         firstPwd: '', // 用來存創建密碼時第一次輸入的密碼,便於和第二次比較
 41         regOrLogin: 'reg', // 傳給子組件用於判斷是注冊還是登陸
 42         isShowConfirm: false, // 是否顯示confirm密碼
 43       }
 44     },
 45     methods: {
 46       onceDraw (e) {
 47         if (e.success) {
 48           console.log('第一次')
 49           this.isShowConfirm = true
 50         }
 51       },
 52       fromNinePoint (e) {
 53         if(e.success) {
 54           console.log('父組件:', e.pwd, '手勢密碼設置完成,登錄')
 55           this.send(e.pwd.join(''))
 56         }
 57       },
 58       send (gesPwd) {
 59         console.log('手勢密碼:', gesPwd)
 60         this.$axios.post('http://*****/*****/****/gesturePasswordSetup', {
 61           username: '123',
 62           gesturePassword: gesPwd, // 手勢密碼
 63         })
 64         .then((res) => {
 65           console.log('返回的數據:', res)
 66           let flag = res.data.flagStr
 67           if (flag === 'Succ') {
 68             console.log('設置成功')
 69           }
 70         })
 71         .catch((res) => {
 72           console.log('報錯:', res)
 73         })
 74       },
 75       // 跳過
 76       skip () {
 77         this.$router.push({name: '/'})
 78       }
 79     }
 80   }
 81 </script>
 82 
 83 <style lang="less" scoped>
 84   .createGesture{
 85     height: 100%;
 86   }
 87   .picture{
 88     padding-top: 0.533rem;
 89     text-align: center;
 90     img {
 91       height: 3rem;
 92     }
 93   }
 94   .words{
 95     text-align: center;
 96     color: #498bcb;
 97     p:nth-child(1) {
 98       margin: 0.267rem 0;
 99       font-size: 0.723rem;
100     }
101     p:nth-child(2) {
102       font-size: 0.373rem;
103     }
104   }
105   .bottom{
106     z-index: 2000;
107     margin-top: .3rem /* 30/37.5 */;
108     width: 100%;
109     p{
110       padding: 0 0.5rem;
111       font-size: inherit;
112     }
113     div{
114       margin: 0.353rem 0 0.337rem;
115     }
116   }
117 </style>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM