fix: 代码整合修复 - Entity类型、题库生成、评估流程等14项修复

This commit is contained in:
Developer
2026-05-14 09:55:07 +08:00
parent 122ab5e96f
commit 368eddfd75
17 changed files with 1666 additions and 115 deletions
+83 -1
View File
@@ -50,6 +50,7 @@ export const AssessmentView: React.FC<AssessmentViewProps> = ({
const [showBasis, setShowBasis] = useState(false);
const [templates, setTemplates] = useState<AssessmentTemplate[]>([]);
const [selectedTemplate, setSelectedTemplate] = useState<string | null>(null);
const [timeCheck, setTimeCheck] = useState<{ totalTimeRemaining: number; questionTimeRemaining: number; isTotalTimeout: boolean; isQuestionTimeout: boolean } | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
@@ -91,6 +92,27 @@ export const AssessmentView: React.FC<AssessmentViewProps> = ({
fetchHistory();
}, []);
useEffect(() => {
if (!session || session.status !== 'IN_PROGRESS') {
setTimeCheck(null);
return;
}
const checkTime = async () => {
try {
const data = await assessmentService.checkTimeLimits(session.id);
setTimeCheck(data);
if (data.isTotalTimeout || data.isQuestionTimeout) {
setError(t('timeLimitExceeded'));
}
} catch (err) {
console.error('Failed to check time:', err);
}
};
checkTime();
const interval = setInterval(checkTime, 10000);
return () => clearInterval(interval);
}, [session]);
const isZh = language === 'zh';
const isJa = language === 'ja';
@@ -502,6 +524,12 @@ export const AssessmentView: React.FC<AssessmentViewProps> = ({
{processStep || t('aiIsProcessing')}
</span>
)}
{timeCheck && (
<div className={`flex items-center gap-1 text-[10px] font-bold px-2 py-0.5 rounded-full ${timeCheck.totalTimeRemaining < 60 || timeCheck.questionTimeRemaining < 30 ? 'bg-red-50 text-red-600' : 'bg-slate-100 text-slate-600'}`}>
<span></span>
<span>{Math.floor(timeCheck.totalTimeRemaining / 60)}:{String(timeCheck.totalTimeRemaining % 60).padStart(2, '0')}</span>
</div>
)}
</div>
</div>
@@ -745,10 +773,64 @@ export const AssessmentView: React.FC<AssessmentViewProps> = ({
{t('newAssessmentSession')}
</button>
<button
className="px-8 py-4 bg-white border-2 border-slate-100 text-slate-700 rounded-2xl font-bold hover:bg-slate-50 transition-all active:scale-[0.98]"
onClick={async () => {
if (!session) return;
try {
const result = await assessmentService.exportPdf(session.id);
const blob = new Blob([result.content], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = result.filename;
a.click();
URL.revokeObjectURL(url);
} catch (err) {
console.error('Failed to export PDF:', err);
}
}}
className="px-6 py-4 bg-white border-2 border-slate-100 text-slate-700 rounded-2xl font-bold hover:bg-slate-50 transition-all active:scale-[0.98]"
>
{t('downloadPdfReport')}
</button>
<button
onClick={async () => {
if (!session) return;
try {
const result = await assessmentService.exportExcel(session.id);
const binary = atob(result.buffer);
const array = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
array[i] = binary.charCodeAt(i);
}
const blob = new Blob([array], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = result.filename;
a.click();
URL.revokeObjectURL(url);
} catch (err) {
console.error('Failed to export Excel:', err);
}
}}
className="px-6 py-4 bg-white border-2 border-slate-100 text-slate-700 rounded-2xl font-bold hover:bg-slate-50 transition-all active:scale-[0.98]"
>
{t('exportExcel')}
</button>
<button
onClick={async () => {
if (!session) return;
try {
const cert = await assessmentService.getCertificate(session.id);
alert(`${t('certificate')}: ${cert.level}\n${t('totalScore')}: ${cert.totalScore}\n${t('passed')}: ${cert.passed ? t('yes') : t('no')}`);
} catch (err) {
console.error('Failed to get certificate:', err);
}
}}
className="px-6 py-4 bg-amber-50 border-2 border-amber-200 text-amber-700 rounded-2xl font-bold hover:bg-amber-100 transition-all active:scale-[0.98]"
>
{t('viewCertificate')}
</button>
</div>
</div>
</div>