liveh5-nuxt/app/pages/login/index.vue
xingyy e9896d86d6 feat(login): 实现滑动验证码功能
- 新增滑动验证码
2025-03-12 10:18:30 +08:00

326 lines
9.3 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 { useRouter, useRoute } from 'vue-router';
import Vcode from "vue3-puzzle-vcode";
import { useI18n } from 'vue-i18n'
import countryCode from '../countryRegion/data/index.js'
import {senCode, userLogin,userCaptcha,userCaptchaValidate,} from "@/api/auth/index.js";
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'
const {userInfo,token,selectedZone}= authStore()
const router = useRouter();
const route = useRoute();
const { locale } = useI18n()
const imgs=ref([zu6020])
definePageMeta({
name: 'login',
i18n: 'login.title'
})
const loadingRef=ref({
loading1:false,
loading2:false,
})
const isExist=ref(false)//帐号是否存在 true存在
const isReal=ref(false) //isReal 是否实名过
const codeInput=ref(null)
function goToPage() {
router.push('/countryRegion');
}
const interval=ref(null)
const startCountdown=()=> {
if (interval.value){
clearInterval(interval.value);
}
countdown.value = 60;
interval.value = setInterval(() => {
if (countdown.value > 0) {
countdown.value--;
} else {
clearInterval(interval.value);
}
}, 1000);
}
const countdown = ref(0);
const phoneNum = ref('')
const code = ref('')
const pane = ref(0)
// 根据语言获取默认国家
const getDefaultCountry = () => {
let defaultCode = 'CN' // 默认中国大陆
switch (locale.value) {
case 'zh-CN':
defaultCode = 'CN'
break
case 'zh-TW':
defaultCode = 'TW'
break
case 'ja-JP':
defaultCode = 'JP'
break
case 'en-US':
defaultCode = 'US'
break
}
const country = countryCode.find(c => c.code === defaultCode)
return {
zone: country.zone,
name: locale.value === 'zh-CN' ? country.cn :
locale.value === 'zh-TW' ? country.tw :
locale.value === 'ja-JP' ? country.ja :
country.en
}
}
const defaultCountry = getDefaultCountry()
const selectedCountry = ref('')
onMounted(()=>{
selectedZone.value=route.query.zone || defaultCountry.zone
selectedCountry.value=route.query.countryName || defaultCountry.name
})
const vanSwipeRef=ref(null)
const captcha=ref({
"nonceStr": "397b5e08168c31c4",
"blockX": 256
})
const captchaUrl=ref('')
const captchaVerifyUrl=ref('')
const blockY=ref(0)
const getCode =async () => {
loadingRef.value.loading1=true
const res=await userCaptcha({
"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){
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.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 = ''
pane.value = 0
vanSwipeRef.value?.swipeTo(pane.value)
}
const goLogin =async () => {
loadingRef.value.loading2=true
const res=await userLogin({
telNum:phoneNum.value,
zone:selectedZone.value,
code:code.value
})
if (res.status===0){
userInfo.value=res.data.accountInfo
token.value=res.data.token
if (res.data?.accountInfo?.userExtend?.isReal===0){
await router.push({
path: '/realAuth',
query:{
statusCode:0
}
})
}else if (res.data.isJumpFdd){
const res1=await fddCheck()
if (res1.status===0){
window.location.href=res1.data.h5Url
}
}else {
await router.push('/');
}
}
loadingRef.value.loading2=false
}
const isKeyboardVisible = ref(false)
const windowHeight = ref(window.innerHeight)
const isFocused = ref(false)
onMounted(() => {
// 记录初始窗口高度
windowHeight.value = window.innerHeight
// 监听窗口大小变化
window.addEventListener('resize', () => {
// 如果当前高度明显小于初始高度,认为键盘已打开
isKeyboardVisible.value = window.innerHeight < windowHeight.value * 0.8
})
})
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({
telNum:phoneNum.value,
zone:selectedZone.value,
verifyCaptcha:{
blockX:moveX,
nonceStr:captcha.value.nonceStr
}
})
loadingRef.value.loading1=false
if (res.status===408){
callback(false)
getCode()
}else{
callback(true)
setTimeout(() => {
pane.value = 1
vanSwipeRef.value?.swipeTo(pane.value)
startCountdown();
isShow.value=false
}, 1000)
}
}
</script>
<template>
<div class="w-[100vw] bg-[url('@/static/images/asdfsdd.png')] bg-bottom bg-cover grow-1 px-[31px] pt-[86px]">
<div class="w-full flex justify-center mb-[100px]">
<img class="h-[105px] w-[189px]" src="@/static/images/ghfggff.png" alt="">
</div>
<van-swipe ref="vanSwipeRef" :show-indicators="false" :touchable="false" :lazy-render="true" :loop="false">
<van-swipe-item >
<div v-if="pane===0">
<div class="">
<div class="w-full flex justify-between" @click="goToPage">
<div class="text-[16px] text-[#000]">
{{ selectedCountry }}
</div>
<div><van-icon color="#777" name="arrow" size="14" /></div>
</div>
<div class="border-b-[1.7px] mt-[8px]">
<van-field v-model="phoneNum" clearable :placeholder="$t('login.phonePlaceholder')">
<template #label>
<div class="text-[16px] text-[#1A1A1A] flex align-center justify-start">
+{{ selectedZone }}
</div>
</template>
</van-field>
</div>
</div>
<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-else type="primary" color="#D3D3D3" block style="height: 48px">{{ $t('login.getCode') }}</van-button>
</div>
</div>
</van-swipe-item>
<van-swipe-item>
<div v-if="pane===1">
<div class="flex mb-[16px]">
<div class="text-[16px] text-[#BDBDBD] mr-[10px]">{{ $t('login.hasSendTo') }}</div>
<div class="text-[16px] text-[#000]">+{{ selectedZone }} {{ phoneNum }}</div>
</div>
<div class="relative">
<van-password-input
:value="code"
:gutter="10"
:mask="false"
:focused="isFocused"
/>
<input
v-model="code"
type="tel"
maxlength="6"
ref="codeInput"
class="opacity-0 absolute top-0 left-0 h-full w-full"
@input="code = $event.target.value.replace(/\D/g, '').slice(0, 6)"
@focus="isFocused = true"
@blur="isFocused = false"
/>
</div>
<div class="flex justify-between">
<div :class="`${countdown>0?'text-#BDBDBD':'text-#2B53AC'} text-14px`">
{{ $t('login.reSend') }}<span v-if="countdown>0">({{countdown}})</span>
</div>
<div @click="goBack" class="text-#2B53AC text-14px">
{{ $t('login.back') }}
</div>
</div>
<div class="mt-[17px]">
<van-button v-if="code.length === 6" type="primary" block :loading="loadingRef.loading2" :loading-text="$t('login.login')" style="height: 48px" @click="goLogin">{{ $t('login.login') }}</van-button>
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">{{ $t('login.login') }}</van-button>
</div>
</div>
</van-swipe-item>
</van-swipe>
<div v-if="!isKeyboardVisible" class="text-center text-14px absolute left-1/2 transform translate-x--1/2 bottom-20px">
{{ $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"
@leave="onLeave"
/>
</van-popup>
</div>
</template>
<style scoped lang="scss">
:deep(.van-cell.van-field) {
padding-left: 0;
}
:deep(.van-password-input) {
margin: 0;
}
:deep(.van-password-input__item) {
border: 1px solid #E5E5E5;
width: 41px;
height: 41px;
}
.verify-popup-content {
width: 90vw;
max-width: 350px;
padding: 20px;
box-sizing: border-box;
}
:deep(.van-popup) {
background: transparent;
}
</style>