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 TemplateManagement from './pages/TemplateManagement';
|
||||
import { MaterialModelBinding } from './pages/MaterialModelBinding';
|
||||
import ExportRecordsPage from './pages/ExportRecordsPage';
|
||||
import Navigation from './components/Navigation';
|
||||
import { NotificationSystem, useNotifications } from './components/NotificationSystem';
|
||||
import { useProjectStore } from './store/projectStore';
|
||||
|
|
@ -79,7 +78,6 @@ function App() {
|
|||
<Route path="/ai-classification-settings" element={<AiClassificationSettings />} />
|
||||
<Route path="/templates" element={<TemplateManagement />} />
|
||||
<Route path="/material-model-binding" element={<MaterialModelBinding />} />
|
||||
<Route path="/export-records" element={<ExportRecordsPage />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -11,11 +11,15 @@ import {
|
|||
interface ExportRecordManagerProps {
|
||||
projectId?: string;
|
||||
matchingResultId?: string;
|
||||
showHeader?: boolean; // 是否显示标题和描述
|
||||
compact?: boolean; // 紧凑模式,适用于tab显示
|
||||
}
|
||||
|
||||
const ExportRecordManager: React.FC<ExportRecordManagerProps> = ({
|
||||
projectId,
|
||||
matchingResultId
|
||||
const ExportRecordManager: React.FC<ExportRecordManagerProps> = ({
|
||||
projectId,
|
||||
matchingResultId,
|
||||
showHeader = true,
|
||||
compact = false
|
||||
}) => {
|
||||
const [records, setRecords] = useState<ExportRecord[]>([]);
|
||||
const [statistics, setStatistics] = useState<ExportRecordStatistics | null>(null);
|
||||
|
|
@ -55,7 +59,7 @@ const ExportRecordManager: React.FC<ExportRecordManagerProps> = ({
|
|||
|
||||
const result = await invoke<ExportRecord[]>('list_export_records', { options });
|
||||
setRecords(result);
|
||||
|
||||
|
||||
// 更新总数(这里简化处理,实际应该从后端返回)
|
||||
setPagination(prev => ({ ...prev, total: result.length }));
|
||||
} catch (err) {
|
||||
|
|
@ -70,8 +74,8 @@ const ExportRecordManager: React.FC<ExportRecordManagerProps> = ({
|
|||
try {
|
||||
let stats: ExportRecordStatistics;
|
||||
if (projectId) {
|
||||
stats = await invoke<ExportRecordStatistics>('get_project_export_statistics', {
|
||||
projectId
|
||||
stats = await invoke<ExportRecordStatistics>('get_project_export_statistics', {
|
||||
projectId
|
||||
});
|
||||
} else {
|
||||
stats = await invoke<ExportRecordStatistics>('get_global_export_statistics');
|
||||
|
|
@ -127,8 +131,8 @@ const ExportRecordManager: React.FC<ExportRecordManagerProps> = ({
|
|||
if (!days) return;
|
||||
|
||||
try {
|
||||
const count = await invoke<number>('cleanup_expired_export_records', {
|
||||
days: parseInt(days)
|
||||
const count = await invoke<number>('cleanup_expired_export_records', {
|
||||
days: parseInt(days)
|
||||
});
|
||||
alert(`已清理 ${count} 条过期记录`);
|
||||
await loadExportRecords();
|
||||
|
|
@ -184,31 +188,35 @@ const ExportRecordManager: React.FC<ExportRecordManagerProps> = ({
|
|||
}, [projectId, matchingResultId, filters, pagination.page]);
|
||||
|
||||
return (
|
||||
<div className="export-record-manager p-6">
|
||||
<div className="header mb-6">
|
||||
<h2 className="text-2xl font-bold text-gray-800 mb-4">
|
||||
导出记录管理
|
||||
{projectId && <span className="text-sm text-gray-600 ml-2">(项目范围)</span>}
|
||||
{matchingResultId && <span className="text-sm text-gray-600 ml-2">(匹配结果范围)</span>}
|
||||
</h2>
|
||||
<div className={`export-record-manager ${compact ? 'p-0' : 'p-6'}`}>
|
||||
{showHeader && (
|
||||
<div className="header mb-6">
|
||||
<h2 className="text-2xl font-bold text-gray-800 mb-4">
|
||||
导出记录管理
|
||||
{projectId && <span className="text-sm text-gray-600 ml-2">(项目范围)</span>}
|
||||
{matchingResultId && <span className="text-sm text-gray-600 ml-2">(匹配结果范围)</span>}
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="content">
|
||||
{/* 统计信息 */}
|
||||
{statistics && (
|
||||
<div className="stats-grid grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<div className="stat-card bg-blue-50 p-4 rounded-lg">
|
||||
<div className="text-2xl font-bold text-blue-600">{statistics.total_exports}</div>
|
||||
<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 ${compact ? 'p-3' : 'p-4'} rounded-lg`}>
|
||||
<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>
|
||||
<div className="stat-card bg-green-50 p-4 rounded-lg">
|
||||
<div className="text-2xl font-bold text-green-600">{statistics.successful_exports}</div>
|
||||
<div className={`stat-card bg-green-50 ${compact ? 'p-3' : 'p-4'} rounded-lg`}>
|
||||
<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>
|
||||
<div className="stat-card bg-red-50 p-4 rounded-lg">
|
||||
<div className="text-2xl font-bold text-red-600">{statistics.failed_exports}</div>
|
||||
<div className={`stat-card bg-red-50 ${compact ? 'p-3' : 'p-4'} rounded-lg`}>
|
||||
<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>
|
||||
<div className="stat-card bg-purple-50 p-4 rounded-lg">
|
||||
<div className="text-2xl font-bold text-purple-600">
|
||||
<div className={`stat-card bg-purple-50 ${compact ? 'p-3' : 'p-4'} rounded-lg`}>
|
||||
<div className={`${compact ? 'text-xl' : 'text-2xl'} font-bold text-purple-600`}>
|
||||
{formatFileSize(statistics.total_file_size)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">总文件大小</div>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import {
|
|||
UserGroupIcon,
|
||||
CpuChipIcon,
|
||||
DocumentDuplicateIcon,
|
||||
LinkIcon,
|
||||
DocumentArrowDownIcon
|
||||
LinkIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
|
||||
const Navigation: React.FC = () => {
|
||||
|
|
@ -42,12 +41,6 @@ const Navigation: React.FC = () => {
|
|||
href: '/ai-classification-settings',
|
||||
icon: CpuChipIcon,
|
||||
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 { 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 { useProjectStore } from '../store/projectStore';
|
||||
import { useMaterialStore } from '../store/materialStore';
|
||||
|
|
@ -16,6 +16,7 @@ import { MaterialEditDialog } from '../components/MaterialEditDialog';
|
|||
import { VideoClassificationProgress } from '../components/VideoClassificationProgress';
|
||||
import { AiAnalysisLogViewer } from '../components/AiAnalysisLogViewer';
|
||||
import MaterialCardSkeleton from '../components/MaterialCardSkeleton';
|
||||
import ExportRecordManager from '../components/ExportRecordManager';
|
||||
import { ProjectTemplateBindingList } from '../components/ProjectTemplateBindingList';
|
||||
import { ProjectTemplateBindingForm } from '../components/ProjectTemplateBindingForm';
|
||||
import { MaterialMatchingResultDialog } from '../components/MaterialMatchingResultDialog';
|
||||
|
|
@ -123,7 +124,7 @@ 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<'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);
|
||||
|
||||
// 素材匹配状态
|
||||
|
|
@ -976,6 +977,20 @@ export const ProjectDetails: React.FC = () => {
|
|||
<span className="sm:hidden">匹配</span>
|
||||
</div>
|
||||
</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
|
||||
onClick={() => setActiveTab('usage-stats')}
|
||||
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>
|
||||
)}
|
||||
|
||||
{/* 导出记录选项卡 */}
|
||||
{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 && (
|
||||
<div className="p-4 md:p-6 space-y-6">
|
||||
|
|
|
|||
Loading…
Reference in New Issue