我打赌你已经注意到,今天的大多数应用程序要求你设置一个符合特定规则的密码。这些规则可能是最小长度、包括或不包括特殊字符、大写或小写字母等--在大多数情况下,这几条规则就足以确保你的密码很强大。但我们可以更进一步,使用一个叫做 zxcvbn的库来检查我们的密码强度。
什么是zxcvbn?
zxcvbn 是一个受密码破解者启发的密码强度估算器。通过模式匹配和保守估计,它可以识别和权衡40000个常见的密码,常见的名字姓氏,维基百科上的流行词,不同语言和不同国家的常见词,以及其他常见模式,如日期,重复( ),序列( ),键盘模式( )等等。aaa``abcd``qwertyuiop

以下是密码检查器的几项工作,我们将在这篇文章中介绍。
- 允许用户输入任何密码
- 在每次改变输入时检查密码得分
- 使用彩色的进度条和信息提供视觉反馈
- 当达到指定分数时启用提交按钮
在这篇文章中你会发现
在这篇文章中,我想告诉你如何。
我在StackBlitz上设置了一个演示,这样你就可以看到我们的目标。
好的,这就是我们想要的,所以让我们来实现它吧
第1步:用Tailwind CSS创建一个基本的表单模板
让我们首先为表单创建一个基本模板。我们将使用Tailwind CSS来使造型更容易、更快速。
在我们的PasswordInputForm 组件的模板部分,添加一个input 和一个button 。
<!-- PasswordInputForm.vue -->
<template>
<div class="container mx-auto w-64">
<input
class="block w-full my-1 p-2 border border-grey-30 rounded leading-normal text-grey-80"
type="text"
/>
<button
class="mt-6 px-6 py-1 leading-lg select-none bg-blue-400 text-white font-semibold border border-blue-500 rounded"
>
Submit
</button>
</div>
</template>
现在我们有了一些标记可以使用,让我们给我们的组件添加一些属性,让我们能够使它更有互动性。在我们组件的script 部分,添加password 和isPasswordStrong 属性,这两个属性都是反应式的、可变的ref 对象。
// PasswordInputForm.vue
<script>
import { ref } from "vue";
export default {
setup() {
const password = ref("");
const isPasswordStrong = ref(false);
return { password, isPasswordStrong };
},
};
</script>
在这个阶段,我们需要做的最后一件事是将新创建的属性绑定到模板上。我们使用v-model 指令将我们的password 属性绑定到input 元素上,这允许我们在用户每次改变输入时跟踪当前值,并将该值分配给我们的反应性password ref。
<!-- PasswordInputForm.vue -->
<input
class="block w-full my-1 p-2 border border-grey-30 rounded leading-normal text-grey-80"
type="text"
v-model="password"
/>
我们还可以使用isPasswordStrong ,根据道具的值来启用或禁用我们演示中的蓝色提交按钮。我们可以通过有条件地给按钮设置适当的类来做到这一点。
<!-- PasswordInputForm.vue -->
<button
class="mt-6 px-6 py-1 leading-lg select-none bg-blue-400 text-white font-semibold border border-blue-500 rounded"
:class="[
{
'cursor-not-allowed opacity-50 pointer-events-hover': !isPasswordStrong,
},
]"
>
Submit
</button>
完成这些后,我们最终得到一个基本的模板,看起来像这样。

