feat: Add quick connect group servers

This commit is contained in:
dalaolala committed 2026-03-02 17:25:57 +08:00
1 parent 838b344219
commit 2d07f270ae
3 files changed
+61 -17

No files matched your search

+4 -2
View File
@@ -154,8 +154,10 @@ webssh/
- **断开**: 点击"断开"按钮结束连接
### 快速连接
- 不保存服务器信息,直接输入连接参数
- 适合临时连接或测试使用
- **无需提前配置**:直接输入凭据即可发起 SSH / SFTP 连接
- **独立多标签页架构**:每个终端和独立全屏 SFTP 文件管理器都运行在完全隔离的环境中,互不干扰
- **智能历史与分组**:连接信息(包括可选保存的密码)与分组展开状态自动安全保存在当前浏览器本地中
- **支持导入与导出**:一键导出 JSON 格式的快速连接数据,轻松在不同设备间迁移历史
## 🌐 Nginx 反向代理配置
+1 -1
View File
@@ -140,7 +140,7 @@
<div class="feature-card" @click="$router.push('/quick-connect')" style="cursor: pointer;">
<el-icon size="48" color="#F56C6C"><Connection /></el-icon>
<h3>快速连接</h3>
<p>无需保存服务器信息,快速建立SSH连接</p>
<p>服务器信息保存在本地,支持导入导出</p>
</div>
</el-col>
</el-row>
+56 -14
View File
@@ -48,9 +48,11 @@
:data="treeData"
:props="treeProps"
node-key="id"
default-expand-all
:default-expanded-keys="expandedHistoryKeys"
highlight-current
@node-click="handleTreeNodeClick"
@node-expand="handleNodeExpand"
@node-collapse="handleNodeCollapse"
>
<template #default="{ node, data }">
<div class="tree-node" :class="{ 'is-leaf': data.isLeaf }">
@@ -108,6 +110,24 @@
<el-input v-model="form.name" placeholder="请输入服务器名称(可选)" />
</el-form-item>
<el-form-item label="所属分组" prop="group">
<el-select
v-model="form.group"
filterable
allow-create
default-first-option
placeholder="请输入或选择分组名称(可选)"
style="width: 100%"
>
<el-option
v-for="item in availableGroups"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="主机地址" prop="host">
<el-input v-model="form.host" placeholder="请输入IP地址或域名" />
</el-form-item>
@@ -211,7 +231,6 @@ import { useTerminalStore } from '@/stores/terminal'
import { useAuthStore } from '@/stores/auth'
const HISTORY_KEY = 'webssh_quick_connect_history'
const MAX_HISTORY = 20
const router = useRouter()
const authStore = useAuthStore()
@@ -227,6 +246,7 @@ const historyList = ref([])
const form = reactive({
name: '',
group: '',
host: '',
port: 22,
username: '',
@@ -272,19 +292,30 @@ const rules = {
]
}
// ========== 树形数据 ==========
// ========== 树形数据 & 分组下拉 ==========
// 提取历史记录中出现过的所有分组(去重)
const availableGroups = computed(() => {
const groups = new Set()
historyList.value.forEach(item => {
if (item.group) {
groups.add(item.group)
}
})
return Array.from(groups).sort()
})
const treeProps = {
children: 'children',
label: 'label'
}
// 将 historyList 转换为树形结构,按主机地址分组
// 将 historyList 转换为树形结构,按分组名称分组
const treeData = computed(() => {
const groups = {}
historyList.value.forEach((item, index) => {
const groupKey = item.host
const groupKey = item.group || '未分组'
if (!groups[groupKey]) {
groups[groupKey] = {
id: `group-${groupKey}`,
@@ -313,6 +344,24 @@ const handleTreeNodeClick = (data) => {
}
}
// ========== 分组展开状态持久化 (sessionStorage) ==========
const EXPANDED_HISTORY_KEYS_KEY = 'webssh_quick_connect_expanded_keys'
const expandedHistoryKeys = ref(JSON.parse(sessionStorage.getItem(EXPANDED_HISTORY_KEYS_KEY) || '[]'))
const handleNodeExpand = (data) => {
if (!data.isLeaf && !expandedHistoryKeys.value.includes(data.id)) {
expandedHistoryKeys.value.push(data.id)
sessionStorage.setItem(EXPANDED_HISTORY_KEYS_KEY, JSON.stringify(expandedHistoryKeys.value))
}
}
const handleNodeCollapse = (data) => {
if (!data.isLeaf) {
expandedHistoryKeys.value = expandedHistoryKeys.value.filter(id => id !== data.id)
sessionStorage.setItem(EXPANDED_HISTORY_KEYS_KEY, JSON.stringify(expandedHistoryKeys.value))
}
}
// ========== 历史记录管理 ==========
const loadHistory = () => {
@@ -331,6 +380,7 @@ const persistHistory = () => {
const saveToHistory = () => {
const record = {
name: form.name || '',
group: form.group || '',
host: form.host,
port: form.port,
username: form.username,
@@ -356,15 +406,12 @@ const saveToHistory = () => {
historyList.value.unshift(record)
if (historyList.value.length > MAX_HISTORY) {
historyList.value = historyList.value.slice(0, MAX_HISTORY)
}
persistHistory()
}
const fillFromHistory = (item) => {
form.name = item.name || ''
form.group = item.group || ''
form.host = item.host
form.port = item.port
form.username = item.username
@@ -464,11 +511,6 @@ const handleImportFile = (event) => {
merged.unshift(item)
})
// 限制最大条数
if (merged.length > MAX_HISTORY) {
merged = merged.slice(0, MAX_HISTORY)
}
historyList.value = merged
persistHistory()
ElMessage.success(`成功导入 ${addedCount} 条连接记录`)