import React, { useEffect, useCallback, useState, useRef } from 'react' import { useTabStore } from '../../stores/tabStore' import { useSidebarStore } from '../../stores/sidebarStore' import { getFileName } from '../../lib/fileUtils' import type { FileNode } from '../../types/file' export function Sidebar() { const tabs = useTabStore(s => s.tabs) const activeTabId = useTabStore(s => s.activeTabId) const switchToTab = useTabStore(s => s.switchToTab) const createTab = useTabStore(s => s.createTab) const rootPath = useSidebarStore(s => s.rootPath) const tree = useSidebarStore(s => s.tree) const expandedDirs = useSidebarStore(s => s.expandedDirs) const toggleDir = useSidebarStore(s => s.toggleDir) const setRootPath = useSidebarStore(s => s.setRootPath) const setTree = useSidebarStore(s => s.setTree) const isVisible = useSidebarStore(s => s.isVisible) // 计算当前活动标签的文件路径(用于文件树高亮) const activeFilePath = tabs.find(t => t.id === activeTabId)?.filePath ?? null const [isResizing, setIsResizing] = useState(false) const sidebarRef = useRef(null) // 打开文件夹 const handleOpenFolder = useCallback(async () => { if (!window.electronAPI) return const dirPath = await window.electronAPI.openFolderDialog() if (dirPath) { setRootPath(dirPath) const result = await window.electronAPI.readDirTree(dirPath) if (result.success && result.tree) { setTree(result.tree) window.electronAPI.watchDir(dirPath) } } }, [setRootPath, setTree]) // 刷新目录树 const refreshTree = useCallback(async () => { if (!rootPath || !window.electronAPI) return const result = await window.electronAPI.readDirTree(rootPath) if (result.success && result.tree) { setTree(result.tree) } }, [rootPath, setTree]) // 目录变化监听 useEffect(() => { if (!window.electronAPI) return window.electronAPI.onDirChanged(() => { refreshTree() }) return () => { window.electronAPI?.removeAllListeners('sidebar:dirChanged') } }, [refreshTree]) // 侧边栏宽度调节 useEffect(() => { if (!isResizing) return const handleMouseMove = (e: MouseEvent) => { if (sidebarRef.current) { const newWidth = Math.max(180, Math.min(500, e.clientX)) sidebarRef.current.style.width = newWidth + 'px' } } const handleMouseUp = () => setIsResizing(false) document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mouseup', handleMouseUp) return () => { document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) } }, [isResizing]) // L-12: 提取为 useCallback 避免每次渲染重建 const handleFileClick = useCallback(async (path: string) => { const existing = tabs.find(t => t.filePath === path) if (existing) { switchToTab(existing.id) } else if (window.electronAPI) { const result = await window.electronAPI.readFile(path) if (result.success && result.content) { createTab(path, result.content) } } }, [tabs, switchToTab, createTab]) const independentFiles = tabs.filter(t => { if (!t.filePath) return false if (!rootPath) return true return !t.filePath.startsWith(rootPath) }) if (!isVisible) return null return (