密码强度检查器 - 第1步 - StackBlitz
Vue.js的入门项目
第2步:用以下方法构建密码评分组件zxcvbn
我们的输入已经准备好了。当我们改变输入时,它会反映出组件实例上的相应属性。让我们来实现代码逻辑,让我们得到密码的分数。
我们将把所有这些逻辑封装在一个单独的组件中,称为PasswordScore 。在这一步,我们还将向用户显示一条信息,表明当前密码的强度。
和以前一样,我们将用一个简单的模板开始我们的组件。
<!-- PasswordScore.vue -->
<template>
<div class="relative select-none">
<p class="absolute mt-1 text-sm">
// Here we will show the message to the user
</p>
</div>
</template>
在script 部分,添加一个props选项,定义我们的组件将从外部世界接受什么。我们只需要一个value 道具来告诉我们当前用户的输入是什么,所以我们可以将该属性标记为required - 这将迫使我们总是将该属性从父组件传递给子组件。
<!-- PasswordScore.vue -->
<script>
export default {
props: {
value: {
type: String,
required: true,
},
}
}
</script>
我们在组件中拥有这个值。现在,我们需要使用zxcvbn 库来检索该值的分数。
第一步是将所需的库元素导入我们的组件中。我们在我们的script 部分的顶部这样做。
<!-- PasswordScore.vue -->
<script>
import { zxcvbn, zxcvbnOptions } from "@zxcvbn-ts/core";
import zxcvbnCommonPackage from "@zxcvbn-ts/language-common";
import zxcvbnEnPackage from "@zxcvbn-ts/language-en";
export default {
...
}
</script>
接下来,我们需要通过设置适当的选项来初始化该库。我们希望在组件被创建时立即初始化库。为了在组件创建时启动逻辑,我们只需要把它放在该组件的setup 函数中。关于可以传递给zxcvbn的可用选项的详细描述,我建议查看zxcvbn文档。
// PasswordScore.vue
export default {
...
setup() {
const options = {
dictionary: {
...zxcvbnCommonPackage.dictionary,
...zxcvbnEnPackage.dictionary,
},
graphs: zxcvbnCommonPackage.adjacencyGraphs,
translations: zxcvbnEnPackage.translations,
};
zxcvbnOptions.setOptions(options);
}
}
</script>
使用zxcvbn分数库
好了,我们都准备好了,可以开始使用分数库了。让我们停下来想一想,我们要做的是:在每次输入值发生变化时,获得输入值的分数。
基于另一个被动的值来获得一个值的最好方法是使用一个计算属性。因此,让我们定义一个名为score 的计算属性。从Vue导入一个computed 帮助器,并使用该帮助器给一个const 赋值。
// PasswordScore.vue
export default {
import { computed } from "vue";
...
setup(props) {
...
const score = computed(() => {
const hasValue = props.value && props.value.length > 0;
if (!hasValue) {
return 0;
}
return zxcvbn(props.value).score + 1;
});
...
}
...
}
在计算属性中,首先检查该值是否存在,或者它是否是一个空字符串。如果后者是true ,我们可以为自己节省一些时间,直接返回0 。
如果值不是空的,我们使用zxcvbn ,把我们的value 传递给它。注意我们在这里是如何使用props的--props作为第一个参数被传递给setup 函数,然后使用props.xyz 。然后zxcvbn将返回一个带有score 属性的对象。
很好!我们的score 计算的属性将在每次我们的输入值变化时重新计算,并为我们返回一个分数值。但是我们如何向用户显示这个值呢?
显示zxcvbn的分数值
让我们再实现两个计算属性,以帮助我们向用户显示一个信息,表明当前的分数。
第一个将保存所有我们可以显示给用户的潜在描述和颜色代码。
// PasswordScore.vue
export default {
import { computed } from "vue";
...
setup(props) {
...
const descriptions = computed(() => [
{
color: "bg-red-600",
label: "Weak, my 2 years old son can break it!",
},
{ color: "bg-red-300", label: "Still weak, keep on trying!" },
{ color: "bg-yellow-400", label: "We are getting there..." },
{ color: "bg-green-200", label: "Nice, but you can still do better" },
{
color: "bg-green-400",
label: "Congratulations, you made it!",
},
]);
...
}
...
}
第二个提取并返回我们想显示给用户的描述。其工作原理与上述相同:首先,我们检查该值是否存在,并且不是一个空字符串;然后,如果它存在,我们从描述数组中返回相应的元素。如果它不存在,我们就返回一个通用信息,以鼓励用户使用该输入。
// PasswordScore.vue
export default {
import { computed } from "vue";
...
setup(props) {
...
const description = computed(() =>
props.value && props.value.length > 0
? descriptions.value[score.value - 1]
: {
color: "bg-transparent",
label: "Start typing to check your password",
}
);
...
}
...
}
好了,现在我们已经准备好了描述,我们仍然需要把它展示给用户。让我们使用Vue的模板语法来做。
<!-- PasswordScore.vue -->
<template>
<div class="relative select-none">
<p class="absolute mt-1 text-sm">
{{ description.label }}
</p>
</div>
</template>
还有一件事我们需要记住:在设置函数中定义的每个属性,如果我们想在模板中使用,也需要由设置函数返回。
// PasswordScore.vue
export default {
import { computed } from "vue";
...
setup(props) {
...
return { description };
...
}
...
}
由于我们想在PasswordInputForm 组件中使用我们的PasswordScore 组件,我们需要以导入分数库的同样方式导入PasswordScore 组件,并使用components 选项注册它,这使它对组件实例可用。
<!-- PasswordInputForm.vue -->
<script>
...
import PasswordScore from './PasswordScore.vue';
export default {
components: {
PasswordScore,
},
...
};
</script>
然后,我们可以在模板部分使用该组件,就像下面这样。
<!-- PasswordInputForm.vue -->
<template>
<div class="container mx-auto w-64">
...
<PasswordScore
:value="password"
class="mt-2 mb-6"
/>
...
</div>
</template>
这就是第二步的全部内容!这一步之后,我们应该看到这样的东西。

