366 lines
12 KiB
Vue
366 lines
12 KiB
Vue
<script setup lang="ts">
|
||
import { ref, computed, PropType } from 'vue'; // 导入 ref 和 computed
|
||
import { useI18n } from 'vue-i18n'; // 导入 i18n
|
||
// import { storeToRefs } from 'pinia'; // 移除 storeToRefs 导入,因为 paneVisibility 不再使用
|
||
import WorkspaceConnectionListComponent from './WorkspaceConnectionList.vue'; // 导入连接列表组件
|
||
import { useSessionStore } from '../stores/session.store'; // 导入 session store
|
||
import { useLayoutStore, type PaneName } from '../stores/layout.store'; // 导入布局 store 和类型
|
||
// 导入会话状态类型
|
||
import type { SessionTabInfoWithStatus } from '../stores/session.store'; // 导入更新后的类型
|
||
|
||
// --- Setup ---
|
||
const { t } = useI18n(); // 初始化 i18n
|
||
const layoutStore = useLayoutStore(); // 初始化布局 store
|
||
// const { paneVisibility } = storeToRefs(layoutStore); // 移除:paneVisibility 不再存在
|
||
|
||
// 定义 Props
|
||
const props = defineProps({
|
||
sessions: {
|
||
type: Array as PropType<SessionTabInfoWithStatus[]>, // 使用更新后的类型
|
||
required: true,
|
||
},
|
||
activeSessionId: {
|
||
type: String as PropType<string | null>, // 类型已包含 null
|
||
required: false, // 改为非必需,允许初始为 null
|
||
default: null, // 提供默认值 null
|
||
},
|
||
});
|
||
|
||
// 定义事件
|
||
const emit = defineEmits([
|
||
'activate-session',
|
||
'close-session',
|
||
'open-layout-configurator',
|
||
'request-add-connection-from-popup' // 新增:声明从弹窗发出的添加请求事件
|
||
]);
|
||
|
||
const activateSession = (sessionId: string) => {
|
||
if (sessionId !== props.activeSessionId) {
|
||
emit('activate-session', sessionId);
|
||
}
|
||
};
|
||
|
||
const closeSession = (event: MouseEvent, sessionId: string) => {
|
||
event.stopPropagation(); // 阻止事件冒泡到标签点击事件
|
||
emit('close-session', sessionId);
|
||
};
|
||
|
||
// --- 本地状态 ---
|
||
const sessionStore = useSessionStore(); // Session store 保持不变
|
||
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; // 关闭弹出窗口
|
||
};
|
||
|
||
// 新增:处理从弹窗内部发出的添加连接请求
|
||
const handleRequestAddFromPopup = () => {
|
||
console.log('[TabBar] Received request-add-connection from popup component.');
|
||
showConnectionListPopup.value = false; // 关闭弹窗
|
||
emit('request-add-connection-from-popup'); // 向上发出事件
|
||
};
|
||
|
||
// 新增:处理打开布局配置器的事件
|
||
const openLayoutConfigurator = () => {
|
||
console.log('[TabBar] Emitting open-layout-configurator event');
|
||
emit('open-layout-configurator'); // 发出事件
|
||
};
|
||
|
||
</script>
|
||
|
||
<template>
|
||
<div class="terminal-tab-bar">
|
||
<div class="tabs-and-add-button"> <!-- 新容器包裹标签和+按钮 -->
|
||
<ul class="tab-list">
|
||
<li
|
||
v-for="session in sessions"
|
||
:key="session.sessionId"
|
||
:class="['tab-item', { active: session.sessionId === activeSessionId }]"
|
||
@click="activateSession(session.sessionId)"
|
||
:title="session.connectionName"
|
||
>
|
||
<!-- 添加状态点 -->
|
||
<span :class="['status-dot', `status-${session.status}`]"></span>
|
||
<span class="tab-name">{{ session.connectionName }}</span>
|
||
<button class="close-tab-button" @click="closeSession($event, session.sessionId)" title="关闭标签页">
|
||
× <!-- 使用 HTML 实体 '×' -->
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
<!-- "+" 按钮紧随标签列表 -->
|
||
<button class="add-tab-button" @click="togglePopup" title="新建连接标签页">
|
||
<i class="fas fa-plus"></i>
|
||
</button>
|
||
</div>
|
||
<!-- 按钮容器(推到最右侧) -->
|
||
<div class="action-buttons-container">
|
||
<!-- 新增:布局配置器按钮 -->
|
||
<button class="layout-config-button" @click="openLayoutConfigurator" title="配置工作区布局">
|
||
<i class="fas fa-th-large"></i> <!-- 网格布局图标 -->
|
||
</button>
|
||
</div>
|
||
<!-- 连接列表弹出窗口 (保持不变) -->
|
||
<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"
|
||
@request-add-connection="handleRequestAddFromPopup"
|
||
class="popup-connection-list"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.terminal-tab-bar {
|
||
display: flex;
|
||
background-color: var(--header-bg-color, #e0e0e0); /* 使用变量 */
|
||
/* border-bottom: 1px solid var(--border-color, #bdbdbd); */ /* 移除底部边框,让主内容区处理 */
|
||
border: 1px solid var(--border-color, #bdbdbd); /* 添加完整边框 */
|
||
border-bottom: none; /* 明确移除底部边框 */
|
||
white-space: nowrap;
|
||
height: 2.5rem; /* 固定标签栏高度 */
|
||
box-sizing: border-box; /* 确保 padding 不会增加总高度 */
|
||
border-radius: 5px 5px 0 0; /* Top-left, Top-right, Bottom-right, Bottom-left */
|
||
margin: 0 var(--base-margin, 0.5rem); /* Add horizontal margin to match content area */
|
||
margin-top: var(--base-margin, 0.5rem); /* Add top margin */
|
||
overflow: hidden; /* Clip content to rounded corners */
|
||
}
|
||
|
||
/* 包裹标签和+按钮的容器 */
|
||
.tabs-and-add-button {
|
||
display: flex;
|
||
align-items: center;
|
||
overflow-x: auto; /* 允许标签和+按钮区域滚动 */
|
||
/* flex-grow: 1; */ /* 移除:让其自然宽度 */
|
||
/* max-width: calc(100% - 50px); */ /* 移除宽度限制 */
|
||
flex-shrink: 1; /* 允许在空间不足时收缩 */
|
||
min-width: 0; /* 允许收缩到0 */
|
||
height: 100%; /* 确保高度与父元素相同,消除上下间距 */
|
||
}
|
||
|
||
/* 状态点样式 */
|
||
.status-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
margin-right: 6px;
|
||
display: inline-block;
|
||
vertical-align: middle;
|
||
flex-shrink: 0; /* 防止被压缩 */
|
||
}
|
||
.status-dot.status-disconnected { background-color: #dc3545; } /* 红色 */
|
||
.status-dot.status-connecting { background-color: #ffc107; } /* 黄色 */
|
||
.status-dot.status-connected { background-color: #28a745; } /* 绿色 */
|
||
.status-dot.status-error { background-color: #6c757d; } /* 灰色 (或其他表示错误的颜色) */
|
||
|
||
|
||
.tab-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
display: flex;
|
||
flex-shrink: 0; /* 防止标签列表被压缩 */
|
||
height: 100%; /* 确保占满整个高度 */
|
||
}
|
||
|
||
.tab-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 0.8rem; /* 基础内边距 */
|
||
height: 100%;
|
||
cursor: pointer;
|
||
border-right: 1px solid var(--border-color, #bdbdbd); /* 使用变量 */
|
||
box-sizing: border-box;
|
||
background-color: var(--app-bg-color, #f0f0f0); /* 使用变量 - 未激活标签背景 */
|
||
color: var(--text-color-secondary, #616161); /* 使用变量 - 未激活标签文字颜色 */
|
||
transition: background-color 0.2s ease, color 0.2s ease;
|
||
position: relative; /* 保持相对定位,以防万一需要定位子元素 */
|
||
}
|
||
|
||
.tab-item:hover {
|
||
background-color: var(--header-bg-color, #e0e0e0); /* 使用变量 - 悬停时背景 */
|
||
}
|
||
|
||
.tab-item.active {
|
||
background-color: var(--app-bg-color, #ffffff); /* 使用变量 - 激活标签背景 */
|
||
color: var(--text-color, #333333); /* 使用变量 - 激活标签文字颜色 */
|
||
border-bottom-color: transparent; /* 隐藏激活标签的底部边框,模拟连接效果 */
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.tab-name {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
/* margin-right: 1.5rem; */ /* 移除 */
|
||
line-height: normal; /* 默认行高 */
|
||
flex-grow: 1; /* 保持:允许名称伸展 */
|
||
margin-left: 4px; /* 在状态点和名称之间添加一点间距 */
|
||
text-align: left; /* 保持文本左对齐 */
|
||
}
|
||
|
||
.close-tab-button {
|
||
background: none;
|
||
border: none;
|
||
color: var(--text-color-secondary, #9e9e9e); /* 使用变量 */
|
||
cursor: pointer;
|
||
font-size: 1.1em;
|
||
padding: 0 0.3rem;
|
||
line-height: 1;
|
||
border-radius: 50%;
|
||
margin-left: auto; /* 保持 auto 将按钮推到最右侧 */
|
||
flex-shrink: 0; /* 防止按钮被压缩 */
|
||
/* 移除绝对定位 */
|
||
/* top: 50%; */
|
||
/* right: 0.5rem; */
|
||
/* transform: translateY(-50%); */
|
||
}
|
||
|
||
.close-tab-button:hover {
|
||
background-color: var(--border-color, #bdbdbd); /* 使用变量 */
|
||
color: var(--app-bg-color, #ffffff); /* 使用变量 */
|
||
}
|
||
|
||
.tab-item.active .close-tab-button {
|
||
color: var(--text-color-secondary, #757575); /* 使用变量 */
|
||
}
|
||
.tab-item.active .close-tab-button:hover {
|
||
background-color: var(--header-bg-color, #e0e0e0); /* 使用变量 */
|
||
color: var(--text-color, #333333); /* 使用变量 */
|
||
}
|
||
|
||
/* 添加新标签按钮样式 */
|
||
.add-tab-button {
|
||
background: none;
|
||
border: none;
|
||
border-left: 1px solid var(--border-color, #bdbdbd); /* 使用变量 */
|
||
padding: 0 0.8rem;
|
||
cursor: pointer;
|
||
font-size: 1.1em;
|
||
color: var(--text-color-secondary, #616161); /* 使用变量 */
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100%;
|
||
flex-shrink: 0; /* 防止按钮被压缩 */
|
||
}
|
||
.add-tab-button:hover {
|
||
background-color: var(--header-bg-color, #d0d0d0); /* 使用变量 */
|
||
}
|
||
.add-tab-button i {
|
||
line-height: 1; /* 确保图标垂直居中 */
|
||
}
|
||
|
||
/* 新增:包裹右侧操作按钮的容器 */
|
||
.action-buttons-container {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-left: auto; /* 将整个容器推到右侧 */
|
||
height: 100%;
|
||
flex-shrink: 0; /* 防止被压缩 */
|
||
}
|
||
|
||
/* 新增:布局配置器按钮样式 */
|
||
.layout-config-button {
|
||
background: none;
|
||
border: none;
|
||
border-left: 1px solid var(--border-color, #bdbdbd); /* 使用变量 */
|
||
padding: 0 0.8rem;
|
||
cursor: pointer;
|
||
font-size: 1.1em; /* 与其他按钮一致 */
|
||
color: var(--text-color-secondary, #616161); /* 使用变量 */
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100%;
|
||
flex-shrink: 0;
|
||
}
|
||
.layout-config-button:hover {
|
||
background-color: var(--header-bg-color, #d0d0d0); /* 使用变量 */
|
||
}
|
||
.layout-config-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: var(--app-bg-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: var(--text-color-secondary, #aaa); /* 使用变量 */
|
||
line-height: 1;
|
||
}
|
||
.popup-close-button:hover {
|
||
color: var(--text-color, #333); /* 使用变量 */
|
||
}
|
||
|
||
.popup-content h3 {
|
||
margin-top: 0;
|
||
margin-bottom: 15px;
|
||
text-align: center;
|
||
color: var(--text-color, #333); /* 使用变量 */
|
||
}
|
||
|
||
.popup-connection-list {
|
||
flex-grow: 1; /* 让列表占据剩余空间 */
|
||
overflow-y: auto; /* 列表内容滚动 */
|
||
/* 可能需要覆盖 WorkspaceConnectionList 的一些默认样式 */
|
||
border: 1px solid var(--border-color); /* Use theme variable */
|
||
border-radius: 4px;
|
||
}
|
||
/* 覆盖 WorkspaceConnectionList 内部样式(如果需要) */
|
||
/* :deep(.popup-connection-list .search-add-bar) { */
|
||
/* display: none; */ /* 不再隐藏搜索栏 */
|
||
/* } */
|
||
:deep(.popup-connection-list .connection-list-area) {
|
||
padding: 0; /* 保持移除内边距 */
|
||
background-color: var(--app-bg-color); /* 确保列表背景 */
|
||
}
|
||
|
||
</style>
|