2023-01-06 16:00:15 +08:00
|
|
|
|
<template>
|
2025-10-13 10:27:07 +08:00
|
|
|
|
<div id="app">
|
|
|
|
|
|
<div class="captcha">
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-for="(c, index) in getCtData"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
v-model="getCtData[index]"
|
|
|
|
|
|
ref="input"
|
|
|
|
|
|
inputmode="numeric"
|
|
|
|
|
|
pattern="[0-9]*"
|
|
|
|
|
|
@input="(e) => onInput(e.target.value, index)"
|
|
|
|
|
|
@keydown="(e) => onKeydown(e, index)"
|
|
|
|
|
|
@keypress="(e) => onKeypress(e)"
|
|
|
|
|
|
@focus="onFocus"
|
|
|
|
|
|
@pause="onPause"
|
|
|
|
|
|
:disabled="loading"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2023-01-06 16:00:15 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
<script>
|
2025-10-13 10:27:07 +08:00
|
|
|
|
import { defineComponent } from "vue";
|
2023-01-06 16:00:15 +08:00
|
|
|
|
|
2025-10-13 10:27:07 +08:00
|
|
|
|
export default defineComponent({
|
|
|
|
|
|
props: ["ct"],
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
timeout: null,
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
getCtData() {
|
|
|
|
|
|
return this.ct;
|
|
|
|
|
|
},
|
|
|
|
|
|
ctSize() {
|
|
|
|
|
|
return this.getCtData.length;
|
|
|
|
|
|
},
|
|
|
|
|
|
cIndex() {
|
|
|
|
|
|
let i = this.getCtData.findIndex((item) => item === "");
|
|
|
|
|
|
i = (i + this.ctSize) % this.ctSize;
|
|
|
|
|
|
return i;
|
|
|
|
|
|
},
|
|
|
|
|
|
lastCode() {
|
|
|
|
|
|
return this.getCtData[this.ctSize - 1];
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
cIndex() {
|
|
|
|
|
|
this.resetCaret();
|
|
|
|
|
|
},
|
|
|
|
|
|
lastCode(newVal, oldVal) {
|
|
|
|
|
|
if (newVal && newVal != oldVal) {
|
|
|
|
|
|
this.$refs.input[this.ctSize - 1].blur();
|
|
|
|
|
|
this.sendCaptcha();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
mounted() {
|
|
|
|
|
|
this.resetCaret();
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
onInput(val, index) {
|
|
|
|
|
|
clearTimeout(this.timeout);
|
|
|
|
|
|
this.timeout = setTimeout(() => {
|
|
|
|
|
|
// val = val.replace(/[^0-9]/g, '');
|
|
|
|
|
|
val = String(val).replace(/\D/g, "");
|
|
|
|
|
|
this.getCtData[index] = val;
|
|
|
|
|
|
if (index == this.ctSize - 1) {
|
|
|
|
|
|
this.getCtData[this.ctSize - 1] = val[0]; // 最后一个码,只允许输入一个字符。
|
|
|
|
|
|
} else if (val.length > 1) {
|
|
|
|
|
|
let i = index;
|
|
|
|
|
|
for (
|
|
|
|
|
|
i = index;
|
|
|
|
|
|
i < this.ctSize && i - index < val.length;
|
|
|
|
|
|
i++
|
|
|
|
|
|
) {
|
|
|
|
|
|
this.getCtData[i] = val[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
this.resetCaret();
|
|
|
|
|
|
} else if (!(val + "")) {
|
|
|
|
|
|
this.getCtData[index] = "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 10);
|
|
|
|
|
|
},
|
|
|
|
|
|
onPause() {},
|
|
|
|
|
|
// 重置光标位置。
|
|
|
|
|
|
resetCaret() {
|
|
|
|
|
|
this.$refs.input[this.ctSize - 1].focus();
|
|
|
|
|
|
},
|
|
|
|
|
|
onFocus() {
|
|
|
|
|
|
// 监听 focus 事件,将光标重定位到“第一个空白符的位置”。
|
|
|
|
|
|
let index = this.getCtData.findIndex((item) => item === "");
|
|
|
|
|
|
index = (index + this.ctSize) % this.ctSize;
|
|
|
|
|
|
this.$refs.input[index].focus();
|
|
|
|
|
|
},
|
|
|
|
|
|
onKeypress(e) {
|
|
|
|
|
|
// 只允许输入数字0-9
|
|
|
|
|
|
const char = String.fromCharCode(e.which);
|
|
|
|
|
|
if (!/[0-9]/.test(char)) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onKeydown(e, index) {
|
|
|
|
|
|
// 处理删除键
|
|
|
|
|
|
if (e.key === "Backspace" || e.key === "Delete") {
|
|
|
|
|
|
const val = e.target.value;
|
|
|
|
|
|
if (val === "") {
|
|
|
|
|
|
// 删除上一个input里的值,并对其focus。
|
|
|
|
|
|
if (index > 0) {
|
|
|
|
|
|
this.getCtData[index - 1] = "";
|
|
|
|
|
|
this.$refs.input[index - 1].focus();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 阻止其他非数字字符
|
|
|
|
|
|
else if (
|
|
|
|
|
|
e.key &&
|
|
|
|
|
|
!/[0-9]/.test(e.key) &&
|
|
|
|
|
|
![
|
|
|
|
|
|
"Backspace",
|
|
|
|
|
|
"Delete",
|
|
|
|
|
|
"Tab",
|
|
|
|
|
|
"Enter",
|
|
|
|
|
|
"ArrowLeft",
|
|
|
|
|
|
"ArrowRight",
|
|
|
|
|
|
"ArrowUp",
|
|
|
|
|
|
"ArrowDown",
|
|
|
|
|
|
].includes(e.key)
|
|
|
|
|
|
) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2023-01-06 16:00:15 +08:00
|
|
|
|
|
2025-10-13 10:27:07 +08:00
|
|
|
|
sendCaptcha() {
|
|
|
|
|
|
let password = this.getCtData.map((item) => item).join("");
|
|
|
|
|
|
this.$emit("sendCaptcha", password);
|
|
|
|
|
|
},
|
2023-01-06 16:00:15 +08:00
|
|
|
|
|
2025-10-13 10:27:07 +08:00
|
|
|
|
reset() {
|
|
|
|
|
|
// 重置。一般是验证码错误时触发。
|
|
|
|
|
|
this.getCtData = this.getCtData.map((item) => "");
|
|
|
|
|
|
this.resetCaret();
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
2023-01-06 16:00:15 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
2025-10-13 10:27:07 +08:00
|
|
|
|
.captcha {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
}
|
|
|
|
|
|
input {
|
|
|
|
|
|
width: 8.7rem;
|
|
|
|
|
|
height: 8.7rem;
|
|
|
|
|
|
border: 0.1rem solid #b4bed7;
|
|
|
|
|
|
border-radius: 2rem;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 2.4rem;
|
|
|
|
|
|
line-height: 8.7rem;
|
|
|
|
|
|
outline: none;
|
2026-01-14 16:56:39 +08:00
|
|
|
|
@media (max-width: 767px) {
|
|
|
|
|
|
border-radius: .7rem;
|
|
|
|
|
|
width: 3.5rem;
|
|
|
|
|
|
height: 3.5rem;
|
|
|
|
|
|
font-size: 1.8rem;
|
|
|
|
|
|
line-height: 3.5rem;
|
|
|
|
|
|
}
|
2025-10-13 10:27:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
input:last-of-type {
|
|
|
|
|
|
margin-right: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
input:disabled {
|
|
|
|
|
|
color: #000;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
.msg {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
2023-01-06 16:00:15 +08:00
|
|
|
|
</style>
|