feat: add QuickConnect import, and export capabilities.
This commit is contained in:
1 parent
cf3e171438
commit
b4e8db583d
1 file changed
+113
-11
@@ -18,16 +18,40 @@
|
||||
<el-aside class="history-aside" width="300px">
|
||||
<div class="aside-header">
|
||||
<span class="aside-title">连接历史</span>
|
||||
<el-button
|
||||
v-if="historyList.length > 0"
|
||||
type="danger"
|
||||
size="small"
|
||||
text
|
||||
@click="clearAllHistory"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
清空
|
||||
</el-button>
|
||||
<div class="aside-actions">
|
||||
<el-tooltip content="导入" placement="top">
|
||||
<el-button size="small" text @click="triggerImport">
|
||||
<el-icon><Upload /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="导出" placement="top">
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
:disabled="historyList.length === 0"
|
||||
@click="exportHistory"
|
||||
>
|
||||
<el-icon><Download /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
v-if="historyList.length > 0"
|
||||
type="danger"
|
||||
size="small"
|
||||
text
|
||||
@click="clearAllHistory"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
清空
|
||||
</el-button>
|
||||
</div>
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept=".json"
|
||||
style="display: none;"
|
||||
@change="handleImportFile"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="historyList.length > 0" class="history-tree">
|
||||
@@ -182,7 +206,7 @@
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ArrowLeft, Connection, Delete, Close, Monitor, Lock, Folder } from '@element-plus/icons-vue'
|
||||
import { ArrowLeft, Connection, Delete, Close, Monitor, Lock, Folder, Upload, Download } from '@element-plus/icons-vue'
|
||||
import { useTerminalStore } from '@/stores/terminal'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
@@ -194,6 +218,7 @@ const terminalStore = useTerminalStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const connectForm = ref()
|
||||
const fileInput = ref()
|
||||
const connecting = ref(false)
|
||||
const connectionError = ref('')
|
||||
const savePassword = ref(false)
|
||||
@@ -385,6 +410,77 @@ const clearAllHistory = () => {
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// ========== 导出 / 导入 ==========
|
||||
|
||||
const exportHistory = () => {
|
||||
const data = JSON.stringify(historyList.value, null, 2)
|
||||
const blob = new Blob([data], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `webssh_quick_connect_${new Date().toISOString().slice(0, 10)}.json`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
ElMessage.success(`已导出 ${historyList.value.length} 条连接记录`)
|
||||
}
|
||||
|
||||
const triggerImport = () => {
|
||||
fileInput.value?.click()
|
||||
}
|
||||
|
||||
const handleImportFile = (event) => {
|
||||
const file = event.target.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const imported = JSON.parse(e.target.result)
|
||||
|
||||
if (!Array.isArray(imported)) {
|
||||
ElMessage.error('文件格式错误:需要 JSON 数组')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证每条记录必须有 host 和 username
|
||||
const valid = imported.filter(item => item.host && item.username)
|
||||
if (valid.length === 0) {
|
||||
ElMessage.error('未找到有效的连接记录')
|
||||
return
|
||||
}
|
||||
|
||||
// 合并:按 host+port+username 去重,导入的覆盖旧的
|
||||
let merged = [...historyList.value]
|
||||
let addedCount = 0
|
||||
valid.forEach(item => {
|
||||
const idx = merged.findIndex(
|
||||
h => h.host === item.host && h.port === item.port && h.username === item.username
|
||||
)
|
||||
if (idx !== -1) {
|
||||
merged.splice(idx, 1)
|
||||
}
|
||||
addedCount++
|
||||
merged.unshift(item)
|
||||
})
|
||||
|
||||
// 限制最大条数
|
||||
if (merged.length > MAX_HISTORY) {
|
||||
merged = merged.slice(0, MAX_HISTORY)
|
||||
}
|
||||
|
||||
historyList.value = merged
|
||||
persistHistory()
|
||||
ElMessage.success(`成功导入 ${addedCount} 条连接记录`)
|
||||
} catch {
|
||||
ElMessage.error('文件解析失败,请确认是有效的 JSON 文件')
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
|
||||
// 清空 input 以便重复选择同一文件
|
||||
event.target.value = ''
|
||||
}
|
||||
|
||||
// ========== 连接逻辑 ==========
|
||||
|
||||
const handleConnect = async () => {
|
||||
@@ -507,6 +603,12 @@ onMounted(() => {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.aside-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.history-tree {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
Reference in new issue
Block a user