feat: Optimize project details page layout and information display

Enhanced Tab Structure:
- Added new 'Project Overview' tab as default selection
- Reorganized tab navigation with Brain icon for overview
- Improved tab content scrolling with fixed height containers

 Consolidated Statistics Display:
- Moved all project statistics to Overview tab (materials, videos, audio, images, AI queue)
- Integrated MaterialSegmentStats for comprehensive segment analytics
- Added AI classification progress tracking in overview
- Consolidated project information display with creation/update times

 Improved Layout & Scrolling:
- Fixed scrolling behavior - removed external scrollbars
- Set tab content height to calc(100vh-16rem) for optimal viewport usage
- Added consistent padding (p-4 md:p-6) across all tab content areas
- Optimized content organization for better information hierarchy

 Technical Improvements:
- Added loadSegmentStats function with proper error handling
- Integrated MaterialSegmentStats component with ByClassification view mode
- Updated dependency arrays for proper data loading
- Enhanced project information display with formatted timestamps

 User Experience Benefits:
- Single overview tab provides complete project status at a glance
- Improved navigation with logical information grouping
- Better scrolling performance with contained scroll areas
- Consistent visual hierarchy across all tabs
- Reduced information fragmentation across multiple views

The project details page now provides a more organized and efficient way to view all project-related information, with the overview tab serving as a comprehensive dashboard for project status, statistics, and progress tracking.
This commit is contained in:
imeepos 2025-07-15 21:02:41 +08:00
parent a6f9e82c65
commit d5335d7803
1 changed files with 186 additions and 88 deletions

View File

