ts
复制代码
<template>
<view class="uni-flex uni-flex-center vcode-input-body">
<view
class="vcode-input-item vcode-input-line"
v-for="(v, index) in sum"
:key="index"
:style="getStyle(index)"
>
{{ text[index] ? text[index] : '' }}
<view v-if="focus && text.length === index" class="cursor"></view>
</view>
<input
ref="VcodeInput"
type="number"
class="hidden-input"
:focus="focus"
:maxlength="sum"
@input="inputVal"
@blur="setBlur"
:password="isPassword"
placeholder="验证码"
/>
</view>
</template>
<script>
export default {
name: 'VcodeInput',
props: {
sum: {
type: Number,
default: 6,
},
isBorderLine: {
type: Boolean,
default: false,
},
borderColor: {
type: String,
default: '#DADADA',
},
borderValueColor: {
type: String,
default: '#424456',
},
borderActiveColor: {
type: String,
default: '#000000',
},
isAutoComplete: {
type: Boolean,
default: true,
},
isPassword: {
type: Boolean,
default: false,
},
},
data() {
return {
focus: false,
text: [],
currentIndex: 0,
}
},
created() {
setTimeout(() => {
this.focus = true
}, 300)
},
methods: {
getStyle(index) {
let style = {}
style.borderColor = this.borderColor
if (this.text.length > index) {
style.borderColor = this.borderValueColor
style.color = this.borderValueColor
}
if (this.currentIndex === index) {
style.borderColor = this.borderActiveColor
}
return style
},
setBlur() {
this.focus = false
},
setFocus(index) {
this.focus = true
this.currentIndex = index
// 如果点击的位置在已输入字符之后,则移动到最后一个字符的位置
if (index > this.text.length) {
this.currentIndex = this.text.length
}
// 设置输入框的值,允许修改当前位置的内容
const input = this.$refs.VcodeInput
if (input) {
input.value = this.text.join('')
// 设置光标位置
setTimeout(() => {
input.setSelectionRange(this.currentIndex, this.currentIndex)
}, 0)
}
},
inputVal(e) {
let value = e.detail.value
if (this.isAutoComplete) {
if (value.length >= this.sum) {
this.focus = false
this.$emit('vcodeInput', value)
}
} else {
this.$emit('vcodeInput', value)
}
if (this.isPassword) {
let val = ''
for (let i = 0; i < value.length; i++) {
val += '●'
}
this.text = val
} else {
this.text = value
}
// 更新光标位置到输入位置
this.currentIndex = value.length
},
},
}
</script>
<style lang="scss" scoped>
.vcode-input-body {
width: 100%;
position: relative;
overflow: hidden;
display: flex;
flex-direction: row;
justify-content: center;
margin: 0 auto;
.vcode-input-item {
width: 76rpx;
height: 76rpx;
margin-left: 12rpx;
margin-right: 12rpx;
line-height: 76rpx;
text-align: center;
font-weight: 500;
position: relative;
}
.vcode-input-border {
border-style: solid;
border-width: 2rpx;
border-color: $uni-border-color;
border-radius: 4rpx;
}
.vcode-input-line {
border-bottom-style: solid;
border-bottom-width: 2rpx;
border-color: $uni-border-color;
}
.hidden-input {
width: 1px;
height: 1px;
position: absolute;
left: -1px;
top: -1px;
}
.cursor {
position: absolute;
left: 30%;
bottom: 18rpx;
width: 2rpx;
height: 30rpx;
background-color: #000000;
transform: translateX(-50%);
animation: blink 1s infinite;
}
}
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
</style>
复制代码
<template>
<view v-if="isIos">
<input
ref="codeInputRef"
class="hidden-input"
type="number"
:focus="isFocused"
:maxlength="maxLength"
v-model="verificationCode"
@input="handleCodeInput"
@focus="isFocused = true"
@blur="isFocused = false"
/>
<view class="verification-code-container">
<view class="code-display">
<view
v-for="(_, index) in maxLength"
:key="index"
class="code-cell"
:class="{
active: isFocused && index === verificationCode.length,
filled: index < verificationCode.length,
}"
@click="isFocused = true"
>
{{ verificationCode[index] || '' }}
</view>
</view>
</view>
</view>
<view class="code-input-container" v-if="!isIos">
<view v-for="(digit, index) in props.maxLength" :key="index" class="code-input">
<input
type="number"
maxlength="1"
class="phone-code-input"
v-model="passwordDigits[index]"
:focus="index === focusIndex"
@input="handleInput(index)"
@paste="handlePaste"
/>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch, onMounted, onUnmounted } from 'vue'
const props = defineProps({
maxLength: {
type: Number,
default: 0,
},
})
const emit = defineEmits(['complete'])
const verificationCode = ref('')
const codeInputRef = ref()
const isFocused = ref(true)
const handleCodeInput = (e) => {
const value = e.detail.value.replace(/\D/g, '') // 只保留数字
verificationCode.value = value.slice(0, props.maxLength) // 限制位数
// 输入完成
if (value.length === props.maxLength) {
onCodeComplete()
}
}
// 验证码输入完成
const onCodeComplete = () => {
// submitForm()
emit('complete', verificationCode.value)
uni.hideKeyboard()
}
// 设备类型
const isIos = ref(uni.getSystemInfoSync().platform === 'ios')
// 密码数组,初始化为空
const passwordDigits = ref(Array(props.maxLength).fill('')) // 处理输入
// 当前聚焦的输入框索引
const focusIndex = ref(0)
// 处理输入事件
const handleInput = (index: number) => {
// 如果当前输入框有值,并且不是最后一个输入框,则自动聚焦到下一个输入框
if (passwordDigits.value[index] !== '' && index < props.maxLength - 1) {
focusIndex.value = index + 1
}
if (passwordDigits.value[index] === '' && index > 0) {
focusIndex.value = index - 1
}
}
// 键盘删除事件
const handleKeyUp = (event: PlusKeyKeyEvent) => {
if (
event.keyCode === 67 &&
passwordDigits.value[focusIndex.value] === '' &&
focusIndex.value > 0
) {
focusIndex.value -= 1
}
}
onMounted(() => {
if (!isIos.value) {
plus.key.addEventListener('keyup', handleKeyUp)
}
})
onUnmounted(() => {
if (!isIos.value) {
plus.key.removeEventListener('keyup', handleKeyUp)
}
})
const handlePaste = (event: Event) => {
// 获取剪贴板内容(在某些平台上可能无法直接访问 clipboardData)
if (uni.getClipboardData) {
uni.getClipboardData({
success: (res) => {
// 提取粘贴内容中的所有数字
const pasteContent = res.data.trim() // 去除可能的空格
const extractedNumbers = pasteContent.replace(/\D/g, '') // 只保留数字,替换掉非数字字符
// 如果提取的数字是6位,继续处理
if (extractedNumbers.length === props.maxLength) {
console.log('extractedNumbers', extractedNumbers)
// 填充验证码数组
passwordDigits.value = extractedNumbers.split('')
onCodeComplete()
}
},
fail: (err) => {
console.log('获取剪贴板数据失败', err)
},
})
} else {
console.log('当前平台不支持获取剪贴板数据')
}
}
// 监听密码输入完成
watch(
passwordDigits,
(newValue) => {
// 检查是否所有数字都已输入
const isAllFilled = newValue.every((digit) => digit !== '')
if (isAllFilled) {
// 短暂延迟以确保用户看到最后一个数字
setTimeout(() => {
emit('complete', passwordDigits.value.join(''))
}, 200)
}
},
{ deep: true },
)
</script>
<style lang="scss" scoped>
.verification-code-container {
position: relative;
padding: 20rpx;
}
.hidden-input {
position: absolute;
left: -9999rpx;
opacity: 0;
width: 0;
height: 0;
}
.code-display {
display: flex;
justify-content: center;
}
.code-cell {
width: 80rpx;
height: 80rpx;
margin: 0 10rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 40rpx;
border-bottom: 2rpx solid #ddd;
position: relative;
}
.code-cell.filled {
border-bottom-color: #007aff;
}
.code-cell.active::after {
content: '';
position: absolute;
bottom: 5rpx;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background-color: #007aff;
animation: blink 1s infinite;
}
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
.code-input-container {
display: flex;
justify-content: space-between;
position: relative;
margin-bottom: 40rpx;
}
.code-input {
width: 80rpx;
height: 100rpx;
text-align: center;
font-size: 40rpx;
outline: none;
background: transparent;
}
.phone-code-input {
width: 80rpx;
height: 100rpx;
text-align: center;
font-size: 40rpx;
outline: none;
background: transparent;
border-bottom: 2rpx solid #ccc;
}
</style>