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:
parent
a6f9e82c65
commit
d5335d7803
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Reference in New Issue