Ben Lin
2024-08-09 d7b0c15619e89d31c74f8db7a680b4c6a0009add
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
<template>
  <div :class="prefixCls" class="relative">
    <Input.Password
      v-if="showInput"
      v-bind="$attrs"
      allowClear
      :value="innerValueRef"
      @change="handleChange"
      :disabled="disabled"
    >
      <template #[item]="data" v-for="item in Object.keys($slots)">
        <slot :name="item" v-bind="data || {}"></slot>
      </template>
    </Input.Password>
    <div :class="`${prefixCls}-bar`">
      <div :class="`${prefixCls}-bar--fill`" :data-score="getPasswordStrength"></div>
    </div>
  </div>
</template>
 
<script lang="ts" setup>
  import { computed, ref, watch, unref, watchEffect } from 'vue';
  import { Input } from 'ant-design-vue';
  import { zxcvbn } from '@zxcvbn-ts/core';
  import { useDesign } from '@/hooks/web/useDesign';
  import { propTypes } from '@/utils/propTypes';
 
  defineOptions({ name: 'StrengthMeter' });
 
  const props = defineProps({
    value: propTypes.string,
    showInput: propTypes.bool.def(true),
    disabled: propTypes.bool,
  });
 
  const emit = defineEmits(['score-change', 'change']);
 
  const innerValueRef = ref('');
  const { prefixCls } = useDesign('strength-meter');
 
  const getPasswordStrength = computed(() => {
    const { disabled } = props;
    if (disabled) return -1;
    const innerValue = unref(innerValueRef);
    const score = innerValue ? zxcvbn(unref(innerValueRef)).score : -1;
    emit('score-change', score);
    return score;
  });
 
  function handleChange(e) {
    emit('change', e.target.value);
    innerValueRef.value = e.target.value;
  }
 
  watchEffect(() => {
    innerValueRef.value = props.value || '';
  });
 
  watch(
    () => unref(innerValueRef),
    (val) => {
      emit('change', val);
    },
  );
</script>
<style lang="less" scoped>
  @prefix-cls: ~'@{namespace}-strength-meter';
 
  .@{prefix-cls} {
    &-bar {
      position: relative;
      height: 6px;
      margin: 10px auto 6px;
      border-radius: 6px;
      background-color: @disabled-color;
 
      &::before,
      &::after {
        content: '';
        display: block;
        position: absolute;
        z-index: 10;
        width: 20%;
        height: inherit;
        border-width: 0 5px;
        border-style: solid;
        border-color: @white;
        background-color: transparent;
      }
 
      &::before {
        left: 20%;
      }
 
      &::after {
        right: 20%;
      }
 
      &--fill {
        position: absolute;
        width: 0;
        height: inherit;
        transition:
          width 0.5s ease-in-out,
          background 0.25s;
        border-radius: inherit;
        background-color: transparent;
 
        &[data-score='0'] {
          width: 20%;
          background-color: darken(@error-color, 10%);
        }
 
        &[data-score='1'] {
          width: 40%;
          background-color: @error-color;
        }
 
        &[data-score='2'] {
          width: 60%;
          background-color: @warning-color;
        }
 
        &[data-score='3'] {
          width: 80%;
          background-color: fade(@success-color, 50%);
        }
 
        &[data-score='4'] {
          width: 100%;
          background-color: @success-color;
        }
      }
    }
  }
</style>