|
@@ -0,0 +1,175 @@
|
|
|
+<template>
|
|
|
+ <!-- 滑动验证码容器 -->
|
|
|
+ <div class="slider-container" ref="containerRef">
|
|
|
+ <!-- 滑动遮罩层,显示滑动进度 -->
|
|
|
+ <div class="slider-mask" :style="{ width: sliderMaskWidth }" />
|
|
|
+ <!-- 滑块按钮 -->
|
|
|
+ <div
|
|
|
+ class="slider"
|
|
|
+ ref="sliderRef"
|
|
|
+ @mousedown.prevent="handleDragStart"
|
|
|
+ :class="{ 'slider-success': verified }"
|
|
|
+ :style="{ left: sliderLeft }"
|
|
|
+ >
|
|
|
+ <!-- 滑块图标 -->
|
|
|
+ <el-icon><DArrowRight /></el-icon>
|
|
|
+ </div>
|
|
|
+ <!-- 提示文本 -->
|
|
|
+ <div class="slider-text" :class="{ 'slider-text-success': verified }">
|
|
|
+ {{ verified ? "验证通过" : "请按住滑块,拖动至最右侧" }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { DArrowRight } from "@element-plus/icons-vue"
|
|
|
+
|
|
|
+const emit = defineEmits(["verifySuccess"])
|
|
|
+
|
|
|
+// DOM 引用
|
|
|
+const containerRef = ref<HTMLElement | null>(null) // 容器元素引用
|
|
|
+const sliderRef = ref<HTMLElement | null>(null) // 滑块元素引用
|
|
|
+
|
|
|
+// 状态变量
|
|
|
+const sliderLeft = ref("0px") // 滑块左侧位置
|
|
|
+const sliderMaskWidth = ref("0px") // 遮罩层宽度
|
|
|
+const verified = ref(false) // 验证状态
|
|
|
+
|
|
|
+// 拖动相关变量
|
|
|
+let startX = 0 // 开始拖动时的 X 坐标
|
|
|
+let sliderLeft_temp = 0 // 临时存储滑块位置
|
|
|
+
|
|
|
+/**
|
|
|
+ * 开始拖动处理
|
|
|
+ * @param e 鼠标事件对象
|
|
|
+ */
|
|
|
+const handleDragStart = (e: MouseEvent) => {
|
|
|
+ if (verified.value) return // 如果已验证通过,不再处理
|
|
|
+ startX = e.clientX
|
|
|
+ sliderLeft_temp = parseInt(sliderLeft.value)
|
|
|
+ // 添加鼠标移动和松开事件监听
|
|
|
+ document.addEventListener("mousemove", handleDragMove)
|
|
|
+ document.addEventListener("mouseup", handleDragEnd)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 拖动过程处理
|
|
|
+ * @param e 鼠标事件对象
|
|
|
+ */
|
|
|
+const handleDragMove = (e: MouseEvent) => {
|
|
|
+ if (verified.value) return
|
|
|
+ const container = containerRef.value
|
|
|
+ if (!container) return
|
|
|
+
|
|
|
+ // 计算拖动距离
|
|
|
+ const diff = e.clientX - startX
|
|
|
+ const containerWidth = container.offsetWidth
|
|
|
+ const sliderWidth = sliderRef.value?.offsetWidth || 0
|
|
|
+ const maxLeft = containerWidth - sliderWidth
|
|
|
+
|
|
|
+ // 计算新位置并限制范围
|
|
|
+ let newLeft = sliderLeft_temp + diff
|
|
|
+ if (newLeft < 0) newLeft = 0
|
|
|
+ if (newLeft > maxLeft) newLeft = maxLeft
|
|
|
+
|
|
|
+ // 更新滑块和遮罩层位置
|
|
|
+ sliderLeft.value = newLeft + "px"
|
|
|
+ sliderMaskWidth.value = newLeft + sliderWidth / 2 + "px"
|
|
|
+
|
|
|
+ // 检查是否验证成功
|
|
|
+ if (newLeft === maxLeft) {
|
|
|
+ verified.value = true
|
|
|
+ emit("verifySuccess", true) // 触发验证成功
|
|
|
+ handleDragEnd()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 结束拖动处理
|
|
|
+ */
|
|
|
+const handleDragEnd = () => {
|
|
|
+ if (!verified.value) {
|
|
|
+ // 如果未验证成功,重置位置
|
|
|
+ sliderLeft.value = "0px"
|
|
|
+ sliderMaskWidth.value = "0px"
|
|
|
+ }
|
|
|
+ // 移除事件监听
|
|
|
+ document.removeEventListener("mousemove", handleDragMove)
|
|
|
+ document.removeEventListener("mouseup", handleDragEnd)
|
|
|
+}
|
|
|
+
|
|
|
+// 组件卸载时清理事件监听
|
|
|
+onUnmounted(() => {
|
|
|
+ document.removeEventListener("mousemove", handleDragMove)
|
|
|
+ document.removeEventListener("mouseup", handleDragEnd)
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 滑动验证码容器样式 */
|
|
|
+.slider-container {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 30px;
|
|
|
+ background-color: #B3B3B3;
|
|
|
+ /* border: 1px solid #B3B3B3 ; */
|
|
|
+ border-radius: 4px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 滑动遮罩层样式 */
|
|
|
+.slider-mask {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ height: 100%;
|
|
|
+ background-color: #18961C;
|
|
|
+ border-radius: 4px;
|
|
|
+ /* transition: width 0.1s linear; */
|
|
|
+}
|
|
|
+
|
|
|
+/* 滑块按钮样式 */
|
|
|
+.slider {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 50px;
|
|
|
+ height: 30px;
|
|
|
+ background: #FFFFFF;
|
|
|
+ border: 1px solid #B3B3B3 ;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: background-color 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #B3B3B3;
|
|
|
+}
|
|
|
+
|
|
|
+/* 验证成功时滑块样式 */
|
|
|
+.slider-success {
|
|
|
+ /* background-color: #18961C; */
|
|
|
+}
|
|
|
+
|
|
|
+/* 提示文本样式 */
|
|
|
+.slider-text {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ user-select: none;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #FFFFFF;
|
|
|
+ width: auto;
|
|
|
+
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+}
|
|
|
+
|
|
|
+/* 验证成功时文本样式 */
|
|
|
+.slider-text-success {
|
|
|
+ color: #FFFFFF;
|
|
|
+}
|
|
|
+</style>
|