Files
AIclinicalresearch/frontend-v2/src/modules/dc/pages/tool-b/Step3Processing.tsx
HaHafeng a3586cdf30 fix(dc-tool-b): 修复滚动条问题 - 适配MainLayout固定高度
- tool-b/index.tsx: min-h-screen  h-full overflow-y-auto
- 所有Step组件添加 h-full overflow-y-auto
- 修复:MainLayout改为固定高度后,工具B底部按钮可见性问题
- 效果:页面无滚动条,内容可内部滚动
2025-12-10 19:28:08 +08:00

144 lines
5.4 KiB
TypeScript

import React, { useEffect, useState, useRef } from 'react';
import { ToolBState } from './index';
import * as toolBApi from '../../api/toolB';
interface Step3ProcessingProps {
state: ToolBState;
updateState: (updates: Partial<ToolBState>) => void;
onComplete: () => void;
}
const Step3Processing: React.FC<Step3ProcessingProps> = ({ state, updateState, onComplete }) => {
const [logs, setLogs] = useState<string[]>([]);
const hasStarted = useRef(false); // 🔑 防止React Strict Mode重复执行
useEffect(() => {
// 🔑 如果已经启动过,直接返回
if (hasStarted.current) {
return;
}
hasStarted.current = true;
let intervalId: NodeJS.Timeout | null = null;
let failureCount = 0;
const MAX_FAILURES = 3;
const startTask = async () => {
try {
// 1. 创建任务
setLogs(prev => [...prev, '正在创建提取任务...']);
const { taskId } = await toolBApi.createTask({
projectName: `${state.fileName}_提取任务`,
sourceFileKey: state.fileKey,
textColumn: state.selectedColumn,
diseaseType: state.diseaseType,
reportType: state.reportType,
targetFields: state.fields.map(f => ({ name: f.name, desc: f.desc }))
});
updateState({ taskId });
setLogs(prev => [...prev, `任务创建成功 (ID: ${taskId})`]);
setLogs(prev => [...prev, '初始化双模型引擎 (DeepSeek-V3 & Qwen-Max)...']);
// 2. 轮询进度
intervalId = setInterval(async () => {
try {
const progressData = await toolBApi.getTaskProgress(taskId);
// 重置失败计数
failureCount = 0;
updateState({ progress: progressData.progress });
// 更新日志
if (progressData.progress > 20 && progressData.progress < 25) {
setLogs(prev => [...prev, 'PII 脱敏完成...']);
}
if (progressData.progress > 40 && progressData.progress < 45) {
setLogs(prev => [...prev, `DeepSeek: 提取进度 ${progressData.progress}%`]);
}
if (progressData.progress > 50 && progressData.progress < 55) {
setLogs(prev => [...prev, `Qwen: 提取进度 ${progressData.progress}%`]);
}
if (progressData.progress > 80 && progressData.progress < 85) {
setLogs(prev => [...prev, '正在进行交叉验证 (Cross-Validation)...']);
}
// 完成时
if (progressData.status === 'completed' || progressData.progress >= 100) {
if (intervalId) clearInterval(intervalId);
setLogs(prev => [...prev, '✅ 提取完成!']);
setTimeout(onComplete, 800);
}
// 失败时
if (progressData.status === 'failed') {
if (intervalId) clearInterval(intervalId);
setLogs(prev => [...prev, '❌ 任务失败,请重试']);
}
} catch (error: any) {
console.error('Failed to fetch progress:', error);
failureCount++;
// 失败次数过多,停止轮询
if (failureCount >= MAX_FAILURES) {
if (intervalId) clearInterval(intervalId);
setLogs(prev => [...prev, `❌ 进度查询失败 (连续${MAX_FAILURES}次),已停止`]);
setLogs(prev => [...prev, `错误信息: ${error.message || error}`]);
}
}
}, 1000);
} catch (error: any) {
console.error('Failed to start task:', error);
setLogs(prev => [...prev, `❌ 创建任务失败: ${error.message || error}`]);
}
};
startTask();
// Cleanup: 组件卸载时清除定时器
return () => {
if (intervalId) {
clearInterval(intervalId);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="max-w-4xl mx-auto w-full space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500 mt-8 h-full overflow-y-auto">
<div className="text-center">
<h3 className="text-xl font-bold text-slate-900 mb-2">...</h3>
<p className="text-sm text-slate-500">DeepSeek-V3 & Qwen-Max </p>
</div>
{/* 进度条 */}
<div className="bg-white p-6 rounded-xl border border-slate-200">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-slate-700"></span>
<span className="text-2xl font-bold text-purple-600">{state.progress || 0}%</span>
</div>
<div className="w-full h-3 bg-slate-100 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-purple-500 to-pink-500 transition-all duration-500 ease-out"
style={{ width: `${state.progress || 0}%` }}
/>
</div>
</div>
{/* 日志输出 */}
<div className="bg-slate-900 text-slate-100 p-6 rounded-xl font-mono text-xs h-80 overflow-y-auto">
{logs.map((log, index) => (
<div key={index} className="mb-1 opacity-90 hover:opacity-100 transition-opacity">
<span className="text-slate-500">[{new Date().toLocaleTimeString()}]</span> {log}
</div>
))}
</div>
</div>
);
};
export default Step3Processing;