
代码实现如上效果(不能灵活删除)
<template><view class="uni-flex uni-flex-center vcode-input-body"><viewclass="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><inputref="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.borderColorif (this.text.length > index) {style.borderColor = this.borderValueColorstyle.color = this.borderValueColor}if (this.currentIndex === index) {style.borderColor = this.borderActiveColor}return style},setBlur() {this.focus = false},setFocus(index) {this.focus = truethis.currentIndex = indexif (index > this.text.length) {this.currentIndex = this.text.length}const input = this.$refs.VcodeInputif (input) {input.value = this.text.join('')setTimeout(() => {input.setSelectionRange(this.currentIndex, this.currentIndex)}, 0)}},inputVal(e) {let value = e.detail.valueif (this.isAutoComplete) {if (value.length >= this.sum) {this.focus = falsethis.$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>
灵活删除代码实现(区分ios和安卓端)
<template><view v-if="isIos"><inputref="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"><viewv-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"><inputtype="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>