You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

433 lines
12 KiB

4 months ago
<template>
<view>
<view class="cu-modal" :class="show?'show':''" @click.stop="close()">
<view @click.stop @touchmove.stop="'diyname'" class="cu-dialog captchaDialog bg-white text-left"
style="border-radius: 40rpx">
<view class="padding-xs margin-top text-center">
<text class="text-grey text-df">人机验证</text>
</view>
<view class="margin-top-xs text-center">
<text class="text-lg text-black">{{title}}</text>
</view>
<view class="margin-top-lg flex justify-center text-sl">
<view class="captchaImage flex-center bg-white">
<view v-if="captchaSrc!=''" class="captchaData" :style="{ transform: `rotate(${imageTransform}deg)`, backgroundImage: `url(${captchaSrc})` }"></view>
<text v-if="pageLoading || captchaSrcLoading" class="captchaIcon wlIcon-jiazai2 text-orange wlIconfont-spin"></text>
<text v-if="captchaError" name="close" class="captchaIcon wlIcon-31guanbi text-white"></text>
<template v-if="isDrag">
<!-- 辅助虚线 -->
<view class="verticalArrow"></view>
<view class="transverseArrow"></view>
</template>
</view>
</view>
<view class="capFoot margin-top-lg margin-bottom-sm padding-lr-sm">
<view class="slider solid" id="slider">
<view class="sliderItem clickActive-gray text-xxl" :class="captchaError?'shake':''" id="sliderItem"
@touchstart="dragStart" @touchmove.prevent="dragMove" @touchend="dragEnd"
@mousedown="dragStart" @mousemove.prevent="dragMove" @mouseup="dragEnd"
@dragstart="dragStart" @dragend="dragEnd" @dragover.stop @drop="dragMove" :style="{
left: `${sliderItemLeft}px`,
backgroundColor: captchaError?'red':(isDrag?'#fe6600':'#fff')
}">
<text v-if="dragLoading || pageLoading || captchaSrcLoading" class="wlIcon-jiazai2 wlIconfont-spin"></text>
<text v-else :class="dragIcon" class="wlIcon-shoushouzhang"></text>
</view>
</view>
</view>
<view class="text-center text-sm margin-bottom">
<view class="text-xs text-gray">
<text></text>
<text class="padding-lr-xs text-red">Wanl</text>
<text>提供技术支持</text>
</view>
</view>
<view class="captchaCloseView">
<text @click="close" name="close" class="wlIcon-31guanbi text-white text-sl"></text>
</view>
</view>
</view>
</view>
</template>
<script>
let pmsSuccess = null; //验证成功事件
let pmsError = null; //弹窗关闭事件
export default {
data() {
return {
title: '',
show: false, //控制弹窗是否展示
captchaSrc: "", //验证码图片 base64
isDrag: false, //是否拖动中
dragStartTime: 0, //拖动开始时间
dragUseTime: 0, //拖动用时
mouseTrackList: [], //鼠标轨迹列表
sliderItemInitX: 0, //滑动按钮初始X坐标
sliderItemLeftMax: 0, //滑块允许距离左边最大长度
sliderItemLeft: 0,
captchaSrcLoading: false, //请求图片验证码中
pageLoading: false, //页面加载中,验证码图片加载中
dragLoading: false, //拖动按钮loading状态
captchaError: false, //是否验证失败
append: {} //附加信息
}
},
computed: {
/**
* 图片旋转角度
*/
imageTransform() {
if (this.sliderItemLeft == 0) return 0;
return Math.ceil((360 / this.sliderItemLeftMax) * this.sliderItemLeft * 100) / 100;
},
/**
* 拖动条上的图标
*/
dragIcon() {
let white = 'text-white';
let black = 'text-black';
if (this.captchaError) return white;
if (this.isDrag) return white;
return black;
}
},
methods: {
/**
* 外部调用
*/
init(append) {
return new Promise((yes, err) => {
pmsSuccess = yes;
pmsError = err;
this.append = append;
this.captchaSrc = false;
this.captchaError = false;
this.isDrag = false;
this.captchaSrcLoading = false;
this.pageLoading = false;
this.dragLoading = false;
this.show = true;
if (this.sliderItemLeftMax == 0) {
this.pageLoading = true;
setTimeout(() => {
this.$nextTick(() => {
this.getRect('#slider').then(res => {
let sliderWidth = res.width;
this.getRect('#sliderItem').then(res => {
this.sliderItemInitX = res.left;
this.sliderItemLeftMax = sliderWidth - res.width;
this.pageLoading = false;
this.getCaptchaSrc()
}).catch((err) => {
console.log("获取节点信息失败 2 -> ", err);
})
}).catch((err) => {
console.log("获取节点信息失败 1 -> ", err);
})
})
}, 500)
} else {
this.getCaptchaSrc()
}
})
},
/**
* 手动关闭弹窗
*/
close() {
this.show = false;
pmsError(); //执行关闭回调事件
},
/**
* 拖动开始事件
*/
dragStart(e) {
let that = this;
if (that.dragLoading) return false;
if (that.pageLoading) return false;
if (that.captchaSrcLoading) return false;
that.mouseTrackList = []; //初始化轨迹列表
that.dragStartTime = new Date().getTime(); //记录开始时间
that.isDrag = true;
that.addMouseTrack(e);
},
/**
* 手指移动事件
*/
dragMove(e) {
let that = this;
if (that.dragLoading) return false;
if (that.pageLoading) return false;
if (that.captchaSrcLoading) return false;
if (!that.isDrag) return false;
let maringLeft = (e.clientX ? e.clientX : e.changedTouches[0].clientX) - that.sliderItemInitX - 30;
if (maringLeft < 0) maringLeft = 0;
if (maringLeft > that.sliderItemLeftMax) maringLeft = that.sliderItemLeftMax;
that.sliderItemLeft = maringLeft;
that.addMouseTrack(e);
},
/**
* 拖动结束事件
*/
dragEnd(e) {
let that = this;
if (that.dragLoading) return false;
if (that.pageLoading) return false;
if (that.captchaSrcLoading) return false;
that.addMouseTrack(e);
that.isDrag = false;
that.dragLoading = true; //开启loading准备ajax
that.dragUseTime = new Date().getTime() - that.dragStartTime; //计算拖动时间
that.checkCaptcha();
},
/**
* 加载验证码
*/
getCaptchaSrc() {
this.captchaSrcLoading = true;
this.sliderItemLeft = 0;
this.captchaSrc = '';
this.title = '拖动滑块使图片角度为正';
uni.request({
url: '/wanlshop/sms/captcha',
method: 'POST',
withCredentials: true,
complete: res => {
this.captchaSrcLoading = false;
if(res.res.code == 1){
this.dragLoading = false;
this.captchaSrc = `data:image/png;base64,${res.res.data.captchaSrc}`
// // #ifdef MP
// uni.removeStorageSync('wanlshop:cookie');
// uni.setStorageSync('wanlshop:cookie', res.header['Set-Cookie'].split(";")[0]);
// // #endif
// #ifdef MP || H5
uni.removeStorageSync('wanlshop:cookie');
uni.setStorageSync('wanlshop:cookie', res.res.data.captchaCookie);
// #endif
}else{
this.$wanlshop.msg(res.res.msg);
setTimeout(()=>{ // 防止闪烁,延迟一秒
this.close(); //关闭验证码
},1600)
}
}
});
},
/**
* 提交效验
*/
checkCaptcha() {
uni.request({
url: '/wanlshop/sms/check',
method: 'POST',
withCredentials: true,
// #ifdef MP || H5
header:{
'Cookie': uni.getStorageSync('wanlshop:cookie')
},
// #endif
data: {
append: JSON.stringify(this.append),
rotationAngle: this.imageTransform, //旋转角度,为'坏人'增加繁琐的工作
mouseTrackList: JSON.stringify(this.mouseTrackList), //滑动轨迹
dragUseTime: this.dragUseTime, //拖动用时
dragStartTime: this.dragStartTime, //拖动开始时间
keyO: Math.ceil(new Date().getTime() * Math.random() * 678951),
keyA: Math.ceil(new Date().getTime() * Math.random() * 123456),
keyD: Math.ceil(new Date().getTime() * Math.random() * 781545),
hash: Math.ceil(new Date().getTime() * Math.random() * 456789),
md5Key: Math.ceil(new Date().getTime() * Math.random() * 7891),
},
complete: res => {
if(res.res.code == 1){
this.title = '恭喜你,验证成功!';
this.show = false;
pmsSuccess(); //回调
}else{
this.title = res.res.msg;
this.dragLoading = false;
this.captchaError = true;
setTimeout(() => {
this.captchaError = false;
if(res.res.code == 0){
this.getCaptchaSrc();
}else{
if(res.res.data.getNewCaptcha){
this.getCaptchaSrc();
}else{
this.captchaSrcLoading = false;
this.sliderItemLeft = 0;
}
}
}, 1000)
}
}
});
},
/**
* 添加滑动数据进鼠标轨迹列表
*/
addMouseTrack() {
let lastTime = this.mouseTrackList == '' ? this.dragStartTime : this.mouseTrackList[this.mouseTrackList
.length - 1].t;
let now = new Date().getTime();
if (lastTime + 200 <= now) {
this.mouseTrackList.push({
r: Math.ceil(this.imageTransform / 360 * 10000) / 100, //当前角度
t: now, //当前时间戳
})
}
},
getRect(selector, all) {
return new Promise(resolve => {
let query = null;
// 支付宝小程序不能加后面的.in(this),是它自身的限制
// #ifndef MP-ALIPAY
query = uni.createSelectorQuery().in(this)
// #endif
// #ifdef MP-ALIPAY
query = uni.createSelectorQuery()
// #endif
query[all ? 'selectAll' : 'select'](selector)
.boundingClientRect(rect => {
if (all && Array.isArray(rect) && rect.length) {
resolve(rect)
}
if (!all && rect) {
resolve(rect)
}
})
.exec()
})
}
}
}
</script>
<style lang="scss" scoped>
.captchaImage {
height: 320rpx;
width: 320rpx;
border-radius: 50%;
overflow: hidden;
position: relative;
display: flex;
align-items: center;
justify-content: center;
.captchaData{
height: 320rpx;
width: 320rpx;
border-radius: 50%;
overflow: hidden;
background-color: #f5f5f5;
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
}
.captchaIcon {
position: absolute;
}
.verticalArrow {
position: absolute;
height: 320rpx;
border-left: 1px dashed #fff;
margin-left: 1px;
width: 1px;
}
.transverseArrow {
position: absolute;
height: 1px;
width: 320rpx;
border-top: 1px dashed #fff;
margin-top: 1px;
overflow: visible;
}
}
.capFoot {
height: 60px;
display: flex;
flex-direction: row;
justify-content: center;
.slider {
width: 580rpx;
height: 90rpx;
background-color: #f5f5f5;
border-radius: 900rpx;
overflow: hidden;
position: relative;
.sliderItem {
height: 88rpx;
width: 110rpx;
border-radius: 900rpx;
background-color: white;
position: absolute;
left: 0;
top: 1rpx;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
-webkit-box-shadow: 0 21px 52px 0 rgba(82, 82, 82, .2);
box-shadow: 0 21px 52px 0 rgba(82, 82, 82, .2);
}
}
}
.shake {
animation: shake 800ms ease-in-out;
}
@keyframes shake {
/* 水平抖动,核心代码 */
10%,
90% {
transform: translate3d(-1px, 0, 0);
}
20%,
80% {
transform: translate3d(+2px, 0, 0);
}
30%,
70% {
transform: translate3d(-3px, 0, 0);
}
40%,
60% {
transform: translate3d(+3px, 0, 0);
}
50% {
transform: translate3d(-3px, 0, 0);
}
}
.captchaDialog {
position: relative;
overflow: visible;
}
.captchaCloseView {
position: absolute;
bottom: -150rpx;
width: 100%;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>