<template> <div id="VueCaptcha" class="vue-captcha" v-bind:style="`border: 1px solid ${color};`"> <div class="content-captcha"> <canvas class="vue-captcha-img" width="150" height="25"></canvas> </div> <div class="content-text" v-show="opc === 1 && !auth"> <input type="text" v-on:keyup="inputText" v-model="text" placeholder="insert key..." /> </div> <div class="content-ok" v-show="auth" v-bind:style="`background-color: ${color};`"> <small> <i class="fa fa-thumbs-up"></i> success.!</small> </div> <div class="content-fixed" v-show="opc === 2 && !auth" v-bind:style="`top:${contenTextTop}px;left:${contenTextLeft}px;`"> <table class="vue-captcha-table" cellspaceng="0"> <tr v-show="mode === 'text'"> <td class="vue-captcha-table-item" v-on:click="selectItem">1</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">2</td> <td class="vue-captcha-table-item" v-on:click="selectItem">3</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">4</td> <td class="vue-captcha-table-item" v-on:click="selectItem">5</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">6</td> <td class="vue-captcha-table-item" v-on:click="selectItem">7</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">8</td> <td class="vue-captcha-table-item" v-on:click="selectItem">9</td> <td class="vue-captcha-table-item vue-captcha-table-item-right" v-on:click="selectItem">0</td> </tr> <tr v-show="mode === 'text'"> <td class="vue-captcha-table-item" v-on:click="selectItem">q</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">w</td> <td class="vue-captcha-table-item" v-on:click="selectItem">e</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">r</td> <td class="vue-captcha-table-item" v-on:click="selectItem">t</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">y</td> <td class="vue-captcha-table-item" v-on:click="selectItem">u</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">i</td> <td class="vue-captcha-table-item" v-on:click="selectItem">o</td> <td class="vue-captcha-table-item vue-captcha-table-item-right" v-on:click="selectItem">p</td> </tr> <tr v-show="mode === 'text'"> <td class="vue-captcha-table-item" v-on:click="selectItem">a</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">s</td> <td class="vue-captcha-table-item" v-on:click="selectItem">d</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">f</td> <td class="vue-captcha-table-item" v-on:click="selectItem">g</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">h</td> <td class="vue-captcha-table-item" v-on:click="selectItem">j</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">k</td> <td class="vue-captcha-table-item" v-on:click="selectItem">l</td> </tr> <tr v-show="mode === 'text'"> <td class="vue-captcha-table-item"></td> <td class="vue-captcha-table-item" v-on:click="selectItem">z</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">x</td> <td class="vue-captcha-table-item" v-on:click="selectItem">c</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">v</td> <td class="vue-captcha-table-item" v-on:click="selectItem">b</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItem">n</td> <td class="vue-captcha-table-item" v-on:click="selectItem">m</td> </tr> <tr v-show="mode === 'math'"> <td class="vue-captcha-table-item" v-on:click="selectItemNum">{{arrayForNum[0]}}</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItemNum">{{arrayForNum[1]}}</td> <td class="vue-captcha-table-item" v-on:click="selectItemNum">{{arrayForNum[2]}}</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItemNum">{{arrayForNum[3]}}</td> <td class="vue-captcha-table-item" v-on:click="selectItemNum">{{arrayForNum[4]}}</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItemNum">{{arrayForNum[5]}}</td> <td class="vue-captcha-table-item" v-on:click="selectItemNum">{{arrayForNum[6]}}</td> <td class="vue-captcha-table-item vue-captcha-table-item-center" v-on:click="selectItemNum">{{arrayForNum[7]}}</td> <td class="vue-captcha-table-item" v-on:click="selectItemNum">{{arrayForNum[8]}}</td> <td class="vue-captcha-table-item vue-captcha-table-item-right" v-on:click="selectItemNum">{{arrayForNum[9]}}</td> </tr> </table> </div> <div class="options" v-show="!auth"> <button type="button" v-on:click="picture" v-bind:style="`color: ${color};`" title="Refresh"> <i class="fa fa-refresh"></i> </button> <button type="button" v-on:click="opc = (opc === 1) ? 0 : 1" v-bind:style="`color: ${color};`" v-show="resolve === 'text' || resolve === 'all'" title="Written"> <i class="fa fa-pencil"></i> </button> <button type="button" v-on:click="ContentFixed" v-bind:style="`color: ${color};`" v-show="resolve === 'digit' || resolve === 'all'" title="Digit"> <i class="fa fa-keyboard-o"></i> </button> </div> </div> </template> <script> 'use strict'; import uuid from 'uuid' import 'font-awesome/css/font-awesome.css' export default { name: 'Vuecaptcha', props: { callSuccess: { type: Function, required: true }, color: { type: String, required: false, default: '#1D9D74' }, mode: { type: String, required: false, default: 'text' // (default) 'text' -> alphanumeric | 'math' -> operations math }, resolve: { type: String, required: false, default: 'all' // (default) 'all' -> written and digit | 'text' -> only written | 'digit' -> only digit } }, data () { return { key: '', auth: false, opc: 0, text: '', contenTextTop: 200, contenTextLeft: 200, errorSelect: 0, success: 0, preSuccess: false, arrayForNum: [] } }, mounted () { if ( (this.mode === 'text' || this.mode === 'math') ) { if ( (this.resolve === 'all' || this.resolve === 'text' || this.resolve === 'digit') ) { this.picture() } else { console.error('[Vue-Captcha] Error: option resolve = \'all\' Or \'text\' Or \'digit\', Not valid \''+this.resolve+'\'.') } } else { console.error('[Vue-Captcha] Error: option mode = \'text\' Or \'math\'.') } }, watch: { errorSelect (error) { if (error > 0) { // console.error('[Vue-captcha] Try Errors: ', error) } }, success (acert) { if (acert === 6) { this.callSuccess() } } }, methods: { getKey () { let newKey = null if (this.mode === 'text') { do { newKey = uuid() newKey = newKey.replace('-', '') newKey = newKey.substr(5, 6) } while( this.codex(newKey) ) this.key = newKey } else if (this.mode === 'math') { newKey = this.operation() this.key = eval( newKey ) } this.opc = 0 return newKey }, picture () { const image = this.$el.children[0].children[0] let ctx = image.getContext("2d") ctx.clearRect(0, 0, image.width, image.height) ctx.font = "25px Arial" ctx.strokeStyle = this.color; ctx.moveTo(0, 5); ctx.lineTo(150, 5); ctx.stroke(); ctx.moveTo(0, 12.5); ctx.lineTo(150, 12.5); ctx.stroke(); ctx.moveTo(0, 20); ctx.lineTo(150, 20); ctx.stroke(); let left = (this.mode === 'text') ? 35 : 32 ctx.fillText(this.getKey(), left, 22) }, inputText () { let text = (this.mode === 'math') ? Number.parseInt( this.text ) : this.text if (this.key === text) { this.auth = true this.success = 6 } }, ContentFixed () { let el = this.$el let _x = 0 let _y = 0 /*** position of component for show keyboard ***/ while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) { _x += el.offsetLeft - el.scrollLeft; _y += el.offsetTop - el.scrollTop; el = el.offsetParent; } this.contenTextTop = _y + 10 this.contenTextLeft = _x + 210 this.opc = (this.opc === 2) ? 0 : 2 }, selectItem (e) { if (this.mode === 'math') return let value = e.target.innerText if (e.target.className.indexOf(' red') === -1) { if (e.target.className.indexOf(' blue') === -1) { if (this.key.indexOf(value) === -1) { this.errorSelect += 1 e.target.className += ' red' } else { this.success += 1 e.target.className += ' blue' } } else { e.target.className = e.target.className.replace(' blue', '') this.success-- } } else { e.target.className = e.target.className.replace(' red', '') this.errorSelect-- } this.auth = (this.success === 6 && this.errorSelect === 0) }, selectItemNum (e) { if (this.mode === 'text') return let value = e.target.innerText let key = String(this.key) if (value !== key) { if (e.target.className.indexOf(' red') === -1) { e.target.className += ' red' this.errorSelect++ } else { e.target.className = e.target.className.replace(' red','') this.errorSelect-- } } if ( (value === key || this.preSuccess) && this.errorSelect === 0) { this.auth = true this.success = 6 } else if (value === key) { this.preSuccess = true e.target.className += ' blue' } }, codex (key) { let no = false for (let x=0; x<key.length; x++) { let count = -1 for (let y=0; y<key.length; y++) { if (key[x] === key[y]) { count++ } } if (count > 0) { no = true break } } return no }, operation () { let max = 20 let a = Math.floor((Math.random() * max) + 10) let b = Math.floor((Math.random() * max) + 10) let o = Math.floor((Math.random() * 9) + 0) let cadena = `${a} + ${b}` let result = eval( cadena ) /*** CONTROL ARRAY FOR RANDOM SELECT ***/ do { let pre = Math.floor((Math.random() * max) + 10) if (pre !== result) { this.arrayForNum.push( pre ) } } while(this.arrayForNum.length < 10) this.arrayForNum[o] = result /*** ***/ return cadena } } } </script> <style scoped> .vue-captcha { width: 180px; text-align: center; padding: 5px; } .vue-captcha-img { border: 1px solid #D3D3D3; } .options button { width: 45px; height: 20px; cursor: pointer; background-color: #FFF; border: 1px solid; margin: 2px; outline: transparent; } .options button:focus, .options button:active { box-shadow: 0 0 0 2px #FFF, 0 0 0 3px #1D9D74; } div.content-text { padding: 5px; } div.content-text > input { width: 100%; min-width: 100%; max-width: 100%; height: 25px; min-height: 25px; max-height: 25px; font-size: 13.33px; } .content-ok { width: 150px; border-color: #e0f1e9; color: #f3faf8; margin-left: 10px; font-family: proxima-nova, 'Helvetica Neue', Helvetica, Arial, sans-serif; } .vue-captcha-btn { padding: 1px 6px 1px 6px; } .vue-captcha-table { border-collapse: collapse; } .vue-captcha-table-item { padding: 5px; padding-left: 8px; padding-right: 8px; cursor: pointer; font-family: proxima-nova, 'Helvetica Neue', Helvetica, Arial, sans-serif; border-bottom: 1px solid #1D9D74; } .vue-captcha-table-item-center { border-left: 1px solid #1D9D74; border-right: 1px solid #1D9D74; } .vue-captcha-table-item-right { border-left: 1px solid #1D9D74; } .content-fixed { position: fixed; border: 0px solid #1D9D74; background-color: lightgrey; } .red { background-color: #c7254e; } .blue { background-color: #488dd8; } </style>