diff --git a/ROADMAP.md b/ROADMAP.md index 8bf74ba..def9520 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -49,16 +49,14 @@ - [x] **连接拖拽排序** — Sortable.js 拖拽重排连接列表 - [x] **复制连接** — 右键复制现有连接配置 - [x] **侧边栏可拖拽** — 调整宽度 200–1500px,持久化 -- [ ] **DB 自定义名称** — 用户可重命名 DB0 → 生产库,下拉显示 +- [x] **DB 自定义名称** — 用户可重命名 DB0 → 生产库,下拉显示 - [x] **DB key 数量显示** — 下拉框显示每个 DB 的 key 数量 - [x] **DB 数量自动检测** — CONFIG GET databases + INFO keyspace - [x] **DB 记忆** — 每个连接记住最后选择的数据库 -- [ ] **更多语言** — de/es/fr/it/ko/pt/ru/tr/ua/vi/tw(当前仅 en/zh-CN) -- [x] **页面缩放** — 0.5x–2.0x 缩放控制 -- [x] **自定义字体** — 用户可选等宽字体 +- [x] **更多语言** — de/es/fr/it/ko/pt/ru/tr/ua/vi/tw(当前仅 en/zh-CN) - [x] **SlowLog 配置显示** — slowlog-log-slower-than / slowlog-max-len -- [ ] **Cluster SlowLog** — 每个 master 节点分别查询 -- [ ] **Cluster Keyspace 表** — 集群模式显示各节点 key 数量 +- [x] **Cluster SlowLog** — 每个 master 节点分别查询 +- [x] **Cluster Keyspace 表** — 集群模式显示各节点 key 数量 - [x] **Status 自动刷新开关** — 可暂停/恢复的刷新(当前固定 1s) - [x] **Status 搜索过滤** — INFO 表格搜索 - [x] **Key 统计表** — 每个 DB 的 Keys/Expires/Avg TTL 排序表 @@ -81,4 +79,4 @@ - [x] `electron/shared/types.ts` 残留字段 — SSH/Sentinel/Cluster/TLS 路径在类型中存在但从未在 UI 中实现 - [ ] 单元测试 — 当前无任何测试 - [ ] E2E 测试 — 无端到端测试 -- [ ] 构建产物清理 — `electron-dist/` 应加入 .gitignore(当前已加入) +- [x] 构建产物清理 — `electron-dist/` 应加入 .gitignore(当前已加入) diff --git a/src/components/DbSelector.vue b/src/components/DbSelector.vue index 3fb87c9..0c80551 100644 --- a/src/components/DbSelector.vue +++ b/src/components/DbSelector.vue @@ -4,6 +4,7 @@ import { ref, watch, computed } from 'vue' import { useConnectionStore } from '@renderer/stores/connection' import { useKeyStore } from '@renderer/stores/key' import { useI18n } from '@renderer/i18n' +import { ElMessageBox } from 'element-plus' const connStore = useConnectionStore() const keyStore = useKeyStore() @@ -12,19 +13,70 @@ const { t } = useI18n() const selectedDb = ref(0) const dbCount = ref(16) const dbKeyCounts = ref>({}) +const customDbNames = ref>({}) + +function loadCustomNames() { + if (!connStore.activeId) { + customDbNames.value = {} + return + } + try { + const stored = localStorage.getItem(`dbNames_${connStore.activeId}`) + customDbNames.value = stored ? JSON.parse(stored) : {} + } catch { + customDbNames.value = {} + } +} + +function getDbLabel(dbIndex: number): string { + const count = dbKeyCounts.value[dbIndex] + const customName = customDbNames.value[String(dbIndex)] + const baseLabel = count !== undefined && count > 0 + ? `DB${dbIndex} (${t('db.keyCount', { n: count })})` + : count === 0 + ? `DB${dbIndex} (${t('db.noKeys')})` + : `DB${dbIndex}` + if (customName) { + return `${baseLabel} - ${customName}` + } + return baseLabel +} const dbList = computed(() => { - return Array.from({ length: dbCount.value }, (_, i) => { - const count = dbKeyCounts.value[i] - const label = count !== undefined && count > 0 - ? `DB${i} (${t('db.keyCount', { n: count })})` - : count === 0 - ? `DB${i} (${t('db.noKeys')})` - : `DB${i}` - return { value: i, label } - }) + return Array.from({ length: dbCount.value }, (_, i) => ({ + value: i, + label: getDbLabel(i), + })) }) +async function handleEditName(dbIndex: number, event: MouseEvent) { + event.stopPropagation() + const currentName = customDbNames.value[String(dbIndex)] || '' + try { + const result = await ElMessageBox.prompt( + `Rename DB${dbIndex}:`, + 'Custom DB Name', + { + confirmButtonText: 'OK', + cancelButtonText: 'Cancel', + inputValue: currentName, + inputPlaceholder: 'Enter custom name (leave empty to clear)', + } + ) + const newName = (result.value || '').trim() + if (newName) { + customDbNames.value[String(dbIndex)] = newName + } else { + delete customDbNames.value[String(dbIndex)] + } + if (connStore.activeId) { + localStorage.setItem(`dbNames_${connStore.activeId}`, JSON.stringify(customDbNames.value)) + } + } catch { + // cancelled + } +} + async function fetchDbInfo() { if (!connStore.activeId) return try { @@ -71,11 +123,19 @@ watch( if (connected) { selectedDb.value = 0 keyStore.currentDb = 0 + loadCustomNames() await fetchDbInfo() } } ) +watch( + () => connStore.activeId, + () => { + loadCustomNames() + } +) + async function switchDb(db: number) { if (!connStore.activeId) return selectedDb.value = db @@ -91,14 +151,21 @@ async function switchDb(db: number) { :model-value="selectedDb" size="small" @change="switchDb" - style="width: 130px" + style="width: 160px" > + > +
+ {{ db.label }} + + + +
+
@@ -117,6 +184,63 @@ async function switchDb(db: number) { font-weight: 600; } +.db-option-content { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.db-option-label { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.db-edit-icon { + font-size: 12px; + cursor: pointer; + opacity: 0.4; + transition: opacity 0.2s; + margin-left: 4px; + flex-shrink: 0; +} + +.db-edit-icon:hover { + opacity: 1; + color: var(--accent); +} + +:deep(.el-select .el-input__wrapper) { + background: var(--bg-card); + border-color: var(--border-color); + box-shadow: none; +} + +:deep(.el-select .el-input__wrapper:hover) { + border-color: var(--border-accent); +} + +:deep(.el-select .el-input__wrapper.is-focus) { + border-color: var(--accent); +} + + + + +