418 lines
13 KiB
Vue
418 lines
13 KiB
Vue
<template>
|
||
<div class="space-y-6">
|
||
<!-- 算法选择 -->
|
||
<div class="card p-4">
|
||
<h3 class="text-md font-medium text-primary mb-3">选择加密算法</h3>
|
||
<div class="grid grid-cols-2 md:grid-cols-6 gap-2">
|
||
<button
|
||
v-for="algorithm in algorithms"
|
||
:key="algorithm.type"
|
||
:class="[
|
||
'px-3 py-2 text-sm font-medium rounded transition-all',
|
||
activeAlgorithm === algorithm.type
|
||
? 'bg-primary text-white shadow-sm'
|
||
: 'bg-block text-secondary border hover:bg-hover'
|
||
]"
|
||
@click="setActiveAlgorithm(algorithm.type)"
|
||
>
|
||
{{ algorithm.name }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 操作模式选择(仅对支持编码/解码的算法显示) -->
|
||
<div v-if="currentAlgorithm?.isEncodeDecode" class="card p-4">
|
||
<h3 class="text-md font-medium text-primary mb-3">操作模式</h3>
|
||
<div class="flex gap-2">
|
||
<button
|
||
:class="[
|
||
'flex-1 px-4 py-2 text-sm font-medium rounded transition-all',
|
||
!isDecoding ? 'bg-primary text-white' : 'bg-block text-secondary border'
|
||
]"
|
||
@click="isDecoding = false"
|
||
>
|
||
{{ currentAlgorithm?.type === 'aes' ? '加密' : '编码' }}
|
||
</button>
|
||
<button
|
||
:class="[
|
||
'flex-1 px-4 py-2 text-sm font-medium rounded transition-all',
|
||
isDecoding ? 'bg-primary text-white' : 'bg-block text-secondary border'
|
||
]"
|
||
@click="isDecoding = true"
|
||
>
|
||
{{ currentAlgorithm?.type === 'aes' ? '解密' : '解码' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 输入和处理区域 -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<!-- 输入区域 -->
|
||
<div class="card p-4">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<h3 class="text-md font-medium text-primary">输入内容</h3>
|
||
<div class="flex gap-2">
|
||
<button @click="loadExample" class="btn-secondary text-sm">
|
||
<FontAwesomeIcon :icon="['fas', 'redo']" class="mr-1" />
|
||
示例
|
||
</button>
|
||
<button @click="clearAll" class="btn-secondary text-sm">
|
||
<FontAwesomeIcon :icon="['fas', 'eraser']" class="mr-1" />
|
||
清空
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-4">
|
||
<!-- 密钥输入(仅对需要密钥的算法显示) -->
|
||
<div v-if="currentAlgorithm?.needsKey">
|
||
<label class="block text-sm text-secondary font-medium mb-2">密钥</label>
|
||
<input
|
||
v-model="secretKey"
|
||
type="text"
|
||
class="input-field w-full"
|
||
placeholder="请输入加密密钥..."
|
||
/>
|
||
</div>
|
||
|
||
<!-- 文本输入 -->
|
||
<div>
|
||
<label class="block text-sm text-secondary font-medium mb-2">
|
||
{{ isDecoding && currentAlgorithm?.isEncodeDecode ? '待解密/解码内容' : '待加密/编码内容' }}
|
||
</label>
|
||
<textarea
|
||
v-model="inputText"
|
||
class="textarea-field h-36 w-full font-mono resize-y"
|
||
:placeholder="getInputPlaceholder()"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 处理按钮 -->
|
||
<button
|
||
@click="processOperation"
|
||
class="btn-primary w-full flex items-center justify-center gap-2"
|
||
:disabled="!inputText.trim() || (currentAlgorithm?.needsKey && !secretKey.trim())"
|
||
>
|
||
<FontAwesomeIcon :icon="['fas', 'lock']" />
|
||
{{ getProcessButtonText() }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 输出区域 -->
|
||
<div class="card p-4">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<h3 class="text-md font-medium text-primary">输出结果</h3>
|
||
<button
|
||
v-if="output"
|
||
@click="copyToClipboard"
|
||
class="btn-secondary text-sm"
|
||
:disabled="!output"
|
||
>
|
||
<FontAwesomeIcon :icon="copied ? ['fas', 'check'] : ['fas', 'copy']" class="mr-1" />
|
||
{{ copied ? '已复制' : '复制' }}
|
||
</button>
|
||
</div>
|
||
|
||
<div class="space-y-4">
|
||
<!-- 结果显示 -->
|
||
<div>
|
||
<label class="block text-sm text-secondary font-medium mb-2">
|
||
{{ isDecoding && currentAlgorithm?.isEncodeDecode ? '解密/解码结果' : '加密/编码结果' }}
|
||
</label>
|
||
<textarea
|
||
v-model="output"
|
||
readonly
|
||
class="textarea-field h-36 w-full font-mono resize-y bg-block"
|
||
placeholder="结果将在这里显示..."
|
||
/>
|
||
</div>
|
||
|
||
<!-- 结果信息 -->
|
||
<div v-if="output" class="text-sm text-tertiary">
|
||
<div>字符长度: {{ output.length }}</div>
|
||
<div v-if="currentAlgorithm?.type !== 'aes' && !isDecoding">
|
||
哈希值: {{ currentAlgorithm?.name }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 错误和成功消息 -->
|
||
<div v-if="error" class="p-3 bg-red-900/20 border border-red-700/30 rounded-lg text-error">
|
||
<FontAwesomeIcon :icon="['fas', 'exclamation-triangle']" class="mr-2" />
|
||
{{ error }}
|
||
</div>
|
||
|
||
<div v-if="success" class="p-3 bg-green-900/20 border border-green-700/30 rounded-lg text-green-400">
|
||
<FontAwesomeIcon :icon="['fas', 'check']" class="mr-2" />
|
||
{{ success }}
|
||
</div>
|
||
|
||
<!-- 算法说明 -->
|
||
<div class="card p-4">
|
||
<h3 class="text-md font-medium text-primary mb-3">
|
||
<FontAwesomeIcon :icon="['fas', 'info-circle']" class="mr-2" />
|
||
算法说明
|
||
</h3>
|
||
<div class="text-sm text-secondary">
|
||
{{ getAlgorithmDescription() }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, watch } from 'vue'
|
||
import CryptoJS from 'crypto-js'
|
||
|
||
// 加密算法类型
|
||
type CryptoType = 'md5' | 'sha1' | 'sha256' | 'sha512' | 'aes' | 'base64'
|
||
|
||
// 算法配置
|
||
interface AlgorithmConfig {
|
||
type: CryptoType
|
||
name: string
|
||
needsKey: boolean
|
||
isEncodeDecode: boolean
|
||
description: string
|
||
}
|
||
|
||
// 响应式状态
|
||
const activeAlgorithm = ref<CryptoType>('md5')
|
||
const inputText = ref('')
|
||
const secretKey = ref('')
|
||
const output = ref('')
|
||
const isDecoding = ref(false)
|
||
const copied = ref(false)
|
||
const error = ref<string | null>(null)
|
||
const success = ref<string | null>(null)
|
||
|
||
// 算法配置
|
||
const algorithms: AlgorithmConfig[] = [
|
||
{
|
||
type: 'md5',
|
||
name: 'MD5',
|
||
needsKey: false,
|
||
isEncodeDecode: false,
|
||
description: 'MD5是一种广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值。常用于文件校验和密码存储。'
|
||
},
|
||
{
|
||
type: 'sha1',
|
||
name: 'SHA1',
|
||
needsKey: false,
|
||
isEncodeDecode: false,
|
||
description: 'SHA-1是一种密码散列函数,可以产生一个160位(20字节)的散列值。比MD5更安全,但现在也被认为不够安全。'
|
||
},
|
||
{
|
||
type: 'sha256',
|
||
name: 'SHA256',
|
||
needsKey: false,
|
||
isEncodeDecode: false,
|
||
description: 'SHA-256是SHA-2家族的一种,可以产生一个256位(32字节)的散列值。目前被认为是安全的哈希算法。'
|
||
},
|
||
{
|
||
type: 'sha512',
|
||
name: 'SHA512',
|
||
needsKey: false,
|
||
isEncodeDecode: false,
|
||
description: 'SHA-512是SHA-2家族的一种,可以产生一个512位(64字节)的散列值。比SHA-256更安全,计算量也更大。'
|
||
},
|
||
{
|
||
type: 'aes',
|
||
name: 'AES',
|
||
needsKey: true,
|
||
isEncodeDecode: true,
|
||
description: 'AES(高级加密标准)是一种对称加密算法,需要相同的密钥进行加密和解密。广泛用于数据保护。'
|
||
},
|
||
{
|
||
type: 'base64',
|
||
name: 'Base64',
|
||
needsKey: false,
|
||
isEncodeDecode: true,
|
||
description: 'Base64是一种编码方式,常用于在文本环境中传输二进制数据。不是加密算法,只是编码转换。'
|
||
}
|
||
]
|
||
|
||
// 当前算法配置
|
||
const currentAlgorithm = computed(() =>
|
||
algorithms.find(algo => algo.type === activeAlgorithm.value)
|
||
)
|
||
|
||
// 获取输入提示文本
|
||
const getInputPlaceholder = (): string => {
|
||
if (isDecoding.value && currentAlgorithm.value?.isEncodeDecode) {
|
||
return currentAlgorithm.value.type === 'aes'
|
||
? '请输入要解密的密文...'
|
||
: '请输入要解码的内容...'
|
||
}
|
||
return '请输入要处理的文本内容...'
|
||
}
|
||
|
||
// 获取处理按钮文本
|
||
const getProcessButtonText = (): string => {
|
||
if (!currentAlgorithm.value) return '处理'
|
||
|
||
if (currentAlgorithm.value.isEncodeDecode) {
|
||
return isDecoding.value
|
||
? (currentAlgorithm.value.type === 'aes' ? '解密' : '解码')
|
||
: (currentAlgorithm.value.type === 'aes' ? '加密' : '编码')
|
||
}
|
||
|
||
return '生成哈希'
|
||
}
|
||
|
||
// 获取算法描述
|
||
const getAlgorithmDescription = (): string => {
|
||
return currentAlgorithm.value?.description || ''
|
||
}
|
||
|
||
// 设置活动算法
|
||
const setActiveAlgorithm = (type: CryptoType) => {
|
||
activeAlgorithm.value = type
|
||
output.value = ''
|
||
error.value = null
|
||
success.value = null
|
||
|
||
// 如果不支持编码/解码,重置解码状态
|
||
if (!currentAlgorithm.value?.isEncodeDecode) {
|
||
isDecoding.value = false
|
||
}
|
||
}
|
||
|
||
// 处理操作
|
||
const processOperation = () => {
|
||
error.value = null
|
||
success.value = null
|
||
output.value = ''
|
||
|
||
if (!inputText.value.trim()) {
|
||
error.value = '请输入要处理的内容'
|
||
return
|
||
}
|
||
|
||
if (currentAlgorithm.value?.needsKey && !secretKey.value.trim()) {
|
||
error.value = '请输入密钥'
|
||
return
|
||
}
|
||
|
||
try {
|
||
let result = ''
|
||
|
||
switch (activeAlgorithm.value) {
|
||
case 'md5':
|
||
result = CryptoJS.MD5(inputText.value).toString()
|
||
break
|
||
|
||
case 'sha1':
|
||
result = CryptoJS.SHA1(inputText.value).toString()
|
||
break
|
||
|
||
case 'sha256':
|
||
result = CryptoJS.SHA256(inputText.value).toString()
|
||
break
|
||
|
||
case 'sha512':
|
||
result = CryptoJS.SHA512(inputText.value).toString()
|
||
break
|
||
|
||
case 'aes':
|
||
if (isDecoding.value) {
|
||
// 解密操作
|
||
try {
|
||
const decrypted = CryptoJS.AES.decrypt(inputText.value, secretKey.value)
|
||
result = decrypted.toString(CryptoJS.enc.Utf8)
|
||
|
||
if (!result) {
|
||
throw new Error('解密失败,请检查密文和密钥是否正确')
|
||
}
|
||
} catch {
|
||
throw new Error('解密失败,请检查密文和密钥是否正确')
|
||
}
|
||
} else {
|
||
// 加密操作
|
||
result = CryptoJS.AES.encrypt(inputText.value, secretKey.value).toString()
|
||
}
|
||
break
|
||
|
||
case 'base64':
|
||
if (isDecoding.value) {
|
||
// Base64解码
|
||
try {
|
||
result = CryptoJS.enc.Base64.parse(inputText.value).toString(CryptoJS.enc.Utf8)
|
||
} catch {
|
||
throw new Error('Base64解码失败,请检查输入内容格式')
|
||
}
|
||
} else {
|
||
// Base64编码
|
||
result = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(inputText.value))
|
||
}
|
||
break
|
||
}
|
||
|
||
output.value = result
|
||
success.value = isDecoding.value ? '解密/解码成功' : '加密/编码成功'
|
||
} catch (err) {
|
||
console.error('处理错误:', err)
|
||
error.value = `处理失败: ${err instanceof Error ? err.message : '未知错误'}`
|
||
}
|
||
}
|
||
|
||
// 复制到剪贴板
|
||
const copyToClipboard = async () => {
|
||
if (!output.value) return
|
||
|
||
try {
|
||
await navigator.clipboard.writeText(output.value)
|
||
copied.value = true
|
||
setTimeout(() => {
|
||
copied.value = false
|
||
}, 2000)
|
||
} catch (err) {
|
||
console.error('复制失败:', err)
|
||
error.value = '复制失败'
|
||
}
|
||
}
|
||
|
||
// 清空所有内容
|
||
const clearAll = () => {
|
||
inputText.value = ''
|
||
secretKey.value = ''
|
||
output.value = ''
|
||
error.value = null
|
||
success.value = null
|
||
}
|
||
|
||
// 加载示例
|
||
const loadExample = () => {
|
||
const examples: Record<CryptoType, { input: string; key?: string }> = {
|
||
md5: { input: 'Hello, World!' },
|
||
sha1: { input: 'Hello, World!' },
|
||
sha256: { input: 'Hello, World!' },
|
||
sha512: { input: 'Hello, World!' },
|
||
aes: { input: 'Hello, World!', key: 'secret-key-12345' },
|
||
base64: { input: 'Hello, World!' }
|
||
}
|
||
|
||
const example = examples[activeAlgorithm.value]
|
||
inputText.value = example.input
|
||
if (example.key) {
|
||
secretKey.value = example.key
|
||
}
|
||
|
||
output.value = ''
|
||
error.value = null
|
||
success.value = null
|
||
}
|
||
|
||
// 清除状态提示的定时器
|
||
watch([error, success], () => {
|
||
if (error.value || success.value) {
|
||
setTimeout(() => {
|
||
error.value = null
|
||
success.value = null
|
||
}, 3000)
|
||
}
|
||
})
|
||
</script> |