feat: Implement quick connect layout change

This commit is contained in:
dalaolala committed 2026-02-28 15:34:48 +08:00
1 parent b4e8db583d
commit de1cf2bbc7
4 files changed
+159 -65

No files matched your search

+14 -9
View File
@@ -34,15 +34,20 @@ const routes = [
}, },
{ {
path: '/quick-connect', path: '/quick-connect',
name: 'QuickConnect', component: () => import('@/views/QuickConnectLayout.vue'),
component: () => import('@/views/QuickConnect.vue'), meta: { requiresAuth: true },
meta: { requiresAuth: true } children: [
}, {
{ path: '',
path: '/quick-connect-terminal', name: 'QuickConnect',
name: 'QuickConnectTerminal', component: () => import('@/views/QuickConnect.vue')
component: () => import('@/views/QuickConnectTerminal.vue'), },
meta: { requiresAuth: true } {
path: 'terminal',
name: 'QuickConnectTerminal',
component: () => import('@/views/QuickConnectTerminal.vue')
}
]
}, },
{ {
path: '/terminal-new', path: '/terminal-new',
+2 -34
View File
@@ -1,17 +1,6 @@
<template> <template>
<div class="quick-connect-page"> <div class="quick-connect-page">
<el-container class="quick-connect-container"> <el-container class="quick-connect-container">
<!-- 顶部工具栏 -->
<el-header class="quick-connect-header">
<div class="header-left">
<el-button @click="$router.push('/dashboard')" size="small">
<el-icon><ArrowLeft /></el-icon>
返回
</el-button>
<span class="page-title">快速连接</span>
</div>
</el-header>
<!-- 主内容区域:左侧历史树 + 右侧表单 --> <!-- 主内容区域:左侧历史树 + 右侧表单 -->
<el-container class="main-body"> <el-container class="main-body">
<!-- 左侧:连接历史树 --> <!-- 左侧:连接历史树 -->
@@ -526,7 +515,7 @@ const handleConnect = async () => {
terminalStore.quickConnect(connectionInfo) terminalStore.quickConnect(connectionInfo)
connecting.value = false connecting.value = false
router.push('/quick-connect-terminal') router.push('/quick-connect/terminal')
} catch (error) { } catch (error) {
connecting.value = false connecting.value = false
@@ -541,7 +530,7 @@ onMounted(() => {
<style scoped> <style scoped>
.quick-connect-page { .quick-connect-page {
height: 100vh; height: 100%;
background-color: #f0f2f5; background-color: #f0f2f5;
} }
@@ -551,27 +540,6 @@ onMounted(() => {
flex-direction: column; flex-direction: column;
} }
.quick-connect-header {
background: white;
border-bottom: 1px solid #e4e7ed;
display: flex;
align-items: center;
padding: 0 20px;
flex-shrink: 0;
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.page-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
/* 主体布局 */ /* 主体布局 */
.main-body { .main-body {
flex: 1; flex: 1;
+121
View File
@@ -0,0 +1,121 @@
<template>
<div class="quick-connect-layout">
<!-- 固定顶部栏 -->
<div class="layout-header" :class="{ dark: isTerminal }">
<el-button @click="$router.push('/dashboard')" size="small" text :style="{ color: isTerminal ? '#aaa' : '#888' }">
<el-icon><ArrowLeft /></el-icon>
返回
</el-button>
<span class="header-divider"></span>
<span class="page-title" v-if="!isTerminal">快速连接</span>
<!-- 终端 Tab -->
<div v-if="isTerminal" class="terminal-tab">
<span class="tab-dot"></span>
<span class="tab-label">{{ tabLabel }}</span>
</div>
</div>
<!-- 子页面内容 -->
<div class="layout-content">
<router-view />
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { ArrowLeft } from '@element-plus/icons-vue'
import { useTerminalStore } from '@/stores/terminal'
const route = useRoute()
const terminalStore = useTerminalStore()
const isTerminal = computed(() => route.path.includes('/terminal'))
const tabLabel = computed(() => {
const conn = terminalStore.currentConnection
if (!conn) return '终端'
const name = conn.name || ''
const userHost = `${conn.username || ''}@${conn.host || ''}:${conn.port || 22}`
return name ? `${name} (${userHost})` : userHost
})
</script>
<style scoped>
.quick-connect-layout {
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.layout-header {
background: #fafafa;
border-bottom: 1px solid #e8e8e8;
display: flex;
align-items: center;
padding: 0 14px;
height: 36px;
flex-shrink: 0;
z-index: 100;
gap: 8px;
transition: background-color 0.2s, border-color 0.2s;
}
.layout-header.dark {
background: #252526;
border-bottom: 1px solid #3e3e42;
}
.header-divider {
width: 1px;
height: 14px;
background: #dcdfe6;
}
.layout-header.dark .header-divider {
background: #555;
}
.page-title {
font-size: 13px;
font-weight: 500;
color: #606266;
}
/* Terminal Tab */
.terminal-tab {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 12px;
background: #1e1e1e;
border-radius: 4px;
border: 1px solid #3e3e42;
}
.tab-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #67c23a;
flex-shrink: 0;
}
.tab-label {
font-size: 12px;
color: #ccc;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 300px;
}
.layout-content {
flex: 1;
min-height: 0;
overflow: hidden;
}
</style>
+22 -22
View File
@@ -1,18 +1,6 @@
<template> <template>
<div class="terminal-page"> <div class="terminal-page">
<el-container class="terminal-container"> <el-container class="terminal-container">
<!-- 顶部工具栏 -->
<el-header class="terminal-header" height="36px">
<div class="header-left">
<el-button @click="goBack" size="small" text style="color: #ccc;">
<el-icon><ArrowLeft /></el-icon>
返回
</el-button>
<span class="server-name">{{ serverTitle }}</span>
<span class="server-info">{{ connectionDisplay }}</span>
</div>
</el-header>
<!-- 主内容区域 --> <!-- 主内容区域 -->
<el-container class="main-content"> <el-container class="main-content">
<!-- SFTP文件管理器侧边栏 --> <!-- SFTP文件管理器侧边栏 -->
@@ -531,7 +519,6 @@ const resetConnectedState = () => {
if (term) { if (term) {
term.reset() term.reset()
term.focus() term.focus()
term.write('\x1b[32mSSH连接已建立,可以开始输入命令\x1b[0m\r\n\r\n')
term.write('\x1b[?25h') term.write('\x1b[?25h')
if (typeof term.scrollToTop === 'function') { if (typeof term.scrollToTop === 'function') {
term.scrollToTop() term.scrollToTop()
@@ -568,8 +555,6 @@ watch(() => terminalStore.isConnected, (isConnected, wasConnected) => {
term.focus() term.focus()
setTimeout(() => { setTimeout(() => {
term.write('\x1b[32mSSH连接已建立,可以开始输入命令\x1b[0m\r\n\r\n')
term.write('\x1b[?25h') term.write('\x1b[?25h')
if (typeof term.scrollToTop === 'function') { if (typeof term.scrollToTop === 'function') {
@@ -605,17 +590,22 @@ watch(() => terminalStore.connectionError, (error) => {
// 监听窗口大小变化 // 监听窗口大小变化
const handleResize = () => { const handleResize = () => {
if (fitAddon) { if (fitAddon && term) {
fitAddon.fit() fitAddon.fit()
const dimensions = fitAddon.proposeDimensions() const dimensions = fitAddon.proposeDimensions()
if (dimensions) { if (dimensions) {
// 故意减少 1 行,在底部留出真实的换行空间,避免贴底
const adjustedRows = Math.max(1, dimensions.rows - 1)
terminalStore.resizeTerminal({ terminalStore.resizeTerminal({
rows: dimensions.rows, rows: adjustedRows,
cols: dimensions.cols, cols: dimensions.cols,
height: dimensions.height, height: dimensions.height,
width: dimensions.width width: dimensions.width
}) })
term.resize(dimensions.cols, adjustedRows)
} }
} }
} }
@@ -634,7 +624,6 @@ onMounted(async () => {
if (terminalStore.isConnected && term) { if (terminalStore.isConnected && term) {
// 已连接:显示欢迎信息 // 已连接:显示欢迎信息
term.clear() term.clear()
term.write('\x1b[32mSSH连接已建立,可以开始输入命令\x1b[0m\r\n\r\n')
// 写入已缓冲的输出数据 // 写入已缓冲的输出数据
if (terminalStore.terminalOutput) { if (terminalStore.terminalOutput) {
@@ -676,6 +665,8 @@ onUnmounted(() => {
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
background-color: #1e1e1e; background-color: #1e1e1e;
display: flex;
flex-direction: column;
} }
.terminal-container { .terminal-container {
@@ -770,18 +761,19 @@ onUnmounted(() => {
padding: 0 !important; padding: 0 !important;
position: relative; position: relative;
transition: width 0.3s ease; transition: width 0.3s ease;
height: 100%; flex: 1;
min-height: 400px; min-height: 0;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.terminal { .terminal {
flex: 1;
width: 100%; width: 100%;
height: 100%; min-height: 0;
padding: 0; padding: 0;
padding-bottom: 8px; padding-bottom: 8px; /* 轻微底部间隙 */
cursor: text; cursor: text;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
@@ -909,6 +901,14 @@ onUnmounted(() => {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }
/* 强制在 xterm-screen 后方追加一段空白,确保滚动到底部时有足够间距 */
:deep(.xterm-screen::after) {
content: "";
display: block;
height: 32px;
width: 100%;
}
:deep(.xterm-helper-textarea) { :deep(.xterm-helper-textarea) {
opacity: 0 !important; opacity: 0 !important;
color: transparent !important; color: transparent !important;