|  | @@ -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>
 |