- 新增 terminalTabs store 管理标签页状态 - 新增 TerminalWorkspaceView 终端工作区视图 - 修改 ConnectionsView 终端按钮改用标签页模式 - 修改 TerminalView 作为兼容入口自动跳转到工作区 - 同一连接默认只保留一个标签页 - 切换标签时保持各自 SSH 会话不断开
99 lines
3.0 KiB
Vue
99 lines
3.0 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useTerminalTabsStore } from '../stores/terminalTabs'
|
|
import { useConnectionsStore } from '../stores/connections'
|
|
import TerminalWidget from '../components/TerminalWidget.vue'
|
|
import { ArrowLeft, X } from 'lucide-vue-next'
|
|
|
|
const router = useRouter()
|
|
const tabsStore = useTerminalTabsStore()
|
|
const connectionsStore = useConnectionsStore()
|
|
|
|
const tabs = computed(() => tabsStore.tabs)
|
|
|
|
onMounted(() => {
|
|
// 确保连接列表已加载
|
|
if (connectionsStore.connections.length === 0) {
|
|
connectionsStore.fetchConnections()
|
|
}
|
|
})
|
|
|
|
function handleTabClick(tabId: string) {
|
|
tabsStore.activate(tabId)
|
|
}
|
|
|
|
function handleTabClose(tabId: string, event: Event) {
|
|
event.stopPropagation()
|
|
tabsStore.close(tabId)
|
|
}
|
|
|
|
function goBack() {
|
|
router.push('/connections')
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="h-full flex flex-col">
|
|
<!-- 顶部导航栏 -->
|
|
<div class="flex items-center gap-4 px-4 py-3 border-b border-slate-700 bg-slate-800/50">
|
|
<button
|
|
@click="goBack"
|
|
class="p-2 rounded-lg text-slate-400 hover:bg-slate-700 hover:text-slate-100 transition-colors duration-200 cursor-pointer"
|
|
aria-label="返回"
|
|
>
|
|
<ArrowLeft class="w-5 h-5" aria-hidden="true" />
|
|
</button>
|
|
<h2 class="text-lg font-semibold text-slate-100">终端</h2>
|
|
</div>
|
|
|
|
<!-- 标签栏 -->
|
|
<div
|
|
v-if="tabs.length > 0"
|
|
class="flex items-center gap-2 px-4 py-2 border-b border-slate-700 bg-slate-900/50 overflow-x-auto"
|
|
>
|
|
<div
|
|
v-for="tab in tabs"
|
|
:key="tab.id"
|
|
@click="handleTabClick(tab.id)"
|
|
class="flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer transition-colors duration-200 whitespace-nowrap"
|
|
:class="tab.active ? 'bg-slate-700 text-slate-100' : 'bg-slate-800 text-slate-400 hover:bg-slate-700/50 hover:text-slate-200'"
|
|
>
|
|
<span class="text-sm font-medium">{{ tab.title }}</span>
|
|
<button
|
|
@click="(e) => handleTabClose(tab.id, e)"
|
|
class="p-1 rounded hover:bg-slate-600 transition-colors duration-200"
|
|
aria-label="关闭标签"
|
|
>
|
|
<X class="w-3 h-3" aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 终端内容区 -->
|
|
<div class="flex-1 min-h-0 p-4">
|
|
<div v-if="tabs.length === 0" class="flex items-center justify-center h-full text-slate-400">
|
|
<div class="text-center">
|
|
<p class="mb-2">暂无打开的终端</p>
|
|
<button
|
|
@click="goBack"
|
|
class="px-4 py-2 rounded-lg bg-cyan-600 hover:bg-cyan-500 text-white transition-colors duration-200 cursor-pointer"
|
|
>
|
|
返回连接列表
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div v-else class="h-full">
|
|
<div
|
|
v-for="tab in tabs"
|
|
:key="tab.id"
|
|
v-show="tab.active"
|
|
class="h-full"
|
|
>
|
|
<TerminalWidget :connection-id="tab.connectionId" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|