vue 密码强弱设置
resetPass.vue
<style scoped lang="scss">
@import "~@a/style/variables.scss";
.password-score {
display: flex;
border-radius: 4px;
p {
width: 33.3%;
text-align: center;
height: 20px;
line-height: 20px;
font-size: $font-sm;
color: $white;
background-color: #ccc;
&.weak {
border-radius: 4px 0 0 4px;
}
&.strong {
border-radius: 0 4px 4px 0;
}
&.weak.active {
background-color: #f05050;
}
&.middle.active {
background-color: #ffbd4a;
}
&.strong.active {
background-color: #81c868;
}
}
}
</style>
<template>
<yv-dialog title="重置密码" :visible.sync="visible">
<yv-form ref="passwordForm" :model="passwordForm" :rules="passwordFormRule" label-width="100px">
<yv-form-item label="用户名">
<yv-tag type="info">root</yv-tag>
</yv-form-item>
<yv-form-item label="密码" prop="password">
<yv-input style="width: 330px;margin-right: 12px" v-model="passwordForm.password" :type="passwordType"
autocomplete="new-password" :maxlength="30" @change="changePasswordWarning" />
<yv-checkbox v-model="showPassword" @change="changePassword">显示密码</yv-checkbox>
<template slot="tips">
<p class="w400">密码8-30位字符,需同时包含大小写、英文、数字,不能为键盘上连续3位及以上字符,不可包含空格及以下字符# $ % & * < ></p>
<div v-if="showPasswordWarning" class="password-score mt8" style="width: 310px">
<p :class="['weak', this.passwordScore <= 60 ? 'active' : '']">弱</p>
<p :class="['middle', this.passwordScore > 60 && this.passwordScore < 70 ? 'active' : '']">中</p>
<p :class="['strong', this.passwordScore >= 70 ? 'active' : '']">强</p>
</div>
</template>
</yv-form-item>
<yv-form-item label="重复密码" prop="password1">
<yv-input style="width: 330px;margin-right: 12px" v-model="passwordForm.password1" :type="passwordType"
autocomplete="new-password" :maxlength="30" />
</yv-form-item>
</yv-form>
<div slot="footer" class="dialog-footer">
<yv-button size="small" @click="cancelDialog">取 消</yv-button>
<yv-button type="primary" size="small" @click="saveDialog" :loading="saving">
<span v-if="saving">提交中...</span>
<span v-else>确定</span>
</yv-button>
</div>
</yv-dialog>
</template>
<script>
import ApiInstance from '@/api/module/instance'
import { validatePasswordUtil, calcPwdScore } from '@util'
export default {
name: "InstanceResetPassword",
data() {
const validatePassword = (rule, value, callback) => {
if (!value) {
return callback(new Error('密码不能为空'))
} else {
if (validatePasswordUtil(value)) {
callback()
} else {
return callback(new Error('密码格式不正确'))
}
}
}
const validatePassword1 = (rule, value, callback) => {
if (this.passwordForm.password !== value) {
return callback(new Error('两次密码输入不一致'))
}
callback()
}
return {
visible: false,
saving: false,
id:'',
passwordForm: {
username: 'root',
password: '',
password1: ''
},
passwordType: 'password',
showPassword: false,
showPasswordWarning: false,
passwordScore: 0,
passwordFormRule: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 8, max: 17, message: '长度在 8 到 17 个字符', trigger: 'blur' },
{validator: validatePassword, trigger: 'blur'}
],
password1: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
{ min: 8, max: 17, message: '长度在 8 到 17 个字符', trigger: 'blur' },
{validator: validatePassword1, trigger: 'blur'}
],
}
}
},
methods: {
changePasswordWarning (val) {
if (val) {
this.showPasswordWarning = true
if (validatePasswordUtil(val)) {
this.passwordScore = calcPwdScore(val)
} else {
this.passwordScore = 59
}
} else {
this.showPasswordWarning = false
}
},
changePassword (val) {
if (val) {
this.passwordType = 'text'
} else {
this.passwordType = 'password'
}
},
cancelDialog() {
this.$refs.passwordForm.resetFields()
this.visible = false
},
saveDialog() {
this.$refs.passwordForm.validate(valid => {
if (valid) {
this.saving = true
ApiInstance.resetPassword(this.id, this.passwordForm).then(() => {
this.$refs.passwordForm.resetFields()
this.visible = false
}).finally(() => {
this.saving = false;
})
}
})
},
}
}
</script>
utils/index.js
// 深拷贝对象
export function deepClone(obj) {
const _toString = Object.prototype.toString
// null, undefined, non-object, function
if (!obj || typeof obj !== 'object') {
return obj
}
// DOM Node
if (obj.nodeType && 'cloneNode' in obj) {
return obj.cloneNode(true)
}
// Date
if (_toString.call(obj) === '[object Date]') {
return new Date(obj.getTime())
}
// RegExp
if (_toString.call(obj) === '[object RegExp]') {
const flags = []
if (obj.global) { flags.push('g') }
if (obj.multiline) { flags.push('m') }
if (obj.ignoreCase) { flags.push('i') }
return new RegExp(obj.source, flags.join(''))
}
const result = Array.isArray(obj) ? [] : obj.constructor ? new obj.constructor() : {}
for (const key in obj) {
result[key] = deepClone(obj[key])
}
return result
}
/**
* 校验密码
* @param {string} password 密码字段
* @return {Boolean} 是否检验成功
*/
export function validatePasswordUtil(password) {
let value = password
if (value === '') return true;
let isValid = /^(?=.*?[0-9])(?=.*?[A-Z])(?=.*?[a-z])[\x21-\x7e]{8,30}$/g.test(value);
let repeatVaild = /(\w)*(\w)\2{2}(\w)*/g.test(value);
if (isValid && !repeatVaild) {
const k = [['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '='],
['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'],
['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\''],
['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/']];
const K = [['!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+'],
['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|'],
['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"'],
['Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?']];
let ks = value.split('');
let ins = [];
ks.forEach((o) => {
LOOP_k:
for (let i = 0; i < k.length; i++) {
for (let j = 0; j < k[i].length; j++) {
if (k[i][j] === o) {
ins.push(i + ',' + j);
break LOOP_k;
}
}
}
LOOP_K:
for (let m = 0; m < K.length; m++) {
for (let n = 0; n < K[m].length; n++) {
if (K[m][n] === o) {
ins.push(m + ',' + n);
break LOOP_K;
}
}
}
});
let c = 0;
if (ins.length > 2) {
let i0 = ins[0].split(','), m = 0;
for (let i = 1; i < ins.length; i++) {
let ii = ins[i].split(',');
if (ii[0] === i0[0] && Math.abs(ii[1] - i0[1]) === 1 && (m === 0 || m === 'x')) {
c++;
m = 'x';
} else if (ii[1] === i0[1] && Math.abs(ii[0] - i0[0]) === 1 && (m === 0 || m === 'y')) {
c++;
m = 'y';
} else if (Math.abs(ii[0] - i0[0]) === 1 && Math.abs(ii[1] - i0[1]) === 1 && (m === 0 || m == 'z')) {
c++;
m = 'z';
} else {
c = 0;
m = 0;
}
i0 = ins[i].split(',');
if (c > 1) {
isValid = false;
break;
}
}
}
}
return isValid && !repeatVaild
}
/**
* 计算密码强度
* 根据规则计算出一个分数,100分制
* @param {string} password 密码字段
* @return {number} 密码分数
*/
export function calcPwdScore (password) {
let score = 0,pwd = password;
// 密码长度加分 超过8位 +25分 6-8位 5分,小于6位0分
score += (pwd.length >= 8 ? 25 : (pwd.length >= 6 ? 5 : 0));
// 全字母 +5分
if(/^[A-Za-z]$/.test(pwd)) {
score += 5;
}
let arr = pwd.split("");
let digit = 0, d = 0, x = 0,s = 0, arr2 = [],cs = [0,0,0,0];
for(let c = 0; c < arr.length; c++) {
if(/\d/.test(arr[c])) {
digit++;
arr2[c] = 1; // 数字
cs[0] = 1;
} else if(/[A-Z]/.test(arr[c])) {
d++;
arr2[c] = 2; // 大写
cs[1] = 1;
} else if(/[a-z]/.test(arr[c])) {
x++;
arr2[c] = 3; // 小写
cs[2] = 1;
} else {
s++;
arr2[c] = 4; // 特殊字符
cs[3] = 1;
}
}
// 包含一位数字 +5分 三位以上数字 + 20分
if(digit === 1) {
score += 5;
} else if(digit > 2) {
score += 15;
}
// 大小写字母混合 +15分
if(d > 0 && x > 0) {
score += 15;
} else if(d === arr.length || x === arr.length) {
// 全大写或全小写 + 5分
score += 5;
}
// 含有特殊字符 +20分
if(s > 0) {
score += 20;
}
// 计算组合
var cl = 0;
for(let k = 0; k < 4; k++) {
cl += cs[k];
}
switch (cl) {
case 2:
score += 5;
break;
case 3:
score += 15;
break;
case 4:
score += 25;
break;
}
// 扣分项目 发现连续类型相同 扣1分
for(let i = 0; i < arr2.length; i++) {
if(i !== arr2.length - 1 && i < 8) {
// eslint-disable-next-line no-mixed-spaces-and-tabs
if(arr2[i] === arr2[i+1]) {
score--;
}
}
}
return score;
}
版权声明:本文为qq_43415562原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。