PasswordStrengthContent.vue 4.22 KB
<!-- PasswordStrengthContent.vue -->
<template>
  <div class="password-strength-content">
    <div class="strength-label">密码需满足以下要求:</div>

    <div class="strength-bar">
      <div
        class="strength-fill"
        :class="levelClass"
        :style="{ width: fillWidth }"
      ></div>
    </div>
    <div class="strength-text" :class="levelClass">
      密码强度:{{ strengthText }}
    </div>

    <div class="requirements">
      <div v-for="(req, key) in requirements" :key="key" class="requirement" :class="getReqClass(key)">
        <span class="requirement-icon">{{ getReqIcon(key) }}</span>
        <span>{{ req.label }}</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { useStorage } from '@/hooks/useStorage'


const props = defineProps({ 
  password: {
    type: String,
    default: ''
  },
  storeKey: {
    type: String,
    default: 'firstUnmetRequirement'
  }
});

const emits = defineEmits(['getCheckResult']);


const { setStorage } = useStorage()
const checkPasswordStrength = (pwd) => {
  if (!pwd) return { score: 0, level: 'weak', checks: {} };
  const checks = {
    length: pwd.length >= 8,
    lower: /[a-z]/.test(pwd),
    upper: /[A-Z]/.test(pwd),
    number: /[0-9]/.test(pwd),
    special: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(pwd)
  };
  const score = Object.values(checks).filter(Boolean).length;
  let level = 'weak';
  if (score >= 5) level = 'strong';
  else if (score >= 3) level = 'medium';
  console.log(checks,'checkPasswordStrength');
  findFirstUnmetRequirement(checks)
  return { score, level, checks };
};

const result = computed(() => checkPasswordStrength(props.password));
const fillWidth = computed(() => `${(result.value.score / 5) * 100}%`);
const strengthText = computed(() => ({ weak: '弱', medium: '中', strong: '强' })[result.value.level]);
const levelClass = computed(() => result.value.level);

const requirements = {
  number: { label: '包含数字 (0-9)' },
  length: { label: '至少 8 个字符' },
  lower: { label: '包含小写字母 (a-z)' },
  upper: { label: '包含大写字母 (A-Z)' },
  special: { label: '包含特殊符号' }
};

const getReqClass = (key) => (result.value.checks[key] ? 'met' : props.password.length >= 3 ? 'unmet' : 'pending');
const getReqIcon = (key) => {
  if (result.value.checks[key]) return '✓';
  if (props.password.length >= 3) return '✗';
  return '○';
};

/**
 * 匹配未满足的规则
 * @param checks 
 */
const findFirstUnmetRequirement = (checks) => {
  if (!props.password) return null;
  console.log(checks,'firstUnmetRequirement');
  for (const key in requirements) {
    const isMet = checks[key];
    if (!isMet) {
      let label = requirements[key].label;
      setStorage(props.storeKey, label)
      return label; // 返回未满足的第一条规则的 label
    }
  }
  setStorage(props.storeKey, '')
  return null; // 全部满足
};


// 暴露方法给父组件
defineExpose({
});
</script>

<style scoped lang="scss">
/* 同前,省略样式 */
.password-strength-content {
  font-size: 14px;
}

.strength-label {
  margin-bottom: 12px;
  color: #333;
  font-weight: 500;
}

.strength-bar {
  height: 6px;
  border-radius: 3px;
  background: #e9ecef;
  overflow: hidden;
  margin: 8px 0;
}

.strength-fill {
  height: 100%;
  border-radius: 3px;
  transition: all 0.3s ease-out;
}

.strength-fill.weak {
  background-color: #dc3545;
}

.strength-fill.medium {
  background-color: #ffc107;
}

.strength-fill.strong {
  background-color: #28a745;
}

.strength-text {
  font-weight: 600;
  font-size: 13px;
  margin-bottom: 12px;
}

.strength-text.weak {
  color: #dc3545;
}

.strength-text.medium {
  color: #e0a800;
}

.strength-text.strong {
  color: #28a745;
}

.requirements {
  color: #555;
}

.requirement {
  display: flex;
  align-items: center;
  margin: 4px 0;
}

.requirement-icon {
  width: 16px;
  text-align: center;
  margin-right: 6px;
  font-weight: bold;
}

.requirement.met .requirement-icon {
  color: #28a745;
}

.requirement.unmet .requirement-icon {
  color: #dc3545;
}

.requirement.pending .requirement-icon {
  color: #adb5bd;
}

.requirement.met {
  color: #28a745;
}

.requirement.unmet {
  color: #dc3545;
}

.requirement.pending {
  color: #6c757d;
}
</style>