fix: 修复批量删除需要点击两次的问题
问题分析: - 第一次点击批量删除时,onConfirm回调中使用的batchDeleteConfirm.resultIds可能因为React状态更新的异步性而被清空 - handleBatchDelete函数内部会重置batchDeleteConfirm状态,导致竞态条件 解决方案: - 将对话框关闭逻辑从handleBatchDelete中移出 - 创建handleConfirmBatchDelete函数,在调用删除前先复制resultIds数组并立即关闭对话框 - 修复数据库查询中缺少is_exported和last_exported_at字段的问题 - 添加更好的loading状态管理和用户体验优化 修复内容: - 修复TemplateMatchingResultRepository中SELECT语句缺少新字段的问题 - 重构批量删除的状态管理逻辑,避免竞态条件 - 添加调试日志帮助问题诊断 - 改进loading状态的视觉反馈
This commit is contained in:
parent
f6041c6eea
commit
66ceaf3274
|
|
@ -85,7 +85,8 @@ impl TemplateMatchingResultRepository {
|
|||
"SELECT id, project_id, template_id, binding_id, result_name, description,
|
||||
total_segments, matched_segments, failed_segments, success_rate,
|
||||
used_materials, used_models, matching_duration_ms, quality_score,
|
||||
status, metadata, export_count, created_at, updated_at, is_active
|
||||
status, metadata, export_count, is_exported, last_exported_at,
|
||||
created_at, updated_at, is_active
|
||||
FROM template_matching_results WHERE id = ?1"
|
||||
)?;
|
||||
|
||||
|
|
@ -171,7 +172,8 @@ impl TemplateMatchingResultRepository {
|
|||
let mut query = "SELECT id, project_id, template_id, binding_id, result_name, description,
|
||||
total_segments, matched_segments, failed_segments, success_rate,
|
||||
used_materials, used_models, matching_duration_ms, quality_score,
|
||||
status, metadata, export_count, created_at, updated_at, is_active
|
||||
status, metadata, export_count, is_exported, last_exported_at,
|
||||
created_at, updated_at, is_active
|
||||
FROM template_matching_results WHERE is_active = 1".to_string();
|
||||
let mut params: Vec<String> = Vec::new();
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ interface TemplateMatchingResultCardProps {
|
|||
isSelected?: boolean;
|
||||
onToggleSelect?: () => void;
|
||||
showExportStatus?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const TemplateMatchingResultCard: React.FC<TemplateMatchingResultCardProps> = ({
|
||||
|
|
@ -22,6 +23,7 @@ export const TemplateMatchingResultCard: React.FC<TemplateMatchingResultCardProp
|
|||
isSelected = false,
|
||||
onToggleSelect,
|
||||
showExportStatus = false,
|
||||
disabled = false,
|
||||
}) => {
|
||||
// 格式化时长显示
|
||||
const formatDuration = (ms: number): string => {
|
||||
|
|
@ -83,7 +85,7 @@ export const TemplateMatchingResultCard: React.FC<TemplateMatchingResultCardProp
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={`card card-interactive group ${isSelected ? 'ring-2 ring-blue-500 bg-blue-50' : ''}`}>
|
||||
<div className={`card card-interactive group ${isSelected ? 'ring-2 ring-blue-500 bg-blue-50' : ''} ${disabled ? 'opacity-75' : ''}`}>
|
||||
{/* 卡片头部 */}
|
||||
<div className="card-header">
|
||||
<div className="flex items-start justify-between">
|
||||
|
|
@ -94,7 +96,10 @@ export const TemplateMatchingResultCard: React.FC<TemplateMatchingResultCardProp
|
|||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={onToggleSelect}
|
||||
className="form-checkbox h-4 w-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500"
|
||||
disabled={disabled}
|
||||
className={`form-checkbox h-4 w-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500 ${
|
||||
disabled ? 'opacity-50 cursor-not-allowed' : ''
|
||||
}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -132,29 +132,41 @@ export const TemplateMatchingResultManager: React.FC<TemplateMatchingResultManag
|
|||
|
||||
// 批量删除匹配结果
|
||||
const handleBatchDelete = async (resultIds: string[]) => {
|
||||
console.log('开始批量删除,ID列表:', resultIds);
|
||||
setBatchOperationLoading(true);
|
||||
|
||||
try {
|
||||
const [deletedResults, deletedUsageRecords] = await invoke<[number, number]>(
|
||||
'batch_soft_delete_matching_results_with_usage_reset',
|
||||
{ resultIds }
|
||||
);
|
||||
|
||||
console.log(`删除结果: ${deletedResults} 个匹配结果,${deletedUsageRecords} 条使用记录`);
|
||||
success(`成功删除 ${deletedResults} 个匹配结果,重置 ${deletedUsageRecords} 条使用记录`);
|
||||
|
||||
// 重新加载列表
|
||||
console.log('重新加载列表...');
|
||||
await loadResults();
|
||||
await loadStatistics();
|
||||
console.log('列表重新加载完成');
|
||||
|
||||
// 清空选择
|
||||
setSelectedResults(new Set());
|
||||
setBatchDeleteConfirm({ show: false, resultIds: [] });
|
||||
} catch (err) {
|
||||
console.error('批量删除失败:', err);
|
||||
setError(`批量删除失败: ${err}`);
|
||||
} finally {
|
||||
setBatchOperationLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 确认批量删除
|
||||
const handleConfirmBatchDelete = async () => {
|
||||
const resultIds = [...batchDeleteConfirm.resultIds]; // 复制数组避免状态变化影响
|
||||
setBatchDeleteConfirm({ show: false, resultIds: [] }); // 立即关闭对话框
|
||||
await handleBatchDelete(resultIds);
|
||||
};
|
||||
|
||||
// 切换选择状态
|
||||
const handleToggleSelect = (resultId: string) => {
|
||||
const newSelected = new Set(selectedResults);
|
||||
|
|
@ -291,7 +303,7 @@ export const TemplateMatchingResultManager: React.FC<TemplateMatchingResultManag
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="template-matching-result-manager space-y-6">
|
||||
<div className="template-matching-result-manager space-y-6 relative">
|
||||
{/* 统计面板 */}
|
||||
{showStats && statistics && (
|
||||
<TemplateMatchingResultStatsPanel statistics={statistics} />
|
||||
|
|
@ -378,13 +390,20 @@ export const TemplateMatchingResultManager: React.FC<TemplateMatchingResultManag
|
|||
resultIds: Array.from(selectedResults)
|
||||
})}
|
||||
disabled={batchOperationLoading}
|
||||
className="btn btn-danger btn-sm"
|
||||
className={`btn btn-danger btn-sm ${batchOperationLoading ? 'opacity-75 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
{batchOperationLoading && (
|
||||
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
)}
|
||||
{batchOperationLoading ? '删除中...' : `批量删除 (${selectedResults.size})`}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSelectedResults(new Set())}
|
||||
className="btn btn-secondary btn-sm"
|
||||
disabled={batchOperationLoading}
|
||||
className={`btn btn-secondary btn-sm ${batchOperationLoading ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
取消选择
|
||||
</button>
|
||||
|
|
@ -423,12 +442,13 @@ export const TemplateMatchingResultManager: React.FC<TemplateMatchingResultManag
|
|||
{/* 列表控制栏 */}
|
||||
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border border-gray-200/50 p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<label className="flex items-center space-x-2 cursor-pointer">
|
||||
<label className={`flex items-center space-x-2 ${batchOperationLoading ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedResults.size === results.length && results.length > 0}
|
||||
onChange={handleToggleSelectAll}
|
||||
className="form-checkbox h-4 w-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500"
|
||||
disabled={batchOperationLoading}
|
||||
className="form-checkbox h-4 w-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500 disabled:opacity-50"
|
||||
/>
|
||||
<span className="text-sm font-medium text-gray-700">
|
||||
{selectedResults.size === results.length && results.length > 0 ? '取消全选' : '全选'}
|
||||
|
|
@ -465,8 +485,9 @@ export const TemplateMatchingResultManager: React.FC<TemplateMatchingResultManag
|
|||
onExportToJianying={() => handleExportToJianying(result)}
|
||||
onExportToJianyingV2={() => handleExportToJianyingV2(result)}
|
||||
isSelected={selectedResults.has(result.id)}
|
||||
onToggleSelect={() => handleToggleSelect(result.id)}
|
||||
onToggleSelect={() => !batchOperationLoading && handleToggleSelect(result.id)}
|
||||
showExportStatus={true}
|
||||
disabled={batchOperationLoading}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -527,9 +548,28 @@ export const TemplateMatchingResultManager: React.FC<TemplateMatchingResultManag
|
|||
isOpen={batchDeleteConfirm.show}
|
||||
title="批量删除匹配结果"
|
||||
message={`确定要删除选中的 ${batchDeleteConfirm.resultIds.length} 个匹配结果吗?此操作将同时重置相关资源的使用状态,且不可撤销。`}
|
||||
onConfirm={() => handleBatchDelete(batchDeleteConfirm.resultIds)}
|
||||
onCancel={() => setBatchDeleteConfirm({ show: false, resultIds: [] })}
|
||||
deleting={batchOperationLoading}
|
||||
onConfirm={handleConfirmBatchDelete}
|
||||
onCancel={() => !batchOperationLoading && setBatchDeleteConfirm({ show: false, resultIds: [] })}
|
||||
/>
|
||||
|
||||
{/* 批量操作Loading遮罩 */}
|
||||
{batchOperationLoading && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg p-6 shadow-xl max-w-sm w-full mx-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<svg className="animate-spin h-6 w-6 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-gray-900">正在删除匹配结果</h3>
|
||||
<p className="text-sm text-gray-500">正在删除 {selectedResults.size} 个匹配结果并重置资源状态...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue