Compare commits
	
		
			3 Commits
		
	
	
		
			415267e307
			...
			4c8e63bd7d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 4c8e63bd7d | ||
|  | 13bc4f4883 | ||
|  | 29cf4490e1 | 
| @ -1,367 +0,0 @@ | |||||||
| <template> |  | ||||||
|   <div class="puzzle-container"> |  | ||||||
|     <div class="puzzle-box" :style="{ height: boxHeight + 'px' }"> |  | ||||||
|       <!-- 背景图 --> |  | ||||||
|       <img :src="bgImageUrl" style="width: 320px;height: 191px;"  ref="bgImage" @load="onImageLoad" @error="handleImageError"> |  | ||||||
|       <!-- 滑块 --> |  | ||||||
|       <img  |  | ||||||
|         class="slider-block" |  | ||||||
|         :src="sliderImageUrl" |  | ||||||
|         :style="{ |  | ||||||
|           top: `${blockY}px`, |  | ||||||
|           left: `${moveX}px`, |  | ||||||
|           visibility: loaded ? 'visible' : 'hidden', |  | ||||||
|           width: '50px', |  | ||||||
|           height: '50px' |  | ||||||
|         }" |  | ||||||
|       ></img> |  | ||||||
|       <div v-if="verifySuccess || verifyError" :class="`text-#fff ${verifySuccess?'bg-#52C41A':'bg-#FF4D4F'} h-24px w-100% text-14px absolute left-0 bottom-0 text-center leading-24px`">{{ verifyTip }}</div> |  | ||||||
|     </div> |  | ||||||
|      |  | ||||||
|     <!-- 滑动条 --> |  | ||||||
|     <div class="slider-container"> |  | ||||||
|       <div class="slider-track"> |  | ||||||
|         <div  |  | ||||||
|           class="slider-bar" |  | ||||||
|           :style="{ width: `${moveX}px` }" |  | ||||||
|         ></div> |  | ||||||
|         <div  |  | ||||||
|           class="slider-button" |  | ||||||
|           :style="{ left: `${moveX}px` }" |  | ||||||
|           @mousedown="handleMouseDown" |  | ||||||
|           @touchstart="handleTouchStart" |  | ||||||
|         > |  | ||||||
|           <div class="slider-icon"></div> |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|      |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| </template> |  | ||||||
| 
 |  | ||||||
| <script setup> |  | ||||||
| import { ref, onMounted, onBeforeUnmount } from 'vue' |  | ||||||
| 
 |  | ||||||
