feat: 为连接列表添加搜索

在连接列表页加入前端即时搜索,支持按名称、主机、用户名和端口过滤连接。补充分离的无结果状态与清空操作,提升连接较多时的查找效率。
This commit is contained in:
liumangmang
2026-03-18 23:59:26 +08:00
parent 77518b3f97
commit c1efd72d6d

View File

@@ -1,27 +1,49 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { computed, ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useConnectionsStore } from '../stores/connections' import { useConnectionsStore } from '../stores/connections'
import { useTerminalTabsStore } from '../stores/terminalTabs' import { useTerminalTabsStore } from '../stores/terminalTabs'
import type { Connection, ConnectionCreateRequest } from '../api/connections' import type { Connection, ConnectionCreateRequest } from '../api/connections'
import ConnectionForm from '../components/ConnectionForm.vue' import ConnectionForm from '../components/ConnectionForm.vue'
import { import {
Server, Server,
Plus, Plus,
Terminal, Terminal,
FolderOpen, FolderOpen,
Pencil, Pencil,
Trash2, Trash2,
Key, Key,
Lock, Lock,
} from 'lucide-vue-next' Search,
X,
} from 'lucide-vue-next'
const router = useRouter() const router = useRouter()
const store = useConnectionsStore() const store = useConnectionsStore()
const tabsStore = useTerminalTabsStore() const tabsStore = useTerminalTabsStore()
const showForm = ref(false) const showForm = ref(false)
const editingConn = ref<Connection | null>(null) const editingConn = ref<Connection | null>(null)
const searchQuery = ref('')
const filteredConnections = computed(() => {
const keyword = searchQuery.value.trim().toLowerCase()
if (!keyword) {
return store.connections
}
return store.connections.filter((conn) => {
const fields = [
conn.name,
conn.host,
conn.username,
String(conn.port),
]
return fields.some((field) => field.toLowerCase().includes(keyword))
})
})
onMounted(() => store.fetchConnections()) onMounted(() => store.fetchConnections())
@@ -59,42 +81,80 @@ function openTerminal(conn: Connection) {
router.push('/terminal') router.push('/terminal')
} }
function openSftp(conn: Connection) { function openSftp(conn: Connection) {
router.push(`/sftp/${conn.id}`) router.push(`/sftp/${conn.id}`)
} }
function clearSearch() {
searchQuery.value = ''
}
</script> </script>
<template> <template>
<div class="p-6"> <div class="p-6">
<div class="flex items-center justify-between mb-6"> <div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-semibold text-slate-100">连接列表</h2> <h2 class="text-xl font-semibold text-slate-100">连接列表</h2>
<button <button
@click="openCreate" @click="openCreate"
class="flex items-center gap-2 px-4 py-2 rounded-lg bg-cyan-600 hover:bg-cyan-500 text-white font-medium transition-colors duration-200 cursor-pointer" class="flex items-center gap-2 px-4 py-2 rounded-lg bg-cyan-600 hover:bg-cyan-500 text-white font-medium transition-colors duration-200 cursor-pointer"
aria-label="添加连接" aria-label="添加连接"
> >
<Plus class="w-5 h-5" aria-hidden="true" /> <Plus class="w-5 h-5" aria-hidden="true" />
添加连接 添加连接
</button> </button>
</div> </div>
<div v-if="store.connections.length === 0" class="text-center py-16 bg-slate-800/50 rounded-xl border border-slate-700"> <div class="relative mb-6">
<Server class="w-16 h-16 text-slate-500 mx-auto mb-4" aria-hidden="true" /> <Search class="w-4 h-4 text-slate-500 absolute left-3 top-1/2 -translate-y-1/2" aria-hidden="true" />
<input
v-model="searchQuery"
type="text"
placeholder="搜索名称、主机、用户名或端口"
class="w-full rounded-xl border border-slate-700 bg-slate-800/70 py-3 pl-10 pr-11 text-slate-100 placeholder:text-slate-500 focus:border-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/20"
aria-label="搜索连接"
/>
<button
v-if="searchQuery"
@click="clearSearch"
class="absolute right-3 top-1/2 -translate-y-1/2 rounded-md p-1 text-slate-400 hover:bg-slate-700 hover:text-slate-100 transition-colors duration-200 cursor-pointer"
aria-label="清空搜索"
>
<X class="w-4 h-4" aria-hidden="true" />
</button>
</div>
<div v-if="store.connections.length === 0" class="text-center py-16 bg-slate-800/50 rounded-xl border border-slate-700">
<Server class="w-16 h-16 text-slate-500 mx-auto mb-4" aria-hidden="true" />
<p class="text-slate-400 mb-4">暂无连接</p> <p class="text-slate-400 mb-4">暂无连接</p>
<button <button
@click="openCreate" @click="openCreate"
class="px-4 py-2 rounded-lg bg-cyan-600 hover:bg-cyan-500 text-white transition-colors duration-200 cursor-pointer" class="px-4 py-2 rounded-lg bg-cyan-600 hover:bg-cyan-500 text-white transition-colors duration-200 cursor-pointer"
> >
添加第一个连接 添加第一个连接
</button> </button>
</div> </div>
<div v-else class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <div
<div v-else-if="filteredConnections.length === 0"
v-for="conn in store.connections" class="text-center py-16 bg-slate-800/50 rounded-xl border border-slate-700"
:key="conn.id" >
class="bg-slate-800 rounded-xl border border-slate-700 p-4 hover:border-slate-600 transition-colors duration-200" <Search class="w-16 h-16 text-slate-500 mx-auto mb-4" aria-hidden="true" />
> <p class="text-slate-300 mb-2">未找到匹配的连接</p>
<p class="text-sm text-slate-500 mb-4">试试搜索名称主机用户名或端口</p>
<button
@click="clearSearch"
class="px-4 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 text-slate-200 transition-colors duration-200 cursor-pointer"
>
清空搜索
</button>
</div>
<div v-else class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div
v-for="conn in filteredConnections"
:key="conn.id"
class="bg-slate-800 rounded-xl border border-slate-700 p-4 hover:border-slate-600 transition-colors duration-200"
>
<div class="flex items-start justify-between mb-3"> <div class="flex items-start justify-between mb-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="w-10 h-10 rounded-lg bg-slate-700 flex items-center justify-center"> <div class="w-10 h-10 rounded-lg bg-slate-700 flex items-center justify-center">