feat: 添加锁定布局功能

可以屏蔽鼠标扫过分割线产生的样式闪烁问题
This commit is contained in:
Baobhan Sith
2025-05-05 09:32:38 +08:00
parent b00cbac236
commit 3ea025d65a
4 changed files with 76 additions and 9 deletions
@@ -47,7 +47,8 @@ export const settingsController = {
'timezone', // NEW: 添加时区键
'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键
'rdpModalHeight', // NEW: 添加 RDP 模态框高度键
'ipBlacklistEnabled' // <-- 添加 IP 黑名单启用键
'ipBlacklistEnabled', // <-- 添加 IP 黑名单启用键
'layoutLocked' // +++ 添加布局锁定键 +++
];
const filteredSettings: Record<string, string> = {};
for (const key in settingsToUpdate) {
@@ -2,8 +2,12 @@
import { ref, computed, watch, type Ref, nextTick } from 'vue'; // Import nextTick
import { useI18n } from 'vue-i18n';
import { useLayoutStore, type LayoutNode, type PaneName } from '../stores/layout.store';
import { useSettingsStore } from '../stores/settings.store'; // +++ Import settings store +++
import { storeToRefs } from 'pinia'; // +++ Import storeToRefs +++
import draggable from 'vuedraggable';
import LayoutNodeEditor from './LayoutNodeEditor.vue';
// +++ Import a switch component if available, otherwise use checkbox +++
// Assuming a simple checkbox for now
// --- Props ---
const props = defineProps({
@@ -19,6 +23,8 @@ const emit = defineEmits(['close']);
// --- Setup ---
const { t } = useI18n();
const layoutStore = useLayoutStore();
const settingsStore = useSettingsStore(); // +++ Initialize settings store +++
const { layoutLockedBoolean } = storeToRefs(settingsStore); // +++ Get reactive state +++
// --- State ---
const localLayoutTree: Ref<LayoutNode | null> = ref(null);
@@ -160,6 +166,23 @@ const paneLabels = computed(() => ({ // Assuming labels might depend on i18n
}));
// --- Methods ---
// +++ Method to update layout lock setting +++
const handleLayoutLockChange = async () => { // Removed event parameter
const isLocked = !layoutLockedBoolean.value; // Toggle the current state
console.log(`[LayoutConfigurator] Layout lock toggled: ${isLocked}`);
try {
// +++ Convert boolean to string before sending +++
await settingsStore.updateSetting('layoutLocked', String(isLocked));
// No need to update local state directly, store watcher should handle it if needed,
// but the button's appearance relies on layoutLockedBoolean which comes from the store.
} catch (error) {
console.error('[LayoutConfigurator] Failed to update layout lock setting:', error);
// Optionally show an error message
// No UI element state to revert directly here, the button state depends on layoutLockedBoolean
alert(t('layoutConfigurator.lockUpdateError', '更新布局锁定状态失败。'));
}
};
const closeDialog = () => {
// Use the computed property for the check
if (isModified.value) {
@@ -388,7 +411,32 @@ const handleAvailablePaneDragEnd = (event: any) => {
<div class="flex flex-col">
<!-- Main Layout Preview -->
<section class="flex flex-col min-w-[350px] flex-grow">
<h3 class="mt-0 mb-4 text-base font-semibold text-text-secondary">{{ t('layoutConfigurator.layoutPreview', '主布局预览(拖拽到此处)') }}</h3>
<div class="flex justify-between items-center mb-4"> <!-- +++ Flex container for title and switch +++ -->
<h3 class="mt-0 mb-0 text-base font-semibold text-text-secondary">{{ t('layoutConfigurator.layoutPreview', '主布局预览(拖拽到此处)') }}</h3>
<!-- +++ Layout Lock Switch +++ -->
<div class="flex items-center gap-2">
<label id="layout-lock-label" class="text-sm text-text-secondary cursor-pointer select-none" @click="handleLayoutLockChange">{{ t('layoutConfigurator.lockLayout', '锁定布局') }}</label>
<button
type="button"
@click="handleLayoutLockChange"
:class="[
'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary',
layoutLockedBoolean ? 'bg-primary' : 'bg-gray-300 dark:bg-gray-600'
]"
role="switch"
:aria-checked="layoutLockedBoolean.toString()"
aria-labelledby="layout-lock-label"
>
<span
aria-hidden="true"
:class="[
'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200',
layoutLockedBoolean ? 'translate-x-5' : 'translate-x-0'
]"
></span>
</button>
</div>
</div>
<div class="flex-grow border-2 border-dashed border-border-alt rounded p-4 bg-background-alt flex flex-col overflow-auto min-h-[250px]">
<LayoutNodeEditor
v-if="localLayoutTree"
+21 -4
View File
@@ -55,6 +55,7 @@ interface SettingsState {
dashboardSortOrder?: SortOrder;
showConnectionTags?: string; // 'true' or 'false'
showQuickCommandTags?: string; // 'true' or 'false'
layoutLocked?: string; // 'true' or 'false' - NEW: 布局锁定状态
[key: string]: string | undefined;
}
@@ -261,9 +262,16 @@ export const useSettingsStore = defineStore('settings', () => {
}
if (settings.value.showQuickCommandTags === undefined) {
settings.value.showQuickCommandTags = 'true'; // 默认显示
} // +++ Add missing closing brace +++
// NEW: Layout locked default - Only set if not provided by backend
if (settings.value.layoutLocked === undefined) {
settings.value.layoutLocked = 'false'; // 默认不锁定
console.log('[SettingsStore] layoutLocked not found in fetched settings, set to default: false');
} else {
console.log(`[SettingsStore] layoutLocked found in fetched settings: ${settings.value.layoutLocked}`);
}
// --- 语言设置 ---
const langFromSettings = settings.value.language;
console.log(`[SettingsStore] Language from fetched settings: ${langFromSettings}`); // <-- 添加日志
@@ -348,7 +356,8 @@ export const useSettingsStore = defineStore('settings', () => {
'dashboardSortBy',
'dashboardSortOrder',
'showConnectionTags', // NEW
'showQuickCommandTags' // NEW
'showQuickCommandTags', // NEW
'layoutLocked' // NEW
];
if (!allowedKeys.includes(key)) {
console.error(`[SettingsStore] 尝试更新不允许的设置键: ${key}`);
@@ -432,7 +441,8 @@ export const useSettingsStore = defineStore('settings', () => {
'dashboardSortBy',
'dashboardSortOrder',
'showConnectionTags', // NEW
'showQuickCommandTags' // NEW
'showQuickCommandTags', // NEW
'layoutLocked' // NEW
];
const filteredUpdates: Partial<SettingsState> = {};
let languageUpdate: string | undefined = undefined;
@@ -702,6 +712,11 @@ export const useSettingsStore = defineStore('settings', () => {
return settings.value.showQuickCommandTags !== 'false'; // Default to true
});
// NEW: Getter for layout locked status
const layoutLockedBoolean = computed(() => {
return settings.value.layoutLocked === 'true';
});
return {
settings, // 只包含通用设置
@@ -741,5 +756,7 @@ export const useSettingsStore = defineStore('settings', () => {
// NEW: Expose tag visibility getters
showConnectionTagsBoolean,
showQuickCommandTagsBoolean,
// NEW: Expose layout locked getter
layoutLockedBoolean,
};
});
@@ -22,8 +22,8 @@ import type { ISearchOptions } from '@xterm/addon-search';
// --- Setup ---
const { t } = useI18n();
const sessionStore = useSessionStore();
const settingsStore = useSettingsStore();
const sessionStore = useSessionStore();
const settingsStore = useSettingsStore(); // Keep settingsStore instance
const fileEditorStore = useFileEditorStore();
const layoutStore = useLayoutStore();
const commandHistoryStore = useCommandHistoryStore();
@@ -34,7 +34,7 @@ const isMobile = breakpoints.smaller('md'); // +++ 定义 isMobile (小于 md
// --- 从 Store 获取响应式状态和 Getters ---
const { sessionTabsWithStatus, activeSessionId, activeSession, isRdpModalOpen, rdpConnectionInfo } = storeToRefs(sessionStore); // 使用 storeToRefs 获取 RDP 状态
const { shareFileEditorTabsBoolean } = storeToRefs(settingsStore);
const { shareFileEditorTabsBoolean, layoutLockedBoolean } = storeToRefs(settingsStore); // +++ Add layoutLockedBoolean +++
const { orderedTabs: globalEditorTabs, activeTabId: globalActiveEditorTabId } = storeToRefs(fileEditorStore);
const { layoutTree } = storeToRefs(layoutStore); // 只获取布局树
@@ -563,6 +563,7 @@ const toggleVirtualKeyboard = () => {
:is-root-renderer="true"
:layout-node="layoutTree"
:active-session-id="activeSessionId"
:layout-locked="layoutLockedBoolean"
class="layout-renderer-wrapper"
:editor-tabs="editorTabs"
:active-editor-tab-id="activeEditorTabId"