Files
sweet-hut-ui/src/components/Login/LoginAuth.vue
2025-10-08 22:06:39 +08:00

183 lines
5.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="login-auth">
<div class="slider-container"
@mousedown="startDrag" @mousemove="handleDrag"
@mouseup="endDrag" @mouseleave="endDrag"
@touchstart="startDrag" @touchmove="handleDrag" @touchend="endDrag">
<div ref="draggableRef" class="draggable"
:style="{ left: `${loginAuth.dragLeft}px` }">
<i v-if="authStore.isVerified" class="fas fa-check"></i>
<i v-else class="fas fa-arrow-right"></i>
</div>
<div :style="{ width: loginAuth.progressWidth + '%' }" class="slider-progress"></div>
<div class="slider-message" :style="{color: authStore.isVerified ? '#FFFFFF' : '#696969'}">
<span v-if="authStore.isVerified">验证成功</span>
<span v-else-if="loginAuth.isDragging">拖动中...</span>
<span v-else>向右拖动滑块 完成验证</span>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { useAuthStore } from '@/store/auth'
const draggableRef = ref(null)
const authStore = useAuthStore()
const loginAuth = reactive({
isDragging: false,
initialX: 0,
offsetX: 0,
dragLeft: 0,
progressWidth: 0,
targetWidth: 100,
// 记录拖动的开始时间
startTime: 0,
// 记录拖动过程中的位置
positions: [],
// 记录拖动过程中的时间戳
timestamps: []
})
const startDrag = (event) => {
// 验证成功后禁止拖动
if (authStore.isVerified) return
loginAuth.isDragging = true
const touch = event.changedTouches ? event.changedTouches[0] : event
loginAuth.initialX = touch.clientX
loginAuth.offsetX = draggableRef.value?.offsetLeft
loginAuth.startTime = Date.now()
loginAuth.positions = []
loginAuth.timestamps = []
loginAuth.positions.push(loginAuth.initialX)
loginAuth.timestamps.push(loginAuth.startTime)
}
const handleDrag = (event) => {
if (loginAuth.isDragging && !authStore.isVerified) {
const touch = event.changedTouches ? event.changedTouches[0] : event
const dx = touch.clientX - loginAuth.initialX
const newLeft = loginAuth.offsetX + dx
const sliderWidth = draggableRef.value?.parentNode.offsetWidth
const handleWidth = draggableRef.value?.offsetWidth
if (newLeft < 0) {
loginAuth.dragLeft = 0
loginAuth.progressWidth = 0
} else if (newLeft > sliderWidth - handleWidth) {
loginAuth.dragLeft = sliderWidth - handleWidth
loginAuth.progressWidth = loginAuth.targetWidth
} else {
loginAuth.dragLeft = newLeft
loginAuth.progressWidth = (newLeft / (sliderWidth - handleWidth)) * loginAuth.targetWidth
}
loginAuth.positions.push(touch.clientX)
loginAuth.timestamps.push(Date.now())
}
}
const endDrag = () => {
loginAuth.isDragging = false
if (loginAuth.progressWidth === loginAuth.targetWidth && !checkIsHuman()) {
authStore.isVerified = true
} else {
loginAuth.dragLeft = 0
loginAuth.progressWidth = 0
}
}
const checkIsHuman = () => {
const endTime = Date.now()
const dragDuration = endTime - loginAuth.startTime
const speedChanges = []
for (let i = 1; i < loginAuth.positions.length; i++) {
const distance = loginAuth.positions[i] - loginAuth.positions[i - 1]
const time = loginAuth.timestamps[i] - loginAuth.timestamps[i - 1]
const speed = time > 0 ? distance / time : 0
speedChanges.push(speed)
}
// 计算速度变化的标准差
const meanSpeed = speedChanges.reduce((sum, speed) => sum + speed, 0) / speedChanges.length
const variance = speedChanges.reduce((sum, speed) => sum + Math.pow(speed - meanSpeed, 2), 0) / speedChanges.length
const stdDevSpeed = Math.sqrt(variance)
// 检查是否有停顿
let hasPause = false
for (let i = 1; i < loginAuth.timestamps.length; i++) {
if (loginAuth.timestamps[i] - loginAuth.timestamps[i - 1] > 200) {
hasPause = true
break
}
}
// 综合判断是否为机器操作
return dragDuration >= 100 && stdDevSpeed >= 0.1 && hasPause
}
</script>
<style lang="scss">
.login-auth {
width: 100%;
.slider-container {
position: relative;
height: 32px;
background-color: #E7E9E8;
border: 1px solid #E7E9E8;
border-radius: 5px;
overflow: hidden;
.draggable {
position: absolute;
top: 0;
left: 0;
width: 35px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--el-color-primary);
color: #FFFFFF;
cursor: pointer;
z-index: 3;
border-left: 1px solid #E7E9E8;
border-radius: 3px;
}
.slider-progress {
position: absolute;
top: 0;
left: 0;
height: 100%;
background-color: var(--el-color-primary);
z-index: 1;
}
.slider-message {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
z-index: 2;
user-select: none;
span {
transition: opacity 0.3s ease;
opacity: 1;
}
}
}
}
</style>