feat: Implement quick connect layout change
This commit is contained in:
1 parent
b4e8db583d
commit
de1cf2bbc7
4 files changed
+159
-65
No files matched your search
@@ -34,15 +34,20 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: '/quick-connect',
|
||||
name: 'QuickConnect',
|
||||
component: () => import('@/views/QuickConnect.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/quick-connect-terminal',
|
||||
name: 'QuickConnectTerminal',
|
||||
component: () => import('@/views/QuickConnectTerminal.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
component: () => import('@/views/QuickConnectLayout.vue'),
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'QuickConnect',
|
||||
component: () => import('@/views/QuickConnect.vue')
|
||||
},
|
||||
{
|
||||
path: 'terminal',
|
||||
name: 'QuickConnectTerminal',
|
||||
component: () => import('@/views/QuickConnectTerminal.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/terminal-new',
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
<template>
|
||||
<div class="quick-connect-page">
|
||||
<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">
|
||||
<!-- 左侧:连接历史树 -->
|
||||
@@ -526,7 +515,7 @@ const handleConnect = async () => {
|
||||
terminalStore.quickConnect(connectionInfo)
|
||||
|
||||
connecting.value = false
|
||||
router.push('/quick-connect-terminal')
|
||||
router.push('/quick-connect/terminal')
|
||||
|
||||
} catch (error) {
|
||||
connecting.value = false
|
||||
@@ -541,7 +530,7 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.quick-connect-page {
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
@@ -551,27 +540,6 @@ onMounted(() => {
|
||||
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 {
|
||||
flex: 1;
|
||||
|
||||
@@ -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>
|
||||
@@ -1,18 +1,6 @@
|
||||
<template>
|
||||
<div class="terminal-page">
|
||||
<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">
|
||||
<!-- SFTP文件管理器侧边栏 -->
|
||||
@@ -531,7 +519,6 @@ const resetConnectedState = () => {
|
||||
if (term) {
|
||||
term.reset()
|
||||
term.focus()
|
||||
term.write('\x1b[32mSSH连接已建立,可以开始输入命令\x1b[0m\r\n\r\n')
|
||||
term.write('\x1b[?25h')
|
||||
if (typeof term.scrollToTop === 'function') {
|
||||
term.scrollToTop()
|
||||
@@ -568,8 +555,6 @@ watch(() => terminalStore.isConnected, (isConnected, wasConnected) => {
|
||||
term.focus()
|
||||
|
||||
setTimeout(() => {
|
||||
term.write('\x1b[32mSSH连接已建立,可以开始输入命令\x1b[0m\r\n\r\n')
|
||||
|
||||
term.write('\x1b[?25h')
|
||||
|
||||
if (typeof term.scrollToTop === 'function') {
|
||||
@@ -605,17 +590,22 @@ watch(() => terminalStore.connectionError, (error) => {
|
||||
|
||||
// 监听窗口大小变化
|
||||
const handleResize = () => {
|
||||
if (fitAddon) {
|
||||
if (fitAddon && term) {
|
||||
fitAddon.fit()
|
||||
|
||||
const dimensions = fitAddon.proposeDimensions()
|
||||
if (dimensions) {
|
||||
// 故意减少 1 行,在底部留出真实的换行空间,避免贴底
|
||||
const adjustedRows = Math.max(1, dimensions.rows - 1)
|
||||
|
||||
terminalStore.resizeTerminal({
|
||||
rows: dimensions.rows,
|
||||
rows: adjustedRows,
|
||||
cols: dimensions.cols,
|
||||
height: dimensions.height,
|
||||
width: dimensions.width
|
||||
})
|
||||
|
||||
term.resize(dimensions.cols, adjustedRows)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -634,7 +624,6 @@ onMounted(async () => {
|
||||
if (terminalStore.isConnected && term) {
|
||||
// 已连接:显示欢迎信息
|
||||
term.clear()
|
||||
term.write('\x1b[32mSSH连接已建立,可以开始输入命令\x1b[0m\r\n\r\n')
|
||||
|
||||
// 写入已缓冲的输出数据
|
||||
if (terminalStore.terminalOutput) {
|
||||
@@ -676,6 +665,8 @@ onUnmounted(() => {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-color: #1e1e1e;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.terminal-container {
|
||||
@@ -770,18 +761,19 @@ onUnmounted(() => {
|
||||
padding: 0 !important;
|
||||
position: relative;
|
||||
transition: width 0.3s ease;
|
||||
height: 100%;
|
||||
min-height: 400px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
padding-bottom: 8px;
|
||||
padding-bottom: 8px; /* 轻微底部间隙 */
|
||||
cursor: text;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@@ -909,6 +901,14 @@ onUnmounted(() => {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
|
||||
/* 强制在 xterm-screen 后方追加一段空白,确保滚动到底部时有足够间距 */
|
||||
:deep(.xterm-screen::after) {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.xterm-helper-textarea) {
|
||||
opacity: 0 !important;
|
||||
color: transparent !important;
|
||||
|
||||
Reference in new issue
Block a user