fix: 修复ProjectDetails.tsx中的无限请求问题和模特名称显示

- 修复loadProjectClassificationStats函数的无限循环问题
- 使用useRef跟踪分类统计加载状态,避免重复请求
- 添加模特信息加载功能,显示真实模特名称而非model_id
- 优化useEffect依赖,防止不必要的重新渲染和请求
- 在项目切换和导入完成时正确重置加载状态
This commit is contained in:
imeepos 2025-07-16 18:33:57 +08:00
parent 08fa4eda61
commit 52ce437e63
2 changed files with 54 additions and 18 deletions

View File

@ -29,7 +29,6 @@ pub async fn get_model_by_id(
state: State<'_, AppState>,
id: String,
) -> Result<Option<Model>, String> {
println!("get_model_by_id 命令开始执行ID: {}", id);
let repository_guard = state.get_model_repository()
.map_err(|e| {
@ -43,14 +42,12 @@ pub async fn get_model_by_id(
"模特仓库未初始化".to_string()
})?;
println!("调用 ModelService::get_model_by_id");
let result = ModelService::get_model_by_id(repository, &id)
.map_err(|e| {
println!("ModelService::get_model_by_id 失败: {}", e);
e.to_string()
});
println!("get_model_by_id 命令执行完成,结果: {:?}", result.is_ok());
result
}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState, useCallback, useMemo } from 'react';
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 } from 'lucide-react';
import { invoke } from '@tauri-apps/api/core';
@ -134,6 +134,10 @@ export const ProjectDetails: React.FC = () => {
const [materialModelFilter, setMaterialModelFilter] = useState<string>('全部');
const [materialUsageFilter, setMaterialUsageFilter] = useState<string>('全部');
const [materialClassificationRecords, setMaterialClassificationRecords] = useState<{[materialId: string]: any[]}>({});
const [modelsMap, setModelsMap] = useState<{[modelId: string]: any}>({});
// 用于跟踪分类统计是否已加载的ref
const classificationStatsLoadedRef = useRef<string | null>(null);
// 加载片段统计数据
const loadSegmentStats = useCallback(async (projectId: string) => {
@ -155,12 +159,30 @@ export const ProjectDetails: React.FC = () => {
}
}, []);
// 加载项目分类统计信息
const loadProjectClassificationStats = useCallback(async (projectId: string) => {
// 加载所有模特信息
const loadAllModels = useCallback(async () => {
try {
const models = await invoke('get_all_models') as any[];
const modelMap: {[modelId: string]: any} = {};
models.forEach(model => {
modelMap[model.id] = model;
});
setModelsMap(modelMap);
} catch (error) {
console.error('Failed to load models:', error);
setModelsMap({});
}
}, []);
// 加载项目分类统计信息
const loadProjectClassificationStats = useCallback(async (projectId: string, materialList?: any[]) => {
try {
// 使用传入的素材列表或当前的素材列表
const materialsToProcess = materialList || materials;
// 获取每个素材的分类记录
const classificationRecords: {[materialId: string]: any[]} = {};
for (const material of materials) {
for (const material of materialsToProcess) {
try {
const records = await invoke('get_material_classification_records', { materialId: material.id }) as any[];
classificationRecords[material.id] = records;
@ -175,7 +197,7 @@ export const ProjectDetails: React.FC = () => {
console.error('Failed to load project classification stats:', error);
setMaterialClassificationRecords({});
}
}, [materials]);
}, []);
// 加载项目详情
useEffect(() => {
@ -192,6 +214,9 @@ export const ProjectDetails: React.FC = () => {
// 加载项目素材
if (foundProject) {
// 重置分类统计加载状态
classificationStatsLoadedRef.current = null;
loadMaterials(foundProject.id);
loadMaterialStats(foundProject.id);
// 加载项目的模板绑定
@ -200,18 +225,20 @@ export const ProjectDetails: React.FC = () => {
loadSegmentStats(foundProject.id);
// 加载素材使用状态概览
loadUsageOverview(foundProject.id);
// 加载项目分类统计信息
loadProjectClassificationStats(foundProject.id);
// 加载所有模特信息
loadAllModels();
// 加载项目分类统计信息将在素材加载完成后执行
}
}
}, [id, projects, loadMaterials, loadMaterialStats, bindingActions.fetchTemplatesByProject, loadSegmentStats, loadUsageOverview, loadProjectClassificationStats]);
}, [id, projects, loadMaterials, loadMaterialStats, bindingActions.fetchTemplatesByProject, loadSegmentStats, loadUsageOverview, loadAllModels, loadProjectClassificationStats]);
// 当素材列表变化时,重新加载分类统计信息
// 当素材加载完成后,加载分类统计信息
useEffect(() => {
if (project && materials.length > 0) {
loadProjectClassificationStats(project.id);
if (project && materials.length > 0 && classificationStatsLoadedRef.current !== project.id) {
classificationStatsLoadedRef.current = project.id;
loadProjectClassificationStats(project.id, materials);
}
}, [materials.length, project, loadProjectClassificationStats]);
}, [project?.id, materials.length, loadProjectClassificationStats]);
// 加载模板列表
useEffect(() => {
@ -284,6 +311,8 @@ export const ProjectDetails: React.FC = () => {
console.log('导入完成:', result);
// 重新加载素材列表
if (project) {
// 重置分类统计加载状态,以便重新加载
classificationStatsLoadedRef.current = null;
loadMaterials(project.id);
loadMaterialStats(project.id);
}
@ -591,7 +620,6 @@ export const ProjectDetails: React.FC = () => {
const modelCounts: {[key: string]: number} = {};
materials.forEach(material => {
if (material.model_id) {
// 这里需要根据model_id获取模特名称暂时使用model_id
const modelKey = material.model_id;
modelCounts[modelKey] = (modelCounts[modelKey] || 0) + 1;
} else {
@ -600,15 +628,26 @@ export const ProjectDetails: React.FC = () => {
});
Object.entries(modelCounts).forEach(([modelKey, count]) => {
let label = '未指定';
if (modelKey !== '未指定') {
// 从modelsMap中获取模特的真实名称
const model = modelsMap[modelKey];
if (model) {
label = model.stage_name || model.name;
} else {
label = `模特-${modelKey}`;
}
}
options.push({
label: modelKey === '未指定' ? '未指定' : `模特-${modelKey}`,
label,
value: modelKey,
count
});
});
return options;
}, [materials]);
}, [materials, modelsMap]);
const materialUsageOptions = useMemo(() => {
// 计算使用状态统计