P0-1/P0-2/P1-1: dimensions form + E2E tests + PDF export
P0-1 Backend: dimensions column on template entity + validation P0-1 Frontend: dimensions edit UI in TemplateManager P0-2: routeAfterGrading unit tests (10 cases), service spec fix + certificate tests, jest-e2e.json P1-1: proper PDF generation with embedded CJK font via pdf-lib low-level API
This commit is contained in:
@@ -6,6 +6,7 @@ import { AssessmentSession } from '../entities/assessment-session.entity';
|
||||
import { AssessmentQuestion } from '../entities/assessment-question.entity';
|
||||
import { AssessmentAnswer } from '../entities/assessment-answer.entity';
|
||||
import { AssessmentCertificate } from '../entities/assessment-certificate.entity';
|
||||
import { generateAssessmentPdf } from './pdf-generator';
|
||||
|
||||
@Injectable()
|
||||
export class ExportService {
|
||||
@@ -155,73 +156,55 @@ export class ExportService {
|
||||
where: { questionId: In(questions.map((q) => q.id)) },
|
||||
});
|
||||
|
||||
const content = this.generatePdfContent(session, questions, answers, certificate);
|
||||
return Buffer.from(content, 'utf-8');
|
||||
}
|
||||
|
||||
private generatePdfContent(
|
||||
session: AssessmentSession,
|
||||
questions: AssessmentQuestion[],
|
||||
answers: AssessmentAnswer[],
|
||||
certificate: AssessmentCertificate | null,
|
||||
): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push('='.repeat(60));
|
||||
lines.push(' 人才评估报告');
|
||||
lines.push('='.repeat(60));
|
||||
lines.push('');
|
||||
|
||||
lines.push(`评估ID: ${session.id}`);
|
||||
lines.push(`用户: ${session.user?.displayName || session.user?.username || session.userId}`);
|
||||
lines.push(`状态: ${session.status === 'COMPLETED' ? '已完成' : '进行中'}`);
|
||||
lines.push(`最终分数: ${session.finalScore || '-'}`);
|
||||
lines.push(`评估模板: ${session.template?.name || session.templateJson?.name || '-'}`);
|
||||
lines.push(`评估时间: ${session.startedAt ? new Date(session.startedAt).toLocaleString() : '-'}`);
|
||||
lines.push('');
|
||||
|
||||
if (certificate) {
|
||||
lines.push('-'.repeat(60));
|
||||
lines.push('证书信息');
|
||||
lines.push('-'.repeat(60));
|
||||
lines.push(`等级: ${certificate.level}`);
|
||||
lines.push(`总分: ${certificate.totalScore}`);
|
||||
lines.push(`是否通过: ${certificate.passed ? '是' : '否'}`);
|
||||
lines.push(`颁发时间: ${certificate.issuedAt ? new Date(certificate.issuedAt).toLocaleString() : '-'}`);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push('-'.repeat(60));
|
||||
lines.push('题目详情');
|
||||
lines.push('-'.repeat(60));
|
||||
|
||||
const answerMap = new Map(answers.map((a) => [a.questionId, a]));
|
||||
|
||||
const detailLines: string[] = [];
|
||||
for (let i = 0; i < questions.length; i++) {
|
||||
const q = questions[i];
|
||||
const a = answerMap.get(q.id);
|
||||
lines.push('');
|
||||
lines.push(`第${i + 1}题:`);
|
||||
lines.push(` 题目: ${q.questionText || '-'}`);
|
||||
lines.push(` 用户回答: ${a?.userAnswer || '-'}`);
|
||||
lines.push(` 得分: ${a?.score ?? '-'}`);
|
||||
lines.push(` 反馈: ${a?.feedback || '-'}`);
|
||||
lines.push(` 追问: ${a?.isFollowUp ? '是' : '否'}`);
|
||||
detailLines.push(`Q${i + 1}: ${q.questionText || '-'}`);
|
||||
detailLines.push(` A: ${a?.userAnswer || '-'}`);
|
||||
detailLines.push(` Score: ${a?.score ?? '-'}`);
|
||||
detailLines.push(` Feedback: ${a?.feedback || '-'}`);
|
||||
}
|
||||
|
||||
if (session.finalReport) {
|
||||
lines.push('');
|
||||
lines.push('-'.repeat(60));
|
||||
lines.push('综合评估报告');
|
||||
lines.push('-'.repeat(60));
|
||||
lines.push(session.finalReport);
|
||||
const certificateLines: string[] = [];
|
||||
if (certificate) {
|
||||
certificateLines.push(`Level: ${certificate.level}`);
|
||||
certificateLines.push(`Total Score: ${certificate.totalScore}`);
|
||||
certificateLines.push(`Passed: ${certificate.passed ? 'Yes' : 'No'}`);
|
||||
certificateLines.push(`Issued: ${certificate.issuedAt ? new Date(certificate.issuedAt).toLocaleString() : '-'}`);
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
lines.push('='.repeat(60));
|
||||
lines.push(' 报告结束');
|
||||
lines.push('='.repeat(60));
|
||||
const userName = session.user?.displayName || session.user?.username || session.userId;
|
||||
|
||||
return lines.join('\n');
|
||||
return generateAssessmentPdf({
|
||||
title: 'Assessment Report',
|
||||
subtitle: `${userName} — ${new Date(session.createdAt).toLocaleDateString()}`,
|
||||
sections: [
|
||||
{
|
||||
title: 'Summary',
|
||||
lines: [
|
||||
`Session: ${session.id}`,
|
||||
`User: ${userName}`,
|
||||
`Status: ${session.status}`,
|
||||
`Score: ${session.finalScore ?? '-'}`,
|
||||
`Template: ${session.template?.name || session.templateJson?.name || '-'}`,
|
||||
],
|
||||
},
|
||||
...(certificate ? [{
|
||||
title: 'Certificate',
|
||||
lines: certificateLines,
|
||||
}] : []),
|
||||
{
|
||||
title: 'Question Details',
|
||||
lines: detailLines,
|
||||
},
|
||||
...(session.finalReport ? [{
|
||||
title: 'Final Report',
|
||||
lines: [session.finalReport],
|
||||
}] : []),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user