Files
flaskProject/flask-ui/src/views/ByteToHexView.vue
T
YanLongChangAn cb67026821 feat(router): 添加字节对象转Hex功能页面
- 新增 ByteToHexView.vue 页面组件,实现JSON对象到十六进制字符串转换
- 在路由配置中注册 byte-to-hex 路径
- 在侧边栏菜单中添加字节对象转Hex导航项
- 实现JSON对象解析和十六进制转换核心逻辑
- 添加示例数据和自动转换功能
- 集成复制结果到剪贴板功能
2026-08-15 09:31:09 +08:00

403 lines
8.6 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>
<div class="container">
<div class="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>
</div>
<div class="footer">
按数字键升序取值每个字节补零为两位十六进制
</div>
</div>
</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;
* {
box-sizing: border-box;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
}
// 根容器:占满视口,flex列
.container {
min-height: calc(100vh - 211px);
width: 100%;
display: flex;
flex-direction: column;
background: $bg;
padding: 20px;
}
// 卡片区域:flex:1 撑满剩余空间,居中显示
.card {
background: $card-bg;
//max-width: 720px;
width: 100%;
border-radius: $radius;
//box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
padding: 28px 32px;
margin: 0 auto;
flex: 1; // 让卡片占据尽可能多的空间,但最大宽度限制
align-self: center; // 水平居中
width: 100%;
}
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;
border: 1px solid $border;
border-radius: 10px;
font-family: 'Menlo', 'Cascadia Code', 'Consolas', monospace;
font-size: 13px;
line-height: 1.6;
resize: vertical;
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;
}
// 底部:置于页面最底部,自动与卡片分离
.footer {
margin-top: auto; // 自动推到底部
font-size: 13px;
color: #9ca3af;
text-align: center;
padding: 16px 0 8px;
border-top: 1px solid #e5e7eb;
max-width: 720px;
width: 100%;
align-self: center;
}
</style>