247 lines
7.7 KiB
TypeScript
247 lines
7.7 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;
|
|
}
|
|
|
|
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
|
|
};
|
|
}
|
|
}
|