Files
JRedisDesktop/src/components/ConnectionList.vue
T

190 lines
4.5 KiB
Vue

<script setup lang="ts">
// src/components/ConnectionList.vue
import { ref, computed } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import ConnectionCard from './ConnectionCard.vue'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
import type { ConnectionConfig } from '@renderer/stores/connection'
const connStore = useConnectionStore()
const { t } = useI18n()
const searchQuery = ref('')
const emit = defineEmits<{ edit: [conn: ConnectionConfig | null] }>()
const filteredConnections = computed(() => {
const q = searchQuery.value.toLowerCase()
return connStore.connections
.filter(c => c.name.toLowerCase().includes(q) || c.host.includes(q))
.sort((a, b) => a.order - b.order)
})
async function handleDelete(id: string) {
try {
await ElMessageBox.confirm('Delete this connection?', 'Confirm', {
type: 'warning',
confirmButtonText: 'Delete',
cancelButtonText: 'Cancel',
})
await connStore.deleteConnection(id)
} catch { /* cancelled */ }
}
function handleEdit(conn: ConnectionConfig) {
emit('edit', conn)
}
function handleNew() {
emit('edit', null)
}
async function handleExport() {
try {
const data = await connStore.exportConnections()
const blob = new Blob([data], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `jredis-connections-${new Date().toISOString().slice(0, 10)}.json`
a.click()
URL.revokeObjectURL(url)
ElMessage.success('Connections exported')
} catch (err: any) {
ElMessage.error(err.message)
}
}
async function handleImport() {
try {
const result = await window.electronAPI.dialog.openFile({
title: 'Import Connections',
filters: [{ name: 'JSON', extensions: ['json'] }],
})
if (result) {
const count = await connStore.importConnections(result.content)
ElMessage.success(`Imported ${count} connection(s)`)
await connStore.loadConnections()
}
} catch (err: any) {
ElMessage.error(err.message)
}
}
</script>
<template>
<div class="connection-list">
<div class="list-search">
<input
v-model="searchQuery"
class="search-input"
type="text"
:placeholder="t('connection.search')"
/>
</div>
<div class="list-items">
<ConnectionCard
v-for="conn in filteredConnections"
:key="conn.id"
:connection="conn"
@edit="handleEdit"
@delete="handleDelete"
/>
<div v-if="filteredConnections.length === 0" class="list-empty">
<p v-if="searchQuery">{{ t('connection.noMatch') }}</p>
<p v-else>{{ t('connection.noConnections') }}</p>
</div>
</div>
<div class="list-footer">
<button class="list-add-btn" @click="handleNew">
+ {{ t('connection.new') }}
</button>
<div class="list-actions">
<el-button size="small" text @click="handleImport">
<el-icon><Upload /></el-icon> {{ t('connection.import') }}
</el-button>
<el-button size="small" text @click="handleExport">
<el-icon><Download /></el-icon> {{ t('connection.export') }}
</el-button>
</div>
</div>
</div>
</template>
<style scoped>
.connection-list {
display: flex;
flex-direction: column;
height: 100%;
}
.list-search {
padding: 14px;
}
.search-input {
width: 100%;
padding: 10px 14px;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
color: var(--text-primary);
font-size: 13px;
outline: none;
transition: border-color 0.2s;
}
.search-input::placeholder {
color: var(--text-muted);
}
.search-input:focus {
border-color: var(--border-accent);
}
.list-items {
flex: 1;
overflow-y: auto;
padding: 0 14px;
display: flex;
flex-direction: column;
gap: 8px;
}
.list-empty {
text-align: center;
color: var(--text-muted);
font-size: 13px;
padding: 40px 0;
}
.list-footer {
padding: 14px;
border-top: 1px solid var(--border-color);
}
.list-add-btn {
width: 100%;
padding: 10px;
background: var(--bg-card);
border: 1px dashed var(--border-color);
border-radius: var(--radius-sm);
color: var(--text-muted);
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
margin-bottom: 8px;
}
.list-add-btn:hover {
border-color: var(--border-accent);
color: var(--accent-light);
background: var(--bg-card-hover);
}
.list-actions {
display: flex;
justify-content: center;
gap: 8px;
}
</style>