| const props = defineProps({ |  | ||||||
|   show: { |  | ||||||
|     type: Boolean, |  | ||||||
|     default: false |  | ||||||
|   }, |  | ||||||
|   blockY: { |  | ||||||
|     type: Number, |  | ||||||
|     required: true |  | ||||||
|   }, |  | ||||||
|   bgImageUrl: { |  | ||||||
|     type: String, |  | ||||||
|     required: true |  | ||||||
|   }, |  | ||||||
|   sliderImageUrl: { |  | ||||||
|     type: String, |  | ||||||
|     required: true |  | ||||||
|   } |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| const emit = defineEmits(['leave']) |  | ||||||
| 
 |  | ||||||
| // 响应式状态 |  | ||||||
| const moveX = ref(0) |  | ||||||
| const startX = ref(0) |  | ||||||
| const oldMoveX = ref(0) |  | ||||||
| const maxMoveX = ref(0) |  | ||||||
| const boxHeight = ref(0) |  | ||||||
| const loaded = ref(false) |  | ||||||
| const isDragging = ref(false) |  | ||||||
| const verifySuccess = ref(false) |  | ||||||
| const verifyError = ref(false) |  | ||||||
| const verifyTip = ref('') |  | ||||||
| 
 |  | ||||||
| // 重置方法 |  | ||||||
| const reset = () => { |  | ||||||
|   moveX.value = 0 |  | ||||||
|   verifySuccess.value = false |  | ||||||
|   verifyError.value = false |  | ||||||
|   verifyTip.value = '' |  | ||||||
|   isDragging.value = false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // DOM引用 |  | ||||||
| const bgImage = ref(null) |  | ||||||
| 
 |  | ||||||
| // 方法 |  | ||||||
| const onImageLoad = () => { |  | ||||||
|   if (bgImage.value) { |  | ||||||
|     try { |  | ||||||
|       const img = bgImage.value |  | ||||||
|       // 确保图片已经完全加载 |  | ||||||
|       if (!img.complete) { |  | ||||||
|         return |  | ||||||
|       } |  | ||||||
|        |  | ||||||
|       const scale = img.width / img.naturalWidth // 计算图片缩放比例 |  | ||||||
|       boxHeight.value = img.height |  | ||||||
|        |  | ||||||
|       // 根据图片实际显示大小调整滑块大小 |  | ||||||
|       const blockSize = Math.round(50 * scale) |  | ||||||
|       document.documentElement.style.setProperty('--block-size', blockSize + 'px') |  | ||||||
|        |  | ||||||
|       maxMoveX.value = img.width - blockSize // 使用实际显示的滑块大小计算最大移动距离 |  | ||||||
|       loaded.value = true |  | ||||||
|     } catch (error) { |  | ||||||
|       console.error('Image load error:', error) |  | ||||||
|       // 设置默认值以确保组件仍然可用 |  | ||||||
|       const defaultBlockSize = 50 |  | ||||||
|       document.documentElement.style.setProperty('--block-size', defaultBlockSize + 'px') |  | ||||||
|       maxMoveX.value = 320 - defaultBlockSize // 使用默认容器宽度 |  | ||||||
|       loaded.value = true |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // 添加图片错误处理 |  | ||||||
| const handleImageError = () => { |  | ||||||
|   console.error('Image failed to load') |  | ||||||
|   // 设置默认值以确保组件仍然可用 |  | ||||||
|   const defaultBlockSize = 50 |  | ||||||
|   document.documentElement.style.setProperty('--block-size', defaultBlockSize + 'px') |  | ||||||
|   maxMoveX.value = 320 - defaultBlockSize |  | ||||||
|   loaded.value = true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const handleMouseDown = (event) => { |  | ||||||
|   event.preventDefault() |  | ||||||
|   startX.value = event.clientX |  | ||||||
|   oldMoveX.value = moveX.value |  | ||||||
|   isDragging.value = true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const handleTouchStart = (event) => { |  | ||||||
|   event.preventDefault() |  | ||||||
|   const touch = event.touches[0] |  | ||||||
|   startX.value = touch.clientX |  | ||||||
|   oldMoveX.value = moveX.value |  | ||||||
|   isDragging.value = true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const move = (clientX) => { |  | ||||||
|   let diff = clientX - startX.value |  | ||||||
|   let newMoveX = oldMoveX.value + diff |  | ||||||
|    |  | ||||||
|   // 限制移动范围 |  | ||||||
|   if (newMoveX < 0) newMoveX = 0 |  | ||||||
|   if (newMoveX > maxMoveX.value) newMoveX = maxMoveX.value |  | ||||||
|    |  | ||||||
|   moveX.value = Math.round(newMoveX) // 取整数避免小数点导致的模糊 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const handleMouseMove = (event) => { |  | ||||||
|   if (!isDragging.value) return |  | ||||||
|   move(event.clientX) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const handleTouchMove = (event) => { |  | ||||||
|   if (!isDragging.value) return |  | ||||||
|   event.preventDefault() // 防止页面滚动 |  | ||||||
|   move(event.touches[0].clientX) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const handleMouseUp = async () => { |  | ||||||
|   if (!isDragging.value) return |  | ||||||
|   isDragging.value = false |  | ||||||
|    |  | ||||||
|   try { |  | ||||||
|     emit('leave', moveX.value, (success) => { |  | ||||||
|       if (success) { |  | ||||||
|         verifySuccess.value = true |  | ||||||
|         verifyError.value = false |  | ||||||
|         verifyTip.value = '验证成功' |  | ||||||
|       } else { |  | ||||||
|         verifySuccess.value = false |  | ||||||
|         verifyError.value = true |  | ||||||
|         verifyTip.value = '验证失败' |  | ||||||
|     |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|   } catch (error) { |  | ||||||
|     verifySuccess.value = false |  | ||||||
|     verifyError.value = true |  | ||||||
|     verifyTip.value = '验证失败' |  | ||||||
|   }finally{ |  | ||||||
|     setTimeout(() => { |  | ||||||
|       reset() |  | ||||||
|     }, 2000) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const handleTouchEnd = () => { |  | ||||||
|   handleMouseUp() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // 预加载图片 |  | ||||||
| const preloadImages = () => { |  | ||||||
|   const bgImg = new Image() |  | ||||||
|   const sliderImg = new Image() |  | ||||||
|    |  | ||||||
|   bgImg.onload = () => { |  | ||||||
|     if (bgImage.value) { |  | ||||||
|       onImageLoad() |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   bgImg.src = props.bgImageUrl |  | ||||||
|   sliderImg.src = props.sliderImageUrl |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // 生命周期钩子 |  | ||||||
| onMounted(() => { |  | ||||||
|   preloadImages() |  | ||||||
|   window.addEventListener('mousemove', handleMouseMove) |  | ||||||
|   window.addEventListener('mouseup', handleMouseUp) |  | ||||||
|   window.addEventListener('touchmove', handleTouchMove) |  | ||||||
|   window.addEventListener('touchend', handleTouchEnd) |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| onBeforeUnmount(() => { |  | ||||||
|   window.removeEventListener('mousemove', handleMouseMove) |  | ||||||
|   window.removeEventListener('mouseup', handleMouseUp) |  | ||||||
|   window.removeEventListener('touchmove', handleTouchMove) |  | ||||||
|   window.removeEventListener('touchend', handleTouchEnd) |  | ||||||
| }) |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| <style scoped> |  | ||||||
| :root { |  | ||||||
|   --block-size: 50px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .puzzle-container { |  | ||||||
|   position: relative; |  | ||||||
|   margin: 0 auto; |  | ||||||
|   background: #fff; |  | ||||||
|   padding: 15px; |  | ||||||
|   border-radius: 10px; |  | ||||||
|   touch-action: none; |  | ||||||
|   -webkit-touch-callout: none; |  | ||||||
|   -webkit-user-select: none; |  | ||||||
|   user-select: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .puzzle-box { |  | ||||||
|   position: relative; |  | ||||||
|   width: 100%; |  | ||||||
|   overflow: hidden; |  | ||||||
|   background: #f8f8f8; |  | ||||||
|   border-radius: 8px; |  | ||||||
|   touch-action: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .bg-image { |  | ||||||
|   display: block; |  | ||||||
|   width: 100%; |  | ||||||
|   height: auto; |  | ||||||
|   -webkit-user-select: none; |  | ||||||
|   user-select: none; |  | ||||||
|   pointer-events: none; |  | ||||||
|   object-fit: contain; |  | ||||||
|   max-width: 100%; |  | ||||||
|   -webkit-touch-callout: none; |  | ||||||
|   -webkit-tap-highlight-color: transparent; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .slider-block { |  | ||||||
|   position: absolute; |  | ||||||
|   width: var(--block-size); |  | ||||||
|   height: var(--block-size); |  | ||||||
|   background-size: 100% 100%; |  | ||||||
|   background-repeat: no-repeat; |  | ||||||
|   background-position: center; |  | ||||||
|   cursor: pointer; |  | ||||||
|   -webkit-user-select: none; |  | ||||||
|   user-select: none; |  | ||||||
|   touch-action: none; |  | ||||||
|   will-change: transform; |  | ||||||
|   transform: translateZ(0); |  | ||||||
|   backface-visibility: hidden; |  | ||||||
|   -webkit-touch-callout: none; |  | ||||||
|   -webkit-tap-highlight-color: transparent; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .slider-container { |  | ||||||
|   position: relative; |  | ||||||
|   margin-top: 15px; |  | ||||||
|   height: 40px; |  | ||||||
|   touch-action: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .slider-track { |  | ||||||
|   position: relative; |  | ||||||
|   height: 40px; |  | ||||||
|   background: #f5f5f5; |  | ||||||
|   border-radius: 20px; |  | ||||||
|   touch-action: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .slider-bar { |  | ||||||
|   position: absolute; |  | ||||||
|   width: 100%; |  | ||||||
|   height: 100%; |  | ||||||
|   background: #91d5ff; |  | ||||||
|   border-radius: 20px; |  | ||||||
|   will-change: transform; |  | ||||||
|   transform-origin: left; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .slider-button { |  | ||||||
|   position: absolute; |  | ||||||
|   top: 0; |  | ||||||
|   width: 40px; |  | ||||||
|   height: 40px; |  | ||||||
|   background: #fff; |  | ||||||
|   border-radius: 50%; |  | ||||||
|   box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); |  | ||||||
|   cursor: pointer; |  | ||||||
|   will-change: transform; |  | ||||||
|   transform: translateZ(0); |  | ||||||
|   backface-visibility: hidden; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .slider-icon { |  | ||||||
|   position: absolute; |  | ||||||
|   top: 50%; |  | ||||||
|   left: 50%; |  | ||||||
|   transform: translate(-50%, -50%); |  | ||||||
|   width: 20px; |  | ||||||
|   height: 20px; |  | ||||||
|   background: #1890ff; |  | ||||||
|   border-radius: 50%; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .verify-result-bar { |  | ||||||
|   position: absolute; |  | ||||||
|   left: 0; |  | ||||||
|   top: 0; |  | ||||||
|   width: 100%; |  | ||||||
|   height: 40px; |  | ||||||
|   line-height: 40px; |  | ||||||
|   text-align: center; |  | ||||||
|   font-size: 14px; |  | ||||||
|   border-radius: 20px; |  | ||||||
|   transition: all 0.3s; |  | ||||||
|   z-index: 1; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .verify-result-bar.success { |  | ||||||
|   background: #52c41a; |  | ||||||
|   color: #fff; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .verify-result-bar.error { |  | ||||||
|   background: #ff4d4f; |  | ||||||
|   color: #fff; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* 移除旧的提示样式 */ |  | ||||||
| .verify-tip { |  | ||||||
|   display: none; |  | ||||||
| } |  | ||||||
| </style> |  | ||||||
							
								
								
									
										195
									
								
								app/components/puzzleComponent/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								app/components/puzzleComponent/index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,195 @@ | |||||||
|  | <template> | ||||||
|  |     <div class="m-auto bg-white p-15px rd-10px touch-none select-none"> | ||||||
|  |       <div class="relative w-full overflow-hidden bg-#f8f8f8 rd-8px" > | ||||||
|  |         <!-- 背景图 --> | ||||||
|  |         <img  | ||||||
|  |           :src="options?.canvasSrc"  | ||||||
|  |           class="block pointer-events-none object-contain" | ||||||
|  |           :style="{ width: `${options?.canvasWidth}px`, height: `${options?.canvasHeight}px` }" | ||||||
|  |           ref="bgImage"  | ||||||
|  |           @load="onImageLoad"  | ||||||
|  |           @error="onImageError" | ||||||
|  |         > | ||||||
|  |         <!-- 滑块 --> | ||||||
|  |         <img  | ||||||
|  |           :src="options?.blockSrc" | ||||||
|  |           class="absolute cursor-pointer will-change-transform transform-gpu" | ||||||
|  |    | ||||||
|  |           :class="{ 'transition-all duration-300 ease-out': !isDragging }" | ||||||
|  |           :style="{ | ||||||
|  |             top: `${options?.blockY}px`, | ||||||
|  |             left: `${moveX}px`, | ||||||
|  |             visibility: loaded ? 'visible' : 'hidden', | ||||||
|  |             width: `${options?.blockWidth}px`, height: `${options?.blockHeight}px`  | ||||||
|  |           }" | ||||||
|  |         > | ||||||
|  |         <transition name="fade-slide"> | ||||||
|  |           <div  | ||||||
|  |             v-if="verifyStatus.show"  | ||||||
|  |             class="absolute left-0 bottom-0 w-full h-24px leading-24px text-center text-14px text-white" | ||||||
|  |             :class="verifyStatus.type === 'success' ? 'bg-#52c41a' : 'bg-#ff4d4f'" | ||||||
|  |           > | ||||||
|  |             {{ verifyStatus.message }} | ||||||
|  |           </div> | ||||||
|  |         </transition> | ||||||
|  |       </div> | ||||||
|  |        | ||||||
|  |       <!-- 滑动条 --> | ||||||
|  |       <div class="relative mt-15px h-40px"> | ||||||
|  |         <div class="relative h-40px bg-#f5f5f5 rd-20px"> | ||||||
|  |           <div  | ||||||
|  |             class="absolute h-full bg-#91d5ff rd-20px" | ||||||
|  |             :class="{ 'transition-all duration-300 ease-out': !isDragging }" | ||||||
|  |             :style="{ width: `${moveX}px` }" | ||||||
|  |           ></div> | ||||||
|  |           <div  | ||||||
|  |             class="absolute top-0 w-40px h-40px bg-white rd-full shadow-[0_2px_6px_rgba(0,0,0,0.15)] cursor-pointer will-change-transform" | ||||||
|  |             :class="{ 'transition-all duration-300 ease-out': !isDragging }" | ||||||
|  |             :style="{ left: `${moveX}px` }" | ||||||
|  |             @mousedown.prevent="startDrag" | ||||||
|  |             @touchstart.prevent="startDrag" | ||||||
|  |           > | ||||||
|  |             <div  | ||||||
|  |               class="absolute top-50% left-50% translate--50% w-20px h-20px bg-#1890ff rd-full" | ||||||
|  |               :class="{ 'animate-loading': isVerifying }" | ||||||
|  |             ></div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </template> | ||||||
|  |    | ||||||
|  |   <script setup> | ||||||
|  |   import { ref, reactive, onMounted, onBeforeUnmount } from 'vue' | ||||||
|  |    | ||||||
|  |   // Props | ||||||
|  |   const props = defineProps({ | ||||||
|  |     options:Object | ||||||
|  |   }) | ||||||
|  |    | ||||||
|  |   const emit = defineEmits(['leave']) | ||||||
|  |    | ||||||
|  |   // 状态管理 | ||||||
|  |   const moveX = ref(0) | ||||||
|  |    | ||||||
|  |   const loaded = ref(false) | ||||||
|  |   const isDragging = ref(false) | ||||||
|  |   const isVerifying = ref(false) | ||||||
|  |   const maxMoveX = ref(0) | ||||||
|  |   const bgImage = ref(null) | ||||||
|  |    | ||||||
|  |   const verifyStatus = reactive({ | ||||||
|  |     show: false, | ||||||
|  |     type: '', | ||||||
|  |     message: '' | ||||||
|  |   }) | ||||||
|  |    | ||||||
|  |   const dragState = reactive({ | ||||||
|  |     startX: 0, | ||||||
|  |     oldMoveX: 0 | ||||||
|  |   }) | ||||||
|  |    | ||||||
|  |   // 图片加载处理 | ||||||
|  |   const onImageLoad = () => { | ||||||
|  |     if (!bgImage.value?.complete) return | ||||||
|  |      | ||||||
|  |     const img = bgImage.value | ||||||
|  |     const scale = img.width / img.naturalWidth | ||||||
|  |     const blockSize = Math.round(50 * scale) | ||||||
|  |     maxMoveX.value = img.width - blockSize | ||||||
|  |     loaded.value = true | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   const onImageError = () => { | ||||||
|  |     console.error('Image failed to load') | ||||||
|  |     maxMoveX.value = 270 | ||||||
|  |     loaded.value = true | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // 拖动处理 | ||||||
|  |   const startDrag = (e) => { | ||||||
|  |     isDragging.value = true | ||||||
|  |     dragState.startX = e.touches?.[0].clientX ?? e.clientX | ||||||
|  |     dragState.oldMoveX = moveX.value | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   const onDrag = (e) => { | ||||||
|  |     if (!isDragging.value) return | ||||||
|  |      | ||||||
|  |     const clientX = e.touches?.[0].clientX ?? e.clientX | ||||||
|  |     let newMoveX = dragState.oldMoveX + (clientX - dragState.startX) | ||||||
|  |      | ||||||
|  |     moveX.value = Math.max(0, Math.min(newMoveX, maxMoveX.value)) | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   const endDrag = async () => { | ||||||
|  |     if (!isDragging.value) return | ||||||
|  |      | ||||||
|  |     isDragging.value = false | ||||||
|  |     isVerifying.value = true | ||||||
|  |      | ||||||
|  |     try { | ||||||
|  |       emit('leave', moveX.value, (success) => { | ||||||
|  |         showVerifyResult(success) | ||||||
|  |       }) | ||||||
|  |     } catch (error) { | ||||||
|  |       showVerifyResult(false) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // 验证结果展示 | ||||||
|  |   const showVerifyResult = (success) => { | ||||||
|  |     verifyStatus.show = true | ||||||
|  |     verifyStatus.type = success ? 'success' : 'error' | ||||||
|  |     verifyStatus.message = success ? '验证成功' : '验证失败' | ||||||
|  |     isVerifying.value = false | ||||||
|  |      | ||||||
|  |     if (!success) moveX.value = 0 | ||||||
|  |      | ||||||
|  |     setTimeout(() => { | ||||||
|  |       verifyStatus.show = false | ||||||
|  |       verifyStatus.message = '' | ||||||
|  |       moveX.value = 0 | ||||||
|  |     }, 1500) | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // 事件监听 | ||||||
|  |   onMounted(() => { | ||||||
|  |     window.addEventListener('mousemove', onDrag) | ||||||
|  |     window.addEventListener('mouseup', endDrag) | ||||||
|  |     window.addEventListener('touchmove', onDrag) | ||||||
|  |     window.addEventListener('touchend', endDrag) | ||||||
|  |   }) | ||||||
|  |    | ||||||
|  |   onBeforeUnmount(() => { | ||||||
|  |     window.removeEventListener('mousemove', onDrag) | ||||||
|  |     window.removeEventListener('mouseup', endDrag) | ||||||
|  |     window.removeEventListener('touchmove', onDrag) | ||||||
|  |     window.removeEventListener('touchend', endDrag) | ||||||
|  |   }) | ||||||
|  |   </script> | ||||||
|  |    | ||||||
|  |   <style> | ||||||
|  |   @keyframes loading { | ||||||
|  |     from { transform: translate(-50%, -50%) rotate(0deg); } | ||||||
|  |     to { transform: translate(-50%, -50%) rotate(360deg); } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .animate-loading { | ||||||
|  |     animation: loading 1s linear infinite; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /* 添加过渡动画样式 */ | ||||||
|  |   .fade-slide-enter-active, | ||||||
|  |   .fade-slide-leave-active { | ||||||
|  |     transition: all 0.3s ease; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .fade-slide-enter-from, | ||||||
|  |   .fade-slide-leave-to { | ||||||
|  |     transform: translateY(100%); | ||||||
|  |     opacity: 0; | ||||||
|  |   } | ||||||
|  |   </style> | ||||||
|  |    | ||||||
|  |    | ||||||
| @ -8,7 +8,9 @@ import {authStore} from "@/stores/auth/index.js"; | |||||||
| import {message} from '@/components/x-message/useMessage.js' | import {message} from '@/components/x-message/useMessage.js' | ||||||
| import {fddCheck} from "~/api/goods/index.js"; | import {fddCheck} from "~/api/goods/index.js"; | ||||||
| import zu6020 from '@/static/images/zu6020@2x.png' | import zu6020 from '@/static/images/zu6020@2x.png' | ||||||
| import YourPuzzleComponent from '@/components/YourPuzzleComponent.vue' | 
 | ||||||
|  | import PuzzleComponent from '@/components/puzzleComponent/index.vue' | ||||||
|  | import { ref } from 'vue'; | ||||||
| const {userInfo,token,selectedZone}= authStore() | const {userInfo,token,selectedZone}= authStore() | ||||||
| const router = useRouter(); | const router = useRouter(); | ||||||
| const route = useRoute(); | const route = useRoute(); | ||||||
| @ -84,45 +86,30 @@ onMounted(()=>{ | |||||||
|   selectedCountry.value=route.query.countryName || defaultCountry.name |   selectedCountry.value=route.query.countryName || defaultCountry.name | ||||||
| }) | }) | ||||||
| const vanSwipeRef=ref(null) | const vanSwipeRef=ref(null) | ||||||
|  | 
 | ||||||
| const captcha=ref({ | const captcha=ref({ | ||||||
|   "nonceStr": "397b5e08168c31c4",  |   nonceStr: "",  | ||||||
| "blockX": 256  | blockX: 256 , | ||||||
|  | blockWidth:50, | ||||||
|  | blockHeight:50, | ||||||
|  | canvasWidth:320, | ||||||
|  | canvasHeight:191, | ||||||
|  | place:0, | ||||||
|  | canvasSrc:'', | ||||||
|  | blockSrc:'', | ||||||
|  | blockY:0 | ||||||
| }) | }) | ||||||
| const captchaUrl=ref('') |  | ||||||
| const captchaVerifyUrl=ref('') |  | ||||||
| const blockY=ref(0) |  | ||||||
| const getCode =async () => { | const getCode =async () => { | ||||||
| loadingRef.value.loading1=true | loadingRef.value.loading1=true | ||||||
|   const res=await userCaptcha({ |   const res=await userCaptcha(captcha.value) | ||||||
| "canvasWidth": 320, //画布的宽度(canvasWidth大于41并且(canvasWidth-10)/2 - 1> blockWidth) |  | ||||||
| "canvasHeight": 191, //画布的高度(canvasHeight大于26并且 canvasHeight - blockHeight > 11 |  | ||||||
| "blockWidth": 50, //块图像的宽度(blockWidth大于14) |  | ||||||
| "blockHeight": 50, //块图像的高度(blockHeight大于14) |  | ||||||
| // "blockRadius": 25, //块图像的圆角半径 |  | ||||||
| "place": 0 //图像来源标识,0表示URL下载,1表示本地文件  一般用0 |  | ||||||
|   }) |  | ||||||
|   if (res.status===0){ |   if (res.status===0){ | ||||||
|     captchaUrl.value=`data:image/png;base64,${res.data.canvasSrc}` |     captcha.value.canvasSrc=`data:image/png;base64,${res.data.canvasSrc}` | ||||||
|     captchaVerifyUrl.value=`data:image/png;base64,${res.data.blockSrc}` |     captcha.value.blockSrc=`data:image/png;base64,${res.data.blockSrc}` | ||||||
|     blockY.value=res.data.blockY |     captcha.value.blockY=res.data.blockY | ||||||
|     captcha.value.nonceStr=res.data.nonceStr |     captcha.value.nonceStr=res.data.nonceStr | ||||||
|     isShow.value=true |     isShow.value=true | ||||||
|     loadingRef.value.loading1=false |     loadingRef.value.loading1=false | ||||||
|   } |   } | ||||||
| //   loadingRef.value.loading1=true |  | ||||||
| //  const res=await senCode({ |  | ||||||
| //    telNum:phoneNum.value, |  | ||||||
| //    zone:selectedZone.value |  | ||||||
| //  }) |  | ||||||
| //   loadingRef.value.loading1=false |  | ||||||
| //   if (res.status===0){ |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| //   } |  | ||||||
| //   pane.value = 1 |  | ||||||
| //   vanSwipeRef.value?.swipeTo(pane.value) |  | ||||||
|   |  | ||||||
| //   startCountdown(); |  | ||||||
| } | } | ||||||
| const goBack = () => { | const goBack = () => { | ||||||
|   code.value = '' |   code.value = '' | ||||||
| @ -176,13 +163,7 @@ onUnmounted(() => { | |||||||
|   window.removeEventListener('resize', () => {}) |   window.removeEventListener('resize', () => {}) | ||||||
| }) | }) | ||||||
| const isShow=ref(false) | const isShow=ref(false) | ||||||
| const onSuccess=()=>{ | 
 | ||||||
|   // userCaptchaValidate() |  | ||||||
|   isShow.value=false |  | ||||||
| } |  | ||||||
| const onClose=()=>{ |  | ||||||
|   isShow.value=false |  | ||||||
| } |  | ||||||
| const onLeave =async (moveX, callback) => { | const onLeave =async (moveX, callback) => { | ||||||
|    loadingRef.value.loading1=true |    loadingRef.value.loading1=true | ||||||
|  const res=await senCode({ |  const res=await senCode({ | ||||||
| @ -197,7 +178,7 @@ const onLeave =async (moveX, callback) => { | |||||||
|   if (res.status===408){ |   if (res.status===408){ | ||||||
|   callback(false) |   callback(false) | ||||||
|   getCode() |   getCode() | ||||||
|   }else{ |   }else if([407,0].includes(res.status)){ | ||||||
|     callback(true) |     callback(true) | ||||||
|     setTimeout(() => { |     setTimeout(() => { | ||||||
|         pane.value = 1 |         pane.value = 1 | ||||||
| @ -286,10 +267,8 @@ const onLeave =async (moveX, callback) => { | |||||||
|       {{ $t('login.agreement') }}<span class="text-#3454AF " @click="$router.push('/privacyPolicy')">{{ $t('login.privacyPolicy') }}</span> |       {{ $t('login.agreement') }}<span class="text-#3454AF " @click="$router.push('/privacyPolicy')">{{ $t('login.privacyPolicy') }}</span> | ||||||
|     </div> |     </div> | ||||||
|     <van-popup v-model:show="isShow" round> |     <van-popup v-model:show="isShow" round> | ||||||
|       <YourPuzzleComponent |       <PuzzleComponent | ||||||
|       :blockY="blockY" |       :options="captcha" | ||||||
|       :bgImageUrl="captchaUrl" |  | ||||||
|       :sliderImageUrl="captchaVerifyUrl"  |  | ||||||
|       @leave="onLeave" |       @leave="onLeave" | ||||||
|     /> |     /> | ||||||
|     </van-popup> |     </van-popup> | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user