vue3+ant-design-vue 实现 input 模拟密码框效果

背景

测试:怎么忘记密码和确认密码被自动填充记住的密码了?麻烦不要填充。
我: 反手给密码框加上了 autocomplete="off"
测试:怎么忘记密码和确认密码还有记住密码的提示框?这不符合产品定义。麻烦去掉。
我: 我得研究下~

7s1m4n0mF3.jpg

解决方案

网上找到了很多简单的方案,比如加 readonly 属性再去掉等,都没有效果。只剩下使用 input 模拟密码框交互的方案。

最后根据文章 vue 自定义密码输入框解决浏览器自动填充密码的问题的思路做了 vue3 的版本。

组件代码

代码思路:

  • 创建 input 表单
  • 监听 selectionchange 事件,过滤当前输入框输入时再处理
  • 更新输入值为密码格式
  • 添加预览切换

备注:代码逻辑上,依赖于光标位置的赋值取值时机先后;selectionchange 会比 change 事件后触发。

<script lang="ts" setup>
// src/components/password-input/password-input.vue
// 组件参考来源:https://github.com/lxmghct/my-vue-components/blob/main/src/components/PasswordInput/PasswordInput2.vue
import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons-vue';
import { useVModel } from '@vueuse/core';
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
export interface Props {
  value: string;
}
const emit = defineEmits<{
  'update:value': [Props['value']];
}>();
const props = defineProps<Props>();
const vmodelValue = useVModel(props, 'value', emit);
// 文字转化为原点
const toDot = (value: string) => '\u2022'.repeat(value.length);
const displayValue = ref(toDot(props.value));
const isPreview = ref(false);
// 更新输入值为密码格式
const updateDisplay = (value: string) => {
  if (isPreview.value) {
    displayValue.value = value;
  } else {
    displayValue.value = toDot(value); // 圆点
  }
  const end = inputRef.value.input.selectionEnd;
  const start = inputRef.value.input.selectionStart;
  // 更新值后,光标自动跑到最后,手动更新回原本的位置
  nextTick(() => {
    inputRef.value.input.setSelectionRange(start, end);
    selection.start = start;
    selection.end = end;
  });
};
// 处理输入变化
const handleChange = (e: any) => {
  /*
     修改前的数据:vmodelValue.value, selection.start, selection.end
     修改后的数据:e.target.value, inputRef.value.input.selectionEnd, inputRef.value.input.selectionEnd
     */
  const currentStart = inputRef.value.input.selectionStart;
  const currentValue = e.target.value;
  const originValue = vmodelValue.value;
  // 获取新输入的字符
  const tempEnd = currentValue.length - (originValue.length - selection.end);
  const newStr = currentValue.slice(selection.start, tempEnd);

  // 更新输入框的值
  vmodelValue.value =
    originValue.slice(0, Math.min(selection.start, currentStart)) +
    newStr +
    originValue.slice(selection.end);
};

const inputRef = ref();
// 是否开始监听光标
const isSelect = ref(false);
// 缓存光标的位置
const selection = reactive({
  start: 0,
  end: 0,
});

onMounted(() => {
  document.addEventListener('selectionchange', (e: any) => {
    if (!isSelect.value) return;
    selection.start = inputRef.value.input.selectionStart;
    selection.end = inputRef.value.input.selectionEnd;
  });
});
// 用于实现当前输入框的selectionchange
const handleFocus = () => {
  isSelect.value = true;
};
const handleBlur = () => {
  isSelect.value = false;
};
const visibilityToggle = () => {
  isPreview.value = !isPreview.value;
};
watch(
  () => vmodelValue.value,
  (value: string) => updateDisplay(value),
);
watch(
  () => isPreview.value,
  () => updateDisplay(vmodelValue.value),
);
</script>
<template>
  <div class="password-main">
    <a-input
      ref="inputRef"
      v-model:value="displayValue"
      placeholder="请输入"
      autocomplete="off"
      @change="handleChange"
      @focus="handleFocus"
      @blur="handleBlur"
    >
      <template #suffix>
        <eye-outlined v-if="isPreview" @click="visibilityToggle" />
        <eye-invisible-outlined v-else @click="visibilityToggle" />
      </template>
    </a-input>
  </div>
</template>
<style lang="scss" scoped>
.password-main {
}
</style>

调用方式

<PasswordInput v-model:value="formState.password"></PasswordInput>

完整 a-form 使用

 
<template>
  <div>
    <a-form :model="formState" ref="formRef" :rules="rules" labelAlign="left">
      <a-form-item label="确认密码" name="password" :validateFirst="true">
        <PasswordInput v-model:value="formState.password"></PasswordInput>
      </a-form-item>
    </a-form>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';

import PasswordInput from '@/components/password-input/password-input.vue';
const formState = ref({
  password: '',
});
const formRef = ref();

const rules = {
  password: [{ required: true, message: '请输入密码' }],
};
</script>

image.png

相关组件示例代码可点击查看 github

如果有不足或有问题的地方,欢迎留言评论

文章转自:https://juejin.cn/post/7384618394061733898

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值