Compare commits

...

3 Commits

Author SHA1 Message Date
xingyy
4c8e63bd7d Merge branch 'xingyy' into dev 2025-03-12 11:19:29 +08:00
xingyy
13bc4f4883 refactor(login): 优化登录页面逻辑
- 移除了多余的注释代码
- 简化了验证码发送逻辑
- 删除了未使用的函数
- 优化了代码结构,提高了代码可读性
2025-03-12 11:18:37 +08:00
xingyy
29cf4490e1 refactor(components): 重构滑块验证码组件
- 移除 YourPuzzleComponent.vue 文件
- 新增 puzzleComponent 目录及 index.vue 文件
- 优化验证码组件的结构和功能
- 更新 login 页面中验证码的使用方式
2025-03-12 11:13:46 +08:00
3 changed files with 217 additions and 410 deletions

View File

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

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

View File

@ -8,7 +8,9 @@ import {authStore} from "@/stores/auth/index.js";
import {message} from '@/components/x-message/useMessage.js'
import {fddCheck} from "~/api/goods/index.js";
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 router = useRouter();
const route = useRoute();
@ -84,45 +86,30 @@ onMounted(()=>{
selectedCountry.value=route.query.countryName || defaultCountry.name
})
const vanSwipeRef=ref(null)
const captcha=ref({
"nonceStr": "397b5e08168c31c4",
"blockX": 256
nonceStr: "",
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 () => {
loadingRef.value.loading1=true
const res=await userCaptcha({
"canvasWidth": 320, //(canvasWidth41canvasWidth-10/2 - 1> blockWidth)
"canvasHeight": 191, //(canvasHeight26 canvasHeight - blockHeight > 11
"blockWidth": 50, //(blockWidth14)
"blockHeight": 50, //(blockHeight14)
// "blockRadius": 25, //
"place": 0 //0URL1 0
})
const res=await userCaptcha(captcha.value)
if (res.status===0){
captchaUrl.value=`data:image/png;base64,${res.data.canvasSrc}`
captchaVerifyUrl.value=`data:image/png;base64,${res.data.blockSrc}`
blockY.value=res.data.blockY
captcha.value.canvasSrc=`data:image/png;base64,${res.data.canvasSrc}`
captcha.value.blockSrc=`data:image/png;base64,${res.data.blockSrc}`
captcha.value.blockY=res.data.blockY
captcha.value.nonceStr=res.data.nonceStr
isShow.value=true
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 = () => {
code.value = ''
@ -176,13 +163,7 @@ onUnmounted(() => {
window.removeEventListener('resize', () => {})
})
const isShow=ref(false)
const onSuccess=()=>{
// userCaptchaValidate()
isShow.value=false
}
const onClose=()=>{
isShow.value=false
}
const onLeave =async (moveX, callback) => {
loadingRef.value.loading1=true
const res=await senCode({
@ -197,7 +178,7 @@ const onLeave =async (moveX, callback) => {
if (res.status===408){
callback(false)
getCode()
}else{
}else if([407,0].includes(res.status)){
callback(true)
setTimeout(() => {
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>
</div>
<van-popup v-model:show="isShow" round>
<YourPuzzleComponent
:blockY="blockY"
:bgImageUrl="captchaUrl"
:sliderImageUrl="captchaVerifyUrl"
<PuzzleComponent
:options="captcha"
@leave="onLeave"
/>
</van-popup>