完成一键登录和支付功能
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<view
|
||||
class="verify-code-input"
|
||||
@tap="focusInput"
|
||||
:style="{
|
||||
'--vci-active-color': activeColor,
|
||||
'--vci-filled-color': filledColor,
|
||||
'--vci-border-color': borderColor
|
||||
}"
|
||||
>
|
||||
<view class="vci-title" v-if="title">
|
||||
<text class="vci-title__text">{{ title }}</text>
|
||||
</view>
|
||||
<view class="vci-desc" v-if="desc">
|
||||
<text class="vci-desc__text">{{ desc }}</text>
|
||||
</view>
|
||||
|
||||
<view class="vci-code-box">
|
||||
<view
|
||||
v-for="(item, index) in codeLength"
|
||||
:key="index"
|
||||
class="vci-code-item"
|
||||
:class="{
|
||||
'vci-code-item--active': isFocused && currentIndex === index,
|
||||
'vci-code-item--filled': code.length > index,
|
||||
'vci-code-item--underline': type === 'underline',
|
||||
'vci-code-item--box': type === 'box'
|
||||
}"
|
||||
>
|
||||
<text v-if="code[index] && !mask" class="vci-code-text">{{ code[index] }}</text>
|
||||
<view v-else-if="code[index] && mask" class="vci-code-dot"></view>
|
||||
<view v-if="isFocused && currentIndex === index && code.length === index" class="vci-code-cursor"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="vci-tips" v-if="errorMsg">
|
||||
<text class="vci-tips__text">{{ errorMsg }}</text>
|
||||
</view>
|
||||
|
||||
<input
|
||||
ref="inputRef"
|
||||
class="vci-hidden-input"
|
||||
type="number"
|
||||
:maxlength="codeLength"
|
||||
:value="code"
|
||||
:focus="isFocused"
|
||||
:adjust-position="false"
|
||||
@input="onInput"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
desc: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
length: {
|
||||
type: Number,
|
||||
default: 6
|
||||
},
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'box'
|
||||
},
|
||||
errorMsg: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
autoFocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#1677FF'
|
||||
},
|
||||
filledColor: {
|
||||
type: String,
|
||||
default: '#1A202C'
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#E2E8F0'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'complete', 'focus', 'blur'])
|
||||
|
||||
const code = ref(props.modelValue)
|
||||
const isFocused = ref(false)
|
||||
const currentIndex = ref(0)
|
||||
const inputRef = ref(null)
|
||||
|
||||
const codeLength = computed(() => props.length)
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
code.value = val
|
||||
currentIndex.value = Math.min(val.length, codeLength.value - 1)
|
||||
})
|
||||
|
||||
function focusInput() {
|
||||
isFocused.value = true
|
||||
}
|
||||
|
||||
function onInput(e) {
|
||||
let val = e.detail.value.replace(/\D/g, '').slice(0, codeLength.value)
|
||||
code.value = val
|
||||
currentIndex.value = Math.min(val.length, codeLength.value - 1)
|
||||
emit('update:modelValue', val)
|
||||
if (val.length === codeLength.value) {
|
||||
emit('complete', val)
|
||||
}
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
isFocused.value = true
|
||||
currentIndex.value = code.value.length
|
||||
emit('focus')
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
isFocused.value = false
|
||||
emit('blur')
|
||||
}
|
||||
|
||||
function clear() {
|
||||
code.value = ''
|
||||
currentIndex.value = 0
|
||||
emit('update:modelValue', '')
|
||||
}
|
||||
|
||||
function setFocus(val) {
|
||||
isFocused.value = val
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
clear,
|
||||
focusInput,
|
||||
setFocus
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.verify-code-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vci-title {
|
||||
margin-bottom: 8px;
|
||||
|
||||
&__text {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1A202C;
|
||||
}
|
||||
}
|
||||
|
||||
.vci-desc {
|
||||
margin-bottom: 32px;
|
||||
|
||||
&__text {
|
||||
font-size: 13px;
|
||||
color: #718096;
|
||||
}
|
||||
}
|
||||
|
||||
.vci-code-box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.vci-code-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&--box {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid var(--vci-border-color, #E2E8F0);
|
||||
border-radius: 8px;
|
||||
|
||||
&.vci-code-item--active {
|
||||
border-color: var(--vci-active-color, #1677FF);
|
||||
}
|
||||
|
||||
&.vci-code-item--filled {
|
||||
border-color: var(--vci-filled-color, #1A202C);
|
||||
}
|
||||
}
|
||||
|
||||
&--underline {
|
||||
width: 40px;
|
||||
height: 50px;
|
||||
border-bottom: 2px solid var(--vci-border-color, #E2E8F0);
|
||||
|
||||
&.vci-code-item--active {
|
||||
border-bottom-color: var(--vci-active-color, #FF8C42);
|
||||
}
|
||||
|
||||
&.vci-code-item--filled {
|
||||
border-bottom-color: var(--vci-filled-color, #1A202C);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vci-code-text {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--vci-filled-color, #1A202C);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.vci-code-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: var(--vci-filled-color, #1A202C);
|
||||
}
|
||||
|
||||
.vci-code-cursor {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 24px;
|
||||
background: var(--vci-active-color, #1677FF);
|
||||
animation: vci-blink 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes vci-blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
.vci-tips {
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&__text {
|
||||
font-size: 13px;
|
||||
color: #E53E3E;
|
||||
}
|
||||
}
|
||||
|
||||
.vci-hidden-input {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
top: -9999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user