feat: award页面

This commit is contained in:
2026-01-16 14:30:48 +08:00
parent c9b67c4d3b
commit 6a5a0930e9
45 changed files with 1584 additions and 67 deletions

View File

@@ -0,0 +1,236 @@
<template>
<div class="verification-code-input">
<input
v-for="(code, index) in verificationCode"
:key="index"
type="text"
:value="verificationCode[index]"
ref="inputRefs"
inputmode="numeric"
pattern="[0-9]*"
:disabled="loading"
@input="(e) => onCodeInput(e, index)"
@paste="(e) => onCodePaste(e, index)"
@keydown="(e) => onCodeKeydown(e, index)"
@keypress="(e) => onCodeKeypress(e)"
@focus="onCodeFocus"
class="code-input"
/>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue'
interface Props {
loading?: boolean
}
interface Emits {
(e: 'complete', code: string): void
(e: 'change', code: string): void
}
const props = withDefaults(defineProps<Props>(), {
loading: false
})
const emit = defineEmits<Emits>()
// 验证码输入相关
const verificationCode = ref(['', '', '', '', '', ''])
const inputRefs = ref<HTMLInputElement[]>([])
// 验证码输入相关方法
const onCodeInput = (e: Event, index: number) => {
const input = e.target as HTMLInputElement
const val = input.value
const cleanVal = String(val).replace(/\D/g, '')
// 检查是否已经输入了5位数字不包括当前正在输入的第6位
const filledCount = verificationCode.value.filter(v => v !== '').length
const isAlmostComplete = filledCount >= 5
// 如果已经输入了5位数字检查当前输入是否会导致超过6位
if (isAlmostComplete && cleanVal.length === 1 && verificationCode.value[index] === '') {
// 如果当前输入框是空的说明这是第6位输入允许
// 如果当前输入框有值,说明是替换,不允许
// 这里我们允许输入,但会在设置后检查
}
// 只处理单个字符输入,粘贴由 paste 事件处理
if (cleanVal.length === 1) {
verificationCode.value[index] = cleanVal
// 自动跳转到下一个输入框只有在还没到第6个时
if (index < 5) {
nextTick(() => {
inputRefs.value[index + 1]?.focus()
})
}
// 发出变化事件
const finalCode = verificationCode.value.join('')
emit('change', finalCode)
// 如果完成了6位发出完成事件
if (finalCode.length === 6) {
emit('complete', finalCode)
}
} else if (cleanVal.length === 0) {
// 处理删除
verificationCode.value[index] = ''
emit('change', verificationCode.value.join(''))
}
// 如果是多个字符,可能是粘贴,由 paste 事件处理,这里不做处理
}
const onCodePaste = (e: ClipboardEvent, index: number) => {
e.preventDefault()
// 检查是否已经输入了6位数字
const currentCode = verificationCode.value.join('')
if (currentCode.length === 6) {
return // 如果已经完成,不允许粘贴
}
const pasteData = (e.clipboardData || (window as any).clipboardData).getData('text')
const cleanData = pasteData.replace(/\D/g, '') // 只保留数字
if (cleanData.length === 0) return
console.log('Paste detected:', cleanData)
// 从当前输入框开始填充
for (let i = 0; i < Math.min(cleanData.length, 6 - index); i++) {
verificationCode.value[index + i] = cleanData[i]
}
// 移动焦点到下一个空白输入框
const nextEmptyIndex = verificationCode.value.findIndex((val, i) => i >= index && val === '')
if (nextEmptyIndex !== -1) {
nextTick(() => {
inputRefs.value[nextEmptyIndex]?.focus()
})
} else {
nextTick(() => {
inputRefs.value[5]?.focus()
})
}
// 发出完成事件
emit('complete', verificationCode.value.join(''))
emit('change', verificationCode.value.join(''))
}
const onCodeKeydown = (e: KeyboardEvent, index: number) => {
// 处理删除键
if (e.key === 'Backspace') {
const input = e.target as HTMLInputElement
const val = input.value
if (val === '') {
// 当前输入框为空,删除上一个输入框的值
if (index > 0) {
verificationCode.value[index - 1] = ''
nextTick(() => {
inputRefs.value[index - 1]?.focus()
})
emit('change', verificationCode.value.join(''))
}
} else {
// 当前输入框有值,清空当前输入框
verificationCode.value[index] = ''
emit('change', verificationCode.value.join(''))
}
}
// 阻止其他非数字字符(除了允许的控制键)
else if (
e.key &&
!/[0-9]/.test(e.key) &&
![
'Backspace',
'Delete',
'Tab',
'Enter',
'ArrowLeft',
'ArrowRight',
'ArrowUp',
'ArrowDown'
].includes(e.key)
) {
e.preventDefault()
}
}
const onCodeKeypress = (e: KeyboardEvent) => {
// 检查是否已经输入了6位数字
const currentCode = verificationCode.value.join('')
if (currentCode.length === 6) {
e.preventDefault() // 如果已经完成,阻止任何按键输入
return
}
// 只允许输入数字0-9
const char = String.fromCharCode(e.which || (e as any).keyCode)
if (!/[0-9]/.test(char)) {
e.preventDefault()
}
}
const onCodeFocus = () => {
// 聚焦到第一个空白输入框
const index = verificationCode.value.findIndex(item => item === '')
const focusIndex = index === -1 ? 5 : index
nextTick(() => {
inputRefs.value[focusIndex]?.focus()
})
}
// 暴露给父组件的方法
const reset = () => {
verificationCode.value = ['', '', '', '', '', '']
nextTick(() => {
inputRefs.value[0]?.focus()
})
}
const getCode = () => {
return verificationCode.value.join('')
}
defineExpose({
reset,
getCode,
verificationCode
})
</script>
<style scoped lang="less">
.verification-code-input {
display: flex;
column-gap: 1.2rem;
.code-input {
width: 5rem;
height: 5rem;
border: 0.15rem solid #d5d5d5;
border-radius: 0.8rem;
text-align: center;
font-size: 2rem;
line-height: 5rem;
outline: none;
transition: border-color 0.2s;
&:focus {
border-color: #232323;
}
&:disabled {
background-color: #f5f5f5;
color: #999;
}
}
}
</style>