密码强度检查器 - 第二步 - StackBlitz
Vue.js的入门项目
在下一步,我们将处理提交按钮,使其在分数达到我们的标准时可以点击。
第3步:处理按钮的状态
在这一步中,当分数的值≥4 ,我们将启用提交按钮,而当它低于这个阈值时,又将禁用它。
要做到这一点,我们需要检测我们的阈值何时被满足。这也是我们可以用一个计算的属性来做的事情
这一次,我们将把它称为isPasswordStrong ,我强烈建议对所有布尔类型的变量使用这一命名规则,因为从长远来看,它使编码和阅读代码变得更加容易。
// PasswordScore.vue
export default {
import { computed } from "vue";
...
setup(props) {
...
const isPasswordStrong = computed(() => score.value >= 4);
...
}
...
}
现在我们有了这个值,我们需要与父组件沟通,这个值发生了变化,按钮应该根据我们设置的阈值被启用或禁用。我们可以通过发射一个事件来做到这一点。
要发射一个事件,我们需要首先用我们组件上的emits 属性来定义它。最终,我们将定义两个事件。
- 当密码变强时 (
passed) - 当密码变弱时 (
failed)
// PasswordScore.vue
export default {
import { computed } from "vue";
...
setup(props) {
...
emits: ["passed", "failed"],
...
}
...
}
让我们继续设置,以便当isPasswordStrong 的值发生变化时,我们发出这些事件。我们不能在计算属性里面这样做,因为计算属性不应该引起任何副作用--而在我们的例子中,发射一个事件和改变一个按钮状态就是一个副作用。
使用Vue观察器
但是,不要害怕!Vue提供了合适的工具。Vue为这种情况提供了合适的工具:观察器。观察者寻找我们组件的反应性属性的变化,并允许我们在变化发生时执行任何逻辑。
首先,我们需要从Vue导入一个辅助器,这与我们导入计算属性的方式相同。
// PasswordScore.vue
export default {
import { computed, watch } from "vue";
...
}
然后,我们在setup 函数中定义我们的观察者。
// PasswordScore.vue
export default {
import { computed, watch } from "vue";
...
setup(props, { emit }) {
...
watch(isPasswordStrong, (value) => {
value ? emit("passed") : emit("failed");
});
...
}
...
}
当定义一个观察者时,我们向它传递两个参数。
- 我们想要观察的反应式属性
- 当该属性的值发生变化时,我们要执行的一个回调。
回调可以访问我们正在观察的属性的当前和以前的值,但在我们的案例中,我们只需要当前的值。
有了当前值在手,我们现在可以在回调中向父级组件发出适当的事件。
emit 方法是从哪里来的?
还有一件事我应该指出:emit 方法来自传递给setup 函数的第二个参数。第二个参数是一个context 对象,它持有一些更有用的元素,但我将留给你自己去深入挖掘它。
所以,我们向我们的父组件发出了事件。现在,我们需要捕捉该事件并在父组件中处理它。
我们已经使用内置的v-on 指令(或@ 简称)来观察任何子组件的事件。让我们转而观察由PasswordScore 组件发出的两个事件。
<!-- PasswordInputForm.vue -->
<template>
<div class="container mx-auto w-64">
...
<PasswordScore
:value="password"
class="mt-2 mb-6"
@passed="isPasswordStrong = true"
@failed="isPasswordStrong = false"
/>
...
</div>
</template>
当我们检测到一个适当的事件发生时,我们设置我们的isPasswordStrong 反应式属性的值。这将触发应用于按钮的类的变化,导致按钮相应地被禁用或启用。
<!-- PasswordInputForm.vue -->
<button
class="mt-6 px-6 py-1 leading-lg select-none bg-blue-400 text-white font-semibold border border-blue-500 rounded"
:class="[
{
'cursor-not-allowed opacity-50 pointer-events-hover': !isPasswordStrong,
},
]"
>
Submit
</button>
好了,我们现在有一个表单,它检查输入的分数,以文本框信息的形式向用户显示结果,并在密码足够强大时启用按钮。它看起来应该是这样的。
密码强度检查器 - 第3步 - StackBlitz
Vue.js的入门项目
这就足够了,但我们想让它更醒目一些。接下来,我们将添加一个漂亮的进度条,每次分数变化时都会有动画和颜色变化。
第4步:在表单中添加一个彩色编码的、动画的进度条
让我们从我们的进度条的模板开始。我们将把它放在一个单独的组件中,叫做BaseProgressBar 。
在我们新组件的模板部分,添加两个div 元素。
- 一个静态包装器,将显示我们的进度条的边界
- 更加动态的封装器,会发生变化。
- 它的宽度基于一个
width计算的属性 - 它的颜色,基于一个从
PasswordScore组件传递过来的color属性
- 它的宽度基于一个
BaseProgressBar.vue
为了设置进度条的width ,我们使用内联样式绑定,并根据我们当前的分数值分配一个百分比值给我们的div 元素的原始样式width 属性。对于颜色,我们使用HTML类绑定并指定一个Tailwind类,负责改变我们的div 的背景颜色。
在script 部分,我们添加了另一个props选项,定义了三个属性。
max:我们的value属性的最大允许值value:我们想显示的与max值有关的当前值color:用于改变进度条颜色的背景色Tailwind类。
现在,在setup 函数中,我们定义了一个计算属性,负责根据max 和value 属性计算出当前的条形宽度。
<!-- BaseProgressBar.vue -->
<script>
import { computed } from 'vue';
export default {
props: {
max: {
type: Number,
required: true,
},
value: {
type: Number,
required: true,
},
color: {
type: String,
default: 'bg-transparent',
},
},
setup(props) {
const width = computed(() => (props.value / props.max) * 100);
return { width };
},
};
</script>
就这样,我们的进度条已经准备好了!现在,我们需要在PasswordScore 组件中使用它。
和以前一样,我们导入该组件并注册它。
<!-- PasswordScore.vue -->
<script>
...
import BaseProgressBar from './BaseProgressBar.vue';
export default {
components: {
BaseProgressBar,
},
...
}
</script>
最后,我们在模板部分使用该组件。使用属性绑定将属性传递给该组件。
value:我们的score值max:最大的分数值,我们可以从我们的长度中检索出来。descriptionscolor:我们当前的描述颜色
<!-- PasswordScore.vue -->
<template>
<div class="relative select-none">
<BaseProgressBar
:value="score"
:max="descriptions.length"
:color="description.color"
/>
<p class="absolute mt-1 text-sm">
{{ description.label }}
</p>
</div>
</template>
密码强度检查器 - StackBlitz
Vue.js的入门项目
然后,我们就可以了。我们的密码分数检查器已经准备好了!
总结
让我们回顾一下我们刚刚完成的工作。我们创建了两个组件:一个是密码检查表单,一个是使用我们的密码和zxcvbn ,以获得该密码的分数。然后,我们实现了基于分数的提交按钮的启用或禁用的逻辑。最后,我们添加了一个进度条,向用户提供关于当前分数的反馈。
咻,我们涵盖了很多内容!希望这能给你们带来一些帮助。希望这能为你的工具箱提供一个新的技巧,以及一个Vue的实际使用案例。