操作步骤
- 按住滑块将卡片移动到对应凹槽位置进行验证
- 验证成功提示成功文字并刷新验证码
- 验证失败提示失败文字并刷新验证码
问题
- 如何实现移动
- 卡片如何跟着滑块移动
- 失败/成功后的刷新
- 成功/失败验证
方法
- uni-app的 movable-view (movable-view
必须在movable-area`组件中,并且必须是直接子节点,否则不能移动。) - 绑定style样式, 卡片位置与滑块位置进行绑定
- 失败/成功所有值置于默认状态, 实现刷新
- 将滑块移动轨迹和当前页面渲染的尺寸比值传递给后台 tianai-captcha 进行校验
效果图
验证成功
验证失败
HTML部分
<template>
<view class="container">
<view class="verify-code">
<view class="verify-code-top">安全验证</view>
<view class="verify-box">
<view class="verify-code-middle">
<view class="verify-bg">
<image id="bg" :src="captchaData.background_image" mode="heightFix"></image>
</view>
<view class="verify-slider">
<image id="slider-img" :style="{left: leftDistance+'rpx'}" :src="captchaData.slider_image" mode="heightFix"></image>
</view>
</view>
<view v-if="isActive" @touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend">
<movable-area class="move-block">
<view class="color-change" :style="{width: colorWidth+'rpx'}"></view>
<movable-view class="block-button" :x='x' direction="horizontal" @change="StartMove">
<text class="button-font">|||</text/>
</movable-view>
</movable-area>
</view>
<view v-if="isSuccess" class="check-suc-font">
<text style="color:green">验证成功</text>
</view>
<view v-if="isErr" class="check-err-font">
<text style="color: red;">验证失败</text>
<text>,请控制图块对齐缺口</text>
</view>
</view>
</view>
</view>
</template>
JS部分
<script>
export default {
name:"verifyCode",
data() {
return {
isActive: true,//刷新滑块
colorWidth: 0,//颜色滑块
leftDistance: 0,//默认卡片位置固定为靠左
isSuccess: false,//验证成功
isErr: false,//验证失败
x: 0,//滑块的X距离
xpos: 0,//读取X滑动的距离
bgImg:{//当前环境滑块背景尺寸
width: 0,
height: 0
},
sliderImg:{//当前环境滑块尺寸
width: 0,
height: 0
},
captchaData:{
id: null,//当前生成的滑块ID, 后端生成
background_image: '',//滑块背景图
slider_image: '',//滑块图片
startTime: new Date(),//起始时间
trackArr: [],//滑动轨迹
movePercent: 0,//滑动距离与背景图百分比
background_image_width: 0,//当前环境滑块背景宽
background_image_height: 0,//当前环境滑块背景高
slider_image_width: 0,//当前环境滑块宽
slider_image_height: 0,//当前环境滑块高
end: 206//滑块滑动界限值
}
};
},
onShow() {
this.getVertifyData()
},
methods:{
// 日志打印
printLog(...params) {
console.log(JSON.stringify(params));
},
// 滑块初始位置
touchstart(e){
let startX = e.changedTouches[0].pageX;
let startY = e.changedTouches[0].pageY;
this.captchaData.startX = startX;
this.captchaData.startY = startY;
const pageX = this.captchaData.startX;
const pageY = this.captchaData.startY;
const startTime = this.captchaData.startTime;
const trackArr = this.captchaData.trackArr;
trackArr.push({
x: pageX - startX,
y: pageY - startY,
type: "down",
t: (new Date().getTime() - startTime.getTime())
})
this.printLog('start', startX, startY)
},
// 滑块滑动中位置
touchmove(e){
let pageX = Math.round(e.changedTouches[0].pageX);
let pageY = Math.round(e.changedTouches[0].pageY);
const startX = this.captchaData.startX;
const startY = this.captchaData.startY;
const startTime = this.captchaData.startTime;
const end = this.captchaData.end;
const bgImageWidth = this.captchaData.background_image_width;
const trackArr = this.captchaData.trackArr;
let moveX = pageX - startX;
const track = {
x: pageX - startX,
y: pageY - startY,
type: "move",
t: (new Date().getTime() - startTime.getTime())
};
trackArr.push(track);
if(moveX < 0){
moveX = 0;
} else if(moveX > end){
moveX = end;
}
this.captchaData.moveX = moveX;
this.captchaData.movePercent = moveX / bgImageWidth;
this.printLog("move", track)
},
// 滑块停止滑动
touchend(e){
this.captchaData.stopTime = new Date()
let pageX = Math.round(e.changedTouches[0].pageX);
let pageY = Math.round(e.changedTouches[0].pageY);
const startX = this.captchaData.startX;
const startY = this.captchaData.startY;
const startTime = this.captchaData.startTime;
const trackArr = this.captchaData.trackArr;
const track = {
x: pageX - startX,
y: pageY - startY,
type: "up",
t: (new Date().getTime() - startTime.getTime())
}
trackArr.push(track);
this.printLog("up", track)
this.setVertifyData()
},
// 滑块绑定卡片移动距离
StartMove(e){
this.xpos = e.detail.x
this.colorWidth = this.xpos*2 +80
this.leftDistance = this.xpos*2
},
// 获取背景图和滑块图片
getVertifyData(){
let that = this
this.$http.get('getVertifyData').then(res => {
setTimeout(() => {
// 获取当前设备上渲染的背景图和滑块尺寸
let query = uni.createSelectorQuery().in(this)
query.select('#bg').boundingClientRect(res => {
this.bgImg.width = res.width
this.bgImg.height = res.height
}).exec();
query.select('#slider-img').boundingClientRect(res => {
this.sliderImg.width = res.width
this.sliderImg.height = res.height
}).exec();
}, 1000)
this.captchaData.id = res.data.id
this.captchaData.background_image = res.data.captcha.background_image
this.captchaData.slider_image = res.data.captcha.slider_image
setTimeout(() => {
this.initConfig(this.bgImg.width, this.bgImg.height, this.sliderImg.width, this.sliderImg.height, 206)
}, 1500)
}).catch(err => {
console.log(err);
})
},
// 初始化配置
initConfig(bgImageWidth, bgImageHeight, sliderImageWidth, sliderImageHeight, end){
this.captchaData.background_image_width = bgImageWidth
this.captchaData.background_image_height = bgImageHeight
this.captchaData.slider_image_width = sliderImageWidth
this.captchaData.slider_image_height = sliderImageHeight
this.captchaData.end = end
},
// 校验是否成功
setVertifyData(){
const dataForm = {
bg_image_width: this.captchaData.background_image_width,
bg_image_height: this.captchaData.background_image_height,
slider_image_width: this.captchaData.slider_image_width,
slider_image_height: this.captchaData.slider_image_height,
start_sliding_time: this.captchaData.startTime,
ent_sliding_time: this.captchaData.stopTime,
track_list: this.captchaData.trackArr
}
uni.request({
url: uni.getStorageSync('baseUrl') + 'checkImage?id=' + this.captchaData.id,
method: 'POST',
dataType: 'json',
data: JSON.stringify(dataForm),
header: {
'Content-Type': 'application/json',
'token': uni.getStorageSync('token')
}
}).then(res => {
if(res[1].statusCode == 200){
if(res[1].data == false){
this.isErr = true
setTimeout(() => {
this.refresh()
}, 1000)
} else {
this.isSuccess = true
setTimeout(() => {
this.refresh()
uni.navigateBack({
delta: 1
});
}, 1000)
}
}
}).catch(err => {
console.log(err);
})
},
// 刷新滑块
refresh(){
this.isActive = false
this.$nextTick(() => {
this.isActive = true
})
this.colorWidth = 0
this.x = 0
this.isErr = false
this.isSuccess = false
this.leftDistance = 0
this.captchaData.trackArr = []
this.getVertifyData()
}
}
}
</script>
CSS部分
<style scoped lang="scss">
.container{
.verify-code{
width: 100%;
height: 680rpx;
background-color: #FFFFFF;
padding: 100rpx 0;
z-index: 999;
box-shadow: 0 0 10rpx rgba(227, 227, 227, 0.7);
.verify-code-top{
width: 100%;
text-align: center;
line-height: 60rpx;
font-size: 36rpx;
font-weight: 800;
}
.verify-box{
width:100%;
padding: 20rpx 5%;
background-color: #ffffff;
.verify-code-middle{
width: 100%;
height: 400rpx;
position: relative;
.verify-bg{
width: 100%;
height: 100%;
position: absolute;
image{
width: 100%;
height: 100%;
}
}
.verify-slider{
height: 100%;
position: absolute;
left: 0;
top: 0;
image{
height: 100%;
}
}
}
.move-block{
width: 100%;
height: 80rpx;
margin: 20rpx 0;
background-color:rgba(255, 170, 0, 0.1);
border-radius: 100rpx;
.color-change{
height: 80rpx;
border-radius: 100rpx;
background-color: #ffaa00;
z-index:2;
}
.block-button{
border-radius: 100rpx;
background-color:white;
height: 100rpx;
width: 100rpx;
margin-top: -10rpx;
opacity: 0.5;
touch-action: none;
.button-font{
color: #ffaa00;
text-align: center;
font-size: 65rpx;
padding-left: 25rpx;
}
}
}
.check-suc-font{
width: 100%;
text-align: center;
font-size: 30rpx
}
.check-err-font{
width: 100%;
text-align: center;
font-size: 30rpx
}
}
}
}
</style>