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 {
|
function getDbShortLabel(dbIndex: number): string {
|
||||||
const count = dbKeyCounts.value[dbIndex]
|
|
||||||
const customName = customDbNames.value[String(dbIndex)]
|
const customName = customDbNames.value[String(dbIndex)]
|
||||||
const baseLabel = count !== undefined && count > 0
|
return customName || `DB${dbIndex}`
|
||||||
? `DB${dbIndex} (${t('db.keyCount', { n: count })})`
|
}
|
||||||
: count === 0
|
|
||||||
? `DB${dbIndex} (${t('db.noKeys')})`
|
function getDbKeyCount(dbIndex: number): number | undefined {
|
||||||
: `DB${dbIndex}`
|
return dbKeyCounts.value[dbIndex]
|
||||||
if (customName) {
|
|
||||||
return `${baseLabel} - ${customName}`
|
|
||||||
}
|
|
||||||
return baseLabel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbList = computed(() => {
|
const dbList = computed(() => {
|
||||||
return Array.from({ length: dbCount.value }, (_, i) => ({
|
return Array.from({ length: dbCount.value }, (_, i) => ({
|
||||||
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) {
|
async function handleEditName(dbIndex: number, event: MouseEvent) {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
const currentName = customDbNames.value[String(dbIndex)] || ''
|
const currentName = customDbNames.value[String(dbIndex)] || ''
|
||||||
@@ -80,7 +80,6 @@ async function handleEditName(dbIndex: number, event: MouseEvent) {
|
|||||||
async function fetchDbInfo() {
|
async function fetchDbInfo() {
|
||||||
if (!connStore.activeId) return
|
if (!connStore.activeId) return
|
||||||
try {
|
try {
|
||||||
// Get actual DB count from CONFIG GET databases
|
|
||||||
const configResult = await window.electronAPI.redis.execute(
|
const configResult = await window.electronAPI.redis.execute(
|
||||||
connStore.activeId, 'CONFIG', ['GET', 'databases']
|
connStore.activeId, 'CONFIG', ['GET', 'databases']
|
||||||
)
|
)
|
||||||
@@ -92,7 +91,6 @@ async function fetchDbInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Parse INFO keyspace for per-DB key counts
|
|
||||||
const info = await connStore.getInfo()
|
const info = await connStore.getInfo()
|
||||||
const keyspace: Record<number, number> = {}
|
const keyspace: Record<number, number> = {}
|
||||||
const match = info.match(/# Keyspace[\s\S]*?(?=\n#|$)/)
|
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++) {
|
for (let i = 0; i < dbCount.value; i++) {
|
||||||
if (!(i in keyspace)) {
|
if (!(i in keyspace)) {
|
||||||
keyspace[i] = 0
|
keyspace[i] = 0
|
||||||
@@ -142,31 +139,53 @@ async function switchDb(db: number) {
|
|||||||
connStore.saveLastDb(connStore.activeId, db)
|
connStore.saveLastDb(connStore.activeId, db)
|
||||||
await keyStore.selectDb(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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="db-selector" v-if="connStore.isConnected">
|
<div class="db-selector" v-if="connStore.isConnected">
|
||||||
<span class="db-label">DB</span>
|
<el-dropdown trigger="click" @command="switchDb" placement="bottom-start">
|
||||||
<el-select
|
<div class="db-trigger">
|
||||||
:model-value="selectedDb"
|
<span class="db-trigger-label">{{ currentLabel }}</span>
|
||||||
size="small"
|
<span v-if="currentCount !== undefined && currentCount > 0" class="db-trigger-badge">
|
||||||
@change="switchDb"
|
{{ formatCount(currentCount) }}
|
||||||
style="width: 160px"
|
</span>
|
||||||
>
|
<el-icon class="db-trigger-arrow"><ArrowDown /></el-icon>
|
||||||
<el-option
|
</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"
|
v-for="db in dbList"
|
||||||
:key="db.value"
|
:key="db.value"
|
||||||
:label="db.label"
|
:command="db.value"
|
||||||
:value="db.value"
|
:class="{ 'is-selected': db.value === selectedDb }"
|
||||||
>
|
>
|
||||||
<div class="db-option-content">
|
<div class="db-option">
|
||||||
<span class="db-option-label">{{ db.label }}</span>
|
<div class="db-option-left">
|
||||||
<el-icon class="db-edit-icon" @click="(e: MouseEvent) => handleEditName(db.value, e)">
|
<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 />
|
<EditPen />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
</el-option>
|
</div>
|
||||||
</el-select>
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -174,84 +193,155 @@ async function switchDb(db: number) {
|
|||||||
.db-selector {
|
.db-selector {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
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;
|
font-size: 10px;
|
||||||
color: var(--text-muted);
|
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;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: 6px 12px;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.db-option-label {
|
.db-option-left {
|
||||||
flex: 1;
|
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;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.db-edit-icon {
|
.db-option-right {
|
||||||
font-size: 12px;
|
display: flex;
|
||||||
cursor: pointer;
|
align-items: center;
|
||||||
opacity: 0.4;
|
gap: 6px;
|
||||||
transition: opacity 0.2s;
|
|
||||||
margin-left: 4px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.db-edit-icon:hover {
|
.db-option-count {
|
||||||
opacity: 1;
|
font-size: 10px;
|
||||||
color: var(--accent);
|
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) {
|
.db-option-empty {
|
||||||
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 {
|
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-select .el-input__wrapper) {
|
.db-option-edit {
|
||||||
background: var(--bg-card);
|
font-size: 11px;
|
||||||
border-color: var(--border-color);
|
cursor: pointer;
|
||||||
box-shadow: none;
|
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) {
|
.el-dropdown-menu__item:hover .db-option-edit {
|
||||||
border-color: var(--border-accent);
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-select .el-input__wrapper.is-focus) {
|
.db-option-edit:hover {
|
||||||
border-color: var(--accent);
|
opacity: 1 !important;
|
||||||
|
color: var(--accent);
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.is-selected) .db-option-index {
|
||||||
|
color: var(--accent-light);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user