refactor: integrate export records into project details page
- Add export records tab to ProjectDetails page - Optimize ExportRecordManager for compact tab display - Remove global export records navigation (now project-level) - Add showHeader and compact props to ExportRecordManager - Remove standalone ExportRecordsPage (no longer needed) - Update navigation structure to reflect project-level functionality Export records are now properly scoped to individual projects and accessible through the project details interface.
This commit is contained in:
parent
c190fdae1b
commit
9f84ffe7f4
|
|
@ -8,7 +8,6 @@ import ModelDetail from './pages/ModelDetail';
|
||||||
import AiClassificationSettings from './pages/AiClassificationSettings';
|
import AiClassificationSettings from './pages/AiClassificationSettings';
|
||||||
import TemplateManagement from './pages/TemplateManagement';
|
import TemplateManagement from './pages/TemplateManagement';
|
||||||
import { MaterialModelBinding } from './pages/MaterialModelBinding';
|
import { MaterialModelBinding } from './pages/MaterialModelBinding';
|
||||||
import ExportRecordsPage from './pages/ExportRecordsPage';
|
|
||||||
import Navigation from './components/Navigation';
|
import Navigation from './components/Navigation';
|
||||||
import { NotificationSystem, useNotifications } from './components/NotificationSystem';
|
import { NotificationSystem, useNotifications } from './components/NotificationSystem';
|
||||||
import { useProjectStore } from './store/projectStore';
|
import { useProjectStore } from './store/projectStore';
|
||||||
|
|
@ -79,7 +78,6 @@ function App() {
|
||||||
<Route path="/ai-classification-settings" element={<AiClassificationSettings />} />
|
<Route path="/ai-classification-settings" element={<AiClassificationSettings />} />
|
||||||
<Route path="/templates" element={<TemplateManagement />} />
|
<Route path="/templates" element={<TemplateManagement />} />
|
||||||
<Route path="/material-model-binding" element={<MaterialModelBinding />} />
|
<Route path="/material-model-binding" element={<MaterialModelBinding />} />
|
||||||
<Route path="/export-records" element={<ExportRecordsPage />} />
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,15 @@ import {
|
||||||
interface ExportRecordManagerProps {
|
interface ExportRecordManagerProps {
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
matchingResultId?: string;
|
matchingResultId?: string;
|
||||||
|
showHeader?: boolean; // 是否显示标题和描述
|
||||||
|
compact?: boolean; // 紧凑模式,适用于tab显示
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExportRecordManager: React.FC<ExportRecordManagerProps> = ({
|
const ExportRecordManager: React.FC<ExportRecordManagerProps> = ({
|
||||||
projectId,
|
projectId,
|
||||||
matchingResultId
|
matchingResultId,
|
||||||
|
showHeader = true,
|
||||||
|
compact = false
|
||||||
}) => {
|
}) => {
|
||||||
const [records, setRecords] = useState<ExportRecord[]>([]);
|
const [records, setRecords] = useState<ExportRecord[]>([]);
|
||||||
const [statistics, setStatistics] = useState<ExportRecordStatistics | null>(null);
|
const [statistics, setStatistics] = useState<ExportRecordStatistics | null>(null);
|
||||||
|
|
@ -184,31 +188,35 @@ const ExportRecordManager: React.FC<ExportRecordManagerProps> = ({
|
||||||
}, [projectId, matchingResultId, filters, pagination.page]);
|
}, [projectId, matchingResultId, filters, pagination.page]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="export-record-manager p-6">
|
<div className={`export-record-manager ${compact ? 'p-0' : 'p-6'}`}>
|
||||||
|
{showHeader && (
|
||||||
<div className="header mb-6">
|
<div className="header mb-6">
|
||||||
<h2 className="text-2xl font-bold text-gray-800 mb-4">
|
<h2 className="text-2xl font-bold text-gray-800 mb-4">
|
||||||
导出记录管理
|
导出记录管理
|
||||||
{projectId && <span className="text-sm text-gray-600 ml-2">(项目范围)</span>}
|
{projectId && <span className="text-sm text-gray-600 ml-2">(项目范围)</span>}
|
||||||
{matchingResultId && <span className="text-sm text-gray-600 ml-2">(匹配结果范围)</span>}
|
{matchingResultId && <span className="text-sm text-gray-600 ml-2">(匹配结果范围)</span>}
|
||||||
</h2>
|
</h2>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="content">
|
||||||
{/* 统计信息 */}
|
{/* 统计信息 */}
|
||||||
{statistics && (
|
{statistics && (
|
||||||
<div className="stats-grid grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
<div className={`stats-grid grid ${compact ? 'grid-cols-2 md:grid-cols-4 gap-3 mb-4' : 'grid-cols-2 md:grid-cols-4 gap-4 mb-6'}`}>
|
||||||
<div className="stat-card bg-blue-50 p-4 rounded-lg">
|
<div className={`stat-card bg-blue-50 ${compact ? 'p-3' : 'p-4'} rounded-lg`}>
|
||||||
<div className="text-2xl font-bold text-blue-600">{statistics.total_exports}</div>
|
<div className={`${compact ? 'text-xl' : 'text-2xl'} font-bold text-blue-600`}>{statistics.total_exports}</div>
|
||||||
<div className="text-sm text-gray-600">总导出次数</div>
|
<div className="text-sm text-gray-600">总导出次数</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-card bg-green-50 p-4 rounded-lg">
|
<div className={`stat-card bg-green-50 ${compact ? 'p-3' : 'p-4'} rounded-lg`}>
|
||||||
<div className="text-2xl font-bold text-green-600">{statistics.successful_exports}</div>
|
<div className={`${compact ? 'text-xl' : 'text-2xl'} font-bold text-green-600`}>{statistics.successful_exports}</div>
|
||||||
<div className="text-sm text-gray-600">成功导出</div>
|
<div className="text-sm text-gray-600">成功导出</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-card bg-red-50 p-4 rounded-lg">
|
<div className={`stat-card bg-red-50 ${compact ? 'p-3' : 'p-4'} rounded-lg`}>
|
||||||
<div className="text-2xl font-bold text-red-600">{statistics.failed_exports}</div>
|
<div className={`${compact ? 'text-xl' : 'text-2xl'} font-bold text-red-600`}>{statistics.failed_exports}</div>
|
||||||
<div className="text-sm text-gray-600">失败导出</div>
|
<div className="text-sm text-gray-600">失败导出</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-card bg-purple-50 p-4 rounded-lg">
|
<div className={`stat-card bg-purple-50 ${compact ? 'p-3' : 'p-4'} rounded-lg`}>
|
||||||
<div className="text-2xl font-bold text-purple-600">
|
<div className={`${compact ? 'text-xl' : 'text-2xl'} font-bold text-purple-600`}>
|
||||||
{formatFileSize(statistics.total_file_size)}
|
{formatFileSize(statistics.total_file_size)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600">总文件大小</div>
|
<div className="text-sm text-gray-600">总文件大小</div>
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ import {
|
||||||
UserGroupIcon,
|
UserGroupIcon,
|
||||||
CpuChipIcon,
|
CpuChipIcon,
|
||||||
DocumentDuplicateIcon,
|
DocumentDuplicateIcon,
|
||||||
LinkIcon,
|
LinkIcon
|
||||||
DocumentArrowDownIcon
|
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
const Navigation: React.FC = () => {
|
const Navigation: React.FC = () => {
|
||||||
|
|
@ -42,12 +41,6 @@ const Navigation: React.FC = () => {
|
||||||
href: '/ai-classification-settings',
|
href: '/ai-classification-settings',
|
||||||
icon: CpuChipIcon,
|
icon: CpuChipIcon,
|
||||||
description: '管理AI视频分类规则'
|
description: '管理AI视频分类规则'
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '导出记录',
|
|
||||||
href: '/export-records',
|
|
||||||
icon: DocumentArrowDownIcon,
|
|
||||||
description: '查看和管理导出记录'
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import ExportRecordManager from '../components/ExportRecordManager';
|
|
||||||
|
|
||||||
const ExportRecordsPage: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div className="export-records-page min-h-screen bg-gray-50">
|
|
||||||
<div className="container mx-auto px-4 py-8">
|
|
||||||
<div className="bg-white rounded-lg shadow-sm">
|
|
||||||
<ExportRecordManager />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ExportRecordsPage;
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react';
|
import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { ArrowLeft, FolderOpen, Upload, FileVideo, FileAudio, FileImage, HardDrive, Brain, Loader2, Link, Layers, Calendar, MapPin, Users, CheckCircle, Filter, Shuffle } from 'lucide-react';
|
import { ArrowLeft, FolderOpen, Upload, FileVideo, FileAudio, FileImage, HardDrive, Brain, Loader2, Link, Layers, Calendar, MapPin, Users, CheckCircle, Filter, Shuffle, Download } from 'lucide-react';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { useProjectStore } from '../store/projectStore';
|
import { useProjectStore } from '../store/projectStore';
|
||||||
import { useMaterialStore } from '../store/materialStore';
|
import { useMaterialStore } from '../store/materialStore';
|
||||||
|
|
@ -16,6 +16,7 @@ import { MaterialEditDialog } from '../components/MaterialEditDialog';
|
||||||
import { VideoClassificationProgress } from '../components/VideoClassificationProgress';
|
import { VideoClassificationProgress } from '../components/VideoClassificationProgress';
|
||||||
import { AiAnalysisLogViewer } from '../components/AiAnalysisLogViewer';
|
import { AiAnalysisLogViewer } from '../components/AiAnalysisLogViewer';
|
||||||
import MaterialCardSkeleton from '../components/MaterialCardSkeleton';
|
import MaterialCardSkeleton from '../components/MaterialCardSkeleton';
|
||||||
|
import ExportRecordManager from '../components/ExportRecordManager';
|
||||||
import { ProjectTemplateBindingList } from '../components/ProjectTemplateBindingList';
|
import { ProjectTemplateBindingList } from '../components/ProjectTemplateBindingList';
|
||||||
import { ProjectTemplateBindingForm } from '../components/ProjectTemplateBindingForm';
|
import { ProjectTemplateBindingForm } from '../components/ProjectTemplateBindingForm';
|
||||||
import { MaterialMatchingResultDialog } from '../components/MaterialMatchingResultDialog';
|
import { MaterialMatchingResultDialog } from '../components/MaterialMatchingResultDialog';
|
||||||
|
|
@ -123,7 +124,7 @@ export const ProjectDetails: React.FC = () => {
|
||||||
const [editingBinding, setEditingBinding] = useState<ProjectTemplateBindingDetail | null>(null);
|
const [editingBinding, setEditingBinding] = useState<ProjectTemplateBindingDetail | null>(null);
|
||||||
const [showMaterialEditDialog, setShowMaterialEditDialog] = useState(false);
|
const [showMaterialEditDialog, setShowMaterialEditDialog] = useState(false);
|
||||||
const [editingMaterial, setEditingMaterial] = useState<Material | null>(null);
|
const [editingMaterial, setEditingMaterial] = useState<Material | null>(null);
|
||||||
const [activeTab, setActiveTab] = useState<'overview' | 'materials' | 'segments' | 'templates' | 'matching-results' | 'usage-stats' | 'ai-logs'>('overview');
|
const [activeTab, setActiveTab] = useState<'overview' | 'materials' | 'segments' | 'templates' | 'matching-results' | 'export-records' | 'usage-stats' | 'ai-logs'>('overview');
|
||||||
const [_batchClassificationResult, setBatchClassificationResult] = useState<ProjectBatchClassificationResponse | null>(null);
|
const [_batchClassificationResult, setBatchClassificationResult] = useState<ProjectBatchClassificationResponse | null>(null);
|
||||||
|
|
||||||
// 素材匹配状态
|
// 素材匹配状态
|
||||||
|
|
@ -976,6 +977,20 @@ export const ProjectDetails: React.FC = () => {
|
||||||
<span className="sm:hidden">匹配</span>
|
<span className="sm:hidden">匹配</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('export-records')}
|
||||||
|
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
|
||||||
|
activeTab === 'export-records'
|
||||||
|
? '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">
|
||||||
|
<Download className="w-4 h-4" />
|
||||||
|
<span className="hidden sm:inline">导出记录</span>
|
||||||
|
<span className="sm:hidden">导出</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('usage-stats')}
|
onClick={() => setActiveTab('usage-stats')}
|
||||||
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
|
className={`py-3 px-4 font-medium text-sm transition-all duration-200 whitespace-nowrap rounded-t-lg relative ${
|
||||||
|
|
@ -1402,6 +1417,23 @@ export const ProjectDetails: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 导出记录选项卡 */}
|
||||||
|
{activeTab === 'export-records' && project && (
|
||||||
|
<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">导出记录</h3>
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
查看项目中所有导出操作的记录,包括导出状态、文件信息和统计数据。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ExportRecordManager
|
||||||
|
projectId={project.id}
|
||||||
|
showHeader={false}
|
||||||
|
compact={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 素材使用状态选项卡 */}
|
{/* 素材使用状态选项卡 */}
|
||||||
{activeTab === 'usage-stats' && project && (
|
{activeTab === 'usage-stats' && project && (
|
||||||
<div className="p-4 md:p-6 space-y-6">
|
<div className="p-4 md:p-6 space-y-6">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue