This commit is contained in:
Baobhan Sith
2025-04-20 01:22:41 +08:00
parent 06e52a84dc
commit bc50e572cb
7 changed files with 233 additions and 44 deletions
+6 -2
View File
@@ -26,7 +26,7 @@ const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换
const { isAuthenticated } = storeToRefs(authStore);
const { showPopupFileEditorBoolean } = storeToRefs(settingsStore);
const { isStyleCustomizerVisible } = storeToRefs(appearanceStore);
const { isLayoutVisible } = storeToRefs(layoutStore);
const { isLayoutVisible, isHeaderVisible } = storeToRefs(layoutStore); // 添加 isHeaderVisible
const { isConfiguratorVisible: isFocusSwitcherVisible } = storeToRefs(focusSwitcherStore);
const route = useRoute();
@@ -54,6 +54,9 @@ onMounted(() => {
// +++ 添加全局 Alt 键监听器 +++
window.addEventListener('keydown', handleGlobalKeyDown);
// +++ 加载 Header 可见性状态 +++
layoutStore.loadHeaderVisibility();
});
// +++ 添加卸载钩子以移除监听器 +++
@@ -221,7 +224,8 @@ const isElementVisibleAndFocusable = (element: HTMLElement): boolean => {
<template>
<div id="app-container">
<header v-if="!isWorkspaceRoute || isLayoutVisible"> <!-- *** 添加 v-if *** -->
<!-- *** 修改 v-if 条件以使用 isHeaderVisible *** -->
<header v-if="!isWorkspaceRoute || isHeaderVisible">
<nav ref="navRef">
<div class="nav-left"> <!-- Group left-aligned links -->
<RouterLink to="/">{{ t('nav.dashboard') }}</RouterLink>
@@ -1,18 +1,21 @@
<script setup lang="ts">
import { ref, computed, PropType } from 'vue'; // 导入 ref computed
import { ref, computed, PropType, onMounted, watch } from 'vue'; // 导入 ref, computed, onMounted, watch
import { useI18n } from 'vue-i18n'; // 导入 i18n
import { storeToRefs } from 'pinia'; // *** 重新导入 storeToRefs ***
import { useRoute } from 'vue-router'; // 导入 useRoute
import { storeToRefs } from 'pinia'; // 导入 storeToRefs
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'; // 导入更新后的类型
// *** 假设 layoutStore 会有这些状态和方法 ***
// import { useLayoutStore } from '../stores/layout.store';
// --- Setup ---
const { t } = useI18n(); // 初始化 i18n
const layoutStore = useLayoutStore(); // 初始化布局 store
// const { isLayoutVisible } = storeToRefs(layoutStore); // *** 移除 isLayoutVisible ***
// const route = useRoute(); // *** 移除 route ***
const { isHeaderVisible } = storeToRefs(layoutStore); // 从 layout store 获取主导航栏可见状态
const route = useRoute(); // 获取路由实例
// 定义 Props
const props = defineProps({
@@ -75,9 +78,51 @@ const openLayoutConfigurator = () => {
emit('open-layout-configurator'); // 发出事件
};
// --- Layout Visibility Logic ---
// 移除布局可见性切换逻辑
// --- End Layout Visibility Logic ---
// --- Header Visibility Logic ---
const isWorkspaceRoute = ref(route.path === '/workspace'); // 检查是否在 /workspace 路由
// 监视路由变化
watch(() => route.path, (newPath) => {
isWorkspaceRoute.value = newPath === '/workspace';
if (isWorkspaceRoute.value) {
// 进入 /workspace 时,不需要在这里加载 Header 状态,App.vue 会处理
console.log('[TabBar] Entered /workspace route. Header toggle button is now active.');
}
});
// 组件挂载时检查一次
onMounted(() => {
isWorkspaceRoute.value = route.path === '/workspace';
if (isWorkspaceRoute.value) {
// 初始加载时,不需要在这里加载 Header 状态,App.vue 会处理
console.log('[TabBar] Mounted on /workspace route. Header toggle button is now active.');
}
});
// 切换主导航栏可见性 (只在 workspace 路由下生效)
const toggleHeader = () => {
if (isWorkspaceRoute.value) {
console.log('[TabBar] Toggling header visibility');
// 调用 store action
layoutStore.toggleHeaderVisibility();
} else {
console.log('[TabBar] Not on /workspace route, toggle ignored.');
}
};
// 计算属性,用于确定眼睛图标的类
const eyeIconClass = computed(() => {
// 默认显示眼睛图标,如果主导航栏不可见,则显示斜杠眼睛
// 注意:这里假设 isHeaderVisible 为 true 时是可见的
return isHeaderVisible.value ? 'fas fa-eye' : 'fas fa-eye-slash';
});
// 计算属性,用于按钮的 title
const toggleButtonTitle = computed(() => {
// 调整 i18n key 和默认文本
return isHeaderVisible.value ? t('header.hide', '隐藏顶部导航') : t('header.show', '显示顶部导航');
});
// --- End Header Visibility Logic ---
</script>
@@ -106,7 +151,15 @@ const openLayoutConfigurator = () => {
</button>
</div>
<div class="action-buttons-container">
<!-- 移除布局可见性切换按钮 -->
<!-- Header Toggle Button (Only functional on /workspace) -->
<button
v-if="isWorkspaceRoute"
class="action-button header-toggle-button"
@click="toggleHeader"
:title="toggleButtonTitle"
>
<i :class="eyeIconClass"></i>
</button>
<!-- Layout Configurator Button -->
<button class="action-button layout-config-button" @click="openLayoutConfigurator" :title="t('layout.configure', '配置布局')"> <!-- 使用 t 函数 -->
<i class="fas fa-th-large"></i> <!-- 恢复为田字格图标 -->
+48 -1
View File
@@ -1,5 +1,7 @@
import { defineStore } from 'pinia';
import { ref, computed, watch, type Ref, type ComputedRef } from 'vue';
// 导入 axios 用于 API 调用
import axios from 'axios';
// 定义所有可用面板的名称
export type PaneName = 'connections' | 'terminal' | 'commandBar' | 'fileManager' | 'editor' | 'statusMonitor' | 'commandHistory' | 'quickCommands';
@@ -93,7 +95,9 @@ export const useLayoutStore = defineStore('layout', () => {
'editor', 'statusMonitor', 'commandHistory', 'quickCommands'
]);
// 新增:控制布局(Header/Footer)可见性的状态
const isLayoutVisible: Ref<boolean> = ref(true);
const isLayoutVisible: Ref<boolean> = ref(true); // 控制整体布局(Header/Footer)可见性
// 新增:控制主导航栏(Header)可见性的状态
const isHeaderVisible: Ref<boolean> = ref(true); // 默认可见
// --- 计算属性 ---
// 计算当前布局中正在使用的面板
@@ -176,6 +180,45 @@ export const useLayoutStore = defineStore('layout', () => {
function toggleLayoutVisibility() {
isLayoutVisible.value = !isLayoutVisible.value;
console.log(`[Layout Store] 布局可见性切换为: ${isLayoutVisible.value}`);
// 注意:这个状态目前不与后端同步
}
// 新增 Action: 从后端加载主导航栏可见性设置
async function loadHeaderVisibility() {
console.log('[Layout Store] Attempting to load header visibility from backend...');
try {
// --- 调用后端 API (复用 nav-bar-visibility 接口) ---
const response = await axios.get<{ visible: boolean }>('/api/v1/settings/nav-bar-visibility');
if (response && typeof response.data.visible === 'boolean') {
isHeaderVisible.value = response.data.visible;
console.log(`[Layout Store] Header visibility loaded from backend: ${isHeaderVisible.value}`);
} else {
console.warn('[Layout Store] Invalid response from backend for header visibility, using default.');
isHeaderVisible.value = true; // 默认值
}
} catch (error) {
console.error('[Layout Store] Failed to load header visibility from backend:', error);
// 出错时使用默认值
isHeaderVisible.value = true;
}
}
// 新增 Action: 切换主导航栏可见性并同步到后端
async function toggleHeaderVisibility() {
const newValue = !isHeaderVisible.value;
console.log(`[Layout Store] Toggling header visibility to: ${newValue}`);
isHeaderVisible.value = newValue; // 立即更新 UI
try {
// --- 调用后端 API (复用 nav-bar-visibility 接口) ---
await axios.put('/api/v1/settings/nav-bar-visibility', { visible: newValue });
console.log('[Layout Store] Header visibility saved to backend.');
} catch (error) {
console.error('[Layout Store] Failed to save header visibility to backend:', error);
// 可选:如果保存失败,回滚状态?
// isHeaderVisible.value = !newValue;
// alert('Failed to save preference.'); // 或者通知用户
}
}
// --- 持久化 ---
// 监听 layoutTree 的变化,并自动保存到 localStorage
@@ -217,5 +260,9 @@ export const useLayoutStore = defineStore('layout', () => {
// 新增:暴露布局可见性状态和切换方法
isLayoutVisible,
toggleLayoutVisibility,
// 新增:暴露主导航栏可见性状态和操作
isHeaderVisible,
loadHeaderVisibility,
toggleHeaderVisibility,
};
});
@@ -28,7 +28,7 @@ const commandHistoryStore = useCommandHistoryStore();
const { sessionTabsWithStatus, activeSessionId, activeSession } = storeToRefs(sessionStore);
const { shareFileEditorTabsBoolean } = storeToRefs(settingsStore);
const { orderedTabs: globalEditorTabs, activeTabId: globalActiveEditorTabId } = storeToRefs(fileEditorStore);
const { layoutTree } = storeToRefs(layoutStore); // 获取布局树
const { layoutTree } = storeToRefs(layoutStore); // 获取布局树
// --- 计算属性 (用于动态绑定编辑器 Props) ---
// 这些计算属性现在需要传递给 LayoutRenderer
@@ -61,6 +61,7 @@ const currentSearchTerm = ref(''); // 当前搜索的关键词
onMounted(() => {
console.log('[工作区视图] 组件已挂载。');
// 确保布局已初始化 (layoutStore 内部会处理)
// 确保布局已初始化 (layoutStore 内部会处理)
});
onBeforeUnmount(() => {
@@ -310,6 +311,7 @@ const handleCloseEditorTab = (tabId: string) => {
<template>
<div class="workspace-view">
<!-- TerminalTabBar 始终渲染 -->
<TerminalTabBar
:sessions="sessionTabsWithStatus"
:active-session-id="activeSessionId"
@@ -319,6 +321,7 @@ const handleCloseEditorTab = (tabId: string) => {
@request-add-connection-from-popup="handleRequestAddConnection"
/>
<!-- 移除 :class 绑定 -->
<div class="main-content-area">
<LayoutRenderer
v-if="layoutTree"
@@ -370,7 +373,7 @@ const handleCloseEditorTab = (tabId: string) => {
display: flex;
background-color: transparent;
flex-direction: column;
height: calc(100vh - 3.5rem); /* 保持原始高度计算 */
height: calc(100vh); /* 恢复原始高度计算 (假设 header 固定高度为 3.5rem) */
overflow: hidden;
}