From b15e8212526fd54a00acbe43a2911dd931445dff Mon Sep 17 00:00:00 2001 From: Developer Date: Thu, 21 May 2026 15:42:59 +0800 Subject: [PATCH] feat: enriched certificate with template name, dimension scores, question details + Modal UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - generateCertificate: return templateName, questionDetails, dimensionScores - Frontend: replace alert() with certificate Modal showing level, scores, dimensions, questions - Status label: change from '已验证' to '合格' --- server/src/assessment/assessment.service.ts | 15 +++++- web/components/views/AssessmentView.tsx | 60 ++++++++++++++++++++- web/utils/translations.ts | 6 +-- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/server/src/assessment/assessment.service.ts b/server/src/assessment/assessment.service.ts index d7396c3..f91e4db 100644 --- a/server/src/assessment/assessment.service.ts +++ b/server/src/assessment/assessment.service.ts @@ -1500,6 +1500,13 @@ const initialState: Partial = { const level = this.determineLevel(session.finalScore || 0); const qrCode = `cert://${sessionId}-${Date.now()}`; + const questionDetails = (session.questions_json || []).map((q: any, i: number) => ({ + index: i + 1, + questionText: q.questionText?.substring(0, 100) || '', + questionType: q.questionType || 'SHORT_ANSWER', + dimension: q.dimension || '', + })); + const certificate = this.certificateRepository.create({ userId, sessionId, @@ -1512,7 +1519,13 @@ const initialState: Partial = { passed: (session as any).passed || false, }); - return this.certificateRepository.save(certificate); + const saved = await this.certificateRepository.save(certificate); + return { + ...saved, + templateName: session.template?.name || session.templateJson?.name || '-', + userName: session.user?.displayName || session.user?.username || '', + questionDetails, + } as any; } private determineLevel(score: number): string { diff --git a/web/components/views/AssessmentView.tsx b/web/components/views/AssessmentView.tsx index 4197d76..f3516d9 100644 --- a/web/components/views/AssessmentView.tsx +++ b/web/components/views/AssessmentView.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; +import { createPortal } from 'react-dom'; import { Brain, Send, @@ -54,6 +55,8 @@ export const AssessmentView: React.FC = ({ const [timeCheck, setTimeCheck] = useState<{ totalTimeRemaining: number; questionTimeRemaining: number; isTotalTimeout: boolean; isQuestionTimeout: boolean } | null>(null); const [selectedChoice, setSelectedChoice] = useState(null); const [autoSubmitted, setAutoSubmitted] = useState(false); + const [showCertModal, setShowCertModal] = useState(false); + const [certData, setCertData] = useState(null); const isTimedOut = timeCheck?.isTotalTimeout || timeCheck?.isQuestionTimeout; const messagesEndRef = useRef(null); @@ -640,7 +643,7 @@ export const AssessmentView: React.FC = ({ })} +
+ +

{certData.level}

+

{certData.templateName || '-'}

+
+
+
+ 总分 +

{certData.totalScore}/10

+
+
+ 结果 +

{certData.passed ? '合格' : '不合格'}

+
+
+ {certData.dimensionScores && ( +
+ 维度得分 +
+ {Object.entries(certData.dimensionScores).map(([dim, score]: [string, any]) => ( +
+ {dim} + {score}/10 +
+ ))} +
+
+ )} + {certData.questionDetails && ( +
+ 题目列表 +
+ {certData.questionDetails.map((qd: any) => ( +
+ #{qd.index} {qd.questionText} +
+ ))} +
+
+ )} + + , + document.body + )} + {error && (