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 expandDirs = useSidebarStore(s => s.expandDirs) // 计算当前活动标签的文件路径(用于文件树高亮) const activeFilePath = tabs.find(t => t.id === activeTabId)?.filePath ?? null // 自动展开到活动文件所在的目录 useEffect(() => { if (!activeFilePath || !rootPath) return if (!activeFilePath.startsWith(rootPath)) return // 提取文件的所有父目录路径 const dirsToExpand: string[] = [] let dir = activeFilePath.replace(/[/\\][^/\\]+$/, '') // 移除文件名 while (dir && dir.length >= rootPath.length && dir !== rootPath) { dirsToExpand.push(dir) dir = dir.replace(/[/\\][^/\\]+$/, '') } if (dirsToExpand.length > 0) { expandDirs(dirsToExpand) } }, [activeFilePath, rootPath, expandDirs]) 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 (