update
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, type Ref } from 'vue'; // Re-added computed
|
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 draggable from 'vuedraggable';
|
import draggable from 'vuedraggable';
|
||||||
@@ -22,9 +22,14 @@ const layoutStore = useLayoutStore();
|
|||||||
|
|
||||||
// --- State ---
|
// --- State ---
|
||||||
const localLayoutTree: Ref<LayoutNode | null> = ref(null);
|
const localLayoutTree: Ref<LayoutNode | null> = ref(null);
|
||||||
const hasChanges = ref(false);
|
// State for current edits
|
||||||
|
// const localLayoutTree: Ref<LayoutNode | null> = ref(null); // REMOVE DUPLICATE
|
||||||
const localSidebarPanes: Ref<{ left: PaneName[], right: PaneName[] }> = ref({ left: [], right: [] });
|
const localSidebarPanes: Ref<{ left: PaneName[], right: PaneName[] }> = ref({ left: [], right: [] });
|
||||||
const localAvailablePanes: Ref<PaneName[]> = ref([]); // New state for available panes
|
const localAvailablePanes: Ref<PaneName[]> = ref([]);
|
||||||
|
|
||||||
|
// State for original values to compare against
|
||||||
|
const originalLayoutTree: Ref<LayoutNode | null> = ref(null);
|
||||||
|
const originalSidebarPanes: Ref<{ left: PaneName[], right: PaneName[] }> = ref({ left: [], right: [] });
|
||||||
|
|
||||||
// --- Dialog State ---
|
// --- Dialog State ---
|
||||||
const dialogRef = ref<HTMLElement | null>(null);
|
const dialogRef = ref<HTMLElement | null>(null);
|
||||||
@@ -32,80 +37,44 @@ const dialogRef = ref<HTMLElement | null>(null);
|
|||||||
// --- Watchers ---
|
// --- Watchers ---
|
||||||
watch(() => props.isVisible, (newValue) => {
|
watch(() => props.isVisible, (newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
// Load main layout
|
// --- Load initial data and create original copies ---
|
||||||
if (layoutStore.layoutTree) {
|
// Main layout
|
||||||
localLayoutTree.value = JSON.parse(JSON.stringify(layoutStore.layoutTree));
|
const initialLayout = layoutStore.layoutTree ? JSON.parse(JSON.stringify(layoutStore.layoutTree)) : null;
|
||||||
} else {
|
localLayoutTree.value = initialLayout;
|
||||||
localLayoutTree.value = null; // Ensure it's null if store is null
|
originalLayoutTree.value = JSON.parse(JSON.stringify(initialLayout)); // Deep copy for original
|
||||||
}
|
|
||||||
// Load sidebar config
|
|
||||||
if (layoutStore.sidebarPanes) {
|
|
||||||
localSidebarPanes.value = JSON.parse(JSON.stringify(layoutStore.sidebarPanes));
|
|
||||||
} else {
|
|
||||||
localSidebarPanes.value = { left: [], right: [] }; // Default
|
|
||||||
}
|
|
||||||
// Initialize available panes: Always include non-terminal panes, include terminal only if not already used.
|
|
||||||
const initialUsed = getAllLocalUsedPaneNames(localLayoutTree.value, localSidebarPanes.value);
|
|
||||||
const nonTerminalPanes = layoutStore.allPossiblePanes.filter(p => p !== 'terminal');
|
|
||||||
const available = [...nonTerminalPanes]; // Start with all non-terminal panes
|
|
||||||
if (!initialUsed.has('terminal')) {
|
|
||||||
// Add terminal only if it's not used in the current layout
|
|
||||||
// Try to insert it at its original position for consistency
|
|
||||||
const terminalOriginalIndex = layoutStore.allPossiblePanes.indexOf('terminal');
|
|
||||||
let inserted = false;
|
|
||||||
for (let i = 0; i < available.length; i++) {
|
|
||||||
const currentPane = available[i];
|
|
||||||
const currentOriginalIndex = layoutStore.allPossiblePanes.indexOf(currentPane);
|
|
||||||
if (terminalOriginalIndex < currentOriginalIndex) {
|
|
||||||
available.splice(i, 0, 'terminal');
|
|
||||||
inserted = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!inserted) {
|
|
||||||
available.push('terminal'); // Add to end if needed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
localAvailablePanes.value = available;
|
|
||||||
|
|
||||||
hasChanges.value = false; // Reset changes flag on open
|
// Sidebar config
|
||||||
console.log('[LayoutConfigurator] Dialog opened, initialized available panes (non-terminals always present).');
|
const initialSidebars = layoutStore.sidebarPanes ? JSON.parse(JSON.stringify(layoutStore.sidebarPanes)) : { left: [], right: [] };
|
||||||
|
localSidebarPanes.value = initialSidebars;
|
||||||
|
originalSidebarPanes.value = JSON.parse(JSON.stringify(initialSidebars)); // Deep copy for original
|
||||||
|
|
||||||
|
// Initialize available panes: Include 'terminal' only if it's not already used.
|
||||||
|
const initialUsed = getAllLocalUsedPaneNames(localLayoutTree.value, localSidebarPanes.value);
|
||||||
|
if (initialUsed.has('terminal')) {
|
||||||
|
// Terminal is used, available list excludes it
|
||||||
|
localAvailablePanes.value = layoutStore.allPossiblePanes.filter(p => p !== 'terminal');
|
||||||
} else {
|
} else {
|
||||||
localLayoutTree.value = null; // Clear main layout
|
// Terminal is not used, available list includes all
|
||||||
localSidebarPanes.value = { left: [], right: [] }; // Clear sidebars
|
localAvailablePanes.value = [...layoutStore.allPossiblePanes];
|
||||||
localAvailablePanes.value = []; // Clear available panes
|
}
|
||||||
console.log('[LayoutConfigurator] Dialog closed.');
|
// Ensure original order is maintained
|
||||||
|
localAvailablePanes.value.sort((a, b) =>
|
||||||
|
layoutStore.allPossiblePanes.indexOf(a) - layoutStore.allPossiblePanes.indexOf(b)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('[LayoutConfigurator] Dialog opened, initial data loaded and original copies created.');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// --- Clear all state on close ---
|
||||||
|
localLayoutTree.value = null;
|
||||||
|
originalLayoutTree.value = null;
|
||||||
|
localSidebarPanes.value = { left: [], right: [] };
|
||||||
|
originalSidebarPanes.value = { left: [], right: [] };
|
||||||
|
localAvailablePanes.value = [];
|
||||||
|
console.log('[LayoutConfigurator] Dialog closed, state cleared.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch for changes in the main layout tree
|
|
||||||
watch(localLayoutTree, (newValue, oldValue) => {
|
|
||||||
// Check if it's not the initial load and the dialog is visible
|
|
||||||
if (oldValue !== undefined && oldValue !== null && props.isVisible) {
|
|
||||||
// Use stringify for a simple deep comparison
|
|
||||||
if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
|
|
||||||
console.log('[LayoutConfigurator] Main layout tree changed.');
|
|
||||||
hasChanges.value = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, { deep: true });
|
|
||||||
|
|
||||||
// Watch for changes in the sidebar configuration
|
|
||||||
watch(localSidebarPanes, (newValue, oldValue) => {
|
|
||||||
// Check if it's not the initial load and the dialog is visible
|
|
||||||
if (oldValue !== undefined && props.isVisible) {
|
|
||||||
const newJson = JSON.stringify(newValue);
|
|
||||||
const oldJson = JSON.stringify(oldValue);
|
|
||||||
console.log('[LayoutConfigurator Watcher] localSidebarPanes changed.');
|
|
||||||
// Use stringify for a simple deep comparison, including order changes
|
|
||||||
if (newJson !== oldJson) {
|
|
||||||
console.log('[LayoutConfigurator Watcher] Sidebar panes changed, setting hasChanges.');
|
|
||||||
hasChanges.value = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, { deep: true });
|
|
||||||
|
|
||||||
|
|
||||||
// --- Helper Functions ---
|
// --- Helper Functions ---
|
||||||
function getMainLayoutUsedPaneNames(node: LayoutNode | null): Set<PaneName> {
|
function getMainLayoutUsedPaneNames(node: LayoutNode | null): Set<PaneName> {
|
||||||
const usedNames = new Set<PaneName>();
|
const usedNames = new Set<PaneName>();
|
||||||
@@ -128,32 +97,56 @@ function getAllLocalUsedPaneNames(mainNode: LayoutNode | null, sidebars: { left:
|
|||||||
return usedNames;
|
return usedNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to add pane back to available list if not present
|
// --- Restore Helper Functions for Terminal ---
|
||||||
|
// Helper to add 'terminal' back to available list if not present
|
||||||
function addPaneToAvailableList(paneName: PaneName) {
|
function addPaneToAvailableList(paneName: PaneName) {
|
||||||
if (!localAvailablePanes.value.includes(paneName) && layoutStore.allPossiblePanes.includes(paneName)) {
|
// Only act if the pane is 'terminal' and it's not already available
|
||||||
// Maintain original order if possible, otherwise just add
|
if (paneName === 'terminal' && !localAvailablePanes.value.includes('terminal')) {
|
||||||
// Find the original index in allPossiblePanes
|
// Maintain original order if possible
|
||||||
const originalIndex = layoutStore.allPossiblePanes.indexOf(paneName);
|
const originalIndex = layoutStore.allPossiblePanes.indexOf('terminal');
|
||||||
let inserted = false;
|
let inserted = false;
|
||||||
// Try to insert based on original order relative to existing available panes
|
|
||||||
for (let i = 0; i < localAvailablePanes.value.length; i++) {
|
for (let i = 0; i < localAvailablePanes.value.length; i++) {
|
||||||
const currentAvailablePane = localAvailablePanes.value[i];
|
const currentAvailablePane = localAvailablePanes.value[i];
|
||||||
const currentOriginalIndex = layoutStore.allPossiblePanes.indexOf(currentAvailablePane);
|
const currentOriginalIndex = layoutStore.allPossiblePanes.indexOf(currentAvailablePane);
|
||||||
if (originalIndex < currentOriginalIndex) {
|
if (originalIndex < currentOriginalIndex) {
|
||||||
localAvailablePanes.value.splice(i, 0, paneName);
|
localAvailablePanes.value.splice(i, 0, 'terminal');
|
||||||
inserted = true;
|
inserted = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!inserted) {
|
if (!inserted) {
|
||||||
localAvailablePanes.value.push(paneName); // Add to end if no suitable spot found
|
localAvailablePanes.value.push('terminal'); // Add to end if no suitable spot found
|
||||||
}
|
}
|
||||||
console.log(`[LayoutConfigurator] Added '${paneName}' back to available panes.`);
|
console.log(`[LayoutConfigurator] Added 'terminal' back to available panes.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to remove 'terminal' from available list
|
||||||
|
function removePaneFromAvailableList(paneName: PaneName) {
|
||||||
|
if (paneName === 'terminal') {
|
||||||
|
const index = localAvailablePanes.value.indexOf('terminal');
|
||||||
|
if (index > -1) {
|
||||||
|
localAvailablePanes.value.splice(index, 1);
|
||||||
|
console.log(`[LayoutConfigurator] Removed 'terminal' from available panes.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// --- Computed ---
|
// --- Computed ---
|
||||||
// Panel Labels for display
|
// Panel Labels for display
|
||||||
|
// Real-time comparison to determine if changes exist
|
||||||
|
const isModified = computed(() => {
|
||||||
|
// Compare current local state with the original snapshot
|
||||||
|
const currentLayoutJson = JSON.stringify(localLayoutTree.value);
|
||||||
|
const originalLayoutJson = JSON.stringify(originalLayoutTree.value);
|
||||||
|
const currentSidebarJson = JSON.stringify(localSidebarPanes.value);
|
||||||
|
const originalSidebarJson = JSON.stringify(originalSidebarPanes.value);
|
||||||
|
|
||||||
|
// Return true if either layout or sidebars differ from the original
|
||||||
|
const modified = currentLayoutJson !== originalLayoutJson || currentSidebarJson !== originalSidebarJson;
|
||||||
|
// console.log(`[LayoutConfigurator] isModified computed: ${modified}`); // Debug log
|
||||||
|
return modified;
|
||||||
|
});
|
||||||
|
|
||||||
const paneLabels = computed(() => ({ // Assuming labels might depend on i18n
|
const paneLabels = computed(() => ({ // Assuming labels might depend on i18n
|
||||||
connections: t('layout.pane.connections', '连接列表'),
|
connections: t('layout.pane.connections', '连接列表'),
|
||||||
terminal: t('layout.pane.terminal', '终端'),
|
terminal: t('layout.pane.terminal', '终端'),
|
||||||
@@ -168,7 +161,8 @@ const paneLabels = computed(() => ({ // Assuming labels might depend on i18n
|
|||||||
|
|
||||||
// --- Methods ---
|
// --- Methods ---
|
||||||
const closeDialog = () => {
|
const closeDialog = () => {
|
||||||
if (hasChanges.value) {
|
// Use the computed property for the check
|
||||||
|
if (isModified.value) {
|
||||||
if (confirm(t('layoutConfigurator.confirmClose', '有未保存的更改,确定要关闭吗?'))) {
|
if (confirm(t('layoutConfigurator.confirmClose', '有未保存的更改,确定要关闭吗?'))) {
|
||||||
emit('close');
|
emit('close');
|
||||||
}
|
}
|
||||||
@@ -177,25 +171,28 @@ const closeDialog = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveLayout = () => {
|
const saveLayout = async () => { // Make async
|
||||||
// Save main layout
|
console.log('[LayoutConfigurator] Attempting to save layout...');
|
||||||
if (localLayoutTree.value) {
|
try {
|
||||||
layoutStore.updateLayoutTree(localLayoutTree.value);
|
// Save main layout and wait for persistence
|
||||||
console.log('[LayoutConfigurator] Main layout saved to Store.');
|
console.log('[LayoutConfigurator] Updating main layout tree in store...');
|
||||||
} else {
|
await layoutStore.updateLayoutTree(localLayoutTree.value); // Await the async action
|
||||||
// Handle potentially empty layout based on store logic
|
console.log('[LayoutConfigurator] Main layout tree update awaited.');
|
||||||
layoutStore.updateLayoutTree(null); // Assuming null is valid for empty
|
|
||||||
console.log('[LayoutConfigurator] Main layout is empty, saved null to Store.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save sidebar config
|
// Save sidebar config and wait for persistence
|
||||||
const sidebarConfigToSave = JSON.parse(JSON.stringify(localSidebarPanes.value));
|
const sidebarConfigToSave = JSON.parse(JSON.stringify(localSidebarPanes.value));
|
||||||
console.log('[LayoutConfigurator] Preparing to save sidebar config:', sidebarConfigToSave); // Log before sending
|
console.log('[LayoutConfigurator] Updating sidebar panes in store:', sidebarConfigToSave);
|
||||||
layoutStore.updateSidebarPanes(sidebarConfigToSave);
|
await layoutStore.updateSidebarPanes(sidebarConfigToSave); // Await the async action
|
||||||
console.log('[LayoutConfigurator] Sidebar config sent to Store.');
|
console.log('[LayoutConfigurator] Sidebar panes update awaited.');
|
||||||
|
|
||||||
hasChanges.value = false;
|
// isModified will update automatically based on comparison with original state after save
|
||||||
emit('close');
|
emit('close'); // Close dialog *after* save is complete
|
||||||
|
console.log('[LayoutConfigurator] Layout saved successfully, dialog closed.');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[LayoutConfigurator] Error saving layout:', error);
|
||||||
|
// Optionally notify the user about the error
|
||||||
|
alert(t('layoutConfigurator.saveError', '保存布局时出错,请稍后再试。'));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetToDefault = () => {
|
const resetToDefault = () => {
|
||||||
@@ -208,30 +205,20 @@ const resetToDefault = () => {
|
|||||||
const defaultSidebarPanes = layoutStore.getSystemDefaultSidebarPanes();
|
const defaultSidebarPanes = layoutStore.getSystemDefaultSidebarPanes();
|
||||||
localSidebarPanes.value = JSON.parse(JSON.stringify(defaultSidebarPanes));
|
localSidebarPanes.value = JSON.parse(JSON.stringify(defaultSidebarPanes));
|
||||||
|
|
||||||
// Reset available panes using the new logic
|
// Reset available panes: Include 'terminal' only if it's not used in the default layout.
|
||||||
const defaultUsed = getAllLocalUsedPaneNames(localLayoutTree.value, localSidebarPanes.value);
|
const defaultUsed = getAllLocalUsedPaneNames(localLayoutTree.value, localSidebarPanes.value);
|
||||||
const nonTerminalPanesDefault = layoutStore.allPossiblePanes.filter(p => p !== 'terminal');
|
if (defaultUsed.has('terminal')) {
|
||||||
const availableDefault = [...nonTerminalPanesDefault];
|
localAvailablePanes.value = layoutStore.allPossiblePanes.filter(p => p !== 'terminal');
|
||||||
if (!defaultUsed.has('terminal')) {
|
} else {
|
||||||
const terminalOriginalIndex = layoutStore.allPossiblePanes.indexOf('terminal');
|
localAvailablePanes.value = [...layoutStore.allPossiblePanes];
|
||||||
let inserted = false;
|
|
||||||
for (let i = 0; i < availableDefault.length; i++) {
|
|
||||||
const currentPane = availableDefault[i];
|
|
||||||
const currentOriginalIndex = layoutStore.allPossiblePanes.indexOf(currentPane);
|
|
||||||
if (terminalOriginalIndex < currentOriginalIndex) {
|
|
||||||
availableDefault.splice(i, 0, 'terminal');
|
|
||||||
inserted = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
// Ensure original order
|
||||||
if (!inserted) {
|
localAvailablePanes.value.sort((a, b) =>
|
||||||
availableDefault.push('terminal');
|
layoutStore.allPossiblePanes.indexOf(a) - layoutStore.allPossiblePanes.indexOf(b)
|
||||||
}
|
);
|
||||||
}
|
|
||||||
localAvailablePanes.value = availableDefault;
|
|
||||||
|
|
||||||
console.log('[LayoutConfigurator] Reset to default layout, sidebar panes, and available panes (non-terminals always present).');
|
console.log('[LayoutConfigurator] Reset to default layout, sidebar panes, and available panes.');
|
||||||
hasChanges.value = true; // Mark as changed after reset
|
// isModified computed property will detect the change automatically by comparing with original state
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -251,8 +238,8 @@ const handleNodeUpdate = (updatedNode: LayoutNode) => {
|
|||||||
console.log('[LayoutConfigurator] Received node update from editor:', updatedNode);
|
console.log('[LayoutConfigurator] Received node update from editor:', updatedNode);
|
||||||
// Assuming the update is for the root node for simplicity
|
// Assuming the update is for the root node for simplicity
|
||||||
// v-model on LayoutNodeEditor might handle this, but explicit update is safer
|
// v-model on LayoutNodeEditor might handle this, but explicit update is safer
|
||||||
|
// Update the local tree; isModified will react automatically
|
||||||
localLayoutTree.value = updatedNode;
|
localLayoutTree.value = updatedNode;
|
||||||
// No need to set hasChanges here, the watcher on localLayoutTree handles it
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle remove requests from LayoutNodeEditor (for main layout) - CORRECTED VERSION
|
// Handle remove requests from LayoutNodeEditor (for main layout) - CORRECTED VERSION
|
||||||
@@ -265,24 +252,11 @@ function findAndRemoveNode(node: LayoutNode | null, parentNodeId: string | undef
|
|||||||
const removedNode = updatedChildren.splice(nodeIndex, 1)[0]; // Remove and get the node
|
const removedNode = updatedChildren.splice(nodeIndex, 1)[0]; // Remove and get the node
|
||||||
console.log(`[LayoutConfigurator] Removing node at index ${nodeIndex} from parent ${parentNodeId}`);
|
console.log(`[LayoutConfigurator] Removing node at index ${nodeIndex} from parent ${parentNodeId}`);
|
||||||
|
|
||||||
// Add the pane back to available list if it was a pane node
|
// If the removed node was the terminal pane, add it back to available list
|
||||||
if (removedNode.type === 'pane' && removedNode.component) {
|
if (removedNode.type === 'pane' && removedNode.component === 'terminal') {
|
||||||
addPaneToAvailableList(removedNode.component);
|
addPaneToAvailableList('terminal');
|
||||||
}
|
|
||||||
// If the removed node was a container, recursively add its children back
|
|
||||||
else if (removedNode.type === 'container') { // Check type directly
|
|
||||||
function addPanesFromContainer(containerNode: LayoutNode | null) { // Accept null
|
|
||||||
if (!containerNode || !containerNode.children) return; // Guard against null/undefined children
|
|
||||||
containerNode.children.forEach(child => {
|
|
||||||
if (child.type === 'pane' && child.component) {
|
|
||||||
addPaneToAvailableList(child.component);
|
|
||||||
} else if (child.type === 'container') {
|
|
||||||
addPanesFromContainer(child); // Recurse into nested containers
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
addPanesFromContainer(removedNode);
|
|
||||||
}
|
}
|
||||||
|
// No need to handle containers specifically for adding back, only terminal matters.
|
||||||
return { ...node, children: updatedChildren };
|
return { ...node, children: updatedChildren };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,15 +278,16 @@ const handleNodeRemove = (payload: { parentNodeId: string | undefined; nodeIndex
|
|||||||
console.log('[LayoutConfigurator] Received node remove request:', payload);
|
console.log('[LayoutConfigurator] Received node remove request:', payload);
|
||||||
if (payload.parentNodeId === undefined && payload.nodeIndex === 0) {
|
if (payload.parentNodeId === undefined && payload.nodeIndex === 0) {
|
||||||
if (confirm(t('layoutConfigurator.confirmClearLayout', '确定要清空整个布局吗?所有面板将返回可用列表。'))) {
|
if (confirm(t('layoutConfigurator.confirmClearLayout', '确定要清空整个布局吗?所有面板将返回可用列表。'))) {
|
||||||
// Add all panes from the tree back to available list before clearing
|
// Add all panes from the tree back to available list before clearing - REMOVED, no longer needed
|
||||||
const usedInTree = getMainLayoutUsedPaneNames(localLayoutTree.value); // Single declaration
|
// const usedInTree = getMainLayoutUsedPaneNames(localLayoutTree.value);
|
||||||
usedInTree.forEach(paneName => addPaneToAvailableList(paneName)); // Correctly call the helper
|
// usedInTree.forEach(paneName => addPaneToAvailableList(paneName));
|
||||||
// Clear the tree
|
// Clear the tree
|
||||||
|
// Update the local tree; isModified will react automatically
|
||||||
localLayoutTree.value = null;
|
localLayoutTree.value = null;
|
||||||
}
|
}
|
||||||
} else if (payload.parentNodeId) {
|
} else if (payload.parentNodeId) {
|
||||||
|
// Update the local tree; isModified will react automatically
|
||||||
localLayoutTree.value = findAndRemoveNode(localLayoutTree.value, payload.parentNodeId, payload.nodeIndex);
|
localLayoutTree.value = findAndRemoveNode(localLayoutTree.value, payload.parentNodeId, payload.nodeIndex);
|
||||||
// Watcher on localLayoutTree handles hasChanges
|
|
||||||
} else {
|
} else {
|
||||||
console.warn('[LayoutConfigurator] Invalid remove payload:', payload);
|
console.warn('[LayoutConfigurator] Invalid remove payload:', payload);
|
||||||
}
|
}
|
||||||
@@ -323,10 +298,12 @@ const removeSidebarPane = (side: 'left' | 'right', index: number) => {
|
|||||||
const removedPane = localSidebarPanes.value[side].splice(index, 1)[0]; // Remove and get pane name
|
const removedPane = localSidebarPanes.value[side].splice(index, 1)[0]; // Remove and get pane name
|
||||||
if (removedPane) {
|
if (removedPane) {
|
||||||
console.log(`[LayoutConfigurator] Removed pane '${removedPane}' from ${side} sidebar at index ${index}.`);
|
console.log(`[LayoutConfigurator] Removed pane '${removedPane}' from ${side} sidebar at index ${index}.`);
|
||||||
addPaneToAvailableList(removedPane); // Correctly call the helper
|
// If the removed pane was 'terminal', add it back to available list
|
||||||
|
if (removedPane === 'terminal') {
|
||||||
|
addPaneToAvailableList('terminal');
|
||||||
}
|
}
|
||||||
// Explicitly set hasChanges flag (watcher might not catch splice reliably?)
|
}
|
||||||
hasChanges.value = true;
|
// isModified will react automatically
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handler for vuedraggable end event to ensure changes flag is set and handle added items
|
// Handler for vuedraggable end event to ensure changes flag is set and handle added items
|
||||||
@@ -351,44 +328,21 @@ const onDraggableChange = (event: any, side: 'left' | 'right') => { // Add side
|
|||||||
console.log(`[LayoutConfigurator] Item moved or removed within/from ${side} sidebar.`);
|
console.log(`[LayoutConfigurator] Item moved or removed within/from ${side} sidebar.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure changes flag is set for any modification (add, remove, move)
|
// isModified will react automatically
|
||||||
hasChanges.value = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle drag end from the available panes list
|
// Handle drag end from the available panes list
|
||||||
const handleAvailablePaneDragEnd = (event: any) => {
|
const handleAvailablePaneDragEnd = (event: any) => {
|
||||||
// Check if the item was dropped into a different list (main layout or sidebars)
|
// Check if the item was dropped into a different list
|
||||||
if (event.to !== event.from) {
|
if (event.to !== event.from) {
|
||||||
// Find the component (Draggable) associated with the source item element
|
const paneName = event.oldIndex !== undefined ? localAvailablePanes.value[event.oldIndex] : null;
|
||||||
// This might rely on internal structure, adjust if needed or find a better way
|
|
||||||
const draggedItemElement = event.item; // The original element in the source list
|
|
||||||
let paneName: PaneName | null = null;
|
|
||||||
|
|
||||||
// Attempt to get data via Vue's internal context (might be unstable)
|
|
||||||
// Note: __draggable_component__ might not be reliable across versions. Consider data attributes if this fails.
|
|
||||||
if ((draggedItemElement as any)?.__draggable_component__?.context?.element) {
|
|
||||||
paneName = (draggedItemElement as any).__draggable_component__.context.element as PaneName;
|
|
||||||
} else {
|
|
||||||
// Fallback: Try getting from the data array using oldIndex if context fails
|
|
||||||
if (event.oldIndex !== undefined && localAvailablePanes.value[event.oldIndex]) {
|
|
||||||
paneName = localAvailablePanes.value[event.oldIndex];
|
|
||||||
console.warn("[LayoutConfigurator] Using index fallback to get pane name in drag end.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
// If 'terminal' was dragged out, remove it from the available list
|
||||||
if (paneName === 'terminal') {
|
if (paneName === 'terminal') {
|
||||||
console.log('[LayoutConfigurator] "terminal" pane dropped elsewhere. Removing from available list.');
|
removePaneFromAvailableList('terminal');
|
||||||
// Find the precise index in the *current* state of localAvailablePanes, as it might have shifted
|
|
||||||
const currentIndex = localAvailablePanes.value.indexOf('terminal');
|
|
||||||
if (currentIndex > -1) {
|
|
||||||
localAvailablePanes.value.splice(currentIndex, 1);
|
|
||||||
} else {
|
|
||||||
console.warn('[LayoutConfigurator] Could not find "terminal" in available list to remove after drag.');
|
|
||||||
}
|
|
||||||
} else if (paneName) {
|
} else if (paneName) {
|
||||||
console.log(`[LayoutConfigurator] Non-terminal pane "${paneName}" dropped elsewhere. Kept in available list (clone).`);
|
console.log(`[LayoutConfigurator] Non-terminal pane "${paneName}" dropped elsewhere (clone).`);
|
||||||
// Do nothing, item remains in localAvailablePanes
|
// Other panes are clones, do nothing to the available list
|
||||||
} else {
|
} else {
|
||||||
console.error('[LayoutConfigurator] Could not determine dragged pane name in handleAvailablePaneDragEnd.');
|
console.error('[LayoutConfigurator] Could not determine dragged pane name in handleAvailablePaneDragEnd.');
|
||||||
}
|
}
|
||||||
@@ -404,7 +358,7 @@ const handleAvailablePaneDragEnd = (event: any) => {
|
|||||||
<div ref="dialogRef" class="layout-configurator-dialog">
|
<div ref="dialogRef" class="layout-configurator-dialog">
|
||||||
|
|
||||||
<header class="dialog-header">
|
<header class="dialog-header">
|
||||||
<h2>{{ t('layoutConfigurator.title', '配置工作区布局') }}</h2>
|
<h2>{{ t('layoutConfigurator.title', '布局管理器') }}</h2>
|
||||||
<button class="close-button" @click="closeDialog" :title="t('common.close', '关闭')">×</button>
|
<button class="close-button" @click="closeDialog" :title="t('common.close', '关闭')">×</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -526,8 +480,8 @@ const handleAvailablePaneDragEnd = (event: any) => {
|
|||||||
|
|
||||||
<footer class="dialog-footer">
|
<footer class="dialog-footer">
|
||||||
<button @click="closeDialog" class="button-secondary">{{ t('common.cancel', '取消') }}</button>
|
<button @click="closeDialog" class="button-secondary">{{ t('common.cancel', '取消') }}</button>
|
||||||
<button @click="saveLayout" class="button-primary" :disabled="!hasChanges">
|
<button @click="saveLayout" class="button-primary" :disabled="!isModified">
|
||||||
{{ t('common.save', '保存') }} {{ hasChanges ? '*' : '' }}
|
{{ t('common.save', '保存') }}{{ isModified ? '*' : '' }}
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -546,9 +546,9 @@ const getIconClasses = (paneName: PaneName): string[] => {
|
|||||||
<div :class="['sidebar-panel', 'left-sidebar-panel', { active: !!activeLeftSidebarPane }]">
|
<div :class="['sidebar-panel', 'left-sidebar-panel', { active: !!activeLeftSidebarPane }]">
|
||||||
<button class="close-sidebar-btn" @click="closeSidebars" title="Close Sidebar">×</button>
|
<button class="close-sidebar-btn" @click="closeSidebars" title="Close Sidebar">×</button>
|
||||||
<component
|
<component
|
||||||
v-if="currentLeftSidebarComponent && (activeLeftSidebarPane !== 'fileManager' || activeSession)"
|
v-if="currentLeftSidebarComponent && (!['fileManager', 'statusMonitor'].includes(activeLeftSidebarPane) || activeSession)"
|
||||||
:is="currentLeftSidebarComponent"
|
:is="currentLeftSidebarComponent"
|
||||||
:key="`left-panel-${activeLeftSidebarPane}`"
|
:key="`left-panel-${activeLeftSidebarPane ?? 'null'}`"
|
||||||
v-bind="sidebarProps(activeLeftSidebarPane)"
|
v-bind="sidebarProps(activeLeftSidebarPane)"
|
||||||
/>
|
/>
|
||||||
<!-- Placeholder if FileManager is selected but no active session -->
|
<!-- Placeholder if FileManager is selected but no active session -->
|
||||||
@@ -559,16 +559,23 @@ const getIconClasses = (paneName: PaneName): string[] => {
|
|||||||
<div class="empty-session-tip">文件管理器需要活动会话</div>
|
<div class="empty-session-tip">文件管理器需要活动会话</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Placeholder if StatusMonitor is selected but no active session -->
|
||||||
|
<div v-else-if="activeLeftSidebarPane === 'statusMonitor' && !activeSession" class="sidebar-pane-content pane-placeholder empty-session">
|
||||||
|
<div class="empty-session-content">
|
||||||
|
<i class="fas fa-plug"></i>
|
||||||
|
<span>无活动会话</span>
|
||||||
|
<div class="empty-session-tip">状态监视器需要活动会话</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right Sidebar Panel -->
|
<!-- Right Sidebar Panel -->
|
||||||
<div :class="['sidebar-panel', 'right-sidebar-panel', { active: !!activeRightSidebarPane }]">
|
<div :class="['sidebar-panel', 'right-sidebar-panel', { active: !!activeRightSidebarPane }]">
|
||||||
<button class="close-sidebar-btn" @click="closeSidebars" title="Close Sidebar">×</button>
|
<button class="close-sidebar-btn" @click="closeSidebars" title="Close Sidebar">×</button>
|
||||||
<component
|
<component
|
||||||
v-if="currentRightSidebarComponent && (activeRightSidebarPane !== 'fileManager' || activeSession)"
|
v-if="currentRightSidebarComponent && (!['fileManager', 'statusMonitor'].includes(activeRightSidebarPane) || activeSession)"
|
||||||
:is="currentRightSidebarComponent"
|
:is="currentRightSidebarComponent"
|
||||||
:key="`right-panel-${activeRightSidebarPane}`"
|
:key="`right-panel-${activeRightSidebarPane ?? 'null'}`"
|
||||||
v-bind="sidebarProps(activeRightSidebarPane)"
|
|
||||||
/>
|
/>
|
||||||
<!-- Placeholder if FileManager is selected but no active session -->
|
<!-- Placeholder if FileManager is selected but no active session -->
|
||||||
<div v-else-if="activeRightSidebarPane === 'fileManager' && !activeSession" class="sidebar-pane-content pane-placeholder empty-session">
|
<div v-else-if="activeRightSidebarPane === 'fileManager' && !activeSession" class="sidebar-pane-content pane-placeholder empty-session">
|
||||||
@@ -578,6 +585,14 @@ const getIconClasses = (paneName: PaneName): string[] => {
|
|||||||
<div class="empty-session-tip">文件管理器需要活动会话</div>
|
<div class="empty-session-tip">文件管理器需要活动会话</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Placeholder if StatusMonitor is selected but no active session -->
|
||||||
|
<div v-else-if="activeRightSidebarPane === 'statusMonitor' && !activeSession" class="sidebar-pane-content pane-placeholder empty-session">
|
||||||
|
<div class="empty-session-content">
|
||||||
|
<i class="fas fa-plug"></i>
|
||||||
|
<span>无活动会话</span>
|
||||||
|
<div class="empty-session-tip">状态监视器需要活动会话</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right Sidebar Buttons (Only render if root) -->
|
<!-- Right Sidebar Buttons (Only render if root) -->
|
||||||
|
|||||||
@@ -675,7 +675,7 @@
|
|||||||
"remove": "移除"
|
"remove": "移除"
|
||||||
},
|
},
|
||||||
"layoutConfigurator": {
|
"layoutConfigurator": {
|
||||||
"title": "配置工作区布局",
|
"title": "布局管理器",
|
||||||
"availablePanes": "可用面板",
|
"availablePanes": "可用面板",
|
||||||
"layoutPreview": "主布局预览(拖拽到此处)",
|
"layoutPreview": "主布局预览(拖拽到此处)",
|
||||||
"resetDefault": "恢复默认",
|
"resetDefault": "恢复默认",
|
||||||
|
|||||||
@@ -257,35 +257,54 @@ export const useLayoutStore = defineStore('layout', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Helper for debounced persistence ---
|
||||||
|
// We still might want debounce if updates happen rapidly outside the configurator (e.g., pane resize)
|
||||||
|
let persistLayoutDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
const debouncedPersistLayout = () => {
|
||||||
|
if (persistLayoutDebounceTimer) clearTimeout(persistLayoutDebounceTimer);
|
||||||
|
persistLayoutDebounceTimer = setTimeout(async () => { // Make async
|
||||||
|
await persistLayoutTree(); // Await the async persist function
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
// 更新整个布局树(通常由配置器保存时调用)
|
// 更新整个布局树(通常由配置器保存时调用)
|
||||||
function updateLayoutTree(newTree: LayoutNode | null) { // <-- Allow null
|
async function updateLayoutTree(newTree: LayoutNode | null) { // Make async
|
||||||
// 可选:添加验证逻辑 (如果 newTree 不是 null)
|
// 可选:添加验证逻辑
|
||||||
if (newTree) {
|
if (newTree) {
|
||||||
// TODO: Add validation for LayoutNode structure if needed
|
// TODO: Add validation
|
||||||
}
|
}
|
||||||
layoutTree.value = newTree; // Assign null or the new tree
|
// Check if the tree actually changed before updating and persisting
|
||||||
|
if (JSON.stringify(newTree) !== JSON.stringify(layoutTree.value)) {
|
||||||
|
layoutTree.value = newTree;
|
||||||
console.log('[Layout Store] 布局树已更新。 New tree:', newTree);
|
console.log('[Layout Store] 布局树已更新。 New tree:', newTree);
|
||||||
// 保存将在 watch 中自动触发
|
// --- Directly call persist ---
|
||||||
|
await persistLayoutTree(); // Await persistence directly
|
||||||
|
} else {
|
||||||
|
console.log('[Layout Store] updateLayoutTree called but tree is unchanged.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:更新侧栏配置
|
// 新增:更新侧栏配置
|
||||||
function updateSidebarPanes(newPanes: { left: PaneName[], right: PaneName[] }) {
|
async function updateSidebarPanes(newPanes: { left: PaneName[], right: PaneName[] }) { // Make async
|
||||||
// --- Add Validation ---
|
// --- Add Validation ---
|
||||||
if (newPanes &&
|
if (newPanes &&
|
||||||
isValidPaneNameArray(newPanes.left, allPossiblePanes.value) &&
|
isValidPaneNameArray(newPanes.left, allPossiblePanes.value) &&
|
||||||
isValidPaneNameArray(newPanes.right, allPossiblePanes.value))
|
isValidPaneNameArray(newPanes.right, allPossiblePanes.value))
|
||||||
{
|
{
|
||||||
|
// Check if panes actually changed
|
||||||
|
if (JSON.stringify(newPanes) !== JSON.stringify(sidebarPanes.value)) {
|
||||||
sidebarPanes.value = newPanes as { left: PaneName[], right: PaneName[] }; // Assign validated data
|
sidebarPanes.value = newPanes as { left: PaneName[], right: PaneName[] }; // Assign validated data
|
||||||
// Log the value immediately after update
|
|
||||||
console.log('[Layout Store] 侧栏配置已通过验证并更新。 New sidebarPanes value:', JSON.parse(JSON.stringify(sidebarPanes.value)));
|
console.log('[Layout Store] 侧栏配置已通过验证并更新。 New sidebarPanes value:', JSON.parse(JSON.stringify(sidebarPanes.value)));
|
||||||
// 保存将在 watch 中自动触发
|
// --- Directly call persist ---
|
||||||
|
await persistSidebarPanes(); // Await persistence directly
|
||||||
|
} else {
|
||||||
|
console.log('[Layout Store] updateSidebarPanes called but panes are unchanged.');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('[Layout Store] updateSidebarPanes 接收到无效的侧栏配置数据,未更新状态:', newPanes);
|
console.error('[Layout Store] updateSidebarPanes 接收到无效的侧栏配置数据,未更新状态:', newPanes);
|
||||||
// 可选:抛出错误或通知用户
|
// 可选:抛出错误或通知用户
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 递归查找并更新节点大小
|
// 递归查找并更新节点大小
|
||||||
function findAndUpdateNodeSize(node: LayoutNode | null, nodeId: string, childrenSizes: { index: number; size: number }[]): LayoutNode | null {
|
function findAndUpdateNodeSize(node: LayoutNode | null, nodeId: string, childrenSizes: { index: number; size: number }[]): LayoutNode | null {
|
||||||
if (!node) return null;
|
if (!node) return null;
|
||||||
@@ -313,18 +332,18 @@ export const useLayoutStore = defineStore('layout', () => {
|
|||||||
// 新增 Action: 更新特定容器节点的子节点大小
|
// 新增 Action: 更新特定容器节点的子节点大小
|
||||||
function updateNodeSizes(nodeId: string, childrenSizes: { index: number; size: number }[]) {
|
function updateNodeSizes(nodeId: string, childrenSizes: { index: number; size: number }[]) {
|
||||||
console.log(`[Layout Store] 请求更新节点 ${nodeId} 的子节点大小:`, childrenSizes);
|
console.log(`[Layout Store] 请求更新节点 ${nodeId} 的子节点大小:`, childrenSizes);
|
||||||
|
const originalJson = JSON.stringify(layoutTree.value); // Store original state
|
||||||
const updatedTree = findAndUpdateNodeSize(layoutTree.value, nodeId, childrenSizes);
|
const updatedTree = findAndUpdateNodeSize(layoutTree.value, nodeId, childrenSizes);
|
||||||
if (updatedTree && updatedTree !== layoutTree.value) {
|
|
||||||
// 只有在树实际发生变化时才更新 ref 以触发 watch
|
|
||||||
layoutTree.value = updatedTree;
|
|
||||||
console.log(`[Layout Store] 节点 ${nodeId} 的子节点大小已更新。`);
|
|
||||||
} else if (updatedTree === layoutTree.value) {
|
|
||||||
console.log(`[Layout Store] 未找到节点 ${nodeId} 或大小未改变。`);
|
|
||||||
} else {
|
|
||||||
console.error(`[Layout Store] 更新节点 ${nodeId} 大小后得到无效的树结构。`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (updatedTree && JSON.stringify(updatedTree) !== originalJson) { // Compare with original JSON
|
||||||
|
layoutTree.value = updatedTree;
|
||||||
|
console.log(`[Layout Store] 节点 ${nodeId} 的子节点大小已更新,触发防抖保存。`);
|
||||||
|
// --- Use debounced persist for resize ---
|
||||||
|
debouncedPersistLayout();
|
||||||
|
} else {
|
||||||
|
console.log(`[Layout Store] 未找到节点 ${nodeId} 或大小未改变。`);
|
||||||
|
}
|
||||||
|
}
|
||||||
// 新增 Action: 切换布局(Header/Footer)的可见性
|
// 新增 Action: 切换布局(Header/Footer)的可见性
|
||||||
function toggleLayoutVisibility() {
|
function toggleLayoutVisibility() {
|
||||||
isLayoutVisible.value = !isLayoutVisible.value;
|
isLayoutVisible.value = !isLayoutVisible.value;
|
||||||
@@ -383,27 +402,19 @@ export const useLayoutStore = defineStore('layout', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 新增 Action: 将当前主布局树持久化到后端和 localStorage
|
// 新增 Action: 将当前主布局树持久化到后端和 localStorage
|
||||||
async function persistLayoutTree() {
|
async function persistLayoutTree() { // Make async
|
||||||
if (!layoutTree.value) {
|
// ... (existing try/catch logic for backend and localStorage) ...
|
||||||
console.warn('[Layout Store] persistLayoutTree: layoutTree is null, cannot persist.');
|
// Ensure apiClient calls are awaited if they return promises
|
||||||
// TODO: 考虑是否需要删除后端设置或发送空布局
|
|
||||||
// await apiClient.put('/settings/layout', null); // 发送 null 或空对象
|
|
||||||
localStorage.removeItem(LAYOUT_STORAGE_KEY);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const layoutToSave = JSON.stringify(layoutTree.value);
|
|
||||||
// 1. 保存到后端
|
|
||||||
try {
|
try {
|
||||||
console.log('[Layout Store] Attempting to save main layout to backend...');
|
console.log('[Layout Store] Attempting to save main layout to backend...');
|
||||||
// Send the layoutTree value directly (which can be null)
|
await apiClient.put('/settings/layout', layoutTree.value); // await
|
||||||
await apiClient.put('/settings/layout', layoutTree.value);
|
|
||||||
console.log('[Layout Store] 主布局已成功保存到后端 (sent value):', layoutTree.value);
|
console.log('[Layout Store] 主布局已成功保存到后端 (sent value):', layoutTree.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Layout Store] 保存主布局到后端失败:', error);
|
console.error('[Layout Store] 保存主布局到后端失败:', error);
|
||||||
}
|
}
|
||||||
// 2. 保存到 localStorage
|
// localStorage is synchronous
|
||||||
try {
|
try {
|
||||||
// If layoutTree.value is null, layoutToSave will be 'null'
|
const layoutToSave = JSON.stringify(layoutTree.value);
|
||||||
localStorage.setItem(LAYOUT_STORAGE_KEY, layoutToSave);
|
localStorage.setItem(LAYOUT_STORAGE_KEY, layoutToSave);
|
||||||
console.log('[Layout Store] 主布局已自动保存到 localStorage (saved value):', layoutToSave);
|
console.log('[Layout Store] 主布局已自动保存到 localStorage (saved value):', layoutToSave);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -412,18 +423,18 @@ export const useLayoutStore = defineStore('layout', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 新增 Action: 将当前侧栏配置持久化到后端和 localStorage
|
// 新增 Action: 将当前侧栏配置持久化到后端和 localStorage
|
||||||
async function persistSidebarPanes() {
|
async function persistSidebarPanes() { // Make async
|
||||||
const sidebarsToSave = JSON.stringify(sidebarPanes.value);
|
// ... (existing try/catch logic for backend and localStorage) ...
|
||||||
// 1. 保存到后端 (假设 API 端点为 /settings/sidebar)
|
|
||||||
try {
|
try {
|
||||||
console.log('[Layout Store] Attempting to save sidebar config to backend...');
|
console.log('[Layout Store] Attempting to save sidebar config to backend...');
|
||||||
await apiClient.put('/settings/sidebar', sidebarPanes.value); // 新 API 端点
|
await apiClient.put('/settings/sidebar', sidebarPanes.value); // await
|
||||||
console.log('[Layout Store] 侧栏配置已成功保存到后端。');
|
console.log('[Layout Store] 侧栏配置已成功保存到后端。');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Layout Store] 保存侧栏配置到后端失败:', error);
|
console.error('[Layout Store] 保存侧栏配置到后端失败:', error);
|
||||||
}
|
}
|
||||||
// 2. 保存到 localStorage
|
// localStorage is synchronous
|
||||||
try {
|
try {
|
||||||
|
const sidebarsToSave = JSON.stringify(sidebarPanes.value);
|
||||||
localStorage.setItem(SIDEBAR_STORAGE_KEY, sidebarsToSave);
|
localStorage.setItem(SIDEBAR_STORAGE_KEY, sidebarsToSave);
|
||||||
console.log('[Layout Store] 侧栏配置已自动保存到 localStorage。');
|
console.log('[Layout Store] 侧栏配置已自动保存到 localStorage。');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -432,42 +443,9 @@ export const useLayoutStore = defineStore('layout', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- 持久化 Watchers ---
|
// --- REMOVE the old watchers that called persist ---
|
||||||
let layoutDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
// watch(layoutTree, ...); // REMOVE THIS
|
||||||
let sidebarDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
// watch(sidebarPanes, ...); // REMOVE THIS
|
||||||
|
|
||||||
// 监听主布局树变化
|
|
||||||
watch(
|
|
||||||
layoutTree,
|
|
||||||
(newTree, oldTree) => {
|
|
||||||
if (oldTree === undefined) return; // 避免初始化触发
|
|
||||||
if (JSON.stringify(newTree) !== JSON.stringify(oldTree)) {
|
|
||||||
console.log('[Layout Store] Main layout tree changed, scheduling persistence...');
|
|
||||||
if (layoutDebounceTimer) clearTimeout(layoutDebounceTimer);
|
|
||||||
layoutDebounceTimer = setTimeout(() => {
|
|
||||||
persistLayoutTree();
|
|
||||||
}, 1000); // 1秒防抖
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// 监听侧栏配置变化
|
|
||||||
watch(
|
|
||||||
sidebarPanes,
|
|
||||||
(newPanes, oldPanes) => {
|
|
||||||
if (oldPanes === undefined) return; // 避免初始化触发
|
|
||||||
if (JSON.stringify(newPanes) !== JSON.stringify(oldPanes)) {
|
|
||||||
console.log('[Layout Store] Sidebar panes changed, scheduling persistence...');
|
|
||||||
if (sidebarDebounceTimer) clearTimeout(sidebarDebounceTimer);
|
|
||||||
sidebarDebounceTimer = setTimeout(() => {
|
|
||||||
persistSidebarPanes();
|
|
||||||
}, 1000); // 1秒防抖
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- 初始化 ---
|
// --- 初始化 ---
|
||||||
// Store 创建时自动初始化布局和侧栏
|
// Store 创建时自动初始化布局和侧栏
|
||||||
initializeLayout();
|
initializeLayout();
|
||||||
|
|||||||
Reference in New Issue
Block a user