1
0
Snowz 9218f57271 chore: 添加初始项目文件和依赖项
初始化项目,添加 favicon.ico、screenshot.png 等静态资源文件,以及 Vue、TailwindCSS 等依赖项。配置了 Vite 和 PostCSS,并生成了基本的项目结构。
2025-04-14 18:19:10 +08:00

320 lines
12 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 py-6 px-4 sm:px-6 lg:px-8">
<div class="max-w-md mx-auto">
<div class="text-center mb-4">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">密码生成器</h1>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
安全现代易用的密码生成工具
</p>
</div>
<div class="bg-white dark:bg-gray-800 shadow-lg rounded-lg p-5 space-y-5">
<!-- 说明部分 -->
<div class="bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-700 rounded-lg p-3">
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">使用说明</h3>
<ul class="mt-1 text-sm text-yellow-700 dark:text-yellow-300 list-disc pl-5 space-y-0.5">
<li>密码计算全部在本地进行确保安全</li>
<li>建议使用复杂的记忆密码</li>
<li>区分代码建议使用网站特征qq, github</li>
</ul>
</div>
<div class="space-y-4">
<!-- 记忆密码 -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
记忆密码
</label>
<div class="relative rounded-md shadow-sm">
<input
v-model="memoryPassword"
:type="showPassword ? 'text' : 'password'"
class="input pr-20"
placeholder="输入你的记忆密码"
/>
<button
@click="togglePasswordVisibility"
class="absolute right-0 top-0 h-full px-3 border-l border-gray-300 dark:border-gray-600
text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200
flex items-center justify-center transition-colors duration-200"
>
{{ showPassword ? '隐藏' : '显示' }}
</button>
</div>
</div>
<!-- 区分代码 -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
区分代码
</label>
<div class="relative rounded-md shadow-sm">
<input
v-model="distinguishCode"
type="text"
class="input"
placeholder="输入区分代码qq"
/>
</div>
</div>
<!-- 密码选项 -->
<div class="space-y-3">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
密码长度
</label>
<select v-model="passwordLength" class="input py-1.5">
<option v-for="length in [10,11,12,13,14,15,16,17,18,19,20]" :key="length" :value="length">
{{ length }} 位
</option>
</select>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" v-model="usePunctuation" class="w-4 h-4 rounded text-primary-600 focus:ring-primary-500" />
<span class="text-sm text-gray-700 dark:text-gray-300">使用标点</span>
</label>
</div>
<div>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" v-model="useUpperCase" class="w-4 h-4 rounded text-primary-600 focus:ring-primary-500" />
<span class="text-sm text-gray-700 dark:text-gray-300">大写字母</span>
</label>
</div>
<div>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" v-model="useNumbers" class="w-4 h-4 rounded text-primary-600 focus:ring-primary-500" />
<span class="text-sm text-gray-700 dark:text-gray-300">使用数字</span>
</label>
</div>
<div>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" v-model="useSpecialChars" class="w-4 h-4 rounded text-primary-600 focus:ring-primary-500" />
<span class="text-sm text-gray-700 dark:text-gray-300">特殊字符</span>
</label>
</div>
</div>
</div>
<!-- 生成的密码 -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
生成的密码
</label>
<div class="flex space-x-2">
<div class="relative flex-1">
<input
:value="generatedPassword"
type="text"
class="input py-1.5 font-mono"
readonly
/>
</div>
<button
@click="copyPassword"
class="btn btn-primary py-1.5"
>
复制
</button>
</div>
</div>
<!-- 密码强度指示器 -->
<div v-if="generatedPassword" class="space-y-1">
<div class="flex justify-between text-sm">
<span class="text-gray-600 dark:text-gray-400">密码强度</span>
<span :class="passwordStrengthClass">{{ passwordStrengthText }}</span>
</div>
<div class="h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div
:class="passwordStrengthBarClass"
:style="{ width: passwordStrengthPercentage + '%' }"
class="h-full transition-all duration-300"
></div>
</div>
</div>
<!-- 生成按钮 -->
<button
@click="generatePassword"
class="w-full btn btn-primary py-2 text-base font-semibold"
:disabled="isGenerating"
>
{{ isGenerating ? '生成中...' : '生成密码' }}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
name: 'App',
setup() {
const memoryPassword = ref('')
const distinguishCode = ref('')
const passwordLength = ref(16)
const usePunctuation = ref(true)
const useUpperCase = ref(true)
const useNumbers = ref(true)
const useSpecialChars = ref(true)
const showPassword = ref(false)
const generatedPassword = ref('')
const isGenerating = ref(false)
const togglePasswordVisibility = () => {
showPassword.value = !showPassword.value
}
// 生成随机字节数组
const getRandomBytes = (length) => {
const array = new Uint8Array(length)
window.crypto.getRandomValues(array)
return array
}
const generatePassword = async () => {
if (!memoryPassword.value || !distinguishCode.value) {
alert('请填写记忆密码和区分代码')
return
}
isGenerating.value = true
try {
// 使用 Web Crypto API 进行更安全的密码生成
const encoder = new TextEncoder()
const baseData = encoder.encode(memoryPassword.value + distinguishCode.value)
// 添加随机因子,确保每次生成的密码都不同
const randomSalt = getRandomBytes(16)
const combinedData = new Uint8Array(baseData.length + randomSalt.length)
combinedData.set(baseData)
combinedData.set(randomSalt, baseData.length)
const hashBuffer = await window.crypto.subtle.digest('SHA-512', combinedData)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
// 根据选项生成密码
let password = ''
const chars = {
lowercase: 'abcdefghijklmnopqrstuvwxyz',
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
numbers: '0123456789',
special: '!@#$%^&*()_+-=[]{}|;:,.<>?'
}
let availableChars = chars.lowercase
if (useUpperCase.value) availableChars += chars.uppercase
if (useNumbers.value) availableChars += chars.numbers
if (useSpecialChars.value) availableChars += chars.special
// 使用更多的熵来生成密码
const randomValues = getRandomBytes(passwordLength.value * 2)
for (let i = 0; i < passwordLength.value; i++) {
const combinedEntropy = (hashArray[i] + randomValues[i]) % availableChars.length
password += availableChars[combinedEntropy]
}
generatedPassword.value = password
} catch (error) {
console.error('密码生成失败:', error)
alert('密码生成失败,请重试')
} finally {
isGenerating.value = false
}
}
const copyPassword = async () => {
if (!generatedPassword.value) return
try {
await navigator.clipboard.writeText(generatedPassword.value)
alert('密码已复制到剪贴板')
} catch (err) {
console.error('复制失败:', err)
// 如果 clipboard API 失败,使用传统方法
const input = document.createElement('textarea')
input.value = generatedPassword.value
document.body.appendChild(input)
input.select()
document.execCommand('copy')
document.body.removeChild(input)
alert('密码已复制到剪贴板')
}
}
const passwordStrength = computed(() => {
if (!generatedPassword.value) return 0
let strength = 0
const password = generatedPassword.value
// 长度得分
strength += Math.min(password.length * 4, 40)
// 字符类型得分
if (password.match(/[A-Z]/)) strength += 10
if (password.match(/[a-z]/)) strength += 10
if (password.match(/[0-9]/)) strength += 10
if (password.match(/[^A-Za-z0-9]/)) strength += 10
// 重复字符扣分
const uniqueChars = new Set(password).size
strength -= (password.length - uniqueChars) * 2
return Math.max(0, Math.min(100, strength))
})
const passwordStrengthPercentage = computed(() => passwordStrength.value)
const passwordStrengthText = computed(() => {
if (passwordStrength.value < 40) return '弱'
if (passwordStrength.value < 60) return '中等'
if (passwordStrength.value < 80) return '强'
return '极强'
})
const passwordStrengthClass = computed(() => {
if (passwordStrength.value < 40) return 'text-red-600 dark:text-red-400'
if (passwordStrength.value < 60) return 'text-yellow-600 dark:text-yellow-400'
if (passwordStrength.value < 80) return 'text-blue-600 dark:text-blue-400'
return 'text-green-600 dark:text-green-400'
})
const passwordStrengthBarClass = computed(() => {
if (passwordStrength.value < 40) return 'bg-red-500'
if (passwordStrength.value < 60) return 'bg-yellow-500'
if (passwordStrength.value < 80) return 'bg-blue-500'
return 'bg-green-500'
})
return {
memoryPassword,
distinguishCode,
passwordLength,
usePunctuation,
useUpperCase,
useNumbers,
useSpecialChars,
showPassword,
generatedPassword,
isGenerating,
togglePasswordVisibility,
generatePassword,
copyPassword,
passwordStrengthPercentage,
passwordStrengthText,
passwordStrengthClass,
passwordStrengthBarClass
}
}
}
</script>