feat: 添加锁定布局功能
可以屏蔽鼠标扫过分割线产生的样式闪烁问题
This commit is contained in:
@@ -47,7 +47,8 @@ export const settingsController = {
|
|||||||
'timezone', // NEW: 添加时区键
|
'timezone', // NEW: 添加时区键
|
||||||
'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键
|
'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键
|
||||||
'rdpModalHeight', // NEW: 添加 RDP 模态框高度键
|
'rdpModalHeight', // NEW: 添加 RDP 模态框高度键
|
||||||
'ipBlacklistEnabled' // <-- 添加 IP 黑名单启用键
|
'ipBlacklistEnabled', // <-- 添加 IP 黑名单启用键
|
||||||
|
'layoutLocked' // +++ 添加布局锁定键 +++
|
||||||
];
|
];
|
||||||
const filteredSettings: Record<string, string> = {};
|
const filteredSettings: Record<string, string> = {};
|
||||||
for (const key in settingsToUpdate) {
|
for (const key in settingsToUpdate) {
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
import { ref, computed, watch, type Ref, nextTick } from 'vue'; // Import nextTick
|
import { ref, computed, watch, type Ref, nextTick } from 'vue'; // Import nextTick
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useLayoutStore, type LayoutNode, type PaneName } from '../stores/layout.store';
|
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 draggable from 'vuedraggable';
|
||||||
import LayoutNodeEditor from './LayoutNodeEditor.vue';
|
import LayoutNodeEditor from './LayoutNodeEditor.vue';
|
||||||
|
// +++ Import a switch component if available, otherwise use checkbox +++
|
||||||
|
// Assuming a simple checkbox for now
|
||||||
|
|
||||||
// --- Props ---
|
// --- Props ---
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -19,6 +23,8 @@ const emit = defineEmits(['close']);
|
|||||||
// --- Setup ---
|
// --- Setup ---
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const layoutStore = useLayoutStore();
|
const layoutStore = useLayoutStore();
|
||||||
|
const settingsStore = useSettingsStore(); // +++ Initialize settings store +++
|
||||||
|
const { layoutLockedBoolean } = storeToRefs(settingsStore); // +++ Get reactive state +++
|
||||||
|
|
||||||
// --- State ---
|
// --- State ---
|
||||||
const localLayoutTree: Ref<LayoutNode | null> = ref(null);
|
const localLayoutTree: Ref<LayoutNode | null> = ref(null);
|
||||||
@@ -160,6 +166,23 @@ const paneLabels = computed(() => ({ // Assuming labels might depend on i18n
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Methods ---
|
// --- 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 = () => {
|
const closeDialog = () => {
|
||||||
// Use the computed property for the check
|
// Use the computed property for the check
|
||||||
if (isModified.value) {
|
if (isModified.value) {
|
||||||
@@ -388,7 +411,32 @@ const handleAvailablePaneDragEnd = (event: any) => {
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<!-- Main Layout Preview -->
|
<!-- Main Layout Preview -->
|
||||||
<section class="flex flex-col min-w-[350px] flex-grow">
|
<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]">
|
<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
|
<LayoutNodeEditor
|
||||||
v-if="localLayoutTree"
|
v-if="localLayoutTree"
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ interface SettingsState {
|
|||||||
dashboardSortOrder?: SortOrder;
|
dashboardSortOrder?: SortOrder;
|
||||||
showConnectionTags?: string; // 'true' or 'false'
|
showConnectionTags?: string; // 'true' or 'false'
|
||||||
showQuickCommandTags?: string; // 'true' or 'false'
|
showQuickCommandTags?: string; // 'true' or 'false'
|
||||||
|
layoutLocked?: string; // 'true' or 'false' - NEW: 布局锁定状态
|
||||||
[key: string]: string | undefined;
|
[key: string]: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +262,13 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
}
|
}
|
||||||
if (settings.value.showQuickCommandTags === undefined) {
|
if (settings.value.showQuickCommandTags === undefined) {
|
||||||
settings.value.showQuickCommandTags = 'true'; // 默认显示
|
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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -348,7 +356,8 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
'dashboardSortBy',
|
'dashboardSortBy',
|
||||||
'dashboardSortOrder',
|
'dashboardSortOrder',
|
||||||
'showConnectionTags', // NEW
|
'showConnectionTags', // NEW
|
||||||
'showQuickCommandTags' // NEW
|
'showQuickCommandTags', // NEW
|
||||||
|
'layoutLocked' // NEW
|
||||||
];
|
];
|
||||||
if (!allowedKeys.includes(key)) {
|
if (!allowedKeys.includes(key)) {
|
||||||
console.error(`[SettingsStore] 尝试更新不允许的设置键: ${key}`);
|
console.error(`[SettingsStore] 尝试更新不允许的设置键: ${key}`);
|
||||||
@@ -432,7 +441,8 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
'dashboardSortBy',
|
'dashboardSortBy',
|
||||||
'dashboardSortOrder',
|
'dashboardSortOrder',
|
||||||
'showConnectionTags', // NEW
|
'showConnectionTags', // NEW
|
||||||
'showQuickCommandTags' // NEW
|
'showQuickCommandTags', // NEW
|
||||||
|
'layoutLocked' // NEW
|
||||||
];
|
];
|
||||||
const filteredUpdates: Partial<SettingsState> = {};
|
const filteredUpdates: Partial<SettingsState> = {};
|
||||||
let languageUpdate: string | undefined = undefined;
|
let languageUpdate: string | undefined = undefined;
|
||||||
@@ -702,6 +712,11 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
return settings.value.showQuickCommandTags !== 'false'; // Default to true
|
return settings.value.showQuickCommandTags !== 'false'; // Default to true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// NEW: Getter for layout locked status
|
||||||
|
const layoutLockedBoolean = computed(() => {
|
||||||
|
return settings.value.layoutLocked === 'true';
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
settings, // 只包含通用设置
|
settings, // 只包含通用设置
|
||||||
@@ -741,5 +756,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
// NEW: Expose tag visibility getters
|
// NEW: Expose tag visibility getters
|
||||||
showConnectionTagsBoolean,
|
showConnectionTagsBoolean,
|
||||||
showQuickCommandTagsBoolean,
|
showQuickCommandTagsBoolean,
|
||||||
|
// NEW: Expose layout locked getter
|
||||||
|
layoutLockedBoolean,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import type { ISearchOptions } from '@xterm/addon-search';
|
|||||||
// --- Setup ---
|
// --- Setup ---
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const sessionStore = useSessionStore();
|
const sessionStore = useSessionStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore(); // Keep settingsStore instance
|
||||||
const fileEditorStore = useFileEditorStore();
|
const fileEditorStore = useFileEditorStore();
|
||||||
const layoutStore = useLayoutStore();
|
const layoutStore = useLayoutStore();
|
||||||
const commandHistoryStore = useCommandHistoryStore();
|
const commandHistoryStore = useCommandHistoryStore();
|
||||||
@@ -34,7 +34,7 @@ const isMobile = breakpoints.smaller('md'); // +++ 定义 isMobile (小于 md
|
|||||||
|
|
||||||
// --- 从 Store 获取响应式状态和 Getters ---
|
// --- 从 Store 获取响应式状态和 Getters ---
|
||||||
const { sessionTabsWithStatus, activeSessionId, activeSession, isRdpModalOpen, rdpConnectionInfo } = storeToRefs(sessionStore); // 使用 storeToRefs 获取 RDP 状态
|
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 { orderedTabs: globalEditorTabs, activeTabId: globalActiveEditorTabId } = storeToRefs(fileEditorStore);
|
||||||
const { layoutTree } = storeToRefs(layoutStore); // 只获取布局树
|
const { layoutTree } = storeToRefs(layoutStore); // 只获取布局树
|
||||||
|
|
||||||
@@ -563,6 +563,7 @@ const toggleVirtualKeyboard = () => {
|
|||||||
:is-root-renderer="true"
|
:is-root-renderer="true"
|
||||||
:layout-node="layoutTree"
|
:layout-node="layoutTree"
|
||||||
:active-session-id="activeSessionId"
|
:active-session-id="activeSessionId"
|
||||||
|
:layout-locked="layoutLockedBoolean"
|
||||||
class="layout-renderer-wrapper"
|
class="layout-renderer-wrapper"
|
||||||
:editor-tabs="editorTabs"
|
:editor-tabs="editorTabs"
|
||||||
:active-editor-tab-id="activeEditorTabId"
|
:active-editor-tab-id="activeEditorTabId"
|
||||||
|
|||||||
Reference in New Issue
Block a user