|
Before Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
@ -1,191 +0,0 @@ |
|||||
#app { |
|
||||
.main-container { |
|
||||
min-height: 100%; |
|
||||
transition: margin-left 0.28s; |
|
||||
margin-left: $sideBarWidth; |
|
||||
position: relative; |
|
||||
} |
|
||||
.sidebar-container { |
|
||||
transition: width 0.28s; |
|
||||
width: $sideBarWidth !important; |
|
||||
background-color: $menuBg; |
|
||||
height: 100%; |
|
||||
position: fixed; |
|
||||
font-size: 0; |
|
||||
top: 0; |
|
||||
bottom: 0; |
|
||||
left: 0; |
|
||||
z-index: 1001; |
|
||||
overflow: hidden; |
|
||||
|
|
||||
// reset element-ui css |
|
||||
.horizontal-collapse-transition { |
|
||||
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; |
|
||||
} |
|
||||
.scrollbar-wrapper { |
|
||||
overflow-x: hidden !important; |
|
||||
} |
|
||||
.el-scrollbar__bar.is-vertical { |
|
||||
right: 0; |
|
||||
} |
|
||||
.el-scrollbar { |
|
||||
height: 100%; |
|
||||
} |
|
||||
&.has-logo { |
|
||||
.el-scrollbar { |
|
||||
height: calc(100% - 50px); |
|
||||
} |
|
||||
} |
|
||||
.is-horizontal { |
|
||||
display: none; |
|
||||
} |
|
||||
a { |
|
||||
display: inline-block; |
|
||||
width: 100%; |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
.svg-icon { |
|
||||
margin-right: 16px; |
|
||||
} |
|
||||
.sub-el-icon { |
|
||||
margin-right: 12px; |
|
||||
margin-left: -2px; |
|
||||
} |
|
||||
.el-menu { |
|
||||
border: none; |
|
||||
height: 100%; |
|
||||
width: 100% !important; |
|
||||
} |
|
||||
|
|
||||
// menu hover |
|
||||
.submenu-title-noDropdown, |
|
||||
.el-submenu__title { |
|
||||
&:hover { |
|
||||
background-color: $menuHover !important; |
|
||||
} |
|
||||
} |
|
||||
.is-active > .el-submenu__title { |
|
||||
color: $subMenuActiveText !important; |
|
||||
} |
|
||||
& .nest-menu .el-submenu > .el-submenu__title, |
|
||||
& .el-submenu .el-menu-item { |
|
||||
min-width: $sideBarWidth !important; |
|
||||
background-color: $subMenuBg !important; |
|
||||
&:hover { |
|
||||
background-color: $subMenuHover !important; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
.hideSidebar { |
|
||||
.sidebar-container { |
|
||||
width: 54px !important; |
|
||||
} |
|
||||
.main-container { |
|
||||
margin-left: 54px; |
|
||||
} |
|
||||
.submenu-title-noDropdown { |
|
||||
padding: 0 !important; |
|
||||
position: relative; |
|
||||
.el-tooltip { |
|
||||
padding: 0 !important; |
|
||||
.svg-icon { |
|
||||
margin-left: 20px; |
|
||||
} |
|
||||
.sub-el-icon { |
|
||||
margin-left: 19px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
.el-submenu { |
|
||||
overflow: hidden; |
|
||||
& > .el-submenu__title { |
|
||||
padding: 0 !important; |
|
||||
.svg-icon { |
|
||||
margin-left: 20px; |
|
||||
} |
|
||||
.sub-el-icon { |
|
||||
margin-left: 19px; |
|
||||
} |
|
||||
.el-submenu__icon-arrow { |
|
||||
display: none; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
.el-menu--collapse { |
|
||||
.el-submenu { |
|
||||
& > .el-submenu__title { |
|
||||
& > span { |
|
||||
height: 0; |
|
||||
width: 0; |
|
||||
overflow: hidden; |
|
||||
visibility: hidden; |
|
||||
display: inline-block; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
.el-menu--collapse .el-menu .el-submenu { |
|
||||
min-width: $sideBarWidth !important; |
|
||||
} |
|
||||
|
|
||||
// mobile responsive |
|
||||
.mobile { |
|
||||
.main-container { |
|
||||
margin-left: 0; |
|
||||
} |
|
||||
.sidebar-container { |
|
||||
transition: transform 0.28s; |
|
||||
width: $sideBarWidth !important; |
|
||||
} |
|
||||
&.hideSidebar { |
|
||||
.sidebar-container { |
|
||||
pointer-events: none; |
|
||||
transition-duration: 0.3s; |
|
||||
transform: translate3d(-$sideBarWidth, 0, 0); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
.withoutAnimation { |
|
||||
.main-container, |
|
||||
.sidebar-container { |
|
||||
transition: none; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// when menu collapsed |
|
||||
.el-menu--vertical { |
|
||||
& > .el-menu { |
|
||||
.svg-icon { |
|
||||
margin-right: 16px; |
|
||||
} |
|
||||
.sub-el-icon { |
|
||||
margin-right: 12px; |
|
||||
margin-left: -2px; |
|
||||
} |
|
||||
} |
|
||||
.nest-menu .el-submenu > .el-submenu__title, |
|
||||
.el-menu-item { |
|
||||
&:hover { |
|
||||
// you can use $subMenuHover |
|
||||
background-color: $menuHover !important; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// the scroll bar appears when the subMenu is too long |
|
||||
>.el-menu--popup { |
|
||||
max-height: 100vh; |
|
||||
overflow-y: auto; |
|
||||
&::-webkit-scrollbar-track-piece { |
|
||||
background: #d3dce6; |
|
||||
} |
|
||||
&::-webkit-scrollbar { |
|
||||
width: 6px; |
|
||||
} |
|
||||
&::-webkit-scrollbar-thumb { |
|
||||
background: #99a9bf; |
|
||||
border-radius: 20px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +0,0 @@ |
|||||
import Vue from 'vue' |
|
||||
|
|
||||
const component = require('./main.vue').default |
|
||||
const constructor = Vue.extend(component) |
|
||||
|
|
||||
const exampleNotice = options => { |
|
||||
options = options || {} |
|
||||
let instance = new constructor({ |
|
||||
data: options |
|
||||
}) |
|
||||
instance.vm = instance.$mount() |
|
||||
instance.dom = instance.vm.$el |
|
||||
document.body.appendChild(instance.dom) |
|
||||
return instance.vm |
|
||||
} |
|
||||
|
|
||||
export default { |
|
||||
install: Vue => { |
|
||||
Vue.prototype[`$${component.name}`] = exampleNotice |
|
||||
} |
|
||||
} |
|
||||
@ -1,42 +0,0 @@ |
|||||
<template> |
|
||||
<transition name="notice"> |
|
||||
<div v-if="show" class="notice"> |
|
||||
{{ content }} |
|
||||
</div> |
|
||||
</transition> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
export default { |
|
||||
name: 'ExampleNotice', |
|
||||
data() { |
|
||||
return { |
|
||||
show: false, |
|
||||
content: '' |
|
||||
} |
|
||||
}, |
|
||||
mounted() { |
|
||||
this.show = true |
|
||||
setTimeout(() => { |
|
||||
this.show = false |
|
||||
}, 2000) |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
.notice { |
|
||||
padding: 10px; |
|
||||
background-color: #eee; |
|
||||
border-radius: 10px; |
|
||||
@include position-center(xy); |
|
||||
} |
|
||||
.notice-leave-active, |
|
||||
.notice-enter-active { |
|
||||
transition: all 0.3s; |
|
||||
} |
|
||||
.notice-enter, |
|
||||
.notice-leave-to { |
|
||||
opacity: 0; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,245 @@ |
|||||
|
<template> |
||||
|
<div style="position: relative" |
||||
|
> |
||||
|
<div class="verify-img-out"> |
||||
|
<div class="verify-img-panel" :style="{'width': setSize.imgWidth, |
||||
|
'height': setSize.imgHeight, |
||||
|
'background-size' : setSize.imgWidth + ' '+ setSize.imgHeight, |
||||
|
'margin-bottom': vSpace + 'px'}" |
||||
|
> |
||||
|
<div class="verify-refresh" style="z-index:3" @click="refresh" v-show="showRefresh"> |
||||
|
<i class="iconfont icon-refresh"></i> |
||||
|
</div> |
||||
|
<img :src="'data:image/png;base64,'+pointBackImgBase" |
||||
|
ref="canvas" |
||||
|
alt="" style="width:100%;height:100%;display:block" |
||||
|
@click="bindingClick?canvasClick($event):undefined"> |
||||
|
|
||||
|
<div v-for="(tempPoint, index) in tempPoints" :key="index" class="point-area" |
||||
|
:style="{ |
||||
|
'background-color':'#1abd6c', |
||||
|
color:'#fff', |
||||
|
'z-index':9999, |
||||
|
width:'20px', |
||||
|
height:'20px', |
||||
|
'text-align':'center', |
||||
|
'line-height':'20px', |
||||
|
'border-radius': '50%', |
||||
|
position:'absolute', |
||||
|
top:parseInt(tempPoint.y-10) + 'px', |
||||
|
left:parseInt(tempPoint.x-10) + 'px' |
||||
|
}"> |
||||
|
{{index + 1}} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- 'height': this.barSize.height, --> |
||||
|
<div class="verify-bar-area" |
||||
|
:style="{'width': setSize.imgWidth, |
||||
|
'color': this.barAreaColor, |
||||
|
'border-color': this.barAreaBorderColor, |
||||
|
'line-height':this.barSize.height}"> |
||||
|
<span class="verify-msg">{{text}}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<script type="text/babel"> |
||||
|
/** |
||||
|
* VerifyPoints |
||||
|
* @description 点选 |
||||
|
* */ |
||||
|
import {resetSize, _code_chars, _code_color1, _code_color2} from './../utils/util' |
||||
|
import {aesEncrypt} from "./../utils/ase" |
||||
|
import {reqGet,reqCheck} from "../api/verifition-api" |
||||
|
|
||||
|
export default { |
||||
|
name: 'VerifyPoints', |
||||
|
props: { |
||||
|
//弹出式pop,固定fixed |
||||
|
mode: { |
||||
|
type: String, |
||||
|
default: 'fixed' |
||||
|
}, |
||||
|
captchaType:{ |
||||
|
type:String, |
||||
|
}, |
||||
|
//间隔 |
||||
|
vSpace: { |
||||
|
type: Number, |
||||
|
default: 5 |
||||
|
}, |
||||
|
imgSize: { |
||||
|
type: Object, |
||||
|
default() { |
||||
|
return { |
||||
|
width: '310px', |
||||
|
height: '155px' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
barSize: { |
||||
|
type: Object, |
||||
|
default() { |
||||
|
return { |
||||
|
width: '310px', |
||||
|
height: '40px' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
secretKey:'', //后端返回的ase加密秘钥 |
||||
|
checkNum:3, //默认需要点击的字数 |
||||
|
fontPos: [], //选中的坐标信息 |
||||
|
checkPosArr: [], //用户点击的坐标 |
||||
|
num: 1, //点击的记数 |
||||
|
pointBackImgBase:'', //后端获取到的背景图片 |
||||
|
poinTextList:[], //后端返回的点击字体顺序 |
||||
|
backToken:'', //后端返回的token值 |
||||
|
setSize: { |
||||
|
imgHeight: 0, |
||||
|
imgWidth: 0, |
||||
|
barHeight: 0, |
||||
|
barWidth: 0 |
||||
|
}, |
||||
|
tempPoints: [], |
||||
|
text: '', |
||||
|
barAreaColor: undefined, |
||||
|
barAreaBorderColor: undefined, |
||||
|
showRefresh: true, |
||||
|
bindingClick: true |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
resetSize() { |
||||
|
return resetSize |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
init() { |
||||
|
//加载页面 |
||||
|
this.fontPos.splice(0, this.fontPos.length) |
||||
|
this.checkPosArr.splice(0, this.checkPosArr.length) |
||||
|
this.num = 1 |
||||
|
this.getPictrue(); |
||||
|
this.$nextTick(() => { |
||||
|
this.setSize = this.resetSize(this) //重新设置宽度高度 |
||||
|
this.$parent.$emit('ready', this) |
||||
|
}) |
||||
|
}, |
||||
|
canvasClick(e) { |
||||
|
this.checkPosArr.push(this.getMousePos(this.$refs.canvas, e)); |
||||
|
if (this.num == this.checkNum) { |
||||
|
this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e)); |
||||
|
//按比例转换坐标值 |
||||
|
this.checkPosArr = this.pointTransfrom(this.checkPosArr,this.setSize); |
||||
|
//等创建坐标执行完 |
||||
|
setTimeout(() => { |
||||
|
// var flag = this.comparePos(this.fontPos, this.checkPosArr); |
||||
|
//发送后端请求 |
||||
|
var captchaVerification = this.secretKey? aesEncrypt(this.backToken+'---'+JSON.stringify(this.checkPosArr),this.secretKey):this.backToken+'---'+JSON.stringify(this.checkPosArr) |
||||
|
let data = { |
||||
|
captchaType:this.captchaType, |
||||
|
"pointJson":this.secretKey? aesEncrypt(JSON.stringify(this.checkPosArr),this.secretKey):JSON.stringify(this.checkPosArr), |
||||
|
"token":this.backToken |
||||
|
} |
||||
|
reqCheck(data).then(res=>{ |
||||
|
if (res.repCode == "0000") { |
||||
|
this.barAreaColor = '#4cae4c' |
||||
|
this.barAreaBorderColor = '#5cb85c' |
||||
|
this.text = '验证成功' |
||||
|
this.bindingClick = false |
||||
|
if (this.mode=='pop') { |
||||
|
setTimeout(()=>{ |
||||
|
this.$parent.clickShow = false; |
||||
|
this.refresh(); |
||||
|
},1500) |
||||
|
} |
||||
|
this.$parent.$emit('success', {captchaVerification}) |
||||
|
}else{ |
||||
|
this.$parent.$emit('error', this) |
||||
|
this.barAreaColor = '#d9534f' |
||||
|
this.barAreaBorderColor = '#d9534f' |
||||
|
this.text = '验证失败' |
||||
|
setTimeout(() => { |
||||
|
this.refresh(); |
||||
|
}, 700); |
||||
|
} |
||||
|
}) |
||||
|
}, 400); |
||||
|
} |
||||
|
if (this.num < this.checkNum) { |
||||
|
this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e)); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
//获取坐标 |
||||
|
getMousePos: function (obj, e) { |
||||
|
var x = e.offsetX |
||||
|
var y = e.offsetY |
||||
|
return {x, y} |
||||
|
}, |
||||
|
//创建坐标点 |
||||
|
createPoint: function (pos) { |
||||
|
this.tempPoints.push(Object.assign({}, pos)) |
||||
|
return ++this.num; |
||||
|
}, |
||||
|
refresh: function () { |
||||
|
this.tempPoints.splice(0, this.tempPoints.length) |
||||
|
this.barAreaColor = '#000' |
||||
|
this.barAreaBorderColor = '#ddd' |
||||
|
this.bindingClick = true |
||||
|
this.fontPos.splice(0, this.fontPos.length) |
||||
|
this.checkPosArr.splice(0, this.checkPosArr.length) |
||||
|
this.num = 1 |
||||
|
this.getPictrue(); |
||||
|
this.text = '验证失败' |
||||
|
this.showRefresh = true |
||||
|
}, |
||||
|
|
||||
|
// 请求背景图片和验证图片 |
||||
|
getPictrue(){ |
||||
|
let data = { |
||||
|
captchaType:this.captchaType |
||||
|
} |
||||
|
reqGet(data).then(res=>{ |
||||
|
if (res.repCode == "0000") { |
||||
|
this.pointBackImgBase = res.repData.originalImageBase64 |
||||
|
this.backToken = res.repData.token |
||||
|
this.secretKey = res.repData.secretKey |
||||
|
this.poinTextList = res.repData.wordList |
||||
|
this.text = '请依次点击【' + this.poinTextList.join(",") + '】' |
||||
|
}else{ |
||||
|
this.text = res.repMsg; |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
//坐标转换函数 |
||||
|
pointTransfrom(pointArr,imgSize){ |
||||
|
var newPointArr = pointArr.map(p=>{ |
||||
|
let x = Math.round(310 * p.x/parseInt(imgSize.imgWidth)) |
||||
|
let y =Math.round(155 * p.y/parseInt(imgSize.imgHeight)) |
||||
|
return {x,y} |
||||
|
}) |
||||
|
// console.log(newPointArr,"newPointArr"); |
||||
|
return newPointArr |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
// type变化则全面刷新 |
||||
|
type: { |
||||
|
immediate: true, |
||||
|
handler() { |
||||
|
this.init() |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
// 禁止拖拽 |
||||
|
this.$el.onselectstart = function () { |
||||
|
return false |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
</script> |
||||
@ -0,0 +1,347 @@ |
|||||
|
<template> |
||||
|
<div style="position: relative;"> |
||||
|
<div v-if="type === '2'" class="verify-img-out" |
||||
|
:style="{height: (parseInt(setSize.imgHeight) + vSpace) + 'px'}" |
||||
|
> |
||||
|
<div class="verify-img-panel" :style="{width: setSize.imgWidth, |
||||
|
height: setSize.imgHeight,}"> |
||||
|
<img :src="'data:image/png;base64,'+backImgBase" alt="" style="width:100%;height:100%;display:block"> |
||||
|
<div class="verify-refresh" @click="refresh" v-show="showRefresh"><i class="iconfont icon-refresh"></i> |
||||
|
</div> |
||||
|
<transition name="tips"> |
||||
|
<span class="verify-tips" v-if="tipWords" :class="passFlag ?'suc-bg':'err-bg'">{{tipWords}}</span> |
||||
|
</transition> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- 公共部分 --> |
||||
|
<div class="verify-bar-area" :style="{width: setSize.imgWidth, |
||||
|
height: barSize.height, |
||||
|
'line-height':barSize.height}"> |
||||
|
<span class="verify-msg" v-text="text"></span> |
||||
|
<div class="verify-left-bar" |
||||
|
:style="{width: (leftBarWidth!==undefined)?leftBarWidth: barSize.height, height: barSize.height, 'border-color': leftBarBorderColor, transaction: transitionWidth}"> |
||||
|
<span class="verify-msg" v-text="finishText"></span> |
||||
|
<div class="verify-move-block" |
||||
|
@touchstart="start" |
||||
|
@mousedown="start" |
||||
|
:style="{width: barSize.height, height: barSize.height, 'background-color': moveBlockBackgroundColor, left: moveBlockLeft, transition: transitionLeft}"> |
||||
|
<i :class="['verify-icon iconfont', iconClass]" |
||||
|
:style="{color: iconColor}"></i> |
||||
|
<div v-if="type === '2'" class="verify-sub-block" |
||||
|
:style="{'width':Math.floor(parseInt(setSize.imgWidth)*47/310)+ 'px', |
||||
|
'height': setSize.imgHeight, |
||||
|
'top':'-' + (parseInt(setSize.imgHeight) + vSpace) + 'px', |
||||
|
'background-size': setSize.imgWidth + ' ' + setSize.imgHeight, |
||||
|
}"> |
||||
|
<img :src="'data:image/png;base64,'+blockBackImgBase" alt="" style="width:100%;height:100%;display:block"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<script type="text/babel"> |
||||
|
/** |
||||
|
* VerifySlide |
||||
|
* @description 滑块 |
||||
|
* */ |
||||
|
import {aesEncrypt} from "./../utils/ase" |
||||
|
import {resetSize} from './../utils/util' |
||||
|
import {reqGet,reqCheck} from "../api/verifition-api" |
||||
|
|
||||
|
// "captchaType":"blockPuzzle", |
||||
|
export default { |
||||
|
name: 'VerifySlide', |
||||
|
props: { |
||||
|
captchaType:{ |
||||
|
type:String, |
||||
|
}, |
||||
|
type: { |
||||
|
type: String, |
||||
|
default: '1' |
||||
|
}, |
||||
|
//弹出式pop,固定fixed |
||||
|
mode: { |
||||
|
type: String, |
||||
|
default: 'fixed' |
||||
|
}, |
||||
|
vSpace: { |
||||
|
type: Number, |
||||
|
default: 5 |
||||
|
}, |
||||
|
explain: { |
||||
|
type: String, |
||||
|
default: '向右滑动完成验证' |
||||
|
}, |
||||
|
imgSize: { |
||||
|
type: Object, |
||||
|
default() { |
||||
|
return { |
||||
|
width: '310px', |
||||
|
height: '155px' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
blockSize: { |
||||
|
type: Object, |
||||
|
default() { |
||||
|
return { |
||||
|
width: '50px', |
||||
|
height: '50px' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
barSize: { |
||||
|
type: Object, |
||||
|
default() { |
||||
|
return { |
||||
|
width: '310px', |
||||
|
height: '40px' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
secretKey:'', //后端返回的加密秘钥 字段 |
||||
|
passFlag:'', //是否通过的标识 |
||||
|
backImgBase:'', //验证码背景图片 |
||||
|
blockBackImgBase:'', //验证滑块的背景图片 |
||||
|
backToken:"", //后端返回的唯一token值 |
||||
|
startMoveTime:"", //移动开始的时间 |
||||
|
endMovetime:'', //移动结束的时间 |
||||
|
tipsBackColor:'', //提示词的背景颜色 |
||||
|
tipWords:'', |
||||
|
text: '', |
||||
|
finishText:'', |
||||
|
setSize: { |
||||
|
imgHeight: 0, |
||||
|
imgWidth: 0, |
||||
|
barHeight: 0, |
||||
|
barWidth: 0 |
||||
|
}, |
||||
|
top: 0, |
||||
|
left: 0, |
||||
|
moveBlockLeft: undefined, |
||||
|
leftBarWidth: undefined, |
||||
|
// 移动中样式 |
||||
|
moveBlockBackgroundColor: undefined, |
||||
|
leftBarBorderColor: '#ddd', |
||||
|
iconColor: undefined, |
||||
|
iconClass: 'icon-right', |
||||
|
status: false, //鼠标状态 |
||||
|
isEnd: false, //是够验证完成 |
||||
|
showRefresh: true, |
||||
|
transitionLeft: '', |
||||
|
transitionWidth: '' |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
barArea() { |
||||
|
return this.$el.querySelector('.verify-bar-area') |
||||
|
}, |
||||
|
resetSize() { |
||||
|
return resetSize |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
init() { |
||||
|
this.text = this.explain |
||||
|
this.getPictrue(); |
||||
|
this.$nextTick(() => { |
||||
|
let setSize = this.resetSize(this) //重新设置宽度高度 |
||||
|
for (let key in setSize) { |
||||
|
this.$set(this.setSize, key, setSize[key]) |
||||
|
} |
||||
|
this.$parent.$emit('ready', this) |
||||
|
}) |
||||
|
|
||||
|
var _this = this |
||||
|
|
||||
|
window.removeEventListener("touchmove", function (e) { |
||||
|
_this.move(e); |
||||
|
}); |
||||
|
window.removeEventListener("mousemove", function (e) { |
||||
|
_this.move(e); |
||||
|
}); |
||||
|
|
||||
|
//鼠标松开 |
||||
|
window.removeEventListener("touchend", function () { |
||||
|
_this.end(); |
||||
|
}); |
||||
|
window.removeEventListener("mouseup", function () { |
||||
|
_this.end(); |
||||
|
}); |
||||
|
|
||||
|
window.addEventListener("touchmove", function (e) { |
||||
|
_this.move(e); |
||||
|
}); |
||||
|
window.addEventListener("mousemove", function (e) { |
||||
|
_this.move(e); |
||||
|
}); |
||||
|
|
||||
|
//鼠标松开 |
||||
|
window.addEventListener("touchend", function () { |
||||
|
_this.end(); |
||||
|
}); |
||||
|
window.addEventListener("mouseup", function () { |
||||
|
_this.end(); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
//鼠标按下 |
||||
|
start: function (e) { |
||||
|
e = e || window.event |
||||
|
if (!e.touches) { //兼容PC端 |
||||
|
var x = e.clientX; |
||||
|
} else { //兼容移动端 |
||||
|
var x = e.touches[0].pageX; |
||||
|
} |
||||
|
this.startLeft =Math.floor(x - this.barArea.getBoundingClientRect().left); |
||||
|
this.startMoveTime = +new Date(); //开始滑动的时间 |
||||
|
if (this.isEnd == false) { |
||||
|
this.text = '' |
||||
|
this.moveBlockBackgroundColor = '#337ab7' |
||||
|
this.leftBarBorderColor = '#337AB7' |
||||
|
this.iconColor = '#fff' |
||||
|
e.stopPropagation(); |
||||
|
this.status = true; |
||||
|
} |
||||
|
}, |
||||
|
//鼠标移动 |
||||
|
move: function (e) { |
||||
|
e = e || window.event |
||||
|
if (this.status && this.isEnd == false) { |
||||
|
if (!e.touches) { //兼容PC端 |
||||
|
var x = e.clientX; |
||||
|
} else { //兼容移动端 |
||||
|
var x = e.touches[0].pageX; |
||||
|
} |
||||
|
var bar_area_left = this.barArea.getBoundingClientRect().left; |
||||
|
var move_block_left = x - bar_area_left //小方块相对于父元素的left值 |
||||
|
if (move_block_left >= this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2) { |
||||
|
move_block_left = this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2; |
||||
|
} |
||||
|
if (move_block_left <= 0) { |
||||
|
move_block_left = parseInt(parseInt(this.blockSize.width) / 2); |
||||
|
} |
||||
|
//拖动后小方块的left值 |
||||
|
this.moveBlockLeft = (move_block_left - this.startLeft) + "px" |
||||
|
this.leftBarWidth = (move_block_left - this.startLeft) + "px" |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
//鼠标松开 |
||||
|
end: function () { |
||||
|
this.endMovetime = +new Date(); |
||||
|
var _this = this; |
||||
|
//判断是否重合 |
||||
|
if (this.status && this.isEnd == false) { |
||||
|
var moveLeftDistance = parseInt((this.moveBlockLeft || '').replace('px', '')); |
||||
|
moveLeftDistance = moveLeftDistance * 310/ parseInt(this.setSize.imgWidth) |
||||
|
let data = { |
||||
|
captchaType:this.captchaType, |
||||
|
"pointJson":this.secretKey ? aesEncrypt(JSON.stringify({x:moveLeftDistance,y:5.0}),this.secretKey):JSON.stringify({x:moveLeftDistance,y:5.0}), |
||||
|
"token":this.backToken |
||||
|
} |
||||
|
reqCheck(data).then(res=>{ |
||||
|
if (res.repCode == "0000") { |
||||
|
this.moveBlockBackgroundColor = '#5cb85c' |
||||
|
this.leftBarBorderColor = '#5cb85c' |
||||
|
this.iconColor = '#fff' |
||||
|
this.iconClass = 'icon-check' |
||||
|
this.showRefresh = false |
||||
|
this.isEnd = true; |
||||
|
if (this.mode=='pop') { |
||||
|
setTimeout(()=>{ |
||||
|
this.$parent.clickShow = false; |
||||
|
this.refresh(); |
||||
|
},1500) |
||||
|
} |
||||
|
this.passFlag = true |
||||
|
this.tipWords = `${((this.endMovetime-this.startMoveTime)/1000).toFixed(2)}s验证成功` |
||||
|
var captchaVerification = this.secretKey ? aesEncrypt(this.backToken+'---'+JSON.stringify({x:moveLeftDistance,y:5.0}),this.secretKey):this.backToken+'---'+JSON.stringify({x:moveLeftDistance,y:5.0}) |
||||
|
setTimeout(()=>{ |
||||
|
this.tipWords = "" |
||||
|
this.$parent.closeBox(); |
||||
|
this.$parent.$emit('success', {captchaVerification}) |
||||
|
},1000) |
||||
|
}else{ |
||||
|
this.moveBlockBackgroundColor = '#d9534f' |
||||
|
this.leftBarBorderColor = '#d9534f' |
||||
|
this.iconColor = '#fff' |
||||
|
this.iconClass = 'icon-close' |
||||
|
this.passFlag = false |
||||
|
setTimeout(function () { |
||||
|
_this.refresh(); |
||||
|
}, 1000); |
||||
|
this.$parent.$emit('error',this) |
||||
|
this.tipWords = "验证失败" |
||||
|
setTimeout(()=>{ |
||||
|
this.tipWords = "" |
||||
|
},1000) |
||||
|
} |
||||
|
}) |
||||
|
this.status = false; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
refresh: function () { |
||||
|
this.showRefresh = true |
||||
|
this.finishText = '' |
||||
|
|
||||
|
this.transitionLeft = 'left .3s' |
||||
|
this.moveBlockLeft = 0 |
||||
|
|
||||
|
this.leftBarWidth = undefined |
||||
|
this.transitionWidth = 'width .3s' |
||||
|
|
||||
|
this.leftBarBorderColor = '#ddd' |
||||
|
this.moveBlockBackgroundColor = '#fff' |
||||
|
this.iconColor = '#000' |
||||
|
this.iconClass = 'icon-right' |
||||
|
this.isEnd = false |
||||
|
|
||||
|
this.getPictrue() |
||||
|
setTimeout(() => { |
||||
|
this.transitionWidth = '' |
||||
|
this.transitionLeft = '' |
||||
|
this.text = this.explain |
||||
|
}, 300) |
||||
|
}, |
||||
|
|
||||
|
// 请求背景图片和验证图片 |
||||
|
getPictrue(){ |
||||
|
let data = { |
||||
|
captchaType:this.captchaType |
||||
|
} |
||||
|
reqGet(data).then(res=>{ |
||||
|
if (res.repCode == "0000") { |
||||
|
this.backImgBase = res.repData.originalImageBase64 |
||||
|
this.blockBackImgBase = res.repData.jigsawImageBase64 |
||||
|
this.backToken = res.repData.token |
||||
|
this.secretKey = res.repData.secretKey |
||||
|
}else{ |
||||
|
this.tipWords = res.repMsg; |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
}, |
||||
|
watch: { |
||||
|
// type变化则全面刷新 |
||||
|
type: { |
||||
|
immediate: true, |
||||
|
handler() { |
||||
|
this.init() |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
// 禁止拖拽 |
||||
|
this.$el.onselectstart = function () { |
||||
|
return false |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
@ -0,0 +1,25 @@ |
|||||
|
/** |
||||
|
* 此处可直接引用自己项目封装好的 axios 配合后端联调 |
||||
|
*/ |
||||
|
|
||||
|
import request from "@/api/index.js" //调用项目封装的axios
|
||||
|
|
||||
|
//获取验证图片 以及token
|
||||
|
export function reqGet(data) { |
||||
|
return request({ |
||||
|
url: '/captcha/get', |
||||
|
method: 'post', |
||||
|
data |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//滑动或者点选验证
|
||||
|
export function reqCheck(data) { |
||||
|
return request({ |
||||
|
url: '/captcha/check', |
||||
|
method: 'post', |
||||
|
data |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
|
||||
@ -0,0 +1,11 @@ |
|||||
|
import CryptoJS from 'crypto-js' |
||||
|
/** |
||||
|
* @word 要加密的内容 |
||||
|
* @keyWord String 服务器随机返回的关键字 |
||||
|
* */ |
||||
|
export function aesEncrypt(word,keyWord="XwKsGlMcdPMEhR1B"){ |
||||
|
var key = CryptoJS.enc.Utf8.parse(keyWord); |
||||
|
var srcs = CryptoJS.enc.Utf8.parse(word); |
||||
|
var encrypted = CryptoJS.AES.encrypt(srcs, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7}); |
||||
|
return encrypted.toString(); |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
export function resetSize(vm) { |
||||
|
var img_width, img_height, bar_width, bar_height; //图片的宽度、高度,移动条的宽度、高度
|
||||
|
|
||||
|
var parentWidth = vm.$el.parentNode.offsetWidth || window.offsetWidth |
||||
|
var parentHeight = vm.$el.parentNode.offsetHeight || window.offsetHeight |
||||
|
|
||||
|
if (vm.imgSize.width.indexOf('%') != -1) { |
||||
|
img_width = parseInt(this.imgSize.width) / 100 * parentWidth + 'px' |
||||
|
} else { |
||||
|
img_width = this.imgSize.width; |
||||
|
} |
||||
|
|
||||
|
if (vm.imgSize.height.indexOf('%') != -1) { |
||||
|
img_height = parseInt(this.imgSize.height) / 100 * parentHeight + 'px' |
||||
|
} else { |
||||
|
img_height = this.imgSize.height |
||||
|
} |
||||
|
|
||||
|
if (vm.barSize.width.indexOf('%') != -1) { |
||||
|
bar_width = parseInt(this.barSize.width) / 100 * parentWidth + 'px' |
||||
|
} else { |
||||
|
bar_width = this.barSize.width |
||||
|
} |
||||
|
|
||||
|
if (vm.barSize.height.indexOf('%') != -1) { |
||||
|
bar_height = parseInt(this.barSize.height) / 100 * parentHeight + 'px' |
||||
|
} else { |
||||
|
bar_height = this.barSize.height |
||||
|
} |
||||
|
|
||||
|
return {imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height} |
||||
|
} |
||||
|
|
||||
|
export const _code_chars = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] |
||||
|
export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0'] |
||||
|
export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC'] |
||||
@ -0,0 +1,101 @@ |
|||||
|
<template> |
||||
|
<div class="app-container"> |
||||
|
<div class="pwd-container"> |
||||
|
<label>找回密码</label> |
||||
|
<el-tabs |
||||
|
v-model="retrieveType" class="login-form" style="width: 300px;" |
||||
|
> |
||||
|
<el-tab-pane label="手机号找回" name="phone"> |
||||
|
<el-form ref="form" :model="retrieveAccountForm" label-width="0px"> |
||||
|
<el-form-item label=""> |
||||
|
<el-input v-model="retrieveAccountForm.username" placeholder="请输入手机号" autocomplete="off" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item label=""> |
||||
|
<el-input v-model="retrieveAccountForm.password" show-password placeholder="请输入密码" |
||||
|
autocomplete="off" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
<el-form-item label=""> |
||||
|
<el-input v-model="retrieveAccountForm.code" style="width: 150px;" placeholder="请输入验证码" |
||||
|
autocomplete="off" |
||||
|
/> |
||||
|
<el-button style="margin-left: 20px;" type="primary">发送验证码</el-button> |
||||
|
</el-form-item> |
||||
|
<el-form-item> |
||||
|
<el-button type="primary" style="width: 100%;">登录</el-button> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
</el-tab-pane> |
||||
|
<el-tab-pane label="邮箱注册" name="email"> |
||||
|
<el-form ref="emailRegForm" status-icon :rules="emailRetrieveRules" :model="retrieveAccountForm" |
||||
|
label-width="0px" |
||||
|
> |
||||
|
<el-form-item label="" prop="email"> |
||||
|
<el-input v-model="retrieveAccountForm.email" placeholder="请输入邮箱" autocomplete="off" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="" prop="password"> |
||||
|
<el-input v-model="retrieveAccountForm.password" show-password placeholder="请输入密码" |
||||
|
autocomplete="off" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="" prop="code"> |
||||
|
<el-input v-model="retrieveAccountForm.code" oninput="value=value.replace(/[^\d]/g,'')" |
||||
|
style="width: 150px;" maxlength="4" placeholder="请输入验证码" |
||||
|
autocomplete="off" |
||||
|
/> |
||||
|
<el-button style="margin-left: 20px;" :disabled="validateCodeBtn" type="primary" |
||||
|
@click="sendEmailCodeHandle" |
||||
|
> |
||||
|
{{ validateCodeBtnText }} |
||||
|
</el-button> |
||||
|
</el-form-item> |
||||
|
<el-form-item> |
||||
|
<el-button type="primary" style="width: 100%;" @click="emailRegHandle">确定</el-button> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
</el-tab-pane> |
||||
|
</el-tabs> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'RetrievePwd', |
||||
|
data() { |
||||
|
return { |
||||
|
retrieveType: 'phone', |
||||
|
retrieveAccountForm: { |
||||
|
email: '', |
||||
|
password: '' |
||||
|
}, |
||||
|
emailRetrieveRules: { |
||||
|
email: [ |
||||
|
{required: true, trigger: 'blur', message: '请输入邮箱'}, |
||||
|
{ |
||||
|
pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/, |
||||
|
message: '请输入正确的邮箱,多个使用;分隔' |
||||
|
} |
||||
|
], |
||||
|
// password: [{required: true, trigger: 'blur', validator: validatePassword}], |
||||
|
code: {required: true, trigger: 'blur', message: '请输入验证码'} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.app-container { |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-content: center; |
||||
|
} |
||||
|
.pwd-container { |
||||
|
width: 20%; |
||||
|
height: 50%; |
||||
|
border: 1px red solid; |
||||
|
} |
||||
|
</style> |
||||
@ -1,139 +0,0 @@ |
|||||
<template> |
|
||||
<div class="preview-form"> |
|
||||
<div class="" v-if="!formState"> |
|
||||
<el-row type="flex" justify="center" align="middle"> |
|
||||
<el-col :sm="{span:20}" :xs="{span:24,offset:0}" style="text-align: center"> |
|
||||
<h4 class="form-name-text"> |
|
||||
{{formConf.title}}</h4> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
<el-row type="flex" justify="center" align="middle"> |
|
||||
<el-col :sm="{span:20}" :xs="{span:24,offset:0}" style="text-align: center"> |
|
||||
<p class="form-name-text"> |
|
||||
{{formConf.description}} |
|
||||
</p> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
<el-divider></el-divider> |
|
||||
<parser v-if="formConf.fields.length" :form-conf="formConf" @submit="submitForm"/> |
|
||||
</div> |
|
||||
<div v-if="formState"> |
|
||||
<p style="text-align: center"> |
|
||||
<i class="el-icon-check"/>您已完成本次问卷,感谢您的帮助与支持</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
import Parser from '@/components/parser/Parser' |
|
||||
import {dbDataConvertForItemJson} from '@/utils/convert' |
|
||||
// 若parser是通过安装npm方式集成到项目中的,使用此行引入 |
|
||||
// import Parser from 'form-gen-parser' |
|
||||
window.onload = function() { |
|
||||
document.addEventListener('touchstart', function(event) { |
|
||||
if (event.touches.length > 1) { |
|
||||
event.preventDefault() |
|
||||
} |
|
||||
}) |
|
||||
document.addEventListener('gesturestart', function(event) { |
|
||||
event.preventDefault() |
|
||||
}) |
|
||||
} |
|
||||
export default { |
|
||||
components: { |
|
||||
Parser |
|
||||
}, |
|
||||
props: { |
|
||||
projectKey: '' |
|
||||
}, |
|
||||
data() { |
|
||||
return { |
|
||||
key2: +new Date(), |
|
||||
projectKey: '', |
|
||||
formState: false,//是否填写 |
|
||||
formConf: { |
|
||||
fields: [], |
|
||||
__methods__: { |
|
||||
clickTestButton1() { |
|
||||
console.log( |
|
||||
`%c【测试按钮1】点击事件里可以访问当前表单: |
|
||||
1) formModel='formData', 所以this.formData可以拿到当前表单的model |
|
||||
2) formRef='elForm', 所以this.$refs.elForm可以拿到当前表单的ref(vue组件) |
|
||||
`, |
|
||||
'color:#409EFF;font-size: 15px' |
|
||||
) |
|
||||
console.log('表单的Model:', this.formData) |
|
||||
console.log('表单的ref:', this.$refs.elForm) |
|
||||
} |
|
||||
}, |
|
||||
formRef: 'elForm', |
|
||||
formModel: 'formData', |
|
||||
size: 'small', |
|
||||
labelPosition: 'top', |
|
||||
labelWidth: 100, |
|
||||
formRules: 'rules', |
|
||||
gutter: 15, |
|
||||
disabled: false, |
|
||||
span: 24, |
|
||||
formBtns: true, |
|
||||
resetBtn: true, |
|
||||
unFocusedComponentBorder: true |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
computed: {}, |
|
||||
watch: {}, |
|
||||
created() { |
|
||||
this.formConf.size = window.innerWidth < 480 ? 'medium' : 'small' |
|
||||
}, |
|
||||
mounted() { |
|
||||
this.$api.get(`/user/project/details/${this.$route.query.key}`).then(res => { |
|
||||
if (res.data) { |
|
||||
let fields = res.data.projectItems.map(item => { |
|
||||
return dbDataConvertForItemJson(item) |
|
||||
}) |
|
||||
this.formConf.fields = fields |
|
||||
this.formConf.title = res.data.project.name |
|
||||
this.formConf.description = res.data.project.describe |
|
||||
} |
|
||||
}) |
|
||||
this.projectKey = this.$route.query.key |
|
||||
}, |
|
||||
methods: { |
|
||||
fillFormData(form, data) { |
|
||||
form.fields.forEach(item => { |
|
||||
const val = data[item.__vModel__] |
|
||||
if (val) { |
|
||||
item.__config__.defaultValue = val |
|
||||
} |
|
||||
}) |
|
||||
}, |
|
||||
submitForm(data) { |
|
||||
this.$api.post('/user/project/result/create', { |
|
||||
'projectKey': this.projectKey, |
|
||||
'collectData': data |
|
||||
}).then(res => { |
|
||||
this.formState = true |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
.preview-form { |
|
||||
margin: 15px auto; |
|
||||
width: 800px; |
|
||||
padding: 15px; |
|
||||
background-repeat: repeat; |
|
||||
background-color: rgba(229, 239, 247, 0.87); |
|
||||
} |
|
||||
|
|
||||
@media screen and (max-width: 750px) { |
|
||||
.preview-form { |
|
||||
margin: 0px; |
|
||||
width: 93% !important; |
|
||||
background-color: white; |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,33 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<Verify |
||||
|
ref="verify" |
||||
|
:mode="'pop'" |
||||
|
:captcha-type="'blockPuzzle'" |
||||
|
:img-size="{ width: '330px', height: '155px' }" |
||||
|
@success="'success'" |
||||
|
/> |
||||
|
<button @click="useVerify">调用验证组件</button> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
// 引入组件 |
||||
|
import Verify from '@/components/verifition/Verify' |
||||
|
|
||||
|
export default { |
||||
|
name: 'App', |
||||
|
components: { |
||||
|
Verify |
||||
|
}, |
||||
|
methods: { |
||||
|
success(params) { |
||||
|
// params 返回的二次验证参数, 和登录参数一起回传给登录接口,方便后台进行二次验证 |
||||
|
console.log(params) |
||||
|
}, |
||||
|
useVerify() { |
||||
|
this.$refs.verify.show() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||