Files
Web_BLV_OA_Exam_Prod/api/models/quiz.ts

272 lines
8.8 KiB
TypeScript

import { v4 as uuidv4 } from 'uuid';
import { query, run, get } from '../database';
import { Question } from './question';
export interface QuizRecord {
id: string;
userId: string;
totalScore: number;
correctCount: number;
totalCount: number;
createdAt: string;
}
export interface QuizAnswer {
id: string;
recordId: string;
questionId: string;
userAnswer: string | string[];
score: number;
isCorrect: boolean;
createdAt: string;
questionContent?: string;
questionType?: string;
correctAnswer?: string | string[];
questionScore?: number;
questionAnalysis?: string;
}
export interface SubmitAnswerData {
questionId: string;
userAnswer: string | string[];
score: number;
isCorrect: boolean;
}
export interface SubmitQuizData {
userId: string;
answers: SubmitAnswerData[];
}
export class QuizModel {
// 创建答题记录
static async createRecord(data: { userId: string; totalScore: number; correctCount: number; totalCount: number }): Promise<QuizRecord> {
const id = uuidv4();
const sql = `
INSERT INTO quiz_records (id, user_id, total_score, correct_count, total_count)
VALUES (?, ?, ?, ?, ?)
`;
await run(sql, [id, data.userId, data.totalScore, data.correctCount, data.totalCount]);
return this.findRecordById(id) as Promise<QuizRecord>;
}
// 创建答题答案
static async createAnswer(data: Omit<QuizAnswer, 'id' | 'createdAt'>): Promise<QuizAnswer> {
const id = uuidv4();
const userAnswerStr = Array.isArray(data.userAnswer) ? JSON.stringify(data.userAnswer) : data.userAnswer;
const sql = `
INSERT INTO quiz_answers (id, record_id, question_id, user_answer, score, is_correct)
VALUES (?, ?, ?, ?, ?, ?)
`;
await run(sql, [id, data.recordId, data.questionId, userAnswerStr, data.score, data.isCorrect]);
return {
id,
recordId: data.recordId,
questionId: data.questionId,
userAnswer: data.userAnswer,
score: data.score,
isCorrect: data.isCorrect,
createdAt: new Date().toISOString()
};
}
// 批量创建答题答案
static async createAnswers(recordId: string, answers: SubmitAnswerData[]): Promise<QuizAnswer[]> {
const createdAnswers: QuizAnswer[] = [];
for (const answer of answers) {
const createdAnswer = await this.createAnswer({
recordId,
questionId: answer.questionId,
userAnswer: answer.userAnswer,
score: answer.score,
isCorrect: answer.isCorrect
});
createdAnswers.push(createdAnswer);
}
return createdAnswers;
}
// 提交答题
static async submitQuiz(data: SubmitQuizData): Promise<{ record: QuizRecord; answers: QuizAnswer[] }> {
const totalScore = data.answers.reduce((sum, answer) => sum + answer.score, 0);
const correctCount = data.answers.filter(answer => answer.isCorrect).length;
const totalCount = data.answers.length;
// 创建答题记录
const record = await this.createRecord({
userId: data.userId,
totalScore,
correctCount,
totalCount
});
// 创建答题答案
const answers = await this.createAnswers(record.id, data.answers);
return { record, answers };
}
// 根据ID查找答题记录
static async findRecordById(id: string): Promise<QuizRecord | null> {
const sql = `SELECT id, user_id as userId, total_score as totalScore, correct_count as correctCount, total_count as totalCount, created_at as createdAt FROM quiz_records WHERE id = ?`;
const record = await get(sql, [id]);
return record || null;
}
// 获取用户的答题记录
static async findRecordsByUserId(userId: string, limit = 10, offset = 0): Promise<{ records: QuizRecord[]; total: number }> {
const recordsSql = `
SELECT id, user_id as userId, total_score as totalScore, correct_count as correctCount, total_count as totalCount, created_at as createdAt
FROM quiz_records
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`;
const countSql = `SELECT COUNT(*) as total FROM quiz_records WHERE user_id = ?`;
const [records, countResult] = await Promise.all([
query(recordsSql, [userId, limit, offset]),
get(countSql, [userId])
]);
return {
records,
total: countResult.total
};
}
// 获取所有答题记录(管理员用)
static async findAllRecords(limit = 10, offset = 0): Promise<{ records: any[]; total: number }> {
const recordsSql = `
SELECT r.id, r.user_id as userId, u.name as userName, u.phone as userPhone,
r.total_score as totalScore, r.correct_count as correctCount, r.total_count as totalCount,
r.created_at as createdAt, r.subject_id as subjectId, s.name as subjectName,
r.task_id as taskId
FROM quiz_records r
JOIN users u ON r.user_id = u.id
LEFT JOIN exam_subjects s ON r.subject_id = s.id
ORDER BY r.created_at DESC
LIMIT ? OFFSET ?
`;
const countSql = `SELECT COUNT(*) as total FROM quiz_records`;
const [records, countResult] = await Promise.all([
query(recordsSql, [limit, offset]),
get(countSql)
]);
// 对于每条记录,计算该考试任务的参与人数
const processedRecords = await Promise.all(records.map(async (record) => {
let examCount = 0;
if (record.taskId) {
// 统计该任务的参与人数
const taskCountSql = `SELECT COUNT(DISTINCT user_id) as count FROM quiz_records WHERE task_id = ?`;
const taskCountResult = await get(taskCountSql, [record.taskId]);
examCount = taskCountResult.count || 0;
} else if (record.subjectId) {
// 统计该科目的参与人数
const subjectCountSql = `SELECT COUNT(DISTINCT user_id) as count FROM quiz_records WHERE subject_id = ?`;
const subjectCountResult = await get(subjectCountSql, [record.subjectId]);
examCount = subjectCountResult.count || 0;
}
return {
...record,
examCount
};
}));
return {
records: processedRecords,
total: countResult.total
};
}
// 获取答题答案详情
static async findAnswersByRecordId(recordId: string): Promise<QuizAnswer[]> {
const sql = `
SELECT a.id, a.record_id as recordId, a.question_id as questionId,
a.user_answer as userAnswer, a.score, a.is_correct as isCorrect,
a.created_at as createdAt,
q.content as questionContent, q.type as questionType, q.answer as correctAnswer, q.score as questionScore, q.analysis as questionAnalysis
FROM quiz_answers a
JOIN questions q ON a.question_id = q.id
WHERE a.record_id = ?
ORDER BY a.created_at ASC
`;
const answers = await query(sql, [recordId]);
return answers.map(row => ({
id: row.id,
recordId: row.recordId,
questionId: row.questionId,
userAnswer: this.parseAnswer(row.userAnswer, row.questionType),
score: row.score,
isCorrect: Boolean(row.isCorrect),
createdAt: row.createdAt,
questionContent: row.questionContent,
questionType: row.questionType,
correctAnswer: this.parseAnswer(row.correctAnswer, row.questionType),
questionScore: row.questionScore,
questionAnalysis: row.questionAnalysis ?? ''
}));
}
// 解析答案
private static parseAnswer(answer: string, type: string): string | string[] {
if (type === 'multiple' || type === 'checkbox') {
try {
return JSON.parse(answer);
} catch (e) {
return answer;
}
}
return answer;
}
// 获取统计数据
static async getStatistics(): Promise<{
totalUsers: number;
totalRecords: number;
averageScore: number;
typeStats: Array<{ type: string; total: number; correct: number; correctRate: number }>;
}> {
const totalUsersSql = `SELECT COUNT(*) as total FROM users`;
const totalRecordsSql = `SELECT COUNT(*) as total FROM quiz_records`;
const averageScoreSql = `SELECT AVG(total_score) as average FROM quiz_records`;
const typeStatsSql = `
SELECT q.type,
COUNT(*) as total,
SUM(CASE WHEN qa.is_correct = 1 THEN 1 ELSE 0 END) as correct,
ROUND(SUM(CASE WHEN qa.is_correct = 1 THEN 1.0 ELSE 0.0 END) * 100 / COUNT(*), 2) as correctRate
FROM quiz_answers qa
JOIN questions q ON qa.question_id = q.id
GROUP BY q.type
`;
const [totalUsers, totalRecords, averageScore, typeStats] = await Promise.all([
get(totalUsersSql),
get(totalRecordsSql),
get(averageScoreSql),
query(typeStatsSql)
]);
return {
totalUsers: totalUsers.total,
totalRecords: totalRecords.total,
averageScore: Math.round(averageScore.average * 100) / 100,
typeStats
};
}
}