- tool-b/index.tsx: min-h-screen h-full overflow-y-auto - 所有Step组件添加 h-full overflow-y-auto - 修复:MainLayout改为固定高度后,工具B底部按钮可见性问题 - 效果:页面无滚动条,内容可内部滚动
144 lines
5.4 KiB
TypeScript
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;
|