vue实现交易支付密码输入弹框
参考大佬的vue3写法 https://segmentfault.com/a/1190000042096606
自己改成的vue2写法:
1.封装组件PasswordInput
<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>
2.使用组件:
<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>
标签:
vue
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
2021-12-22 记一次迷之BUG,VUE项目配置CDN相关问题。
2021-12-22 VUE vue.config.js配置CDN,自动注入。
2021-12-22 VUE 配置compression-webpack-plugin打包压缩js,css