This commit is contained in:
Baobhan Sith
2025-04-15 20:39:30 +08:00
parent 37eb5ee9ab
commit 6ee18743ad
14 changed files with 622 additions and 117 deletions
+174 -67
View File
@@ -1,23 +1,33 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue'; // Added watch
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; // 引入 useI18n
import TerminalComponent from '../components/Terminal.vue'; // 引入终端组件
import FileManagerComponent from '../components/FileManager.vue'; // 引入文件管理器组件
import StatusMonitorComponent from '../components/StatusMonitor.vue'; // 引入状态监控组件
import type { Terminal } from 'xterm'; // 引入 Terminal 类型
import { useWebSocketConnection } from '../composables/useWebSocketConnection'; // 只导入 hook
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'; // 移除 computed, useRoute, useRouter
import { useI18n } from 'vue-i18n';
import TerminalComponent from '../components/Terminal.vue';
import FileManagerComponent from '../components/FileManager.vue';
import StatusMonitorComponent from '../components/StatusMonitor.vue';
import WorkspaceConnectionListComponent from '../components/WorkspaceConnectionList.vue';
import AddConnectionFormComponent from '../components/AddConnectionForm.vue'; // 引入表单组件
import type { ConnectionInfo } from '../stores/connections.store'; // 引入 ConnectionInfo 类型
import type { Terminal } from 'xterm';
import { useWebSocketConnection } from '../composables/useWebSocketConnection';
import { useSshTerminal } from '../composables/useSshTerminal'; // 导入 SSH 终端模块
import { useStatusMonitor } from '../composables/useStatusMonitor'; // 导入状态监控模块
import type { ServerStatus } from '../types/server.types'; // 从类型文件导入 ServerStatus
// Removed duplicate/unused import: import type { WebSocketMessage, MessagePayload } from '../types/websocket.types';
import { useStatusMonitor } from '../composables/useStatusMonitor';
import type { ServerStatus } from '../types/server.types';
// import { useConnectionsStore } from '../stores/connections.store'; // 不再直接在此处使用 store
// import { storeToRefs } from 'pinia'; // 不再直接在此处使用 storeToRefs
// --- 接口定义 ---
// ServerStatus 现在从 types/server.types.ts 导入
const { t } = useI18n(); // 获取 t 函数
const route = useRoute();
const connectionId = computed(() => route.params.connectionId as string); // 从路由获取 connectionId
const { t } = useI18n();
// --- 内部状态 ---
const activeConnectionId = ref<string | null>(null);
const showAddEditForm = ref(false); // 控制表单模态框显示
const connectionToEdit = ref<ConnectionInfo | null>(null); // 要编辑的连接
// --- 连接 Store (不再需要在此处直接引用 connections, loading, error) ---
// const connectionsStore = useConnectionsStore();
// const { connections, isLoading: connectionsLoading, error: connectionsError } = storeToRefs(connectionsStore);
// --- WebSocket 连接模块 ---
const {
@@ -51,15 +61,16 @@ const {
// --- 生命周期钩子 ---
onMounted(() => {
if (connectionId.value) {
const wsUrl = `ws://${window.location.hostname}:3001`; // 构建 WebSocket URL
connect(wsUrl, connectionId.value); // 使用 WebSocket 模块的 connect
// 组件挂载时不自动连接,等待用户选择
// if (activeConnectionId.value) {
// const wsUrl = `ws://${window.location.hostname}:3001`;
// connect(wsUrl, activeConnectionId.value);
// 不在此处立即注册,等待 isConnected 变为 true
// registerSshHandlers();
// registerStatusHandlers();
} else {
console.error('[工作区视图] 缺少 connectionId 路由参数。');
}
// } else {
// console.log('[工作区视图] 没有活动的连接 ID。'); // 不再是错误
// }
});
onBeforeUnmount(() => {
@@ -68,28 +79,21 @@ onBeforeUnmount(() => {
unregisterAllStatusHandlers(); // 使用状态监控模块的注销函数
});
// 监听 connectionId 变化 (例如,在工作区之间导航)
watch(connectionId, (newId, oldId) => {
if (newId && newId !== oldId) {
console.log(`[工作区视图] 连接 ID 从 ${oldId} 更改为 ${newId}。正在重新连接...`);
// 断开现有连接,注销处理器,然后为新 ID 连接并注册
disconnect();
unregisterAllSshHandlers();
unregisterAllStatusHandlers(); // 使用状态监控模块的注销函数
// serverStatus 和 statusError 由 useStatusMonitor 自动管理,无需手动重置
// 重新连接
// 监听 activeConnectionId 变化以处理连接切换
watch(activeConnectionId, (newId, oldId) => {
console.log(`[工作区视图] 活动连接 ID 从 ${oldId} 更改为 ${newId}`);
// 断开旧连接 (如果存在)
if (oldId) {
disconnect(); // isConnected 会变为 false,触发清理
}
// 连接新的 WebSocket (如果新 ID 有效)
if (newId) {
console.log(`[工作区视图] 正在连接到 ID: ${newId}...`);
const wsUrl = `ws://${window.location.hostname}:3001`;
connect(wsUrl, newId);
// registerSshHandlers(); // 注册移至 isConnected watch
// registerStatusHandlers(); // 注册移至 isConnected watch
} else if (!newId && oldId) {
// 导航离开工作区视图
disconnect(); // isConnected 会变为 false,自动触发清理
// unregisterAllSshHandlers(); // 注销移至 isConnected watch
// unregisterAllStatusHandlers(); // 注销移至 isConnected watch
}
});
connect(wsUrl, newId); // connect 会处理 isConnected 状态
}
// 注意:处理器的注册/注销现在完全由 isConnected watch 驱动
});
// 监听 WebSocket 连接状态变化来注册/注销处理器
watch(isConnected, (connected) => {
@@ -109,38 +113,98 @@ watch(isConnected, (connected) => {
// 辅助函数:获取终端消息文本 (已移至 useSshTerminal)
// --- 连接列表点击处理 ---
const handleConnectRequest = (id: number | string) => {
console.log(`[工作区视图] 请求激活连接 ID: ${id}`);
activeConnectionId.value = String(id);
};
// --- 表单模态框处理 ---
const handleRequestAddConnection = () => {
connectionToEdit.value = null; // 确保是添加模式
showAddEditForm.value = true;
};
const handleRequestEditConnection = (connection: ConnectionInfo) => {
connectionToEdit.value = connection; // 设置要编辑的连接
showAddEditForm.value = true;
};
const handleFormClose = () => {
showAddEditForm.value = false;
connectionToEdit.value = null; // 清除编辑状态
};
const handleConnectionAdded = () => {
console.log('[工作区视图] 连接已添加');
handleFormClose();
// WorkspaceConnectionList 会自动从 store 更新
};
const handleConnectionUpdated = () => {
console.log('[工作区视图] 连接已更新');
handleFormClose();
// WorkspaceConnectionList 会自动从 store 更新
};
</script>
<template>
<div class="workspace-view">
<div class="status-bar">
<!-- 使用 t 函数渲染状态栏文本 -->
{{ t('workspace.statusBar', { status: statusMessage, id: connectionId }) }}
<!-- 使用 t 函数渲染状态栏文本, 显示 activeConnectionId -->
{{ t('workspace.statusBar', { status: statusMessage, id: activeConnectionId ?? 'N/A' }) }}
<!-- 状态颜色仍然通过 class 绑定 -->
<!-- 使用来自 useWebSocketConnection 的状态 -->
<span :class="`status-${connectionStatus}`"></span>
</div>
<div class="main-content-area">
<div class="left-pane">
<div class="terminal-wrapper">
<!-- 将事件绑定到 useSshTerminal 的处理函数 -->
<!-- 新增左侧边栏 -->
<div class="left-sidebar">
<!-- 监听新的事件 -->
<WorkspaceConnectionListComponent
@connect-request="handleConnectRequest"
@request-add-connection="handleRequestAddConnection"
@request-edit-connection="handleRequestEditConnection"
/>
</div>
<!-- 主工作区 (添加 v-if/v-else), activeConnectionId -->
<div v-if="activeConnectionId" class="main-workspace-area">
<div class="left-pane">
<div class="terminal-wrapper">
<!-- 将事件绑定到 useSshTerminal 的处理函数 -->
<TerminalComponent
@ready="handleTerminalReady"
@data="handleTerminalData"
@resize="handleTerminalResize"
/>
@resize="handleTerminalResize"
/>
</div>
<!-- 文件管理器窗格 -->
<div class="file-manager-wrapper">
<!-- Removed :ws prop. Communication will be handled via composables -->
<FileManagerComponent :is-connected="isConnected" />
</div>
</div>
<!-- 文件管理器窗格 -->
<div class="file-manager-wrapper">
<!-- Removed :ws prop. Communication will be handled via composables -->
<FileManagerComponent :is-connected="isConnected" />
<!-- 状态监控窗格 -->
<div class="status-monitor-wrapper">
<StatusMonitorComponent :status-data="serverStatus" :error="statusError" />
</div>
</div>
<!-- 状态监控窗格 -->
<div class="status-monitor-wrapper">
<StatusMonitorComponent :status-data="serverStatus" :error="statusError" />
<!-- 当没有 connectionId 时显示提示 -->
<div v-else class="main-workspace-area placeholder">
<h2>{{ t('workspace.selectConnectionPrompt') }}</h2>
<p>{{ t('workspace.selectConnectionHint') }}</p>
</div>
</div>
<!-- 添加/编辑连接表单模态框 -->
<AddConnectionFormComponent
v-if="showAddEditForm"
:connection-to-edit="connectionToEdit"
@close="handleFormClose"
@connection-added="handleConnectionAdded"
@connection-updated="handleConnectionUpdated"
/>
</div>
</template>
@@ -150,7 +214,7 @@ watch(isConnected, (connected) => {
flex-direction: column;
/* 调整高度计算以适应可能的 header/footer/status-bar */
height: calc(100vh - 60px - 30px - 2rem); /* 假设 header 60px, footer 30px, padding 2rem */
overflow: hidden; /* 防止页面滚动 */
overflow: hidden;
}
.status-bar {
@@ -169,21 +233,45 @@ watch(isConnected, (connected) => {
.main-content-area {
display: flex;
flex-grow: 1; /* Take remaining vertical space */
overflow: hidden; /* Prevent this container from scrolling */
overflow: hidden;
/* 新增样式 */
border-top: 1px solid #ccc; /* Add a top border for separation */
}
/* 新增左侧边栏样式 */
.left-sidebar {
width: 250px; /* 示例宽度 */
min-width: 200px; /* 最小宽度 */
height: 100%;
border-right: 2px solid #ccc;
overflow-y: auto; /* 如果列表过长则允许滚动 */
display: flex;
flex-direction: column;
}
.left-sidebar > * {
flex-grow: 1; /* 让 WorkspaceConnectionList 填充 */
}
/* 主工作区容器 */
.main-workspace-area {
flex-grow: 1; /* 占据剩余空间 */
display: flex;
height: 100%;
overflow: hidden;
}
.left-pane {
display: flex;
flex-direction: column;
width: 80%; /* Example width, adjust as needed */
/* width: 80%; */ /* 不再固定宽度,改为 flex */
flex-grow: 1; /* 占据主工作区大部分空间 */
height: 100%;
min-width: 300px; /* 保证终端和文件管理器有最小宽度 */
}
.terminal-wrapper {
/* flex-grow: 1; */ /* 不再让终端独占剩余空间 */
height: 60%; /* 示例:终端占 60% 高度 */
/* width: 50%; */ /* Removed width */
/* height: 100%; */ /* Removed height */
background-color: #1e1e1e; /* 终端背景色 */
overflow: hidden; /* 内部滚动由 xterm 处理 */
display: flex; /* Ensure TerminalComponent fills this wrapper */
@@ -196,9 +284,6 @@ watch(isConnected, (connected) => {
.file-manager-wrapper {
height: 40%; /* 示例:文件管理器占 40% 高度 */
/* width: 30%; */ /* Removed width */
/* height: 100%; */ /* Removed height */
/* border-left: 2px solid #ccc; */ /* Removed left border */
border-top: 2px solid #ccc; /* Add top border */
overflow: hidden; /* 防止自身滚动 */
display: flex; /* Ensure FileManagerComponent fills this wrapper */
@@ -209,14 +294,36 @@ watch(isConnected, (connected) => {
}
.status-monitor-wrapper {
width: 20%; /* Example width */
/* width: 20%; */ /* 不再固定宽度,改为 flex-basis */
flex-basis: 250px; /* 示例基础宽度 */
min-width: 200px; /* 最小宽度 */
height: 100%;
border-left: 2px solid #ccc; /* Separator */
overflow: hidden; /* Prevent scrolling */
border-left: 2px solid #ccc;
overflow: hidden;
display: flex; /* Ensure StatusMonitorComponent fills this wrapper */
flex-direction: column;
}
.status-monitor-wrapper > * {
flex-grow: 1; /* Make StatusMonitorComponent fill the wrapper */
}
/* 新增:占位符样式 */
.main-workspace-area.placeholder {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
color: #6c757d;
padding: 2rem;
background-color: #f8f9fa; /* Match sidebar background */
}
.main-workspace-area.placeholder h2 {
margin-bottom: 0.5rem;
font-weight: 300;
color: #495057;
}
.main-workspace-area.placeholder p {
font-size: 1em;
}
</style>