import { defineStore } from 'pinia'; import { ref, computed, watch, type Ref, type ComputedRef } from 'vue'; // 定义所有可用面板的名称 export type PaneName = 'connections' | 'terminal' | 'commandBar' | 'fileManager' | 'editor' | 'statusMonitor' | 'commandHistory' | 'quickCommands'; // 定义布局节点接口 export interface LayoutNode { id: string; // 唯一 ID type: 'pane' | 'container'; // 节点类型:面板或容器 component?: PaneName; // 如果 type 是 'pane',指定要渲染的组件 direction?: 'horizontal' | 'vertical'; // 如果 type 是 'container',指定分割方向 children?: LayoutNode[]; // 如果 type 是 'container',包含子节点数组 size?: number; // 节点在父容器中的大小比例 (例如 20, 50, 30) } // 本地存储的 Key const LAYOUT_STORAGE_KEY = 'nexus_terminal_layout_config'; // 生成唯一 ID 的辅助函数 function generateId(): string { // 简单实现,实际项目中可能使用更健壮的库如 uuid return Math.random().toString(36).substring(2, 15); } // 定义默认布局结构 const getDefaultLayout = (): LayoutNode => ({ id: generateId(), // 根容器 ID type: 'container', direction: 'horizontal', // 主方向:水平分割 children: [ // 左侧边栏:连接列表 { id: generateId(), type: 'pane', component: 'connections', size: 15 }, // 中间主区域:垂直分割 { id: generateId(), type: 'container', direction: 'vertical', size: 60, // 中间区域占比 children: [ // 上方:终端 { id: generateId(), type: 'pane', component: 'terminal', size: 60 }, // 中下方:命令栏 (固定高度,特殊处理或放在终端内?) - 暂时移除,可在配置器中添加 // { id: generateId(), type: 'pane', component: 'commandBar', size: 5 }, // 下方:文件管理器 { id: generateId(), type: 'pane', component: 'fileManager', size: 40 }, ], }, // 右侧边栏:垂直分割 { id: generateId(), type: 'container', direction: 'vertical', size: 25, // 右侧区域占比 children: [ // 上方:编辑器 { id: generateId(), type: 'pane', component: 'editor', size: 60 }, // 下方:状态监视器 { id: generateId(), type: 'pane', component: 'statusMonitor', size: 40 }, // 可选:命令历史和快捷指令可以放在这里,或者作为可添加的面板 // { id: generateId(), type: 'pane', component: 'commandHistory', size: 20 }, // { id: generateId(), type: 'pane', component: 'quickCommands', size: 20 }, ], }, ], }); // 递归查找布局树中所有使用的面板组件名称 function getUsedPaneNames(node: LayoutNode | null): Set { const usedNames = new Set(); if (!node) return usedNames; function traverse(currentNode: LayoutNode) { if (currentNode.type === 'pane' && currentNode.component) { usedNames.add(currentNode.component); } else if (currentNode.type === 'container' && currentNode.children) { currentNode.children.forEach(traverse); } } traverse(node); return usedNames; } // 定义 Store export const useLayoutStore = defineStore('layout', () => { // --- 状态 --- // 核心状态:存储当前布局树结构 const layoutTree: Ref = ref(null); // 存储所有理论上可用的面板名称 const allPossiblePanes: Ref = ref([ 'connections', 'terminal', 'commandBar', 'fileManager', 'editor', 'statusMonitor', 'commandHistory', 'quickCommands' ]); // --- 计算属性 --- // 计算当前布局中正在使用的面板 const usedPanes: ComputedRef> = computed(() => getUsedPaneNames(layoutTree.value)); // 计算当前未在布局中使用的面板(可用于配置器中添加) const availablePanes: ComputedRef = computed(() => { const used = usedPanes.value; return allPossiblePanes.value.filter(pane => !used.has(pane)); }); // --- Actions --- // 初始化布局:尝试从 localStorage 加载,否则使用默认布局 function initializeLayout() { try { const savedLayout = localStorage.getItem(LAYOUT_STORAGE_KEY); if (savedLayout) { const parsedLayout = JSON.parse(savedLayout) as LayoutNode; // 可选:添加验证逻辑确保加载的布局结构有效 layoutTree.value = parsedLayout; console.log('[Layout Store] 从 localStorage 加载布局成功。'); } else { layoutTree.value = getDefaultLayout(); console.log('[Layout Store] 未找到保存的布局,使用默认布局。'); } } catch (error) { console.error('[Layout Store] 加载或解析布局失败:', error); layoutTree.value = getDefaultLayout(); // 出错时回退到默认布局 } } // 更新整个布局树(通常由配置器保存时调用) function updateLayoutTree(newTree: LayoutNode) { // 可选:添加验证逻辑 layoutTree.value = newTree; console.log('[Layout Store] 布局树已更新。'); // 保存将在 watch 中自动触发 } // 新增:递归查找并更新节点大小 function findAndUpdateNodeSize(node: LayoutNode | null, nodeId: string, childrenSizes: { index: number; size: number }[]): LayoutNode | null { if (!node) return null; if (node.id === nodeId && node.type === 'container' && node.children) { const updatedChildren = [...node.children]; childrenSizes.forEach(({ index, size }) => { if (updatedChildren[index]) { updatedChildren[index] = { ...updatedChildren[index], size: size }; } }); return { ...node, children: updatedChildren }; } if (node.type === 'container' && node.children) { const updatedChildren = node.children.map(child => findAndUpdateNodeSize(child, nodeId, childrenSizes)); // 检查是否有子节点被更新 if (updatedChildren.some((child, index) => child !== node.children![index])) { return { ...node, children: updatedChildren.filter(Boolean) as LayoutNode[] }; } } return node; // 未找到或未更新,返回原节点 } // 新增 Action: 更新特定容器节点的子节点大小 function updateNodeSizes(nodeId: string, childrenSizes: { index: number; size: number }[]) { console.log(`[Layout Store] 请求更新节点 ${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} 大小后得到无效的树结构。`); } } // --- 持久化 --- // 监听 layoutTree 的变化,并自动保存到 localStorage watch( layoutTree, (newTree) => { if (newTree) { try { localStorage.setItem(LAYOUT_STORAGE_KEY, JSON.stringify(newTree)); console.log('[Layout Store] 布局已自动保存到 localStorage。'); } catch (error) { console.error('[Layout Store] 保存布局到 localStorage 失败:', error); } } else { // 如果布局被清空,也移除本地存储 localStorage.removeItem(LAYOUT_STORAGE_KEY); console.log('[Layout Store] 布局为空,已从 localStorage 移除。'); } }, { deep: true } // 需要深度监听来捕获嵌套结构的变化 ); // --- 初始化 --- // Store 创建时自动初始化布局 initializeLayout(); // --- 返回 --- return { layoutTree, availablePanes, // 供配置器使用 usedPanes, // 可用于调试或内部逻辑 updateLayoutTree, initializeLayout, // 允许外部重置或重新加载 updateNodeSizes, // *** 新增:暴露更新大小的 action *** // 暴露 generateId 供配置器使用(如果需要) generateId, // 暴露 allPossiblePanes 供配置器显示所有选项 allPossiblePanes, }; });