Files
Web_BLV_OA_Exam_Prod/api/controllers/quizController.ts

286 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Request, Response } from 'express';
import { QuestionModel, QuizModel, SystemConfigModel } from '../models';
import { Question } from '../models/question';
export class QuizController {
static async generateQuiz(req: Request, res: Response) {
try {
const { userId, subjectId, taskId } = req.body;
if (!userId) {
return res.status(400).json({
success: false,
message: '用户ID不能为空'
});
}
if (taskId) {
const { ExamTaskModel } = await import('../models/examTask');
const result = await ExamTaskModel.generateQuizQuestions(taskId, userId);
res.json({
success: true,
data: {
questions: result.questions,
totalScore: result.totalScore,
timeLimit: result.timeLimitMinutes
}
});
return;
}
if (!subjectId) {
return res.status(400).json({
success: false,
message: 'subjectId或taskId必须提供其一'
});
}
const { ExamSubjectModel } = await import('../models/examSubject');
const subject = await ExamSubjectModel.findById(subjectId);
if (!subject) {
return res.status(404).json({
success: false,
message: '考试科目不存在'
});
}
const result = await ExamSubjectModel.generateQuizQuestions(subject);
res.json({
success: true,
data: {
questions: result.questions,
totalScore: result.totalScore,
timeLimit: result.timeLimitMinutes
}
});
} catch (error: any) {
const message = error?.message || '生成试卷失败';
const status = message.includes('不存在')
? 404
: [
'用户ID不能为空',
'subjectId或taskId必须提供其一',
'当前时间不在任务有效范围内',
'用户未被分派到此任务',
'考试次数已用尽',
].some((m) => message.includes(m))
? 400
: 500;
res.status(status).json({
success: false,
message,
});
}
}
static async submitQuiz(req: Request, res: Response) {
try {
const { userId, subjectId, taskId, answers } = req.body;
if (!userId || !answers || !Array.isArray(answers)) {
return res.status(400).json({
success: false,
message: '参数不完整'
});
}
const processedAnswers = [];
let totalPossibleScore = 0;
for (const answer of answers) {
const question = await QuestionModel.findById(answer.questionId);
if (!question) {
processedAnswers.push(answer);
continue;
}
totalPossibleScore += Number(question.score) || 0;
if (question.type === 'multiple') {
const optionCount = question.options ? question.options.length : 0;
const unitScore = optionCount > 0 ? question.score / optionCount : 0;
let userAnsList: string[] = [];
if (Array.isArray(answer.userAnswer)) {
userAnsList = answer.userAnswer;
} else if (typeof answer.userAnswer === 'string') {
try {
userAnsList = JSON.parse(answer.userAnswer);
} catch (e) {
userAnsList = [answer.userAnswer];
}
}
let correctAnsList: string[] = [];
if (Array.isArray(question.answer)) {
correctAnsList = question.answer;
} else if (typeof question.answer === 'string') {
try {
const parsed = JSON.parse(question.answer);
if (Array.isArray(parsed)) correctAnsList = parsed;
else correctAnsList = [question.answer];
} catch {
correctAnsList = [question.answer];
}
}
const userSet = new Set(userAnsList);
const correctSet = new Set(correctAnsList);
let isFullCorrect = true;
if (userSet.size !== correctSet.size) {
isFullCorrect = false;
} else {
for (const a of userSet) {
if (!correctSet.has(a)) {
isFullCorrect = false;
break;
}
}
}
if (isFullCorrect) {
answer.score = question.score;
answer.isCorrect = true;
} else {
let tempScore = 0;
for (const uAns of userAnsList) {
if (correctSet.has(uAns)) {
tempScore += unitScore;
} else {
tempScore -= unitScore;
}
}
let finalScore = Math.max(0, tempScore);
finalScore = Math.round(finalScore * 10) / 10;
answer.score = finalScore;
answer.isCorrect = false;
}
} else if (question.type === 'single' || question.type === 'judgment') {
const isCorrect = answer.userAnswer === question.answer;
answer.score = isCorrect ? question.score : 0;
answer.isCorrect = isCorrect;
}
processedAnswers.push(answer);
}
const result = await QuizModel.submitQuiz({ userId, answers: processedAnswers, totalPossibleScore });
if (subjectId || taskId) {
let finalSubjectId: string | null = subjectId || null;
const finalTaskId: string | null = taskId || null;
// 任务考试场景下如果前端未传subjectId则从任务中反查
if (!finalSubjectId && finalTaskId) {
const { ExamTaskModel } = await import('../models/examTask');
const task = await ExamTaskModel.findById(finalTaskId);
if (task?.subjectId) {
finalSubjectId = task.subjectId;
}
}
const sql = `
UPDATE quiz_records
SET subject_id = ?, task_id = ?
WHERE id = ?
`;
await import('../database').then(({ run }) => run(sql, [finalSubjectId, finalTaskId, result.record.id]));
}
res.json({
success: true,
data: {
recordId: result.record.id,
totalScore: result.record.totalScore,
correctCount: result.record.correctCount,
totalCount: result.record.totalCount
}
});
} catch (error: any) {
res.status(500).json({
success: false,
message: error.message || '提交答题失败'
});
}
}
static async getUserRecords(req: Request, res: Response) {
try {
const { userId } = req.params;
const page = parseInt(req.query.page as string) || 1;
const limit = parseInt(req.query.limit as string) || 10;
const result = await QuizModel.findRecordsByUserId(userId, limit, (page - 1) * limit);
res.json({
success: true,
data: result.records,
pagination: {
page,
limit,
total: result.total,
pages: Math.ceil(result.total / limit)
}
});
} catch (error: any) {
res.status(500).json({
success: false,
message: error.message || '获取答题记录失败'
});
}
}
static async getRecordDetail(req: Request, res: Response) {
try {
const { recordId } = req.params;
const record = await QuizModel.findRecordById(recordId);
if (!record) {
return res.status(404).json({
success: false,
message: '答题记录不存在'
});
}
const answers = await QuizModel.findAnswersByRecordId(recordId);
res.json({
success: true,
data: {
record,
answers
}
});
} catch (error: any) {
res.status(500).json({
success: false,
message: error.message || '获取答题记录详情失败'
});
}
}
static async getAllRecords(req: Request, res: Response) {
try {
const page = parseInt(req.query.page as string) || 1;
const limit = parseInt(req.query.limit as string) || 10;
const result = await QuizModel.findAllRecords(limit, (page - 1) * limit);
res.json({
success: true,
data: result.records,
pagination: {
page,
limit,
total: result.total,
pages: Math.ceil(result.total / limit)
}
});
} catch (error: any) {
res.status(500).json({
success: false,
message: error.message || '获取答题记录失败'
});
}
}
}