@ -31,6 +31,8 @@ import { MaterialMatchingResult, MaterialMatchingRequest } from '../types/materi
import { MaterialSegmentView } from '../components/MaterialSegmentView';
import { formatDistanceToNow } from 'date-fns';
import { zhCN } from 'date-fns/locale';
import { MaterialSegmentStats } from '../components/MaterialSegmentStats';
import { MaterialSegmentViewMode } from '../types/materialSegmentView';
// 格式化时间
const formatTime = (dateString: string) => {
@ -105,15 +107,37 @@ export const ProjectDetails: React.FC = () => {
const [editingBinding, setEditingBinding] = useState<ProjectTemplateBindingDetail | null>(null);
const [showMaterialEditDialog, setShowMaterialEditDialog] = useState(false);
const [editingMaterial, setEditingMaterial] = useState<Material | null>(null);
const [activeTab, setActiveTab] = useState<'materials' | 'segments' | 'templates' | 'debug' | 'ai-logs'>('materials');
const [activeTab, setActiveTab] = useState<'overview' | 'materials' | 'segments' | 'templates' | 'ai-logs'>('overview');
const [_batchClassificationResult, setBatchClassificationResult] = useState<ProjectBatchClassificationResponse | null>(null);
// 素材匹配状态
const [showMatchingResultDialog, setShowMatchingResultDialog] = useState(false);
const [matchingResult, setMatchingResult] = useState<MaterialMatchingResult | null>(null);
const [matchingLoading, setMatchingLoading] = useState(false);
const [segmentStats, setSegmentStats] = useState<any>(null);
const [currentMatchingBinding, setCurrentMatchingBinding] = useState<ProjectTemplateBindingDetail | null>(null);
// 加载片段统计数据
const loadSegmentStats = async (projectId: string) => {
try {
const stats = await invoke('get_material_segment_stats', { projectId });
setSegmentStats(stats);
} catch (error) {
console.error('Failed to load segment stats:', error);
// 设置默认统计数据
setSegmentStats({
total_segments: 0,
total_duration: 0,
classified_segments: 0,
classification_coverage: 0,
by_classification: {},
by_model: {},
by_duration_range: {},
avg_segment_duration: 0
});
}
};
// 加载项目详情
useEffect(() => {
if (!projects.length) {
@ -133,9 +157,11 @@ export const ProjectDetails: React.FC = () => {
loadMaterialStats(foundProject.id);
// 加载项目的模板绑定
bindingActions.fetchTemplatesByProject(foundProject.id);
// 加载片段统计数据
loadSegmentStats(foundProject.id);
}
}
}, [id, projects, loadMaterials, loadMaterialStats, bindingActions]);
}, [id, projects, loadMaterials, loadMaterialStats, bindingActions, loadSegmentStats]);
// 加载模板列表
useEffect(() => {
@ -526,8 +552,96 @@ export const ProjectDetails: React.FC = () => {
</div>
</div>
{/* 美观的项目统计概览 */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 md:gap-4 mb-6">
{/* 主要内容区域 */}
<div className="space-y-6">
{/* 美观的选项卡导航 */}
<div className="bg-white rounded-xl shadow-sm border border-gray-200/50 overflow-hidden">
<div className="border-b border-gray-100">
<nav className="flex space-x-1 px-4 md:px-6 overflow-x-auto" aria-label="Tabs">
<button
onClick={() => setActiveTab('overview')}
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
activeTab === 'overview'
? 'text-primary-600 border-b-2 border-primary-500'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<div className="flex items-center space-x-2">
<Brain className="w-4 h-4" />
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
</div>
</button>
<button
onClick={() => setActiveTab('materials')}
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
activeTab === 'materials'
? 'text-primary-600 border-b-2 border-primary-500'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<div className="flex items-center space-x-2">
<FolderOpen className="w-4 h-4" />
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
</div>
</button>
<button
onClick={() => setActiveTab('segments')}
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
activeTab === 'segments'
? 'text-primary-600 border-b-2 border-primary-500'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<div className="flex items-center space-x-2">
<Layers className="w-4 h-4" />
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
</div>
</button>
<button
onClick={() => setActiveTab('templates')}
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
activeTab === 'templates'
? 'text-primary-600 border-b-2 border-primary-500'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<div className="flex items-center space-x-2">
<Link className="w-4 h-4" />
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
</div>
</button>
<button
onClick={() => setActiveTab('ai-logs')}
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
activeTab === 'ai-logs'
? 'text-primary-600 border-b-2 border-primary-500'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<div className="flex items-center space-x-2">
<HardDrive className="w-4 h-4" />
<span className="hidden sm:inline">AI分析日志</span>
<span className="sm:hidden">AI日志</span>
</div>
</button>
</nav>
</div>
{/* 选项卡内容 */}
<div className="h-[calc(100vh-16rem)] overflow-y-auto custom-scrollbar">
{/* 项目概述选项卡 */}
{activeTab === 'overview' && (
<div className="p-4 md:p-6 space-y-6">
{/* 项目统计概览 */}
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4"></h3>
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 md:gap-4">
{/* 总素材数 */}
<div className="bg-gradient-to-br from-white to-primary-50/30 rounded-xl shadow-sm border border-gray-200/50 p-4 md:p-5 hover:shadow-md transition-all duration-300 hover:-translate-y-1 relative overflow-hidden">
<div className="absolute top-0 right-0 w-16 h-16 bg-gradient-to-br from-primary-100/50 to-primary-200/50 rounded-full -translate-y-8 translate-x-8 opacity-50"></div>
@ -608,77 +722,61 @@ export const ProjectDetails: React.FC = () => {
</div>
</div>
</div>
{/* 主要内容区域 */}
<div className="space-y-6">
{/* 美观的选项卡导航 */}
<div className="bg-white rounded-xl shadow-sm border border-gray-200/50 overflow-hidden">
<div className="border-b border-gray-100">
<nav className="flex space-x-1 px-4 md:px-6 overflow-x-auto" aria-label="Tabs">
<button
onClick={() => setActiveTab('materials')}
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
activeTab === 'materials'
? 'text-primary-600 border-b-2 border-primary-500'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<div className="flex items-center space-x-2">
<FolderOpen className="w-4 h-4" />
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
</div>
</button>
<button
onClick={() => setActiveTab('segments')}
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
activeTab === 'segments'
? 'text-primary-600 border-b-2 border-primary-500'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<div className="flex items-center space-x-2">
<Layers className="w-4 h-4" />
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
</div>
</button>
<button
onClick={() => setActiveTab('templates')}
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
activeTab === 'templates'
? 'text-primary-600 border-b-2 border-primary-500'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<div className="flex items-center space-x-2">
<Link className="w-4 h-4" />
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
</div>
</button>
<button
onClick={() => setActiveTab('ai-logs')}
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
activeTab === 'ai-logs'
? 'text-primary-600 border-b-2 border-primary-500'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<div className="flex items-center space-x-2">
<HardDrive className="w-4 h-4" />
<span className="hidden sm:inline">AI分析日志</span>
<span className="sm:hidden">AI日志</span>
</div>
</button>
</nav>
</div>
{/* 选项卡内容 */}
<div className="p-4 md:p-6 max-h-[calc(100vh-20rem)] overflow-y-auto custom-scrollbar">
{/* 片段管理统计 */}
{project && (
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4"></h3>
<MaterialSegmentStats
stats={segmentStats}
viewMode={MaterialSegmentViewMode.ByClassification}
/>
</div>
)}
{/* AI视频分类进度 */}
{project && (
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">AI分析进度</h3>
<VideoClassificationProgress
projectId={project.id}
autoRefresh={true}
refreshInterval={3000}
/>
</div>
)}
{/* 项目信息 */}
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4"></h3>
<div className="bg-gray-50 rounded-xl p-4 space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-600"></span>
<span className="text-sm text-gray-900 font-mono bg-white px-2 py-1 rounded border">
{project?.path}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-600"></span>
<span className="text-sm text-gray-900">
{project?.created_at ? formatTime(project.created_at) : '-'}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-600"></span>
<span className="text-sm text-gray-900">
{project?.updated_at ? formatTime(project.updated_at) : '-'}
</span>
</div>
</div>
</div>
</div>
)}
{/* 素材管理选项卡 */}
{activeTab === 'materials' && (
<div className="space-y-6">
<div className="p-4 md:p-6 space-y-6">
{/* AI视频分类进度 */}
{project && (
<VideoClassificationProgress
@ -741,14 +839,14 @@ export const ProjectDetails: React.FC = () => {
{/* 片段管理选项卡 */}
{activeTab === 'segments' && project && (
<div className="space-y-6">
<div className="p-4 md:p-6">
<MaterialSegmentView projectId={project.id} />
</div>
)}
{/* 模板绑定选项卡 */}
{activeTab === 'templates' && project && (
<div className="space-y-6">
<div className="p-4 md:p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-medium text-gray-900"></h3>
@ -794,7 +892,7 @@ export const ProjectDetails: React.FC = () => {
{/* AI分析日志选项卡 */}
{activeTab === 'ai-logs' && project && (
<div>
<div className="p-4 md:p-6 space-y-6">
<div className="mb-4">
<h3 className="text-lg font-medium text-gray-900 mb-2">AI分析日志</h3>
<p className="text-sm text-gray-600">