Merge branch 'main' into dev

This commit is contained in:
xingyy 2025-03-12 15:55:45 +08:00
commit 2192624cc9
9 changed files with 175 additions and 116 deletions

View File

@ -1,75 +1,17 @@
<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` }">
<!-- 背景图 -->
<img
: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"
: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> <script setup>
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue' import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
//i18n
// Props import { useI18n } from 'vue-i18n'
const {t} =useI18n()
const props = defineProps({ const props = defineProps({
options:Object options:Object,
loading: Boolean,
}) })
const emit = defineEmits(['leave']) const emit = defineEmits(['leave'])
//
const moveX = ref(0) const moveX = ref(0)
const loaded = ref(false) const loaded = ref(false)
@ -89,7 +31,6 @@
oldMoveX: 0 oldMoveX: 0
}) })
//
const onImageLoad = () => { const onImageLoad = () => {
if (!bgImage.value?.complete) return if (!bgImage.value?.complete) return
@ -141,11 +82,8 @@
const showVerifyResult = (success) => { const showVerifyResult = (success) => {
verifyStatus.show = true verifyStatus.show = true
verifyStatus.type = success ? 'success' : 'error' verifyStatus.type = success ? 'success' : 'error'
verifyStatus.message = success ? '验证成功' : '验证失败' verifyStatus.message = success ? t('components.form.verifySuccess') : t('components.form.verifyFailed')
isVerifying.value = false isVerifying.value = false
if (!success) moveX.value = 0
setTimeout(() => { setTimeout(() => {
verifyStatus.show = false verifyStatus.show = false
verifyStatus.message = '' verifyStatus.message = ''
@ -168,7 +106,77 @@
window.removeEventListener('touchend', endDrag) window.removeEventListener('touchend', endDrag)
}) })
</script> </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> <style>
@keyframes loading { @keyframes loading {
from { transform: translate(-50%, -50%) rotate(0deg); } from { transform: translate(-50%, -50%) rotate(0deg); }
@ -190,6 +198,61 @@
transform: translateY(100%); transform: translateY(100%);
opacity: 0; 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> </style>

View File

@ -1,6 +1,5 @@
<script setup> <script setup>
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import Vcode from "vue3-puzzle-vcode";
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import countryCode from '../countryRegion/data/index.js' import countryCode from '../countryRegion/data/index.js'
import {senCode, userLogin,userCaptcha,userCaptchaValidate,} from "@/api/auth/index.js"; import {senCode, userLogin,userCaptcha,userCaptchaValidate,} from "@/api/auth/index.js";
@ -24,6 +23,7 @@ definePageMeta({
const loadingRef=ref({ const loadingRef=ref({
loading1:false, loading1:false,
loading2:false, loading2:false,
loading3:false,
}) })
const isExist=ref(false)// true const isExist=ref(false)// true
const isReal=ref(false) //isReal const isReal=ref(false) //isReal
@ -100,6 +100,7 @@ blockSrc:'',
blockY:0 blockY:0
}) })
const getCode =async () => { const getCode =async () => {
isShow.value=true
loadingRef.value.loading1=true loadingRef.value.loading1=true
const res=await userCaptcha(captcha.value) const res=await userCaptcha(captcha.value)
if (res.status===0){ if (res.status===0){
@ -107,23 +108,9 @@ loadingRef.value.loading1=true
captcha.value.blockSrc=`data:image/png;base64,${res.data.blockSrc}` captcha.value.blockSrc=`data:image/png;base64,${res.data.blockSrc}`
captcha.value.blockY=res.data.blockY captcha.value.blockY=res.data.blockY
captcha.value.nonceStr=res.data.nonceStr captcha.value.nonceStr=res.data.nonceStr
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 = ''
@ -177,15 +164,8 @@ 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
const res=await senCode({ const res=await senCode({
telNum:phoneNum.value, telNum:phoneNum.value,
zone:selectedZone.value, zone:selectedZone.value,
@ -194,7 +174,6 @@ const onLeave =async (moveX, callback) => {
nonceStr:captcha.value.nonceStr nonceStr:captcha.value.nonceStr
} }
}) })
loadingRef.value.loading1=false
if (res.status===408){ if (res.status===408){
callback(false) callback(false)
getCode() getCode()
@ -249,7 +228,7 @@ const onLeave =async (moveX, callback) => {
</div> </div>
</div> </div>
<div class="mt-[55px]"> <div class="mt-[55px]">
<van-button :loading="loadingRef.loading1" v-if="phoneNum" :loading-text="$t('login.getCode')" color="#2B53AC" block style="height: 48px" @click="getCode">{{ $t('login.getCode') }}</van-button> <van-button v-if="phoneNum" :loading-text="$t('login.getCode')" color="#2B53AC" block style="height: 48px" @click="getCode">{{ $t('login.getCode') }}</van-button>
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">{{ $t('login.getCode') }}</van-button> <van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">{{ $t('login.getCode') }}</van-button>
</div> </div>
</div> </div>
@ -299,6 +278,7 @@ const onLeave =async (moveX, callback) => {
<van-popup v-model:show="isShow" round style="max-width: initial" teleport="body"> <van-popup v-model:show="isShow" round style="max-width: initial" teleport="body">
<PuzzleComponent <PuzzleComponent
v-if="isShow" v-if="isShow"
:loading="loadingRef.loading1"
:options="captcha" :options="captcha"
@leave="onLeave" @leave="onLeave"
/> />

View File

@ -2,6 +2,7 @@
import {publicStore} from "@/stores/public/index.js"; import {publicStore} from "@/stores/public/index.js";
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
import {outBuyList} from "@/api-public/public/index.js"; import {outBuyList} from "@/api-public/public/index.js";
import { onUnmounted } from 'vue'
const {auctionData} = publicStore() const {auctionData} = publicStore()
function formatThousands(num) { function formatThousands(num) {
@ -26,12 +27,29 @@ const headList=[
} }
] ]
const buyList=ref([]) const buyList=ref([])
const timer = ref(null)
const headItem=(statusCode)=>{ const headItem=(statusCode)=>{
return headList.find(x=>x.value===statusCode) return headList.find(x=>x.value===statusCode)
} }
const fetchBuyList = async () => {
const res = await outBuyList({uuid: auctionData.value.uuid})
buyList.value = res.data.buys
}
onMounted(async()=>{ onMounted(async()=>{
const res=await outBuyList({uuid:auctionData.value.uuid}) await fetchBuyList()
buyList.value=res.data.buys timer.value = setInterval(async () => {
await fetchBuyList()
}, 10000)
})
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
}) })
</script> </script>
@ -44,17 +62,16 @@ onMounted(async()=>{
<template v-if="buyList?.length>0"> <template v-if="buyList?.length>0">
<div v-for="(item, index) in buyList" :key="index" class="flex flex-shrink-0"> <div v-for="(item, index) in buyList" :key="index" class="flex flex-shrink-0">
<!-- 将每列宽度改为相等(约86px)添加文本溢出处理 --> <div class="text-start shrink-0 w-1/6 break-words" :style="`color: ${headItem(item.statusCode).color}`">
<div class="text-start shrink-0 w-1/4 truncate" :style="`color: ${headItem(item.statusCode).color}`">
{{ headItem(item.statusCode).label }} {{ headItem(item.statusCode).label }}
</div> </div>
<div class="text-start shrink-0 w-1/4 truncate"> <div class="text-start shrink-0 w-[28%] break-words">
{{ item.auctionType==='local'? $t('live_room.spot'):$t('live_room.network') }} {{ item.auctionType==='local'? $t('live_room.spot'):$t('live_room.network') }}
</div> </div>
<div class="text-start shrink-0 w-1/4 truncate"> <div class="text-start shrink-0 w-[28%] break-words">
{{ item.createdAt }} {{ item.createdAt }}
</div> </div>
<div class="text-start shrink-0 w-1/4 truncate"> <div class="text-start shrink-0 w-[28%] break-words">
{{item.baseCurrency}}{{ formatThousands(item.baseMoney) }} {{item.baseCurrency}}{{ formatThousands(item.baseMoney) }}
</div> </div>
</div> </div>

View File

@ -642,7 +642,9 @@
"submit": "Submit", "submit": "Submit",
"cancel": "Cancel", "cancel": "Cancel",
"pleaseInput": "Please enter", "pleaseInput": "Please enter",
"pleaseSelect": "Please select" "pleaseSelect": "Please select",
"verifySuccess": "Verification successful",
"verifyFailed": "Verification failed"
}, },
"upload": { "upload": {
"text": "Click to Upload", "text": "Click to Upload",

View File

@ -642,7 +642,9 @@
"submit": "送信", "submit": "送信",
"cancel": "キャンセル", "cancel": "キャンセル",
"pleaseInput": "入力してください", "pleaseInput": "入力してください",
"pleaseSelect": "選択してください" "pleaseSelect": "選択してください",
"verifySuccess": "認証成功",
"verifyFailed": "認証失敗"
}, },
"upload": { "upload": {
"text": "アップロードするにはクリック", "text": "アップロードするにはクリック",

View File

@ -646,7 +646,9 @@
"submit": "提交", "submit": "提交",
"cancel": "取消", "cancel": "取消",
"pleaseInput": "请输入", "pleaseInput": "请输入",
"pleaseSelect": "请选择" "pleaseSelect": "请选择",
"verifySuccess": "验证成功",
"verifyFailed": "验证失败"
}, },
"upload": { "upload": {
"text": "点击上传", "text": "点击上传",

View File

@ -642,7 +642,9 @@
"submit": "提交", "submit": "提交",
"cancel": "取消", "cancel": "取消",
"pleaseInput": "請輸入", "pleaseInput": "請輸入",
"pleaseSelect": "請選擇" "pleaseSelect": "請選擇",
"verifySuccess": "驗證成功",
"verifyFailed": "驗證失敗"
}, },
"upload": { "upload": {
"text": "點擊上傳", "text": "點擊上傳",

View File

@ -38,8 +38,7 @@
"vue-demi": "^0.14.10", "vue-demi": "^0.14.10",
"vue-pdf-embed": "^2.1.2", "vue-pdf-embed": "^2.1.2",
"vue-router": "^4.5.0", "vue-router": "^4.5.0",
"vue-signature-pad": "^3.0.2", "vue-signature-pad": "^3.0.2"
"vue3-puzzle-vcode": "1.1.6-nuxt"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/carbon": "^1.2.5", "@iconify-json/carbon": "^1.2.5",

View File

@ -77,9 +77,6 @@ importers:
vue-signature-pad: vue-signature-pad:
specifier: ^3.0.2 specifier: ^3.0.2
version: 3.0.2(vue@3.5.13(typescript@5.7.3)) version: 3.0.2(vue@3.5.13(typescript@5.7.3))
vue3-puzzle-vcode:
specifier: 1.1.6-nuxt
version: 1.1.6-nuxt
devDependencies: devDependencies:
'@iconify-json/carbon': '@iconify-json/carbon':
specifier: ^1.2.5 specifier: ^1.2.5
@ -4621,9 +4618,6 @@ packages:
peerDependencies: peerDependencies:
vue: ^3.2.0 vue: ^3.2.0
vue3-puzzle-vcode@1.1.6-nuxt:
resolution: {integrity: sha512-V3DrPIYznxko8jBAtZtmsNPw9QmkPnFicQ0p9B192vC3ncRv4IDazhLC7D/cY/OGq0OeqXmk2DiOcBR7dyt8GQ==}
vue@3.5.13: vue@3.5.13:
resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
peerDependencies: peerDependencies:
@ -9902,8 +9896,6 @@ snapshots:
signature_pad: 3.0.0-beta.4 signature_pad: 3.0.0-beta.4
vue: 3.5.13(typescript@5.7.3) vue: 3.5.13(typescript@5.7.3)
vue3-puzzle-vcode@1.1.6-nuxt: {}
vue@3.5.13(typescript@5.7.3): vue@3.5.13(typescript@5.7.3):
dependencies: dependencies:
'@vue/compiler-dom': 3.5.13 '@vue/compiler-dom': 3.5.13