update
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const emit = defineEmits(['send-command']);
|
||||
const { t } = useI18n();
|
||||
|
||||
const commandInput = ref('');
|
||||
|
||||
const sendCommand = () => {
|
||||
const command = commandInput.value.trim();
|
||||
if (command) {
|
||||
console.log(`[CommandInputBar] Sending command: ${command}`);
|
||||
emit('send-command', command + '\n'); // 发送命令并附加换行符,模拟回车
|
||||
commandInput.value = ''; // 清空输入框
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="command-input-bar">
|
||||
<div class="input-wrapper">
|
||||
<input
|
||||
type="text"
|
||||
v-model="commandInput"
|
||||
:placeholder="t('commandInputBar.placeholder')"
|
||||
class="command-input"
|
||||
@keydown.enter="sendCommand"
|
||||
/>
|
||||
<!-- 可以在这里添加按钮 -->
|
||||
</div>
|
||||
<!-- 可以在这里添加其他按钮 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.command-input-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
background-color: #f0f0f0; /* 背景色,可调整 */
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
min-height: 40px; /* 保证一定高度 */
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
flex-grow: 1; /* 让输入框容器占据大部分空间 */
|
||||
display: flex;
|
||||
justify-content: center; /* 水平居中输入框 */
|
||||
}
|
||||
|
||||
.command-input {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
width: 60%; /* 输入框宽度,可调整 */
|
||||
max-width: 800px; /* 最大宽度 */
|
||||
outline: none;
|
||||
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.command-input:focus {
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
/* 可以添加按钮样式 */
|
||||
/*
|
||||
.command-input-bar button {
|
||||
margin-left: 10px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
*/
|
||||
</style>
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
import { ref, PropType } from 'vue'; // 导入 ref
|
||||
import WorkspaceConnectionListComponent from './WorkspaceConnectionList.vue'; // 导入连接列表组件
|
||||
import { useSessionStore } from '../stores/session.store'; // 导入 session store
|
||||
// 导入会话状态类型
|
||||
import type { SessionTabInfoWithStatus } from '../stores/session.store'; // 导入更新后的类型
|
||||
|
||||
@@ -28,6 +30,22 @@ const closeSession = (event: MouseEvent, sessionId: string) => {
|
||||
event.stopPropagation(); // 阻止事件冒泡到标签点击事件
|
||||
emit('close-session', sessionId);
|
||||
};
|
||||
|
||||
// --- 新增:弹出窗口状态和处理 ---
|
||||
const sessionStore = useSessionStore();
|
||||
const showConnectionListPopup = ref(false);
|
||||
|
||||
const togglePopup = () => {
|
||||
showConnectionListPopup.value = !showConnectionListPopup.value;
|
||||
};
|
||||
|
||||
// 处理从弹出列表中选择连接的事件
|
||||
const handlePopupConnect = (connectionId: number) => {
|
||||
console.log(`[TabBar] Popup connect request for ID: ${connectionId}`);
|
||||
// 使用 sessionStore 的方法来处理连接请求(它现在总是新建标签)
|
||||
sessionStore.handleConnectRequest(connectionId);
|
||||
showConnectionListPopup.value = false; // 关闭弹出窗口
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -48,8 +66,23 @@ const closeSession = (event: MouseEvent, sessionId: string) => {
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- 可以添加一个 "+" 按钮来打开新标签/连接 -->
|
||||
<!-- <button class="add-tab-button">+</button> -->
|
||||
<!-- 新增 "+" 按钮 -->
|
||||
<button class="add-tab-button" @click="togglePopup" title="新建连接标签页">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
|
||||
<!-- 新增:连接列表弹出窗口 -->
|
||||
<div v-if="showConnectionListPopup" class="connection-list-popup" @click.self="togglePopup">
|
||||
<div class="popup-content">
|
||||
<button class="popup-close-button" @click="togglePopup">×</button>
|
||||
<h3>选择要连接的服务器</h3>
|
||||
<WorkspaceConnectionListComponent
|
||||
@connect-request="handlePopupConnect"
|
||||
@open-new-session="handlePopupConnect"
|
||||
class="popup-connection-list"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -60,7 +93,8 @@ const closeSession = (event: MouseEvent, sessionId: string) => {
|
||||
border-bottom: 1px solid #bdbdbd;
|
||||
overflow-x: auto; /* 如果标签过多则允许水平滚动 */
|
||||
white-space: nowrap;
|
||||
padding: 0 0.5rem; /* 左右留出一点空间 */
|
||||
/* padding: 0 0.5rem; */ /* 移除左右内边距,让标签列表和按钮自己控制 */
|
||||
padding-right: 0.5rem; /* 只保留右侧内边距给按钮 */
|
||||
height: 2.5rem; /* 固定标签栏高度 */
|
||||
box-sizing: border-box; /* 确保 padding 不会增加总高度 */
|
||||
}
|
||||
@@ -83,8 +117,8 @@ const closeSession = (event: MouseEvent, sessionId: string) => {
|
||||
|
||||
.tab-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding: 0; /* 确保列表无内边距 */
|
||||
margin: 0; /* 确保列表无外边距 */
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -156,18 +190,91 @@ const closeSession = (event: MouseEvent, sessionId: string) => {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* 可选:添加新标签按钮样式 */
|
||||
/*
|
||||
/* 添加新标签按钮样式 */
|
||||
.add-tab-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0.5rem 0.8rem;
|
||||
border-left: 1px solid #bdbdbd; /* 左侧分隔线 */
|
||||
padding: 0 0.8rem;
|
||||
/* margin-left: 0.5rem; */ /* 移除左外边距 */
|
||||
cursor: pointer;
|
||||
font-size: 1.2em;
|
||||
font-size: 1.1em;
|
||||
color: #616161;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
flex-shrink: 0; /* 防止按钮被压缩 */
|
||||
}
|
||||
.add-tab-button:hover {
|
||||
background-color: #d0d0d0;
|
||||
}
|
||||
*/
|
||||
.add-tab-button i {
|
||||
line-height: 1; /* 确保图标垂直居中 */
|
||||
}
|
||||
|
||||
/* 弹出窗口样式 */
|
||||
.connection-list-popup {
|
||||
position: fixed; /* 固定定位,覆盖整个屏幕 */
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5); /* 半透明背景 */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000; /* 确保在最上层 */
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
width: 80%;
|
||||
max-width: 500px; /* 限制最大宽度 */
|
||||
max-height: 80vh; /* 限制最大高度 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative; /* 为了关闭按钮定位 */
|
||||
}
|
||||
|
||||
.popup-close-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: #aaa;
|
||||
line-height: 1;
|
||||
}
|
||||
.popup-close-button:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup-content h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup-connection-list {
|
||||
flex-grow: 1; /* 让列表占据剩余空间 */
|
||||
overflow-y: auto; /* 列表内容滚动 */
|
||||
/* 可能需要覆盖 WorkspaceConnectionList 的一些默认样式 */
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* 覆盖 WorkspaceConnectionList 内部样式(如果需要) */
|
||||
/* :deep(.popup-connection-list .search-add-bar) { */
|
||||
/* display: none; */ /* 不再隐藏搜索栏 */
|
||||
/* } */
|
||||
:deep(.popup-connection-list .connection-list-area) {
|
||||
padding: 0; /* 保持移除内边距 */
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -108,19 +108,26 @@ const toggleGroup = (groupName: string) => {
|
||||
expandedGroups.value[groupName] = !expandedGroups.value[groupName];
|
||||
};
|
||||
|
||||
// 处理单击连接
|
||||
// 处理单击连接 (左键)
|
||||
const handleConnect = (connectionId: number) => {
|
||||
console.log(`[WkspConnList] handleConnect (左键) called for ID: ${connectionId}`);
|
||||
emit('connect-request', connectionId);
|
||||
console.log(`[WkspConnList] Emitted 'connect-request' for ID: ${connectionId}`);
|
||||
closeContextMenu(); // 点击连接后关闭菜单
|
||||
};
|
||||
|
||||
// 显示右键菜单
|
||||
const showContextMenu = (event: MouseEvent, connection: ConnectionInfo) => {
|
||||
console.log(`[WkspConnList] showContextMenu (右键) called for ID: ${connection.id}. Event:`, event);
|
||||
event.preventDefault(); // 再次确保阻止默认行为
|
||||
event.stopPropagation(); // 阻止事件冒泡
|
||||
console.log('[WkspConnList] Right-click default prevented and propagation stopped.');
|
||||
contextTargetConnection.value = connection;
|
||||
contextMenuPosition.value = { x: event.clientX, y: event.clientY };
|
||||
contextMenuVisible.value = true;
|
||||
// 添加全局点击监听器以关闭菜单
|
||||
document.addEventListener('click', closeContextMenu, { once: true });
|
||||
return false; // 彻底停止事件处理
|
||||
};
|
||||
|
||||
// 关闭右键菜单
|
||||
@@ -159,7 +166,9 @@ onMounted(() => {
|
||||
|
||||
// 处理中键点击(在新标签页打开)
|
||||
const handleOpenInNewTab = (connectionId: number) => {
|
||||
console.log(`[WkspConnList] handleOpenInNewTab (中键/辅助键) called for ID: ${connectionId}`);
|
||||
emit('open-new-session', connectionId);
|
||||
console.log(`[WkspConnList] Emitted 'open-new-session' for ID: ${connectionId}`);
|
||||
closeContextMenu(); // 如果右键菜单是打开的,也关闭它
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -254,12 +254,22 @@ export function createSshTerminalManager(sessionId: string, wsDeps: SshTerminalD
|
||||
console.log(`[会话 ${sessionId}][SSH终端模块] 已清理。`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 直接发送数据到 SSH 会话 (例如,从命令输入栏)
|
||||
* @param data 要发送的字符串数据
|
||||
*/
|
||||
const sendData = (data: string) => {
|
||||
// console.debug(`[会话 ${sessionId}][SSH终端模块] 直接发送数据:`, data);
|
||||
sendMessage({ type: 'ssh:input', sessionId, payload: { data } });
|
||||
};
|
||||
|
||||
// 返回工厂实例
|
||||
return {
|
||||
// 公共接口
|
||||
handleTerminalReady,
|
||||
handleTerminalData,
|
||||
handleTerminalData, // 这个处理来自 xterm.js 的输入
|
||||
handleTerminalResize,
|
||||
sendData, // 新增:允许外部直接发送数据
|
||||
cleanup
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,15 +106,47 @@ export function createWebSocketConnectionManager(sessionId: string, dbConnection
|
||||
reconnectTimeoutId = null;
|
||||
}
|
||||
|
||||
// 防止重复连接同一实例
|
||||
if (ws.value && (ws.value.readyState === WebSocket.OPEN || ws.value.readyState === WebSocket.CONNECTING)) {
|
||||
console.warn(`[WebSocket ${instanceSessionId}] 连接已打开或正在连接中。`);
|
||||
// --- 修改后的检查逻辑 ---
|
||||
// 只有当 ws 实例存在,且其状态为 OPEN 或 CONNECTING,
|
||||
// 并且我们自己维护的状态也是 connected 或 connecting 时,才阻止连接。
|
||||
if (ws.value &&
|
||||
(ws.value.readyState === WebSocket.OPEN || ws.value.readyState === WebSocket.CONNECTING) &&
|
||||
(connectionStatus.value === 'connected' || connectionStatus.value === 'connecting')
|
||||
) {
|
||||
console.warn(`[WebSocket ${instanceSessionId}] 连接已打开或正在连接中 (readyState: ${ws.value.readyState}, status: ${connectionStatus.value})。 阻止重复连接。`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理状态不一致或旧连接未完全关闭的情况
|
||||
if (ws.value && (ws.value.readyState === WebSocket.OPEN || ws.value.readyState === WebSocket.CONNECTING)) {
|
||||
// readyState 是 OPEN/CONNECTING 但 connectionStatus 是 disconnected/error
|
||||
console.warn(`[WebSocket ${instanceSessionId}] 检测到状态不一致 (readyState: ${ws.value.readyState}, status: ${connectionStatus.value})。尝试关闭旧连接并继续...`);
|
||||
// 临时标记为主动断开,防止 onclose 触发 scheduleReconnect
|
||||
const oldWs = ws.value; // 保存旧 ws 引用
|
||||
const previousIntentionalDisconnect = intentionalDisconnect;
|
||||
intentionalDisconnect = true;
|
||||
// 在关闭前移除监听器,防止旧的 onclose 干扰
|
||||
if (oldWs) {
|
||||
console.log(`[WebSocket ${instanceSessionId}] 移除旧连接的事件监听器...`);
|
||||
oldWs.onopen = null;
|
||||
oldWs.onmessage = null;
|
||||
oldWs.onerror = null;
|
||||
oldWs.onclose = null; // 阻止旧的 onclose 干扰
|
||||
console.log(`[WebSocket ${instanceSessionId}] 关闭旧连接 (强制)...`);
|
||||
oldWs.close(1000, '状态不一致,强制重连');
|
||||
}
|
||||
ws.value = null; // 清理 shallowRef 中的引用
|
||||
intentionalDisconnect = previousIntentionalDisconnect; // 恢复标记
|
||||
console.log(`[WebSocket ${instanceSessionId}] 旧连接处理完毕。`);
|
||||
} else if (ws.value && ws.value.readyState === WebSocket.CLOSING) {
|
||||
console.log(`[WebSocket ${instanceSessionId}] 检测到旧连接正在关闭 (readyState: ${ws.value.readyState})。清理引用并继续创建新连接...`);
|
||||
ws.value = null; // 清理引用,让后续逻辑创建新的
|
||||
}
|
||||
// 如果 ws.value 存在且 readyState 是 CLOSED,它应该已经在 onclose 中被设为 null
|
||||
|
||||
console.log(`[WebSocket ${instanceSessionId}] 尝试连接到: ${url} (DB Conn ID: ${instanceDbConnectionId})`);
|
||||
statusMessage.value = getStatusText('connectingWs', { url });
|
||||
connectionStatus.value = 'connecting';
|
||||
connectionStatus.value = 'connecting'; // 确保状态设置为 connecting
|
||||
isSftpReady.value = false; // 重置 SFTP 状态
|
||||
|
||||
try {
|
||||
|
||||
@@ -519,5 +519,8 @@
|
||||
"untagged": "未标记",
|
||||
"searchPlaceholder": "搜索名称或主机...",
|
||||
"noResults": "未找到匹配 \"{searchTerm}\" 的连接。"
|
||||
}
|
||||
},
|
||||
"commandInputBar": {
|
||||
"placeholder": "在此输入命令后按 Enter 发送到终端..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,38 +198,46 @@ export const useSessionStore = defineStore('session', () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理连接列表的左键点击(连接或激活)
|
||||
* 处理连接列表的左键点击(如果点击的是当前活动标签且断开则重连,否则总是新建标签)
|
||||
*/
|
||||
const handleConnectRequest = (connectionId: number | string) => {
|
||||
const connIdStr = String(connectionId);
|
||||
console.log(`[SessionStore] 处理连接请求: ${connIdStr}`);
|
||||
console.log(`[SessionStore] handleConnectRequest called for ID: ${connIdStr}`);
|
||||
|
||||
let existingSession: SessionState | null = null;
|
||||
let existingSessionId: string | null = null;
|
||||
// 查找是否存在对应 connectionId 的会话
|
||||
for (const [sessionId, session] of sessions.value.entries()) {
|
||||
if (session.connectionId === connIdStr) {
|
||||
existingSession = session;
|
||||
existingSessionId = sessionId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingSessionId) {
|
||||
if (activeSessionId.value !== existingSessionId) {
|
||||
console.log(`[SessionStore] 激活已存在的会话: ${existingSessionId}`);
|
||||
activateSession(existingSessionId);
|
||||
// 检查点击的连接是否是当前活动的标签页
|
||||
if (existingSession && existingSessionId && existingSessionId === activeSessionId.value) {
|
||||
// 是当前活动标签页
|
||||
const currentStatus = existingSession.wsManager.connectionStatus.value;
|
||||
console.log(`[SessionStore] 点击的是当前活动会话 ${existingSessionId},状态: ${currentStatus}`);
|
||||
if (currentStatus === 'disconnected' || currentStatus === 'error') {
|
||||
// 如果已断开或出错,则尝试重连
|
||||
console.log(`[SessionStore] 活动会话 ${existingSessionId} 已断开或出错,尝试重连...`);
|
||||
const wsUrl = `ws://${window.location.hostname}:3001`; // TODO: 从配置获取 URL
|
||||
existingSession.wsManager.connect(wsUrl);
|
||||
// 不需要再调用 activateSession,因为它已经是活动的
|
||||
} else {
|
||||
console.log(`[SessionStore] 点击的连接 ${connIdStr} 已在活动会话 ${existingSessionId} 中,无需操作。`);
|
||||
// 如果状态正常,则无需操作
|
||||
console.log(`[SessionStore] 活动会话 ${existingSessionId} 状态正常,无需操作。`);
|
||||
}
|
||||
} else {
|
||||
// 当前行为:替换当前活动会话(如果存在)
|
||||
if (activeSession.value) {
|
||||
console.log(`[SessionStore] 替换当前会话 ${activeSessionId.value} 为新连接 ${connIdStr}`);
|
||||
closeSession(activeSessionId.value!); // 确保 activeSessionId 存在
|
||||
openNewSession(connIdStr);
|
||||
// 点击的不是当前活动标签(可能是非活动标签,或根本不存在),总是新建标签页
|
||||
if (existingSessionId) {
|
||||
console.log(`[SessionStore] 点击的连接 ${connIdStr} 存在于非活动会话 ${existingSessionId} 中,将打开新会话。`);
|
||||
} else {
|
||||
console.log(`[SessionStore] 当前无活动会话,打开新会话: ${connIdStr}`);
|
||||
openNewSession(connIdStr);
|
||||
console.log(`[SessionStore] 未找到 ID 为 ${connIdStr} 的现有会话,将打开新会话。`);
|
||||
}
|
||||
// 备选行为:总是打开新标签页?需要调整 openNewSession 逻辑
|
||||
openNewSession(connIdStr); // 直接调用 openNewSession
|
||||
}
|
||||
};
|
||||
|
||||
@@ -237,7 +245,7 @@ export const useSessionStore = defineStore('session', () => {
|
||||
* 处理连接列表的中键点击(总是打开新会话)
|
||||
*/
|
||||
const handleOpenNewSession = (connectionId: number | string) => {
|
||||
console.log(`[SessionStore] 处理打开新会话请求: ${connectionId}`);
|
||||
console.log(`[SessionStore] handleOpenNewSession called for ID: ${connectionId}`);
|
||||
openNewSession(connectionId);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,12 +8,13 @@ import StatusMonitorComponent from '../components/StatusMonitor.vue';
|
||||
import WorkspaceConnectionListComponent from '../components/WorkspaceConnectionList.vue';
|
||||
import AddConnectionFormComponent from '../components/AddConnectionForm.vue';
|
||||
import TerminalTabBar from '../components/TerminalTabBar.vue';
|
||||
import { useSessionStore, type SessionTabInfoWithStatus } from '../stores/session.store';
|
||||
import CommandInputBar from '../components/CommandInputBar.vue'; // 导入新组件
|
||||
import { useSessionStore, type SessionTabInfoWithStatus, type SshTerminalInstance } from '../stores/session.store'; // 导入 SshTerminalInstance
|
||||
import type { ConnectionInfo } from '../stores/connections.store';
|
||||
// 导入 splitpanes 组件
|
||||
import { Splitpanes, Pane } from 'splitpanes';
|
||||
// 导入管理器实例类型,用于 FileManagerComponent 的 prop 类型断言
|
||||
import type { SftpManagerInstance } from '../stores/session.store';
|
||||
// import type { SftpManagerInstance } from '../stores/session.store'; // SftpManagerInstance 已在上面导入
|
||||
|
||||
// --- Setup ---
|
||||
const { t } = useI18n();
|
||||
@@ -61,6 +62,20 @@ onBeforeUnmount(() => {
|
||||
console.log('[工作区视图] 连接已更新');
|
||||
handleFormClose();
|
||||
};
|
||||
|
||||
// 处理命令发送
|
||||
const handleSendCommand = (command: string) => {
|
||||
// 类型断言确保 terminalManager 存在 sendData 方法
|
||||
const terminalManager = activeSession.value?.terminalManager as (SshTerminalInstance | undefined);
|
||||
if (terminalManager && typeof terminalManager.sendData === 'function') {
|
||||
console.log(`[WorkspaceView] Sending command to active session ${activeSessionId.value}: ${command.trim()}`);
|
||||
// 注意:CommandInputBar 已经添加了 '\n'
|
||||
terminalManager.sendData(command);
|
||||
} else {
|
||||
console.warn('[WorkspaceView] Cannot send command, no active session or terminal manager with sendData method.');
|
||||
// 可以考虑给用户一个提示
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -79,19 +94,19 @@ onBeforeUnmount(() => {
|
||||
<!-- 左侧边栏 Pane -->
|
||||
<pane size="20" min-size="15" class="sidebar-pane">
|
||||
<WorkspaceConnectionListComponent
|
||||
@connect-request="sessionStore.handleConnectRequest"
|
||||
@open-new-session="sessionStore.handleOpenNewSession"
|
||||
@request-add-connection="handleRequestAddConnection"
|
||||
@request-edit-connection="handleRequestEditConnection"
|
||||
@connect-request="(id) => { console.log(`[WorkspaceView] Received 'connect-request' event for ID: ${id}`); sessionStore.handleConnectRequest(id); }"
|
||||
@open-new-session="(id) => { console.log(`[WorkspaceView] Received 'open-new-session' event for ID: ${id}`); sessionStore.handleOpenNewSession(id); }"
|
||||
@request-add-connection="() => { console.log('[WorkspaceView] Received \'request-add-connection\' event'); handleRequestAddConnection(); }"
|
||||
@request-edit-connection="(conn) => { console.log(`[WorkspaceView] Received 'request-edit-connection' event for connection:`, conn); handleRequestEditConnection(conn); }"
|
||||
/>
|
||||
</pane>
|
||||
|
||||
<!-- 中间区域 Pane (包含终端和文件管理器) -->
|
||||
<pane size="65" min-size="30">
|
||||
<!-- 上下分割 (终端 | 文件管理器) -->
|
||||
<splitpanes :horizontal="true" style="height: 100%">
|
||||
<!-- 终端 Pane -->
|
||||
<pane size="65" min-size="20" class="terminal-pane">
|
||||
<!-- 中间区域 Pane (包含终端、命令栏、文件管理器) -->
|
||||
<pane size="65" min-size="30" class="middle-pane">
|
||||
<!-- 上下分割 (终端 | 命令栏 | 文件管理器) - 禁用双击分割线行为 -->
|
||||
<splitpanes :horizontal="true" style="height: 100%" :dbl-click-splitter="false">
|
||||
<!-- 上方 Pane (终端) -->
|
||||
<pane size="60" min-size="20" class="terminal-pane">
|
||||
<!-- 会话终端区域: 只渲染活动会话的终端 -->
|
||||
<div
|
||||
v-for="tabInfo in sessionTabsWithStatus"
|
||||
@@ -116,13 +131,17 @@ onBeforeUnmount(() => {
|
||||
<h2>{{ t('workspace.selectConnectionPrompt') }}</h2>
|
||||
<p>{{ t('workspace.selectConnectionHint') }}</p>
|
||||
</div>
|
||||
<!-- 终端占位符 -->
|
||||
<div v-if="!activeSessionId" class="terminal-placeholder">
|
||||
<h2>{{ t('workspace.selectConnectionPrompt') }}</h2>
|
||||
<p>{{ t('workspace.selectConnectionHint') }}</p>
|
||||
</div>
|
||||
</pane>
|
||||
<!-- 文件管理器 Pane -->
|
||||
</pane> <!-- End Terminal Pane -->
|
||||
|
||||
<!-- 中间 Pane (命令栏) - 恢复,仅设置 min-size -->
|
||||
<pane size="5" min-size="5" class="command-bar-pane">
|
||||
<CommandInputBar
|
||||
v-if="activeSessionId"
|
||||
@send-command="handleSendCommand"
|
||||
/>
|
||||
</pane> <!-- End Command Bar Pane -->
|
||||
|
||||
<!-- 下方 Pane (文件管理器) - 恢复原始 size -->
|
||||
<pane size="35" min-size="15" class="file-manager-pane">
|
||||
<!-- 为每个会话渲染文件管理器实例,用 v-show 控制 -->
|
||||
<div
|
||||
@@ -148,8 +167,8 @@ onBeforeUnmount(() => {
|
||||
<!-- 文件管理器占位符 -->
|
||||
<div v-if="!activeSessionId" class="pane-placeholder">{{ t('fileManager.noActiveSession') }}</div>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</pane>
|
||||
</splitpanes> <!-- End Terminal/FM Splitpanes -->
|
||||
</pane> <!-- End Middle Pane -->
|
||||
|
||||
<!-- 右侧边栏 Pane (状态监视器) - 添加 status-monitor-pane 类 -->
|
||||
<pane size="15" min-size="10" class="sidebar-pane status-monitor-pane">
|
||||
@@ -190,10 +209,12 @@ onBeforeUnmount(() => {
|
||||
.workspace-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 60px - 30px - 2rem); /* 调整以适应您的 header/footer/padding */
|
||||
height: calc(100vh - 60px - 30px - 2rem); /* 恢复原始高度计算 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 移除 fixed-command-bar 样式 */
|
||||
|
||||
.main-content-area {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -203,19 +224,40 @@ onBeforeUnmount(() => {
|
||||
|
||||
/* 为 Pane 添加一些基本样式 */
|
||||
.sidebar-pane, /* 用于左右侧边栏 */
|
||||
.middle-pane, /* 中间包含终端、命令栏、文件管理器的 Pane */
|
||||
.terminal-pane,
|
||||
.command-bar-pane, /* 命令栏 Pane */
|
||||
.file-manager-pane {
|
||||
display: flex;
|
||||
display: flex; /* 确保 Pane 内容可以正确布局 */
|
||||
flex-direction: column;
|
||||
overflow: hidden; /* Pane 内部内容溢出时隐藏 */
|
||||
background-color: #f8f9fa; /* 默认背景色 */
|
||||
}
|
||||
.middle-pane {
|
||||
padding: 0; /* 移除 middle-pane 的内边距 */
|
||||
}
|
||||
|
||||
/* 命令栏 Pane 特定样式 - 添加 max-height */
|
||||
.command-bar-pane {
|
||||
background-color: #e9ecef; /* 背景色 */
|
||||
justify-content: center; /* 垂直居中输入框 */
|
||||
max-height: 200px; /* 使用 CSS 限制最大高度,例如 200px */
|
||||
overflow: auto; /* 如果内容超出,允许滚动 */
|
||||
}
|
||||
/* 调整内部 CommandInputBar 样式 */
|
||||
.command-bar-pane > .command-input-bar {
|
||||
border: none; /* 移除 CommandInputBar 的边框 */
|
||||
background-color: transparent; /* 移除 CommandInputBar 的背景 */
|
||||
min-height: auto; /* 移除最小高度 */
|
||||
padding: 2px 10px; /* 调整内边距 */
|
||||
}
|
||||
|
||||
.terminal-pane {
|
||||
background-color: #1e1e1e; /* 终端背景 */
|
||||
position: relative; /* 保持相对定位用于占位符 */
|
||||
}
|
||||
.file-manager-pane {
|
||||
border-top: 1px solid #ccc; /* 终端和文件管理器之间的分隔线 */
|
||||
/* 分隔线由 splitpanes 提供 */
|
||||
}
|
||||
|
||||
/* 终端会话包装器 */
|
||||
@@ -234,14 +276,6 @@ onBeforeUnmount(() => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 文件管理器包装器 (内部组件应填充) */
|
||||
.file-manager-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 状态监视器包装器 (内部组件应填充) */
|
||||
.status-monitor-wrapper {
|
||||
flex: 1;
|
||||
@@ -267,14 +301,6 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
|
||||
/* 终端占位符 */
|
||||
.terminal-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* 终端占位符 */
|
||||
.terminal-placeholder {
|
||||
position: absolute;
|
||||
@@ -353,4 +379,9 @@ onBeforeUnmount(() => {
|
||||
bottom: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 尝试提高中间区域水平分割线的 z-index */
|
||||
.middle-pane .splitpanes--horizontal > .splitpanes__splitter {
|
||||
z-index: 10; /* 确保分割线在内容之上 */
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user