2025-05-28 17:36:42 +08:00
|
|
|
<template>
|
2025-06-19 17:03:31 +08:00
|
|
|
<view class="captcha-container" id="container">
|
|
|
|
<view class="font-title">请通过滑块验证</view>
|
|
|
|
<view class="captcha-image" style="position: relative; width: 100%; height: 400rpx; overflow: hidden;">
|
|
|
|
<image :src="bgImage" class="bg-image" mode="widthFix" @load="init" />
|
|
|
|
<view class="overlay" :style="{ width: containerWidth + 'rpx', height: containerHeight + 'rpx' }">
|
|
|
|
<view
|
|
|
|
class="hole"
|
|
|
|
:style="{
|
2025-05-28 17:36:42 +08:00
|
|
|
top: originY + 'rpx',
|
2025-06-19 17:03:31 +08:00
|
|
|
left: originX + 'rpx',
|
2025-05-28 17:36:42 +08:00
|
|
|
width: pieceSize + 'rpx',
|
|
|
|
height: pieceSize + 'rpx',
|
|
|
|
clipPath: clipPath,
|
|
|
|
transform: 'translate(-50%, -50%)',
|
2025-06-03 17:29:22 +08:00
|
|
|
backgroundColor: 'rgba(0,0,0,0.6)'
|
2025-06-19 17:03:31 +08:00
|
|
|
}"
|
|
|
|
></view>
|
2025-05-28 17:36:42 +08:00
|
|
|
|
2025-06-19 17:03:31 +08:00
|
|
|
<view
|
|
|
|
class="piece"
|
|
|
|
:style="{
|
2025-05-28 17:36:42 +08:00
|
|
|
top: originY + 'rpx',
|
2025-06-19 17:03:31 +08:00
|
|
|
left: offsetX + 'rpx',
|
2025-05-28 17:36:42 +08:00
|
|
|
width: pieceSize + 'rpx',
|
|
|
|
height: pieceSize + 'rpx',
|
|
|
|
backgroundImage: `url(${bgImage})`,
|
|
|
|
backgroundSize: containerWidth + 'rpx ' + containerHeight + 'rpx',
|
2025-06-19 17:03:31 +08:00
|
|
|
backgroundPosition: `-${originX+30}rpx -${originY+43}rpx`,
|
2025-05-28 17:36:42 +08:00
|
|
|
clipPath: clipPath,
|
|
|
|
transform: 'translate(-50%, -50%)'
|
2025-06-19 17:03:31 +08:00
|
|
|
}"
|
|
|
|
></view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="slider-bar">
|
|
|
|
<view class="slider-bar-font">向右滑动滑块填充拼图</view>
|
|
|
|
<view
|
|
|
|
class="slider-button"
|
|
|
|
ref="btn"
|
|
|
|
@touchstart="onStart"
|
|
|
|
@touchmove="onMove"
|
|
|
|
@touchend="onEnd"
|
|
|
|
:style="{ left: offsetX + 'rpx', maxWidth: (containerWidth - pieceSize) + 'rpx' }"
|
|
|
|
>
|
|
|
|
<image
|
|
|
|
src="https://www.focusnu.com/media/directive/login/right.png"
|
|
|
|
style="width: 50rpx; height: 50rpx;"
|
|
|
|
mode="widthFix"
|
|
|
|
/>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
2025-05-28 17:36:42 +08:00
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
2025-06-19 17:03:31 +08:00
|
|
|
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue'
|
2025-05-28 17:36:42 +08:00
|
|
|
|
2025-06-19 17:03:31 +08:00
|
|
|
const emit = defineEmits(['success'])
|
2025-06-03 17:29:22 +08:00
|
|
|
|
2025-06-19 17:03:31 +08:00
|
|
|
const pieceSizePx = 50
|
|
|
|
const pieceSize = pieceSizePx * 2
|
|
|
|
const tolerance = 20
|
2025-05-28 17:36:42 +08:00
|
|
|
|
2025-06-19 17:03:31 +08:00
|
|
|
const containerWidth = ref(400)
|
|
|
|
const containerHeight = ref(400)
|
2025-05-28 17:36:42 +08:00
|
|
|
|
2025-06-19 17:03:31 +08:00
|
|
|
const originX = ref(0)
|
|
|
|
const originY = ref(0)
|
|
|
|
const offsetX = ref(0)
|
|
|
|
const dragging = ref(false)
|
|
|
|
const startX = ref(0)
|
2025-05-28 17:36:42 +08:00
|
|
|
|
2025-06-19 17:03:31 +08:00
|
|
|
const bgImage = ref('')
|
2025-05-28 17:36:42 +08:00
|
|
|
|
2025-06-19 17:03:31 +08:00
|
|
|
const instance = getCurrentInstance()
|
2025-05-28 17:36:42 +08:00
|
|
|
|
2025-06-19 17:03:31 +08:00
|
|
|
function getPuzzlePiecePath(size) {
|
|
|
|
const s = size
|
|
|
|
return `
|
|
|
|
M${10 * 2} 0
|
|
|
|
h${s / 3 - 10 * 2}
|
|
|
|
a${10 * 2} ${10 * 2} 0 0 1 0 ${20 * 2}
|
2025-05-28 17:36:42 +08:00
|
|
|
h${s / 3}
|
2025-06-19 17:03:31 +08:00
|
|
|
a${10 * 2} ${10 * 2} 0 0 0 0 -${20 * 2}
|
|
|
|
h${s / 3 - 10 * 2}
|
|
|
|
v${s / 3 - 10 * 2}
|
|
|
|
a${10 * 2} ${10 * 2} 0 0 1 -${20 * 2} 0
|
2025-05-28 17:36:42 +08:00
|
|
|
v${s / 3}
|
2025-06-19 17:03:31 +08:00
|
|
|
a${10 * 2} ${10 * 2} 0 0 0 ${20 * 2} 0
|
|
|
|
v${s / 3 - 10 * 2}
|
|
|
|
h-${s / 3 - 10 * 2}
|
|
|
|
a${10 * 2} ${10 * 2} 0 0 1 0 -${20 * 2}
|
2025-05-28 17:36:42 +08:00
|
|
|
h-${s / 3}
|
2025-06-19 17:03:31 +08:00
|
|
|
a${10 * 2} ${10 * 2} 0 0 0 0 ${20 * 2}
|
|
|
|
h-${s / 3 - 10 * 2}
|
2025-05-28 17:36:42 +08:00
|
|
|
z
|
2025-06-19 17:03:31 +08:00
|
|
|
`
|
|
|
|
}
|
|
|
|
|
|
|
|
const clipPath = `path('${getPuzzlePiecePath(pieceSize)}')`
|
|
|
|
|
|
|
|
function init() {
|
|
|
|
nextTick(() => {
|
|
|
|
if (!instance) {
|
|
|
|
console.error('无法获取组件实例')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
uni.createSelectorQuery()
|
|
|
|
.in(instance.proxy)
|
|
|
|
.select('.bg-image')
|
|
|
|
.boundingClientRect(data => {
|
|
|
|
if (!data) {
|
|
|
|
console.error('无法获取图片尺寸')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
containerWidth.value = data.width * 2
|
|
|
|
containerHeight.value = data.height * 2
|
|
|
|
|
|
|
|
originX.value = Math.random() * (containerWidth.value - pieceSize * 2) + pieceSize
|
|
|
|
if (originX.value < 100) originX.value = 100
|
|
|
|
if (originX.value > 400) originX.value = 400
|
|
|
|
originY.value = containerHeight.value / 2
|
|
|
|
offsetX.value = 0
|
|
|
|
})
|
|
|
|
.exec()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function onStart(e) {
|
|
|
|
dragging.value = true
|
|
|
|
startX.value = e.touches[0].clientX * 2
|
|
|
|
}
|
|
|
|
|
|
|
|
function onMove(e) {
|
|
|
|
if (!dragging.value) return
|
|
|
|
const clientX = e.touches[0].clientX * 2
|
|
|
|
let dx = clientX - startX.value
|
|
|
|
dx = Math.max(0, Math.min(dx, containerWidth.value - pieceSize))
|
|
|
|
offsetX.value = dx
|
|
|
|
}
|
|
|
|
|
|
|
|
function onEnd() {
|
|
|
|
dragging.value = false
|
|
|
|
if (Math.abs(offsetX.value - originX.value) < tolerance) {
|
|
|
|
uni.showToast({
|
|
|
|
title: '验证成功',
|
|
|
|
icon: 'none',
|
|
|
|
duration: 2000
|
|
|
|
})
|
|
|
|
// console.log("????", originX.value)
|
|
|
|
emit('success')
|
|
|
|
} else {
|
|
|
|
offsetX.value = 0
|
|
|
|
uni.showToast({
|
|
|
|
title: '验证失败',
|
|
|
|
icon: 'none',
|
|
|
|
duration: 2000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
const images = [
|
|
|
|
'https://www.focusnu.com/media/directive/login/0.png',
|
|
|
|
'https://www.focusnu.com/media/directive/login/1.png',
|
|
|
|
'https://www.focusnu.com/media/directive/login/2.png',
|
|
|
|
// 'https://www.focusnu.com/media/directive/login/3.png'
|
|
|
|
]
|
|
|
|
bgImage.value = images[Math.floor(Math.random() * images.length)]
|
|
|
|
console.log('加载图片:', bgImage.value)
|
|
|
|
})
|
2025-05-28 17:36:42 +08:00
|
|
|
</script>
|
|
|
|
|
2025-06-03 17:29:22 +08:00
|
|
|
<style scoped lang="scss">
|
2025-06-19 17:03:31 +08:00
|
|
|
.captcha-container {
|
|
|
|
user-select: none;
|
|
|
|
background-color: #fff;
|
|
|
|
width: 600rpx;
|
|
|
|
height: 700rpx;
|
|
|
|
margin: 0 auto;
|
|
|
|
border-radius: 30rpx;
|
|
|
|
overflow: hidden;
|
|
|
|
padding: 0 30rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.captcha-image {
|
|
|
|
position: relative;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
|
|
|
|
.bg-image {
|
|
|
|
position: relative;
|
|
|
|
z-index: 1;
|
|
|
|
display: block;
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.overlay {
|
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
z-index: 100;
|
|
|
|
pointer-events: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
.hole,
|
|
|
|
.piece {
|
|
|
|
position: absolute;
|
|
|
|
z-index: 101;
|
|
|
|
box-shadow: 0 0 6rpx rgba(0, 0, 0, 0.4);
|
|
|
|
}
|
|
|
|
|
|
|
|
.hole {
|
|
|
|
background-color: rgba(0, 0, 0, 0.3);
|
|
|
|
}
|
|
|
|
|
|
|
|
.piece {
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
background-size: cover;
|
|
|
|
cursor: grab;
|
|
|
|
}
|
|
|
|
|
|
|
|
.slider-bar {
|
|
|
|
position: relative;
|
|
|
|
height: 100rpx;
|
|
|
|
margin-top: 0rpx;
|
|
|
|
background: rgb(245, 246, 252);
|
|
|
|
width: 540rpx;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
.slider-bar-font {
|
|
|
|
color: rgb(169, 169, 171);
|
|
|
|
font-size: 28rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.slider-button {
|
|
|
|
position: absolute;
|
|
|
|
top: 0rpx;
|
|
|
|
left: 0;
|
|
|
|
width: 100rpx;
|
|
|
|
height: 100rpx;
|
|
|
|
background: #fff;
|
|
|
|
box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.2);
|
|
|
|
user-select: none;
|
|
|
|
border-radius: 10rpx;
|
|
|
|
border: 3rpx solid rgb(139, 218, 202);
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.font-title {
|
|
|
|
width: 100%;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
height: 100rpx;
|
|
|
|
font-size: 35rpx;
|
|
|
|
font-weight: 700;
|
|
|
|
}
|
|
|
|
</style>
|