fix: 修复项目详情页面模板绑定和素材绑定页面的搜索功能

- 修复项目详情页面模板绑定搜索功能:使用过滤后的绑定详情而不是原始数据
- 修复素材绑定页面搜索功能:添加useEffect监听搜索条件变化并重新加载数据
- 修复素材绑定页面统计功能:实现全局模特绑定统计API和前端调用
- 优化MaterialModelBindingService的getMaterialsByFilter方法,正确处理多重过滤条件
- 添加后端get_global_model_binding_stats命令和相关仓库方法
- 确保搜索和统计功能在所有相关页面正常工作
This commit is contained in:
imeepos 2025-07-15 14:10:29 +08:00
parent 03f410601a
commit de446b6410
7 changed files with 116 additions and 27 deletions

View File

@ -919,6 +919,28 @@ impl MaterialService {
},
})
}
/// 获取全局模特绑定统计
pub fn get_global_model_binding_stats(
repository: &MaterialRepository,
) -> Result<ProjectModelBindingStats> {
debug!("获取全局模特绑定统计");
let bound_count = repository.count_global_bound_materials()?;
let unbound_count = repository.count_global_unbound_materials()?;
let total_count = bound_count + unbound_count;
Ok(ProjectModelBindingStats {
total_materials: total_count,
bound_materials: bound_count,
unbound_materials: unbound_count,
binding_rate: if total_count > 0 {
(bound_count as f64 / total_count as f64) * 100.0
} else {
0.0
},
})
}
}
/// 项目模特绑定统计信息

View File

@ -643,6 +643,32 @@ impl MaterialRepository {
Ok(count as u32)
}
/// 获取全局绑定了模特的素材数量
pub fn count_global_bound_materials(&self) -> Result<u32> {
let conn = self.connection.lock().unwrap();
let count: i64 = conn.query_row(
"SELECT COUNT(*) FROM materials WHERE model_id IS NOT NULL",
[],
|row| row.get(0),
)?;
Ok(count as u32)
}
/// 获取全局未绑定模特的素材数量
pub fn count_global_unbound_materials(&self) -> Result<u32> {
let conn = self.connection.lock().unwrap();
let count: i64 = conn.query_row(
"SELECT COUNT(*) FROM materials WHERE model_id IS NULL",
[],
|row| row.get(0),
)?;
Ok(count as u32)
}
}
/// 模特素材统计信息

View File

@ -80,6 +80,7 @@ pub fn run() {
commands::material_commands::switch_material_model,
commands::material_commands::get_model_material_statistics,
commands::material_commands::get_project_model_binding_stats,
commands::material_commands::get_global_model_binding_stats,
commands::material_commands::update_material,
// 模特管理命令
commands::model_commands::create_model,

View File

@ -1013,6 +1013,21 @@ pub async fn get_project_model_binding_stats(
.map_err(|e| format!("获取项目模特绑定统计失败: {}", e))
}
/// 获取全局模特绑定统计信息命令
#[command]
pub async fn get_global_model_binding_stats(
state: State<'_, AppState>,
) -> Result<crate::business::services::material_service::ProjectModelBindingStats, String> {
let repository_guard = state.get_material_repository()
.map_err(|e| format!("获取素材仓库失败: {}", e))?;
let repository = repository_guard.as_ref()
.ok_or("素材仓库未初始化")?;
MaterialService::get_global_model_binding_stats(repository)
.map_err(|e| format!("获取全局模特绑定统计失败: {}", e))
}
/// 更新素材信息命令(包括模特绑定)
#[command]
pub async fn update_material(

View File

@ -35,7 +35,7 @@ export const MaterialModelBinding: React.FC = () => {
const [materials, setMaterials] = useState<Material[]>([]);
const [models, setModels] = useState<Model[]>([]);
const [stats] = useState<MaterialModelBindingStats | null>(null);
const [stats, setStats] = useState<MaterialModelBindingStats | null>(null);
const [selectedModel, setSelectedModel] = useState<string>('');
const [searchQuery, setSearchQuery] = useState('');
const [filterType, setFilterType] = useState<'all' | 'bound' | 'unbound'>('all');
@ -50,6 +50,13 @@ export const MaterialModelBinding: React.FC = () => {
loadData();
}, []);
// 当搜索条件变化时重新加载素材
useEffect(() => {
if (searchQuery !== '' || filterType !== 'all' || selectedModel !== '') {
loadMaterials();
}
}, [searchQuery, filterType, selectedModel]);
const loadData = async () => {
setLoading(true);
try {
@ -92,9 +99,8 @@ export const MaterialModelBinding: React.FC = () => {
const loadStats = async () => {
try {
// 这里需要一个获取全局统计的API暂时跳过
// const statsData = await invoke<MaterialModelBindingStats>('get_global_model_binding_stats');
// setStats(statsData);
const statsData = await MaterialModelBindingService.getGlobalModelBindingStats();
setStats(statsData);
} catch (err) {
console.error('加载统计失败:', err);
}

View File

@ -61,6 +61,16 @@ export const ProjectDetails: React.FC = () => {
actions: bindingActions
} = useProjectTemplateBindingStore();
// 获取过滤后的绑定详情
const filteredBindingDetails = React.useMemo(() => {
if (!bindingDetails.length) return [];
const filteredBindings = bindingActions.getFilteredBindings();
return bindingDetails.filter(detail =>
filteredBindings.some(binding => binding.id === detail.binding.id)
);
}, [bindingDetails, bindingFilters, bindingActions]);
// 模板状态管理
const { templates, fetchTemplates } = useTemplateStore();
@ -607,7 +617,7 @@ export const ProjectDetails: React.FC = () => {
)}
<ProjectTemplateBindingList
bindings={bindingDetails}
bindings={filteredBindingDetails}
loading={bindingLoading}
selectedIds={selectedBindingIds}
onSelectionChange={bindingActions.setSelectedBindingIds}

View File

@ -112,6 +112,13 @@ export class MaterialModelBindingService {
});
}
/**
*
*/
static async getGlobalModelBindingStats(): Promise<MaterialModelBindingStats> {
return await invoke<MaterialModelBindingStats>('get_global_model_binding_stats');
}
/**
*
*/
@ -156,32 +163,34 @@ export class MaterialModelBindingService {
bound?: boolean;
search?: string;
}): Promise<Material[]> {
let materials: Material[] = [];
// 首先根据主要过滤条件获取素材
if (filter.modelId) {
return this.getMaterialsByModel(filter.modelId);
}
if (filter.bound === false) {
return this.getUnboundMaterials(filter.projectId);
}
if (filter.projectId) {
const materials = await this.getProjectMaterials(filter.projectId);
materials = await this.getMaterialsByModel(filter.modelId);
} else if (filter.bound === false) {
materials = await this.getUnboundMaterials(filter.projectId);
} else if (filter.projectId) {
materials = await this.getProjectMaterials(filter.projectId);
// 如果指定了绑定状态过滤
if (filter.bound === true) {
return materials.filter(m => m.model_id);
materials = materials.filter(m => m.model_id);
}
if (filter.search) {
return materials.filter(m =>
m.name.toLowerCase().includes(filter.search!.toLowerCase())
);
}
return materials;
} else {
// 如果没有指定项目,返回空数组
return [];
}
// 如果没有指定项目,返回空数组
return [];
// 应用搜索过滤
if (filter.search && filter.search.trim()) {
const searchLower = filter.search.toLowerCase();
materials = materials.filter(m =>
m.name.toLowerCase().includes(searchLower)
);
}
return materials;
}
/**