<template>
<div>
<van-button type="primary" @click="sendVerification">获取验证码</van-button>
<van-popup
v-model="captchaVisible"
closeable
position="bottom"
class="login-captcha"
:close-on-click-overlay="false"
safe-area-inset-bottom
>
<div class="login-captcha-title">拖动下方滑块完成验证</div>
<div class="login-captcha-main">
<img alt="网络错误" ref="shade" class="login-captcha-shade" />
<img alt="网络错误" ref="puzzle" class="login-captcha-puzzle" />
<div class="login-captcha-refresh" @click="getCaptchaPuzzle"></div>
</div>
<van-slider
class="login-captcha-slider"
active-color="transparent"
v-model="slider"
@change="getVerification"
>
<template #button>
<div class="login-captcha-slider__button"></div>
</template>
</van-slider>
</van-popup>
</div>
</template>
<script>
import { getCaptchaPuzzle, getVerification } from '@/api/utils'
export default {
data() {
return {
captchaVisible: false,
// 滑块位置 和 滑块图
captchaPuzzle: {
x: 0,
y: 0,
shadeImageUrl: '',
cutoutImageUrl: '',
},
loginForm: {
mobile: '13333333333',
},
slider: 13,
puzzleLeft: 0,
}
},
watch: {
slider(val) {
const max = (1 - this.$refs.puzzle.width / this.$refs.shade.width) * 100
const slope = max / 74
this.puzzleLeft = Math.min(Math.max(0, slope * (val - 13)), max)
this.$refs.puzzle.style.left = this.puzzleLeft + '%'
},
captchaVisible(val) {
if (val) {
this.$nextTick(() => {
this.calcPuzzle()
})
}
},
},
methods: {
sendVerification() {
this.getCaptchaPuzzle().then((val) => {
if (val) {
this.captchaVisible = true
}
})
},
getVerification() {
const { mobile } = this.loginForm
const { y } = this.captchaPuzzle
const x = ((this.puzzleLeft / 100) * this.$refs.shade.naturalWidth).toFixed(0)
getVerification({
Mobile: mobile,
X: x,
Y: y + '',
})
.then(({ data: res }) => {
if (res.code !== '0') {
this.$toast(res.msg || '验证码发送失败,请重试')
this.slider = 13
this.getCaptchaPuzzle()
} else {
this.$toast('短信验证已发送')
this.slider = 13
this.captchaVisible = false
}
})
.catch((err) => {
if (err) {
this.$toast('系统或网络异常')
this.slider = 13
this.getCaptchaPuzzle()
}
})
},
async getCaptchaPuzzle() {
const { mobile } = this.loginForm
return getCaptchaPuzzle({
Mobile: mobile,
})
.then((data) => {
const res = data.data
if (res.code !== '0') {
this.$toast(res.msg || '系统或网络异常')
return false
}
this.captchaPuzzle = res.data
if (this.captchaVisible) {
this.calcPuzzle()
} else {
this.captchaVisible = false
}
return true
})
.catch((err) => {
if (err) {
this.$toast('系统或网络异常')
}
return false
})
},
async calcPuzzle() {
const { x, y } = this.captchaPuzzle,
shade = this.$refs.shade,
puzzle = this.$refs.puzzle
await this.loadImage(shade, this.captchaPuzzle.shadeImageUrl)
puzzle.style.top = (((y + 1) / shade.naturalHeight) * 100).toFixed(6) + '%'
await this.loadImage(puzzle, this.captchaPuzzle.cutoutImageUrl)
puzzle.style.left = x + 'px'
},
async loadImage(img, url) {
return new Promise((resolve, reject) => {
img.onload = () => resolve(img)
img.onerror = reject
img.src = url
})
},
},
}
</script>
<style lang="less" scoped>
.login {
&-captcha {
height: 408px;
text-align: center;
padding: 0 24px;
box-sizing: border-box;
border-radius: 20px 20px 0px 0px;
&-title {
height: 28px;
font-size: 20px;
font-weight: 600;
color: #292b2e;
line-height: 28px;
padding: 32px 0 20px;
}
&-main {
width: 327px;
margin: 0 auto;
border-radius: 8px;
position: relative;
overflow: hidden;
}
&-shade {
display: block;
width: 100%;
}
&-puzzle {
position: absolute;
width: 50px;
}
&-refresh {
position: absolute;
top: 6px;
right: 12px;
width: 24px;
height: 24px;
background: url('../../assets/images/icon_login_refresh.png');
}
&-slider {
display: inline-block;
margin-top: 32px;
width: 290px;
height: 52px;
background: rgba(188, 191, 200, 0.3);
border-radius: 27px;
pointer-events: none;
&::before {
// 向右滑动验证;
content: '\5411\53f3\6ed1\52a8\9a8c\8bc1';
font-size: 16px;
line-height: 52px + 8px + 8px;
color: #8b91a0;
}
.van-slider__bar {
min-width: 13%;
max-width: 87%;
}
.van-slider__button-wrapper {
pointer-events: auto;
}
&__button {
pointer-events: auto;
width: 61px;
height: 44px;
background-size: cover !important;
background: url('../../assets/images/btn_verify.png');
}
}
}
}
</style>