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:
+169
-79
@@ -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 baseLabel
|
||||
return customName || `DB${dbIndex}`
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user