Files
flaskProject/flask-ui/src/views/ByteToHexView.vue
T
YanLongChangAn 4c4de7a5cb refactor(ByteToHexView): 使用 Element Plus 组件重构界面布局
- 将原始 div 结构替换为 el-main 和 el-card 组件
- 移除自定义容器样式,采用 Element Plus 默认样式
- 调整文本域边框样式从 border 改为 outline
- 禁用文本域调整大小功能
- 优化按钮状态样式过渡效果
- 移除页面底部说明区域
- 更新侧边栏菜单项路由配置为实际路径
2026-08-16 16:15:38 +08:00

381 lines
7.9 KiB
Vue
Raw 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>
<el-main>
<el-card>
<h1>🔑 字节对象 Hex 密钥</h1>
<p class="sub">
将键为数字索引的 JSON 对象转换为 32 位十六进制字符串适用于 AES128 密钥
</p>
<label for="jsonInput">输入 JSON 对象</label>
<textarea
id="jsonInput"
v-model="inputText"
placeholder='例如:{"0":14,"1":3,"2":121,...}'
@keydown.ctrl.enter="performConversion"
@keydown.meta.enter="performConversion"
></textarea>
<div class="actions">
<button @click="performConversion">🔄 转换</button>
<button class="secondary" :disabled="!copyEnabled" @click="copyResult">
📋 复制 Hex
</button>
<span class="copy-feedback" :class="{ show: copyFeedbackVisible }"
>已复制</span
>
</div>
<div class="result-area">
<div class="result-label">
<span>转换结果</span>
</div>
<div
class="result-box"
:class="resultClass"
v-text="resultText"
></div>
</div>
<div class="example-hint">
💡 点击
<button class="link-btn" @click="loadExample">加载示例</button>
快速体验
</div>
</el-card>
</el-main>
</template>
<script setup lang="ts">
import {ref, onMounted} from 'vue'
// 示例数据
const EXAMPLE_OBJ: Record<string, number> = {
'0': 14,
'1': 3,
'2': 121,
'3': 117,
'4': 7,
'5': 245,
'6': 22,
'7': 34,
'8': 136,
'9': 163,
'10': 201,
'11': 114,
'12': 195,
'13': 230,
'14': 109,
'15': 204,
}
// 响应式数据
const inputText = ref<string>('')
const resultText = ref<string>('等待转换…')
const resultClass = ref<string>('') // 用于 success / error
const copyFeedbackVisible = ref<boolean>(false)
const copyEnabled = ref<boolean>(false)
// 核心转换函数
function convertToHex(obj: Record<string, unknown>): string {
// 获取所有键并转为数字排序
const keys = Object.keys(obj)
.map((k) => Number(k))
.sort((a, b) => a - b)
// 检查是否有非数字键
if (keys.some((k) => isNaN(k))) {
throw new Error('对象包含非数字键,请确保键为 "0", "1", … 格式')
}
// 检查长度是否为16
if (keys.length !== 16) {
throw new Error(`期望 16 个字节,实际获得 ${keys.length} 个`)
}
// 按顺序取值并转为两位十六进制
const hex = keys
.map((key) => {
const val = obj[key]
if (typeof val !== 'number' || isNaN(val)) {
throw new Error(`键 "${key}" 对应的值不是有效数字 (${val})`)
}
if (val < 0 || val > 255) {
throw new Error(`键 "${key}" 的值 ${val} 超出字节范围 (0-255)`)
}
return val.toString(16).padStart(2, '0')
})
.join('')
return hex
}
// 执行转换
function performConversion(): void {
const raw = inputText.value.trim()
if (!raw) {
resultText.value = '⚠️ 请输入 JSON 数据'
resultClass.value = 'error'
copyEnabled.value = false
copyFeedbackVisible.value = false
return
}
try {
const obj = JSON.parse(raw)
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
throw new Error('输入必须是 JSON 对象(不是数组或原始类型)')
}
const hex = convertToHex(obj)
resultText.value = hex
resultClass.value = 'success'
copyEnabled.value = true
copyFeedbackVisible.value = false
} catch (err: any) {
resultText.value = `❌ 转换失败:${err.message}`
resultClass.value = 'error'
copyEnabled.value = false
copyFeedbackVisible.value = false
}
}
// 加载示例
function loadExample(): void {
inputText.value = JSON.stringify(EXAMPLE_OBJ, null, 2)
performConversion() // 自动转换
}
// 复制结果
function copyResult(): void {
const text = resultText.value
if (!text || resultClass.value === 'error') return
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard
.writeText(text)
.then(() => {
copyFeedbackVisible.value = true
setTimeout(() => (copyFeedbackVisible.value = false), 2000)
})
.catch(() => {
fallbackCopy(text)
})
} else {
fallbackCopy(text)
}
}
function fallbackCopy(text: string): void {
const textarea = document.createElement('textarea')
textarea.value = text
document.body.appendChild(textarea)
textarea.select()
try {
document.execCommand('copy')
copyFeedbackVisible.value = true
setTimeout(() => (copyFeedbackVisible.value = false), 2000)
} catch (_) {
alert('复制失败,请手动复制')
}
document.body.removeChild(textarea)
}
// 组件挂载时加载示例
onMounted(() => {
loadExample()
})
</script>
<style lang="scss" scoped>
// 变量(可根据需要调整)
$primary: #3b82f6;
$primary-hover: #2563eb;
$bg: #f6f8fa;
$card-bg: #ffffff;
$text-dark: #1f2937;
$text-gray: #6b7280;
$border: #d1d5db;
$radius: 16px;
:deep(.el-card__body) {
//height: calc(100vh - 211px + 60px);
height: calc(100vh - 141px);
}
* {
box-sizing: border-box;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
}
h1 {
font-size: 22px;
font-weight: 600;
margin-top: 0;
margin-bottom: 8px;
color: $text-dark;
}
.sub {
color: $text-gray;
font-size: 14px;
margin-top: -4px;
margin-bottom: 20px;
}
label {
font-weight: 500;
font-size: 14px;
color: #374151;
display: block;
margin-bottom: 6px;
}
textarea {
width: 100%;
height: 200px;
padding: 12px 14px;
outline: 1px solid $border;
border: none;
border-radius: 10px;
font-family: 'Menlo', 'Cascadia Code', 'Consolas', monospace;
font-size: 13px;
line-height: 1.6;
resize: none;
transition: border 0.2s, background 0.2s;
background: #fafbfc;
&:focus {
border-color: $primary;
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
background: white;
}
}
.actions {
display: flex;
gap: 12px;
margin: 18px 0 14px;
flex-wrap: wrap;
align-items: center;
}
button {
background: $primary;
color: white;
border: none;
padding: 10px 24px;
border-radius: 8px;
font-weight: 500;
font-size: 14px;
cursor: pointer;
transition: background 0.15s, transform 0.1s;
display: inline-flex;
align-items: center;
gap: 6px;
&:hover {
background: $primary-hover;
}
&:active {
transform: scale(0.96);
}
&.secondary {
background: #e5e7eb;
color: $text-dark;
&:hover {
background: #d1d5db;
}
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
&:hover {
background: #e5e7eb;
}
}
&.link-btn {
background: none;
border: none;
color: $primary;
font-weight: 500;
padding: 0;
font-size: 13px;
text-decoration: underline;
display: inline;
&:hover {
color: $primary-hover;
background: none;
transform: none;
}
}
}
.copy-feedback {
font-size: 13px;
color: #10b981;
margin-left: 12px;
opacity: 0;
transition: opacity 0.3s;
&.show {
opacity: 1;
}
}
.result-area {
margin-top: 8px;
}
.result-label {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
span {
font-weight: 500;
font-size: 14px;
color: #374151;
}
}
.result-box {
background: #f3f4f6;
border-radius: 10px;
padding: 14px 16px;
font-family: 'Menlo', 'Cascadia Code', 'Consolas', monospace;
font-size: 14px;
word-break: break-all;
min-height: 48px;
border: 1px solid #e5e7eb;
color: $text-dark;
transition: background 0.2s, border-color 0.2s;
&.error {
background: #fee2e2;
border-color: #fca5a5;
color: #991b1b;
}
&.success {
background: #ecfdf5;
border-color: #6ee7b7;
}
}
.example-hint {
font-size: 13px;
color: $text-gray;
margin-top: 6px;
}
</style>