<template>
<el-dialog
title=""
:visible.sync="visible"
width="275px"
:before-close="handleClose"
custom-class="passwordInput"
>
<div class="password_box">
<div class="password_title">交易密码</div>
<div class="bs-password-input" ref="passwordInputRef">
<input
ref="realInput"
type="number"
inputmode="numeric"
class="hidden-input"
@input="onInput"
@blur="blur"
/>
<ul
class="bs-password-input-security"
:class="{
'has-gap': hasGap,
}"
@click="focus"
>
<li
class="bs-password-input-item"
:class="{
'is-focus': focusInputIndex === index,
}"
v-for="(pwd, index) in passwords"
:key="index"
>
<span
v-if="mask && pwd !== ' '"
class="password-input-dot flex flex-vc"
>*</span
>
<template v-if="!mask">{{ pwd }}</template>
<div v-if="showInputCursor" class="bs-password-input-cursor"></div>
</li>
</ul>
<div class="bs-password-input-info" v-if="info">{{ info }}</div>
</div>
</div>
<span slot="footer" class="dialog-footer"></span>
</el-dialog>
</template>
<script>
const trim = function (str) {
if (typeof str !== "string" || str.length === 0) {
return str;
}
str += "";
// 清除字符串两端空格,包含换行符、制表符
return str.replace(/(^[\s\n\t]+|[\s\n\t]+$)/g, "");
};
export default {
props: {
visible: {
type: Boolean,
default: false,
},
value: {
// 密码值
type: [String, Number],
default: "",
},
hasGap: {
// 是否有间隙
type: Boolean,
default: false,
},
mask: {
// 是否隐藏密码内容
type: Boolean,
default: true,
},
length: {
// 密码最大长度
type: Number,
default: 6,
},
info: {
// 输入框下方文字提示
type: String,
default: "",
},
},
computed: {
passwords() {
let value = this.value;
if (typeof value !== "string" && typeof value !== "number") {
value = "";
} else {
value = value + "";
}
// console.log('value', value);
let resultArr = value.split("");
let len = this.length;
let diff = value.length - this.length;
if (diff > 0) {
resultArr = value.substr(0, len).split("");
} else if (diff < 0) {
diff = Math.abs(diff);
while (diff > 0) {
resultArr.push(" ");
diff--;
}
}
return resultArr;
},
passwordInputRef() {
return this.$refs["passwordInputRef"];
},
realInput() {
return this.$refs["realInput"];
},
},
data() {
return {
focusInputIndex: null,
nativeInputFocus: false,
showInputCursor: false,
};
},
mounted() {
document.addEventListener("keydown", this.keydownEvent, false);
},
unmounted() {
document.removeEventListener("keydown", this.keydownEvent, false);
},
methods: {
handleClose() {
this.$emit("onClose");
},
calcFocusInputIndex() {
let pwdVal = this.passwords;
let index = -1;
let realPwdVal = trim(pwdVal.join(""));
for (let i = 0, len = pwdVal.length; i < len; i++) {
if (pwdVal[i] === " " && realPwdVal.length !== this.length) {
index = i;
break;
}
}
return index;
},
focus() {
this.$nextTick(() => {
let index = this.calcFocusInputIndex();
if (index > -1) {
this.realInput.focus();
this.nativeInputFocus = true;
this.showInputCursor = true;
this.focusInputIndex = index;
} else {
this.realInput.focus();
this.nativeInputFocus = true;
}
});
},
blur() {
this.showInputCursor = false;
this.focusInputIndex = null;
this.realInput.blur();
this.realInput.value = "";
this.nativeInputFocus = false;
},
onInput(evt) {
let numberReg = /^\d+$/;
let inputValue = evt.target.value;
if (inputValue && !numberReg.test(inputValue)) {
// 如果输入的不是数字则清空输入框
evt.target.value = "";
return;
}
let password = this.passwords.join("");
password = trim(password);
password += inputValue;
evt.target.value = "";
this.$emit("input", password);
if (password.length == this.length) {
this.$emit("complete", password);
}
// 隐藏输入框焦点
this.$nextTick(() => {
let inputIndex = this.calcFocusInputIndex();
if (inputIndex == -1) {
this.blur();
} else {
this.focusInputIndex = inputIndex;
}
});
},
keydownEvent(evt) {
let keyCode = evt.keyCode;
if (!this.nativeInputFocus) {
console.log("未获得焦点");
return;
}
if (keyCode == 8) {
// 删除键
let password = this.passwords.join("");
password = trim(password);
if (password.length == 0) {
return;
}
password = password.substr(0, password.length - 1);
this.$emit("input", password);
// 隐藏输入框焦点
this.$nextTick(() => {
let inputIndex = this.calcFocusInputIndex();
if (inputIndex == -1) {
this.blur();
} else {
this.focusInputIndex = inputIndex;
this.focus();
}
});
}
},
},
};
</script>
<style lang="scss" scoped>
/deep/ .passwordInput {
border-radius: 0;
background: none;
.el-dialog__header {
padding: 0;
.el-dialog__headerbtn {
right: 17px;
top: 17px;
.el-dialog__close {
font-weight: bold;
}
}
}
.el-dialog__body {
padding: 0;
}
.el-dialog__footer {
padding: 0;
}
.password_box {
padding: 21px 32px 24px;
box-shadow: 0px 16px 28px 3px rgba(0, 0, 0, 0.05);
border-radius: 8px;
background-color: var(--whiteColor);
.password_title {
color: var(--blackColor);
margin-bottom: 13px;
font-size: 16px;
font-weight: bold;
text-align: center;
}
}
.bs-password-input {
position: relative;
overflow: hidden;
.hidden-input {
position: absolute;
top: 5px;
z-index: 1;
/* 隐藏光标 start */
color: transparent;
text-shadow: 0 0 0 #000;
/* 隐藏光标 end */
/* 隐藏ios设备光标 start */
text-indent: -999em;
margin-left: -40%;
/* 隐藏ios设备光标 end */
}
}
.bs-password-input-security {
position: relative;
z-index: 5;
display: flex;
height: 36px;
user-select: none;
border-collapse: collapse;
}
.bs-password-input-item {
position: relative;
z-index: 5;
display: flex;
flex: 1;
justify-content: center;
align-items: center;
height: 100%;
cursor: pointer;
font-size: 20px;
background-color: var(--whiteColor);
border: 1px solid rgba(238, 241, 243, 1);
&:not(:first-child)::before {
position: absolute;
top: 0;
left: 0;
bottom: 0;
content: " ";
width: 1px; /*no*/
background-color: #ececec;
}
&.is-focus {
.password-input-dot {
visibility: hidden;
}
.bs-password-input-cursor {
display: block;
}
}
}
.password-input-dot {
font-weight: bold;
height: 100%;
padding-top: 8px;
// width: 12px;
// height: 12px;
// border-radius: 50%;
// background-color: #000;
}
.bs-password-input-cursor {
display: none;
position: absolute;
top: 50%;
left: 50%;
width: 1px; /*no*/
height: 40%;
transform: translate(-50%, -50%);
cursor: pointer;
background-color: rgba(32, 32, 32, 3);
animation: 1s cursor-flicker infinite;
}
.bs-password-input-security {
&.has-gap {
.bs-password-input-item {
&::before {
display: none;
}
&:first-child {
border-radius: 4px 0px 0px 4px;
}
&:not(:first-child) {
border-left: none;
border-radius: 0 4px 4px 0;
}
}
}
}
.bs-password-input-info {
margin-top: 15px;
color: #999;
text-align: center;
}
@keyframes cursor-flicker {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
}
</style>
<template>
<div>
<el-button @click="visible = !visible">Show PasswordInput</el-button>
<PasswordInput
ref="passwordInputRef"
v-model="password"
has-gap
@complete="onPasswordInputComplete"
:visible="visible"
@onClose="handleClose"
/>
</div>
</template>
<script>
import PasswordInput from "@/components/PasswordInput.vue";
export default {
components: {
PasswordInput,
},
data() {
return {
visible: false,
password: "",
};
},
watch: {
visible(val) {
if (val) this.$refs["passwordInputRef"].focus();
},
},
created() {},
methods: {
// 交易密码弹框隐藏
handleClose() {
this.visible = false;
},
onPasswordInputComplete(val) {
console.log("密码输入完成: ", val);
},
},
};
</script>