liveh5-nuxt/app/components/puzzleComponent/index.vue
xingyy 9402c8b719 refactor(app): 优化代码和性能
- 在 puzzleComponent 中添加 watch 监听 loading 状态,以重置验证状态
- 修改 personal-Info 页面中路由跳转方式
- 在 nuxt.config.js 中添加 imagemin 插件,优化图片压缩
- 更新 package.json,添加 vite-plugin-imagemin 依赖
2025-03-14 10:57:50 +08:00

262 lines
6.8 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.

<script setup>
import { ref, reactive, onMounted, onBeforeUnmount, watch } from 'vue'
//i18n
import { useI18n } from 'vue-i18n'
const {t} =useI18n()
const props = defineProps({
options:Object,
loading: Boolean,
})
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
}
watch(()=>{
return props.loading
},(newVal)=>{
if(!newVal){
verifyStatus.show = false
verifyStatus.message = ''
moveX.value = 0
}
})
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 ? t('components.form.verifySuccess') : t('components.form.verifyFailed')
isVerifying.value = false
}
// 事件监听
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>
<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-10px" :style="{ width: `${options?.canvasWidth}px`, height: `${options?.canvasHeight}px` }">
<!-- 加载状态 -->
<div v-if="loading" class="absolute inset-0 flex flex-col items-center justify-center bg-#f8f8f8">
<div class="fancy-loader">
<div class="fancy-loader-bar"></div>
<div class="fancy-loader-bar"></div>
<div class="fancy-loader-bar"></div>
<div class="fancy-loader-bar"></div>
</div>
</div>
<!-- 背景图 -->
<img
v-else
:src="options?.canvasSrc"
class="pointer-events-none w-full h-full"
ref="bgImage"
@load="onImageLoad"
@error="onImageError"
>
<!-- 滑块 -->
<img
:src="options?.blockSrc"
class="absolute cursor-pointer will-change-transform transform-gpu"
v-if="!loading"
: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-25px leading-25px 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>
<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;
}
/* 加载动画样式 */
.fancy-loader {
display: flex;
align-items: flex-end;
justify-content: center;
width: 60px;
height: 40px;
}
.fancy-loader-bar {
width: 6px;
height: 15px;
margin: 0 3px;
background-color: #1890ff;
border-radius: 3px;
animation: fancy-loading 1s ease-in-out infinite;
}
.fancy-loader-bar:nth-child(1) {
animation-delay: 0s;
}
.fancy-loader-bar:nth-child(2) {
animation-delay: 0.2s;
}
.fancy-loader-bar:nth-child(3) {
animation-delay: 0.4s;
}
.fancy-loader-bar:nth-child(4) {
animation-delay: 0.6s;
}
@keyframes fancy-loading {
0% {
transform: scaleY(0.5);
opacity: 0.5;
}
50% {
transform: scaleY(1.2);
opacity: 1;
}
100% {
transform: scaleY(0.5);
opacity: 0.5;
}
}
/* 保留原有的spin动画用于其他地方 */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>