重构项目详情页面

This commit is contained in:
2025-08-13 00:38:54 +08:00
parent 4943e48254
commit bb148f4106
3 changed files with 57 additions and 101 deletions

View File

@@ -430,4 +430,29 @@ pub async fn get_git_diff(
pub async fn get_git_commits(project_path: String, limit: usize) -> Result<Vec<GitCommit>, String> {
// 使用已有的 get_git_history 函数,直接传递 limit 参数
get_git_history(project_path, Some(limit), None).await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_git_status() {
let status_text = "?? test-untracked.txt\nA staged-file.txt\n M modified-file.txt";
let (staged, modified, untracked, conflicted) = parse_git_status(status_text);
println!("Untracked files: {:?}", untracked);
println!("Staged files: {:?}", staged);
println!("Modified files: {:?}", modified);
assert_eq!(untracked.len(), 1);
assert_eq!(untracked[0].path, "test-untracked.txt");
assert_eq!(untracked[0].status, "untracked");
assert_eq!(staged.len(), 1);
assert_eq!(staged[0].path, "staged-file.txt");
assert_eq!(modified.len(), 1);
assert_eq!(modified[0].path, "modified-file.txt");
}
}

View File

@@ -1,5 +1,4 @@
import React, { useState, useEffect, useCallback, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import {
@@ -17,7 +16,6 @@ import {
RefreshCw,
Loader2,
AlertCircle,
GripVertical,
FolderTree,
FileStack,
Maximize2,
@@ -58,7 +56,6 @@ interface FileExplorerPanelEnhancedProps {
onFileSelect?: (path: string) => void;
onFileOpen?: (path: string) => void;
onToggle: () => void;
className?: string;
}
// 获取文件图标
@@ -140,7 +137,6 @@ export const FileExplorerPanelEnhanced: React.FC<FileExplorerPanelEnhancedProps>
onFileSelect,
onFileOpen,
onToggle,
className,
}) => {
const { t } = useTranslation();
const [fileTree, setFileTree] = useState<FileNode[]>([]);
@@ -153,57 +149,10 @@ export const FileExplorerPanelEnhanced: React.FC<FileExplorerPanelEnhancedProps>
const [flattenedNodes, setFlattenedNodes] = useState<FileNode[]>([]);
const [lastClickTime, setLastClickTime] = useState(0);
const [lastClickPath, setLastClickPath] = useState<string | null>(null);
const [width, setWidth] = useState(window.innerWidth * 0.15); // 15% of viewport width
const [isResizing, setIsResizing] = useState(false);
const [viewMode, setViewMode] = useState<"tree" | "folder">("tree");
const panelRef = useRef<HTMLDivElement>(null);
const resizeHandleRef = useRef<HTMLDivElement>(null);
const unlistenRef = useRef<UnlistenFn | null>(null);
// 响应窗口大小变化
useEffect(() => {
const handleResize = () => {
// 保持15%的比例
setWidth(window.innerWidth * 0.15);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// 处理拖拽调整宽度
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isResizing) return;
const newWidth = e.clientX;
const minWidth = window.innerWidth * 0.1; // Min 10%
const maxWidth = window.innerWidth * 0.25; // Max 25%
if (newWidth >= minWidth && newWidth <= maxWidth) {
setWidth(newWidth);
}
};
const handleMouseUp = () => {
setIsResizing(false);
document.body.style.cursor = '';
document.body.style.userSelect = '';
};
if (isResizing) {
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isResizing]);
// 切换节点展开状态
const toggleExpand = useCallback((path: string) => {
setExpandedNodes((prev) => {

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useCallback, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { AnimatePresence } from "framer-motion";
import { invoke } from "@tauri-apps/api/core";
import {
GitBranch,
@@ -14,7 +14,6 @@ import {
FilePlus,
FileX,
FileDiff,
GripVertical,
Check,
Folder,
FolderOpen,
@@ -139,8 +138,6 @@ export const GitPanelEnhanced: React.FC<GitPanelEnhancedProps> = ({
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState("changes");
const [width, setWidth] = useState(window.innerWidth * 0.15); // 15% of viewport width
const [isResizing, setIsResizing] = useState(false);
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
const [selectedPath, setSelectedPath] = useState<string | null>(null);
const [showDiffViewer, setShowDiffViewer] = useState(false);
@@ -148,56 +145,10 @@ export const GitPanelEnhanced: React.FC<GitPanelEnhancedProps> = ({
const [diffStaged, setDiffStaged] = useState(false);
const panelRef = useRef<HTMLDivElement>(null);
const resizeHandleRef = useRef<HTMLDivElement>(null);
const { t } = useTranslation();
const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);
// 响应窗口大小变化
useEffect(() => {
const handleResize = () => {
// 保持15%的比例
setWidth(window.innerWidth * 0.15);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// 处理拖拽调整宽度
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isResizing) return;
const windowWidth = window.innerWidth;
const newWidth = windowWidth - e.clientX;
const minWidth = window.innerWidth * 0.1; // Min 10%
const maxWidth = window.innerWidth * 0.25; // Max 25%
if (newWidth >= minWidth && newWidth <= maxWidth) {
setWidth(newWidth);
}
};
const handleMouseUp = () => {
setIsResizing(false);
document.body.style.cursor = '';
document.body.style.userSelect = '';
};
if (isResizing) {
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isResizing]);
// 加载 Git 状态
const loadGitStatus = useCallback(async () => {
if (!projectPath) return;
@@ -210,6 +161,14 @@ export const GitPanelEnhanced: React.FC<GitPanelEnhancedProps> = ({
path: projectPath, // 修改参数名为 path
});
console.log('[GitPanelEnhanced] Git status loaded:', {
untracked: status.untracked,
untrackedCount: status.untracked.length,
staged: status.staged.length,
modified: status.modified.length,
conflicted: status.conflicted.length
});
setGitStatus(status);
} catch (err) {
console.error("Failed to load git status:", err);
@@ -552,9 +511,11 @@ export const GitPanelEnhanced: React.FC<GitPanelEnhancedProps> = ({
// 渲染文件列表(树形结构)
const renderFileList = (files: GitFileStatus[], statusType: 'modified' | 'staged' | 'untracked' | 'conflicted') => {
console.log(`[GitPanelEnhanced] Rendering ${statusType} files:`, files);
if (files.length === 0) return null;
const fileTree = buildFileTree(files);
console.log(`[GitPanelEnhanced] Built file tree for ${statusType}:`, fileTree);
return (
<div className="space-y-2">
@@ -659,6 +620,26 @@ export const GitPanelEnhanced: React.FC<GitPanelEnhancedProps> = ({
)}
</div>
<div className="flex items-center gap-1">
{/* Debug button */}
<Button
variant="ghost"
size="icon"
onClick={() => {
if (gitStatus) {
console.log('=== GitPanelEnhanced Debug ===');
console.log('Full gitStatus:', gitStatus);
console.log('Untracked files:', gitStatus.untracked);
console.log('Untracked count:', gitStatus.untracked.length);
if (gitStatus.untracked.length > 0) {
console.log('First untracked file:', gitStatus.untracked[0]);
}
alert(`Untracked files: ${gitStatus.untracked.length}\n${JSON.stringify(gitStatus.untracked, null, 2)}`);
}
}}
className="h-7 w-7"
>
?
</Button>
{/* 展开/收起按钮 */}
<TooltipProvider>
<Tooltip>
@@ -780,6 +761,7 @@ export const GitPanelEnhanced: React.FC<GitPanelEnhancedProps> = ({
<div className="p-2 space-y-4">
{gitStatus && (
<>
{console.log('[GitPanelEnhanced] Rendering all file lists with gitStatus:', gitStatus)}
{renderFileList(gitStatus.staged, 'staged')}
{renderFileList(gitStatus.modified, 'modified')}
{renderFileList(gitStatus.untracked, 'untracked')}