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>

 

posted on 2022-12-22 10:49  写最骚的代码  阅读(636)  评论(0编辑  收藏  举报