第一版提交,答题功能OK,题库管理待完善
This commit is contained in:
246
api/models/quiz.ts
Normal file
246
api/models/quiz.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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: QuizRecord[]; 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
|
||||
FROM quiz_records r
|
||||
JOIN users u ON r.user_id = u.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)
|
||||
]);
|
||||
|
||||
return {
|
||||
records,
|
||||
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
|
||||
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
|
||||
}));
|
||||
}
|
||||
|
||||
// 解析答案
|
||||
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(DISTINCT user_id) as total FROM quiz_records`;
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user