This commit is contained in:
Baobhan Sith
2025-04-21 23:45:50 +08:00
parent 1a6067c879
commit ca1fcf9fc3
4 changed files with 193 additions and 16 deletions
@@ -36,7 +36,9 @@ export const settingsController = {
'showPopupFileEditor', 'shareFileEditorTabs', 'ipWhitelistEnabled', 'showPopupFileEditor', 'shareFileEditorTabs', 'ipWhitelistEnabled',
'autoCopyOnSelect', 'dockerStatusIntervalSeconds', 'dockerDefaultExpand', 'autoCopyOnSelect', 'dockerStatusIntervalSeconds', 'dockerDefaultExpand',
'statusMonitorIntervalSeconds', // +++ 添加状态监控间隔键 +++ 'statusMonitorIntervalSeconds', // +++ 添加状态监控间隔键 +++
'workspaceSidebarPersistent' // +++ 添加侧边栏固定键 +++ 'workspaceSidebarPersistent', // +++ 添加侧边栏固定键 +++
'leftSidebarWidth', // +++ 添加左侧栏宽度键 +++
'rightSidebarWidth' // +++ 添加右侧栏宽度键 +++
]; ];
const filteredSettings: Record<string, string> = {}; const filteredSettings: Record<string, string> = {};
for (const key in settingsToUpdate) { for (const key in settingsToUpdate) {
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, defineAsyncComponent, type PropType, type Component, ref, watch } from 'vue'; import { computed, defineAsyncComponent, type PropType, type Component, ref, watch, onMounted } from 'vue'; // +++ Add onMounted +++
import { useI18n } from 'vue-i18n'; // <-- Import useI18n import { useI18n } from 'vue-i18n'; // <-- Import useI18n
// 添加依赖 font-awesome // 添加依赖 font-awesome
import '@fortawesome/fontawesome-free/css/all.min.css'; import '@fortawesome/fontawesome-free/css/all.min.css';
@@ -8,6 +8,7 @@ import { useLayoutStore, type LayoutNode, type PaneName } from '../stores/layout
import { useSessionStore } from '../stores/session.store'; import { useSessionStore } from '../stores/session.store';
import { useFileEditorStore } from '../stores/fileEditor.store'; // <-- Import FileEditorStore import { useFileEditorStore } from '../stores/fileEditor.store'; // <-- Import FileEditorStore
import { useSettingsStore } from '../stores/settings.store'; // +++ Import SettingsStore +++ import { useSettingsStore } from '../stores/settings.store'; // +++ Import SettingsStore +++
import { useSidebarResize } from '../composables/useSidebarResize'; // +++ Import useSidebarResize +++
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { defineEmits } from 'vue'; import { defineEmits } from 'vue';
@@ -71,13 +72,17 @@ const fileEditorStore = useFileEditorStore(); // <-- Initialize FileEditorStore
const settingsStore = useSettingsStore(); // +++ Initialize SettingsStore +++ const settingsStore = useSettingsStore(); // +++ Initialize SettingsStore +++
const { t } = useI18n(); // <-- Get translation function const { t } = useI18n(); // <-- Get translation function
const { activeSession } = storeToRefs(sessionStore); const { activeSession } = storeToRefs(sessionStore);
const { workspaceSidebarPersistentBoolean } = storeToRefs(settingsStore); // +++ Get sidebar setting +++ const { workspaceSidebarPersistentBoolean, leftSidebarWidthPx, rightSidebarWidthPx } = storeToRefs(settingsStore); // +++ Get sidebar setting and width getters +++
const { sidebarPanes } = storeToRefs(layoutStore); const { sidebarPanes } = storeToRefs(layoutStore);
const { orderedTabs: editorTabsFromStore, activeTabId: activeEditorTabIdFromStore } = storeToRefs(fileEditorStore); // <-- Get editor state const { orderedTabs: editorTabsFromStore, activeTabId: activeEditorTabIdFromStore } = storeToRefs(fileEditorStore); // <-- Get editor state
// --- Sidebar State --- // --- Sidebar State ---
const activeLeftSidebarPane = ref<PaneName | null>(null); const activeLeftSidebarPane = ref<PaneName | null>(null);
const activeRightSidebarPane = ref<PaneName | null>(null); const activeRightSidebarPane = ref<PaneName | null>(null);
const leftSidebarPanelRef = ref<HTMLElement | null>(null); // +++ Ref for left panel +++
const rightSidebarPanelRef = ref<HTMLElement | null>(null); // +++ Ref for right panel +++
const leftResizeHandleRef = ref<HTMLElement | null>(null); // +++ Ref for left handle +++
const rightResizeHandleRef = ref<HTMLElement | null>(null); // +++ Ref for right handle +++
// --- Component Mapping --- // --- Component Mapping ---
// 使用 defineAsyncComponent 优化加载,并映射 PaneName 到实际组件 // 使用 defineAsyncComponent 优化加载,并映射 PaneName 到实际组件
@@ -385,6 +390,32 @@ const getIconClasses = (paneName: PaneName): string[] => {
} }
}; };
// --- Sidebar Resize Logic ---
onMounted(() => {
// Left Sidebar Resize
useSidebarResize({
sidebarRef: leftSidebarPanelRef,
handleRef: leftResizeHandleRef,
side: 'left',
onResizeEnd: (newWidth) => {
console.log(`Left sidebar resize ended. New width: ${newWidth}px`);
settingsStore.updateSetting('leftSidebarWidth', `${newWidth}px`);
},
});
// Right Sidebar Resize
useSidebarResize({
sidebarRef: rightSidebarPanelRef,
handleRef: rightResizeHandleRef,
side: 'right',
onResizeEnd: (newWidth) => {
console.log(`Right sidebar resize ended. New width: ${newWidth}px`);
settingsStore.updateSetting('rightSidebarWidth', `${newWidth}px`);
},
});
});
</script> </script>
<template> <template>
@@ -550,11 +581,11 @@ const getIconClasses = (paneName: PaneName): string[] => {
<div <div
v-if="activeLeftSidebarPane || activeRightSidebarPane" v-if="activeLeftSidebarPane || activeRightSidebarPane"
class="sidebar-overlay" class="sidebar-overlay"
></div> ></div>
<!-- Left Sidebar Panel --> <!-- Left Sidebar Panel -->
<div :class="['sidebar-panel', 'left-sidebar-panel', { active: !!activeLeftSidebarPane }]"> <div ref="leftSidebarPanelRef" :class="['sidebar-panel', 'left-sidebar-panel', { active: !!activeLeftSidebarPane }]" :style="{ width: leftSidebarWidthPx }"> <!-- +++ Add ref and bind width +++ -->
<div ref="leftResizeHandleRef" class="resize-handle left-handle"></div> <!-- +++ Left Handle +++ -->
<button class="close-sidebar-btn" @click="closeSidebars" title="Close Sidebar">&times;</button> <button class="close-sidebar-btn" @click="closeSidebars" title="Close Sidebar">&times;</button>
<component <component
v-if="currentLeftSidebarComponent && activeLeftSidebarPane && (!['fileManager', 'statusMonitor'].includes(activeLeftSidebarPane) || activeSession)" v-if="currentLeftSidebarComponent && activeLeftSidebarPane && (!['fileManager', 'statusMonitor'].includes(activeLeftSidebarPane) || activeSession)"
@@ -581,7 +612,8 @@ const getIconClasses = (paneName: PaneName): string[] => {
</div> </div>
<!-- Right Sidebar Panel --> <!-- Right Sidebar Panel -->
<div :class="['sidebar-panel', 'right-sidebar-panel', { active: !!activeRightSidebarPane }]"> <div ref="rightSidebarPanelRef" :class="['sidebar-panel', 'right-sidebar-panel', { active: !!activeRightSidebarPane }]" :style="{ width: rightSidebarWidthPx }"> <!-- +++ Add ref and bind width +++ -->
<div ref="rightResizeHandleRef" class="resize-handle right-handle"></div> <!-- +++ Right Handle +++ -->
<button class="close-sidebar-btn" @click="closeSidebars" title="Close Sidebar">&times;</button> <button class="close-sidebar-btn" @click="closeSidebars" title="Close Sidebar">&times;</button>
<component <component
v-if="currentRightSidebarComponent && activeRightSidebarPane && (!['fileManager', 'statusMonitor'].includes(activeRightSidebarPane) || activeSession)" v-if="currentRightSidebarComponent && activeRightSidebarPane && (!['fileManager', 'statusMonitor'].includes(activeRightSidebarPane) || activeSession)"
@@ -778,9 +810,9 @@ const getIconClasses = (paneName: PaneName): string[] => {
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
/* background-color: rgba(0, 0, 0, 0.4); */ /* <-- 移除背景色 */ /* background-color: rgba(0, 0, 0, 0.4); */ /* <-- 移除背景色 --> */
background-color: transparent; /* <-- 确保完全透明 */ background-color: transparent; /* <-- 确保完全透明 --> */
pointer-events: none; /* <-- 不拦截鼠标事件 */ pointer-events: none; /* <-- 不拦截鼠标事件 --> */
z-index: 100; /* Below panel, above main content */ z-index: 100; /* Below panel, above main content */
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
@@ -798,13 +830,13 @@ const getIconClasses = (paneName: PaneName): string[] => {
position: fixed; /* Use fixed for viewport positioning */ position: fixed; /* Use fixed for viewport positioning */
top: 0; /* Adjust if you have a fixed header */ top: 0; /* Adjust if you have a fixed header */
bottom: 0; /* Adjust if you have a fixed footer */ bottom: 0; /* Adjust if you have a fixed footer */
width: 350px; /* Adjust width as needed */ /* width: 350px; */ /* Width is now controlled by style binding */
max-width: 80vw; max-width: 80vw;
background-color: var(--app-bg-color, white); background-color: var(--app-bg-color, white);
/* box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); */ /* <-- 移除阴影以隐藏边缘 */ /* box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); */ /* <-- 移除阴影以隐藏边缘 --> */
z-index: 110; /* Above overlay */ z-index: 110; /* Above overlay */
transform: translateX(-100%); /* Start hidden */ transform: translateX(-100%); /* Start hidden */
transition: transform 0.3s ease-in-out; transition: transform 0.3s ease-in-out; /* Keep transition for sliding */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; /* Prevent content overflow */ overflow: hidden; /* Prevent content overflow */
@@ -853,6 +885,27 @@ const getIconClasses = (paneName: PaneName): string[] => {
padding-top: 2.5rem; /* Add padding to avoid close button overlap */ padding-top: 2.5rem; /* Add padding to avoid close button overlap */
} }
/* Resize Handle Styles */
.resize-handle {
position: absolute;
top: 0;
bottom: 0;
width: 8px; /* Increased width for easier interaction */
cursor: col-resize;
z-index: 120; /* Above panel content */
background-color: transparent; /* Make it invisible by default */
transition: background-color 0.2s ease;
}
.resize-handle:hover {
background-color: var(--primary-color-light, #a0cfff); /* Highlight on hover */
}
.left-handle {
right: -4px; /* Adjusted position for increased width */
}
.right-handle {
left: -4px; /* Adjusted position for increased width */
}
</style> </style>
<style> /* Global styles for splitpanes if needed, or scoped with :deep() */ <style> /* Global styles for splitpanes if needed, or scoped with :deep() */
@@ -0,0 +1,101 @@
import { ref, onMounted, onUnmounted, type Ref, watch } from 'vue'; // +++ Import watch +++
interface UseSidebarResizeOptions {
sidebarRef: Ref<HTMLElement | null>;
handleRef: Ref<HTMLElement | null>;
side: 'left' | 'right';
minWidth?: number;
maxWidth?: number;
onResizeEnd?: (newWidth: number) => void;
}
export function useSidebarResize({
sidebarRef,
handleRef,
side,
minWidth = 200, // 默认最小宽度
maxWidth = 800, // 默认最大宽度
onResizeEnd,
}: UseSidebarResizeOptions) {
const isDragging = ref(false);
const startX = ref(0);
const startWidth = ref(0);
const handleMouseDown = (event: MouseEvent) => {
console.log(`[useSidebarResize] handleMouseDown triggered for side: ${side}`, { sidebar: sidebarRef.value, handle: handleRef.value }); // +++ Add Log +++
if (!sidebarRef.value || !handleRef.value) {
console.log('[useSidebarResize] MouseDown ignored: sidebarRef or handleRef is null.'); // +++ Add Log +++
return;
}
isDragging.value = true;
startX.value = event.clientX;
startWidth.value = sidebarRef.value.offsetWidth;
document.body.style.cursor = 'col-resize'; // 改变鼠标样式
document.body.style.userSelect = 'none'; // 禁止选择文本
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
const handleMouseMove = (event: MouseEvent) => {
if (!isDragging.value || !sidebarRef.value) return;
const currentX = event.clientX;
const deltaX = currentX - startX.value;
let newWidth: number;
if (side === 'left') {
newWidth = startWidth.value + deltaX;
} else { // side === 'right'
newWidth = startWidth.value - deltaX;
}
// 应用宽度限制
newWidth = Math.max(minWidth, Math.min(newWidth, maxWidth));
sidebarRef.value.style.width = `${newWidth}px`;
};
const handleMouseUp = () => {
if (!isDragging.value) return;
isDragging.value = false;
document.body.style.cursor = ''; // 恢复鼠标样式
document.body.style.userSelect = ''; // 恢复文本选择
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
if (sidebarRef.value && onResizeEnd) {
onResizeEnd(sidebarRef.value.offsetWidth); // 传递最终宽度
}
};
// 使用 watch 监视 handleRef 的变化
watch(handleRef, (newHandle, oldHandle) => {
// 移除旧句柄上的监听器(如果存在)
if (oldHandle) {
oldHandle.removeEventListener('mousedown', handleMouseDown);
}
// 在新句柄上添加监听器(如果存在)
if (newHandle) {
newHandle.addEventListener('mousedown', handleMouseDown);
}
}, { immediate: true }); // immediate: true 确保初始时也能尝试附加
onUnmounted(() => {
// 组件卸载时确保移除监听器
if (handleRef.value) {
handleRef.value.removeEventListener('mousedown', handleMouseDown);
}
// 清理可能残留的全局监听器
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.body.style.cursor = '';
document.body.style.userSelect = '';
});
// 返回 isDragging 状态,可能用于 UI 反馈
return { isDragging };
}
+25 -4
View File
@@ -18,8 +18,10 @@ interface SettingsState {
dockerDefaultExpand?: string; // NEW: Docker 默认展开详情 'true' or 'false' dockerDefaultExpand?: string; // NEW: Docker 默认展开详情 'true' or 'false'
statusMonitorIntervalSeconds?: string; // NEW: 状态监控轮询间隔 (秒) statusMonitorIntervalSeconds?: string; // NEW: 状态监控轮询间隔 (秒)
workspaceSidebarPersistent?: string; // NEW: 工作区侧边栏是否固定 'true' or 'false' workspaceSidebarPersistent?: string; // NEW: 工作区侧边栏是否固定 'true' or 'false'
// Add other general settings keys here as needed leftSidebarWidth?: string; // NEW: 左侧边栏宽度 (e.g., '350px')
[key: string]: string | undefined; // Allow other string settings rightSidebarWidth?: string; // NEW: 右侧边栏宽度 (e.g., '350px')
// Add other general settings keys here as needed
[key: string]: string | undefined; // Allow other string settings
} }
@@ -84,6 +86,13 @@ export const useSettingsStore = defineStore('settings', () => {
if (settings.value.workspaceSidebarPersistent === undefined) { if (settings.value.workspaceSidebarPersistent === undefined) {
settings.value.workspaceSidebarPersistent = 'false'; // 默认不固定 settings.value.workspaceSidebarPersistent = 'false'; // 默认不固定
} }
// NEW: Sidebar width defaults
if (settings.value.leftSidebarWidth === undefined) {
settings.value.leftSidebarWidth = '350px'; // 默认宽度
}
if (settings.value.rightSidebarWidth === undefined) {
settings.value.rightSidebarWidth = '350px'; // 默认宽度
}
// --- 语言设置 --- // --- 语言设置 ---
const langFromSettings = settings.value.language; const langFromSettings = settings.value.language;
@@ -135,7 +144,9 @@ export const useSettingsStore = defineStore('settings', () => {
'showPopupFileEditor', 'shareFileEditorTabs', 'ipWhitelistEnabled', 'showPopupFileEditor', 'shareFileEditorTabs', 'ipWhitelistEnabled',
'autoCopyOnSelect', 'dockerStatusIntervalSeconds', 'dockerDefaultExpand', 'autoCopyOnSelect', 'dockerStatusIntervalSeconds', 'dockerDefaultExpand',
'statusMonitorIntervalSeconds', // +++ 添加状态监控间隔键 +++ 'statusMonitorIntervalSeconds', // +++ 添加状态监控间隔键 +++
'workspaceSidebarPersistent' // +++ 添加侧边栏固定键 +++ 'workspaceSidebarPersistent', // +++ 添加侧边栏固定键 +++
'leftSidebarWidth', // +++ 添加左侧栏宽度键 +++
'rightSidebarWidth' // +++ 添加右侧栏宽度键 +++
]; ];
if (!allowedKeys.includes(key)) { if (!allowedKeys.includes(key)) {
console.error(`[SettingsStore] 尝试更新不允许的设置键: ${key}`); console.error(`[SettingsStore] 尝试更新不允许的设置键: ${key}`);
@@ -170,7 +181,9 @@ export const useSettingsStore = defineStore('settings', () => {
'showPopupFileEditor', 'shareFileEditorTabs', 'ipWhitelistEnabled', 'showPopupFileEditor', 'shareFileEditorTabs', 'ipWhitelistEnabled',
'autoCopyOnSelect', 'dockerStatusIntervalSeconds', 'dockerDefaultExpand', 'autoCopyOnSelect', 'dockerStatusIntervalSeconds', 'dockerDefaultExpand',
'statusMonitorIntervalSeconds', // +++ 添加状态监控间隔键 +++ 'statusMonitorIntervalSeconds', // +++ 添加状态监控间隔键 +++
'workspaceSidebarPersistent' // +++ 添加侧边栏固定键 +++ 'workspaceSidebarPersistent', // +++ 添加侧边栏固定键 +++
'leftSidebarWidth', // +++ 添加左侧栏宽度键 +++
'rightSidebarWidth' // +++ 添加右侧栏宽度键 +++
]; ];
const filteredUpdates: Partial<SettingsState> = {}; const filteredUpdates: Partial<SettingsState> = {};
let languageUpdate: 'en' | 'zh' | undefined = undefined; let languageUpdate: 'en' | 'zh' | undefined = undefined;
@@ -237,6 +250,12 @@ export const useSettingsStore = defineStore('settings', () => {
return settings.value.workspaceSidebarPersistent === 'true'; return settings.value.workspaceSidebarPersistent === 'true';
}); });
// NEW: Getter for left sidebar width
const leftSidebarWidthPx = computed(() => settings.value.leftSidebarWidth || '350px');
// NEW: Getter for right sidebar width
const rightSidebarWidthPx = computed(() => settings.value.rightSidebarWidth || '350px');
// NEW: Getter for Docker default expand setting, returning boolean // NEW: Getter for Docker default expand setting, returning boolean
const dockerDefaultExpandBoolean = computed(() => { const dockerDefaultExpandBoolean = computed(() => {
return settings.value.dockerDefaultExpand === 'true'; return settings.value.dockerDefaultExpand === 'true';
@@ -260,6 +279,8 @@ export const useSettingsStore = defineStore('settings', () => {
dockerDefaultExpandBoolean, // +++ 暴露 Docker 默认展开 getter +++ dockerDefaultExpandBoolean, // +++ 暴露 Docker 默认展开 getter +++
statusMonitorIntervalSecondsNumber, // +++ 暴露状态监控间隔 getter +++ statusMonitorIntervalSecondsNumber, // +++ 暴露状态监控间隔 getter +++
workspaceSidebarPersistentBoolean, // +++ 暴露侧边栏固定 getter +++ workspaceSidebarPersistentBoolean, // +++ 暴露侧边栏固定 getter +++
leftSidebarWidthPx, // +++ 暴露左侧栏宽度 getter +++
rightSidebarWidthPx, // +++ 暴露右侧栏宽度 getter +++
// 移除外观相关的 getters 和 actions // 移除外观相关的 getters 和 actions
loadInitialSettings, loadInitialSettings,
updateSetting, updateSetting,