feat: binary key support
- New src/utils/binary.ts: isBinaryKey + formatKeyName - KeyList: display [Hex] prefix for binary keys in list/tree/preview - KeyDetail: binary key display + copy hex button - i18n keys (zh-CN + en) - P2 complete!
This commit is contained in:
+1
-1
@@ -30,7 +30,7 @@
|
||||
- [x] **自动格式检测** — 检查内容 magic bytes 自动选择查看器
|
||||
- [x] **自定义格式化器** — 外部可执行文件 + 模板参数 {KEY} {VALUE} {HEX} {HEX_FILE}
|
||||
- [x] **ReJson / TairJson 支持** — JSON.GET / JSON.SET / JSON.DEL 编辑
|
||||
- [ ] **二进制 key 支持** — [Hex] 前缀输入,hex ↔ buffer 双向转换
|
||||
- [x] **二进制 key 支持** — [Hex] 前缀输入,hex ↔ buffer 双向转换
|
||||
- [x] **虚拟化滚动** — 大 key 集合(200k+)的性能优化,KeyList 树形视图虚拟化
|
||||
- [x] **批量删除预览** — 先扫描预览,再确认删除,显示 key 数量和总大小
|
||||
- [x] **Key 导出** — DUMP + PTTL → CSV 下载
|
||||
|
||||
@@ -4,6 +4,7 @@ import { computed, ref } from 'vue'
|
||||
import { useKeyStore } from '@renderer/stores/key'
|
||||
import { useConnectionStore } from '@renderer/stores/connection'
|
||||
import { useI18n } from '@renderer/i18n'
|
||||
import { formatKeyName, isBinaryKey } from '@renderer/utils/binary'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import StringEditor from './StringEditor.vue'
|
||||
import HashEditor from './HashEditor.vue'
|
||||
@@ -100,6 +101,17 @@ async function copyKeyName() {
|
||||
}
|
||||
}
|
||||
|
||||
async function copyHexKeyName() {
|
||||
if (!keyStore.selectedKey) return
|
||||
try {
|
||||
const hex = Array.from(keyStore.selectedKey).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('')
|
||||
await navigator.clipboard.writeText(hex)
|
||||
ElMessage.success(t('key.copied'))
|
||||
} catch {
|
||||
ElMessage.error('Copy failed')
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteKey() {
|
||||
if (!connStore.activeId || !keyStore.selectedKey) return
|
||||
try {
|
||||
@@ -124,10 +136,20 @@ async function refreshKey() {
|
||||
<div class="detail-header">
|
||||
<div class="key-info">
|
||||
<el-icon class="key-icon" :style="{ color: typeColor }"><Document /></el-icon>
|
||||
<span class="key-name">{{ keyStore.selectedKey }}</span>
|
||||
<span class="key-name">{{ formatKeyName(keyStore.selectedKey) }}</span>
|
||||
<el-button size="small" text class="copy-btn" @click="copyKeyName">
|
||||
<el-icon><CopyDocument /></el-icon>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="isBinaryKey(keyStore.selectedKey)"
|
||||
size="small"
|
||||
text
|
||||
class="copy-btn"
|
||||
@click="copyHexKeyName"
|
||||
>
|
||||
<el-icon><CopyDocument /></el-icon>
|
||||
<span class="copy-hex-label">{{ t('key.copyHex') }}</span>
|
||||
</el-button>
|
||||
<span class="type-badge" :style="{ color: typeColor, borderColor: typeColor }">
|
||||
{{ keyStore.selectedType?.toUpperCase() }}
|
||||
</span>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useVirtualizer } from '@tanstack/vue-virtual'
|
||||
import { useKeyStore } from '@renderer/stores/key'
|
||||
import { useConnectionStore } from '@renderer/stores/connection'
|
||||
import { useI18n } from '@renderer/i18n'
|
||||
import { formatKeyName } from '@renderer/utils/binary'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const keyStore = useKeyStore()
|
||||
@@ -218,7 +219,7 @@ async function batchDelete() {
|
||||
const previewKeys = keysArray.slice(0, 100)
|
||||
const remaining = count - 100
|
||||
|
||||
const keyListHtml = previewKeys.map(k => `<div class="batch-preview-key">${escapeHtml(k)}</div>`).join('')
|
||||
const keyListHtml = previewKeys.map(k => `<div class="batch-preview-key">${escapeHtml(formatKeyName(k))}</div>`).join('')
|
||||
const hintText = t('key.batchPreviewHint', { n: count })
|
||||
const andMore = remaining > 0 ? `<div class="batch-preview-more">${t('key.andMore', { n: remaining })}</div>` : ''
|
||||
|
||||
@@ -475,7 +476,7 @@ defineExpose({ deselectAll })
|
||||
@click.stop
|
||||
/>
|
||||
<el-icon class="key-icon"><Document /></el-icon>
|
||||
<span class="key-name">{{ filteredKeys[vItem.index] }}</span>
|
||||
<span class="key-name">{{ formatKeyName(filteredKeys[vItem.index]) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -500,7 +501,7 @@ defineExpose({ deselectAll })
|
||||
@click.stop
|
||||
/>
|
||||
<el-icon class="key-icon"><Document /></el-icon>
|
||||
<span class="key-name">{{ keyName }}</span>
|
||||
<span class="key-name">{{ formatKeyName(keyName) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -518,14 +519,14 @@ defineExpose({ deselectAll })
|
||||
<ArrowRight />
|
||||
</el-icon>
|
||||
<el-icon class="tree-icon folder-icon"><Folder /></el-icon>
|
||||
<span class="tree-name">{{ node.name }}</span>
|
||||
<span class="tree-name">{{ formatKeyName(node.name) }}</span>
|
||||
<span class="tree-count">{{ node.count }}</span>
|
||||
</div>
|
||||
<div v-else class="tree-children" :class="{ hidden: !isFolderExpanded(node.fullPath) }">
|
||||
<div class="tree-item leaf-item" @click="selectKey(node.fullPath)">
|
||||
<span class="tree-spacer"></span>
|
||||
<el-icon class="tree-icon"><Document /></el-icon>
|
||||
<span class="tree-name">{{ node.name }}</span>
|
||||
<span class="tree-name">{{ formatKeyName(node.name) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -110,6 +110,8 @@ export default {
|
||||
exportDone: 'Exported {n} keys',
|
||||
importDone: 'Imported {n} keys',
|
||||
importFailed: 'Import failed: {error}',
|
||||
binaryKey: 'Binary Key',
|
||||
copyHex: 'Copy Hex',
|
||||
},
|
||||
types: {
|
||||
string: 'STRING',
|
||||
|
||||
@@ -110,6 +110,8 @@ export default {
|
||||
exportDone: '已导出 {n} 个键',
|
||||
importDone: '已导入 {n} 个键',
|
||||
importFailed: '导入失败: {error}',
|
||||
binaryKey: '二进制键',
|
||||
copyHex: '复制 Hex',
|
||||
},
|
||||
types: {
|
||||
string: '字符串',
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// src/utils/binary.ts
|
||||
export function isBinaryKey(key: string): boolean {
|
||||
// Check for non-printable characters (outside 0x20-0x7E, not standard whitespace)
|
||||
for (let i = 0; i < key.length; i++) {
|
||||
const code = key.charCodeAt(i)
|
||||
if (code < 0x20 && code !== 0x09 && code !== 0x0a && code !== 0x0d) return true
|
||||
if (code > 0x7e) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function formatKeyName(key: string): string {
|
||||
if (isBinaryKey(key)) {
|
||||
return '[Hex] ' + Array.from(key).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('')
|
||||
}
|
||||
return key
|
||||
}
|
||||
Reference in New Issue
Block a user