style: optimize DB selector dropdown

- Replace el-select with custom el-dropdown for compact tab bar fit
- Trigger: compact pill with DB name + key count badge + arrow
- Dropdown: two-column layout (DB index + custom name | key count badge + edit icon)
- Edit icon hidden until hover, colored on hover
- Selected DB highlighted with accent color
- Remove duplicate style block
- Key counts formatted (10k+ → '10k')
This commit is contained in:
2026-07-07 23:49:47 +08:00
parent 3b039714f0
commit baa8917eae
+168 -78
View File
@@ -28,27 +28,27 @@ function loadCustomNames() {
}
}
function getDbLabel(dbIndex: number): string {
const count = dbKeyCounts.value[dbIndex]
function getDbShortLabel(dbIndex: number): string {
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 customName || `DB${dbIndex}`
}
return baseLabel
function getDbKeyCount(dbIndex: number): number | undefined {
return dbKeyCounts.value[dbIndex]
}
const dbList = computed(() => {
return Array.from({ length: dbCount.value }, (_, i) => ({
value: i,
label: getDbLabel(i),
label: getDbShortLabel(i),
count: getDbKeyCount(i),
customName: customDbNames.value[String(i)] || '',
}))
})
const currentLabel = computed(() => getDbShortLabel(selectedDb.value))
const currentCount = computed(() => getDbKeyCount(selectedDb.value))
async function handleEditName(dbIndex: number, event: MouseEvent) {
event.stopPropagation()
const currentName = customDbNames.value[String(dbIndex)] || ''
@@ -80,7 +80,6 @@ async function handleEditName(dbIndex: number, event: MouseEvent) {
async function fetchDbInfo() {
if (!connStore.activeId) return
try {
// Get actual DB count from CONFIG GET databases
const configResult = await window.electronAPI.redis.execute(
connStore.activeId, 'CONFIG', ['GET', 'databases']
)
@@ -92,7 +91,6 @@ async function fetchDbInfo() {
}
try {
// Parse INFO keyspace for per-DB key counts
const info = await connStore.getInfo()
const keyspace: Record<number, number> = {}
const match = info.match(/# Keyspace[\s\S]*?(?=\n#|$)/)
@@ -105,7 +103,6 @@ async function fetchDbInfo() {
}
}
}
// Mark DBs with 0 keys explicitly
for (let i = 0; i < dbCount.value; i++) {
if (!(i in keyspace)) {
keyspace[i] = 0
@@ -142,31 +139,53 @@ async function switchDb(db: number) {
connStore.saveLastDb(connStore.activeId, db)
await keyStore.selectDb(connStore.activeId, db)
}
function formatCount(n: number): string {
if (n >= 10000) return `${(n / 1000).toFixed(0)}k`
return String(n)
}
</script>
<template>
<div class="db-selector" v-if="connStore.isConnected">
<span class="db-label">DB</span>
<el-select
:model-value="selectedDb"
size="small"
@change="switchDb"
style="width: 160px"
>
<el-option
<el-dropdown trigger="click" @command="switchDb" placement="bottom-start">
<div class="db-trigger">
<span class="db-trigger-label">{{ currentLabel }}</span>
<span v-if="currentCount !== undefined && currentCount > 0" class="db-trigger-badge">
{{ formatCount(currentCount) }}
</span>
<el-icon class="db-trigger-arrow"><ArrowDown /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu class="db-dropdown">
<div class="db-dropdown-header">
<span>{{ t('db.label') }}</span>
</div>
<el-dropdown-item
v-for="db in dbList"
:key="db.value"
:label="db.label"
:value="db.value"
:command="db.value"
:class="{ 'is-selected': db.value === selectedDb }"
>
<div class="db-option-content">
<span class="db-option-label">{{ db.label }}</span>
<el-icon class="db-edit-icon" @click="(e: MouseEvent) => handleEditName(db.value, e)">
<div class="db-option">
<div class="db-option-left">
<span class="db-option-index">DB{{ db.value }}</span>
<span v-if="db.customName" class="db-option-name">{{ db.customName }}</span>
</div>
<div class="db-option-right">
<span v-if="db.count !== undefined && db.count > 0" class="db-option-count">
{{ formatCount(db.count) }}
</span>
<span v-else class="db-option-empty">{{ t('db.noKeys') }}</span>
<el-icon class="db-option-edit" @click="(e: MouseEvent) => handleEditName(db.value, e)">
<EditPen />
</el-icon>
</div>
</el-option>
</el-select>
</div>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
@@ -174,84 +193,155 @@ async function switchDb(db: number) {
.db-selector {
display: flex;
align-items: center;
gap: 4px;
padding: 0 8px;
}
.db-label {
/* Trigger button — compact, fits in tab bar */
.db-trigger {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 6px;
cursor: pointer;
font-size: 12px;
color: var(--text-primary);
transition: all 0.15s;
white-space: nowrap;
user-select: none;
}
.db-trigger:hover {
border-color: var(--accent);
background: var(--bg-hover);
}
.db-trigger-label {
font-weight: 600;
font-family: 'Cascadia Code', 'Fira Code', monospace;
}
.db-trigger-badge {
font-size: 10px;
font-weight: 700;
color: var(--accent-light);
background: var(--accent-bg, rgba(99, 102, 241, 0.15));
padding: 1px 6px;
border-radius: 8px;
line-height: 1.4;
}
.db-trigger-arrow {
font-size: 10px;
color: var(--text-muted);
font-weight: 600;
transition: transform 0.15s;
}
.db-option-content {
.db-trigger:hover .db-trigger-arrow {
color: var(--accent-light);
}
/* Dropdown */
:deep(.db-dropdown) {
min-width: 200px;
max-height: 360px;
overflow-y: auto;
}
.db-dropdown-header {
padding: 6px 12px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
border-bottom: 1px solid var(--border-color);
}
:deep(.el-dropdown-menu__item) {
padding: 0;
}
:deep(.el-dropdown-menu__item.is-selected) {
background: var(--bg-card-active);
color: var(--accent-light);
}
.db-option {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 6px 12px;
gap: 8px;
}
.db-option-label {
flex: 1;
.db-option-left {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.db-option-index {
font-size: 12px;
font-weight: 600;
font-family: 'Cascadia Code', 'Fira Code', monospace;
color: var(--text-primary);
flex-shrink: 0;
}
.db-option-name {
font-size: 11px;
color: var(--accent-light);
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;
.db-option-right {
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.db-edit-icon:hover {
opacity: 1;
color: var(--accent);
.db-option-count {
font-size: 10px;
font-weight: 600;
color: var(--accent-light);
background: var(--accent-bg, rgba(99, 102, 241, 0.15));
padding: 1px 6px;
border-radius: 8px;
}
: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);
}
</style>
<style scoped>
.db-selector {
display: flex;
align-items: center;
gap: 4px;
padding: 0 8px;
}
.db-label {
.db-option-empty {
font-size: 10px;
color: var(--text-muted);
font-weight: 600;
}
:deep(.el-select .el-input__wrapper) {
background: var(--bg-card);
border-color: var(--border-color);
box-shadow: none;
.db-option-edit {
font-size: 11px;
cursor: pointer;
opacity: 0;
color: var(--text-muted);
transition: opacity 0.15s, color 0.15s;
padding: 2px;
border-radius: 3px;
}
:deep(.el-select .el-input__wrapper:hover) {
border-color: var(--border-accent);
.el-dropdown-menu__item:hover .db-option-edit {
opacity: 0.6;
}
:deep(.el-select .el-input__wrapper.is-focus) {
border-color: var(--accent);
.db-option-edit:hover {
opacity: 1 !important;
color: var(--accent);
background: var(--bg-hover);
}
:deep(.is-selected) .db-option-index {
color: var(--accent-light);
}
</style>